AI.cpp 146 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362
  1. /*
  2. ===========================================================================
  3. Doom 3 GPL Source Code
  4. Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
  6. Doom 3 Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 Source Code 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. You should have received a copy of the GNU General Public License
  15. along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
  17. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  18. ===========================================================================
  19. */
  20. #include "../../idlib/precompiled.h"
  21. #pragma hdrstop
  22. #include "../Game_local.h"
  23. static const char *moveCommandString[ NUM_MOVE_COMMANDS ] = {
  24. "MOVE_NONE",
  25. "MOVE_FACE_ENEMY",
  26. "MOVE_FACE_ENTITY",
  27. "MOVE_TO_ENEMY",
  28. "MOVE_TO_ENEMYHEIGHT",
  29. "MOVE_TO_ENTITY",
  30. "MOVE_OUT_OF_RANGE",
  31. "MOVE_TO_ATTACK_POSITION",
  32. "MOVE_TO_COVER",
  33. "MOVE_TO_POSITION",
  34. "MOVE_TO_POSITION_DIRECT",
  35. "MOVE_SLIDE_TO_POSITION",
  36. "MOVE_WANDER"
  37. };
  38. /*
  39. =====================
  40. idMoveState::idMoveState
  41. =====================
  42. */
  43. idMoveState::idMoveState() {
  44. moveType = MOVETYPE_ANIM;
  45. moveCommand = MOVE_NONE;
  46. moveStatus = MOVE_STATUS_DONE;
  47. moveDest.Zero();
  48. moveDir.Set( 1.0f, 0.0f, 0.0f );
  49. goalEntity = NULL;
  50. goalEntityOrigin.Zero();
  51. toAreaNum = 0;
  52. startTime = 0;
  53. duration = 0;
  54. speed = 0.0f;
  55. range = 0.0f;
  56. wanderYaw = 0;
  57. nextWanderTime = 0;
  58. blockTime = 0;
  59. obstacle = NULL;
  60. lastMoveOrigin = vec3_origin;
  61. lastMoveTime = 0;
  62. anim = 0;
  63. }
  64. /*
  65. =====================
  66. idMoveState::Save
  67. =====================
  68. */
  69. void idMoveState::Save( idSaveGame *savefile ) const {
  70. savefile->WriteInt( (int)moveType );
  71. savefile->WriteInt( (int)moveCommand );
  72. savefile->WriteInt( (int)moveStatus );
  73. savefile->WriteVec3( moveDest );
  74. savefile->WriteVec3( moveDir );
  75. goalEntity.Save( savefile );
  76. savefile->WriteVec3( goalEntityOrigin );
  77. savefile->WriteInt( toAreaNum );
  78. savefile->WriteInt( startTime );
  79. savefile->WriteInt( duration );
  80. savefile->WriteFloat( speed );
  81. savefile->WriteFloat( range );
  82. savefile->WriteFloat( wanderYaw );
  83. savefile->WriteInt( nextWanderTime );
  84. savefile->WriteInt( blockTime );
  85. obstacle.Save( savefile );
  86. savefile->WriteVec3( lastMoveOrigin );
  87. savefile->WriteInt( lastMoveTime );
  88. savefile->WriteInt( anim );
  89. }
  90. /*
  91. =====================
  92. idMoveState::Restore
  93. =====================
  94. */
  95. void idMoveState::Restore( idRestoreGame *savefile ) {
  96. savefile->ReadInt( (int &)moveType );
  97. savefile->ReadInt( (int &)moveCommand );
  98. savefile->ReadInt( (int &)moveStatus );
  99. savefile->ReadVec3( moveDest );
  100. savefile->ReadVec3( moveDir );
  101. goalEntity.Restore( savefile );
  102. savefile->ReadVec3( goalEntityOrigin );
  103. savefile->ReadInt( toAreaNum );
  104. savefile->ReadInt( startTime );
  105. savefile->ReadInt( duration );
  106. savefile->ReadFloat( speed );
  107. savefile->ReadFloat( range );
  108. savefile->ReadFloat( wanderYaw );
  109. savefile->ReadInt( nextWanderTime );
  110. savefile->ReadInt( blockTime );
  111. obstacle.Restore( savefile );
  112. savefile->ReadVec3( lastMoveOrigin );
  113. savefile->ReadInt( lastMoveTime );
  114. savefile->ReadInt( anim );
  115. }
  116. /*
  117. ============
  118. idAASFindCover::idAASFindCover
  119. ============
  120. */
  121. idAASFindCover::idAASFindCover( const idVec3 &hideFromPos ) {
  122. int numPVSAreas;
  123. idBounds bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) );
  124. // setup PVS
  125. numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS );
  126. hidePVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas );
  127. }
  128. /*
  129. ============
  130. idAASFindCover::~idAASFindCover
  131. ============
  132. */
  133. idAASFindCover::~idAASFindCover() {
  134. gameLocal.pvs.FreeCurrentPVS( hidePVS );
  135. }
  136. /*
  137. ============
  138. idAASFindCover::TestArea
  139. ============
  140. */
  141. bool idAASFindCover::TestArea( const idAAS *aas, int areaNum ) {
  142. idVec3 areaCenter;
  143. int numPVSAreas;
  144. int PVSAreas[ idEntity::MAX_PVS_AREAS ];
  145. areaCenter = aas->AreaCenter( areaNum );
  146. areaCenter[ 2 ] += 1.0f;
  147. numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS );
  148. if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) {
  149. return true;
  150. }
  151. return false;
  152. }
  153. /*
  154. ============
  155. idAASFindAreaOutOfRange::idAASFindAreaOutOfRange
  156. ============
  157. */
  158. idAASFindAreaOutOfRange::idAASFindAreaOutOfRange( const idVec3 &targetPos, float maxDist ) {
  159. this->targetPos = targetPos;
  160. this->maxDistSqr = maxDist * maxDist;
  161. }
  162. /*
  163. ============
  164. idAASFindAreaOutOfRange::TestArea
  165. ============
  166. */
  167. bool idAASFindAreaOutOfRange::TestArea( const idAAS *aas, int areaNum ) {
  168. const idVec3 &areaCenter = aas->AreaCenter( areaNum );
  169. trace_t trace;
  170. float dist;
  171. dist = ( targetPos.ToVec2() - areaCenter.ToVec2() ).LengthSqr();
  172. if ( ( maxDistSqr > 0.0f ) && ( dist < maxDistSqr ) ) {
  173. return false;
  174. }
  175. gameLocal.clip.TracePoint( trace, targetPos, areaCenter + idVec3( 0.0f, 0.0f, 1.0f ), MASK_OPAQUE, NULL );
  176. if ( trace.fraction < 1.0f ) {
  177. return false;
  178. }
  179. return true;
  180. }
  181. /*
  182. ============
  183. idAASFindAttackPosition::idAASFindAttackPosition
  184. ============
  185. */
  186. idAASFindAttackPosition::idAASFindAttackPosition( const idAI *self, const idMat3 &gravityAxis, idEntity *target, const idVec3 &targetPos, const idVec3 &fireOffset ) {
  187. int numPVSAreas;
  188. this->target = target;
  189. this->targetPos = targetPos;
  190. this->fireOffset = fireOffset;
  191. this->self = self;
  192. this->gravityAxis = gravityAxis;
  193. excludeBounds = idBounds( idVec3( -64.0, -64.0f, -8.0f ), idVec3( 64.0, 64.0f, 64.0f ) );
  194. excludeBounds.TranslateSelf( self->GetPhysics()->GetOrigin() );
  195. // setup PVS
  196. idBounds bounds( targetPos - idVec3( 16, 16, 0 ), targetPos + idVec3( 16, 16, 64 ) );
  197. numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS );
  198. targetPVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas );
  199. }
  200. /*
  201. ============
  202. idAASFindAttackPosition::~idAASFindAttackPosition
  203. ============
  204. */
  205. idAASFindAttackPosition::~idAASFindAttackPosition() {
  206. gameLocal.pvs.FreeCurrentPVS( targetPVS );
  207. }
  208. /*
  209. ============
  210. idAASFindAttackPosition::TestArea
  211. ============
  212. */
  213. bool idAASFindAttackPosition::TestArea( const idAAS *aas, int areaNum ) {
  214. idVec3 dir;
  215. idVec3 local_dir;
  216. idVec3 fromPos;
  217. idMat3 axis;
  218. idVec3 areaCenter;
  219. int numPVSAreas;
  220. int PVSAreas[ idEntity::MAX_PVS_AREAS ];
  221. areaCenter = aas->AreaCenter( areaNum );
  222. areaCenter[ 2 ] += 1.0f;
  223. if ( excludeBounds.ContainsPoint( areaCenter ) ) {
  224. // too close to where we already are
  225. return false;
  226. }
  227. numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS );
  228. if ( !gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ) ) {
  229. return false;
  230. }
  231. // calculate the world transform of the launch position
  232. dir = targetPos - areaCenter;
  233. gravityAxis.ProjectVector( dir, local_dir );
  234. local_dir.z = 0.0f;
  235. local_dir.ToVec2().Normalize();
  236. axis = local_dir.ToMat3();
  237. fromPos = areaCenter + fireOffset * axis;
  238. return self->GetAimDir( fromPos, target, self, dir );
  239. }
  240. /*
  241. =====================
  242. idAI::idAI
  243. =====================
  244. */
  245. idAI::idAI() {
  246. aas = NULL;
  247. travelFlags = TFL_WALK|TFL_AIR;
  248. kickForce = 2048.0f;
  249. ignore_obstacles = false;
  250. blockedRadius = 0.0f;
  251. blockedMoveTime = 750;
  252. blockedAttackTime = 750;
  253. turnRate = 360.0f;
  254. turnVel = 0.0f;
  255. anim_turn_yaw = 0.0f;
  256. anim_turn_amount = 0.0f;
  257. anim_turn_angles = 0.0f;
  258. fly_offset = 0;
  259. fly_seek_scale = 1.0f;
  260. fly_roll_scale = 0.0f;
  261. fly_roll_max = 0.0f;
  262. fly_roll = 0.0f;
  263. fly_pitch_scale = 0.0f;
  264. fly_pitch_max = 0.0f;
  265. fly_pitch = 0.0f;
  266. allowMove = false;
  267. allowHiddenMovement = false;
  268. fly_speed = 0.0f;
  269. fly_bob_strength = 0.0f;
  270. fly_bob_vert = 0.0f;
  271. fly_bob_horz = 0.0f;
  272. lastHitCheckResult = false;
  273. lastHitCheckTime = 0;
  274. lastAttackTime = 0;
  275. melee_range = 0.0f;
  276. projectile_height_to_distance_ratio = 1.0f;
  277. projectileDef = NULL;
  278. projectile = NULL;
  279. projectileClipModel = NULL;
  280. projectileRadius = 0.0f;
  281. projectileVelocity = vec3_origin;
  282. projectileGravity = vec3_origin;
  283. projectileSpeed = 0.0f;
  284. chat_snd = NULL;
  285. chat_min = 0;
  286. chat_max = 0;
  287. chat_time = 0;
  288. talk_state = TALK_NEVER;
  289. talkTarget = NULL;
  290. particles.Clear();
  291. restartParticles = true;
  292. useBoneAxis = false;
  293. wakeOnFlashlight = false;
  294. memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
  295. worldMuzzleFlashHandle = -1;
  296. enemy = NULL;
  297. lastVisibleEnemyPos.Zero();
  298. lastVisibleEnemyEyeOffset.Zero();
  299. lastVisibleReachableEnemyPos.Zero();
  300. lastReachableEnemyPos.Zero();
  301. shrivel_rate = 0.0f;
  302. shrivel_start = 0;
  303. fl.neverDormant = false; // AI's can go dormant
  304. current_yaw = 0.0f;
  305. ideal_yaw = 0.0f;
  306. #ifdef _D3XP
  307. spawnClearMoveables = false;
  308. harvestEnt = NULL;
  309. #endif
  310. num_cinematics = 0;
  311. current_cinematic = 0;
  312. allowEyeFocus = true;
  313. allowPain = true;
  314. allowJointMod = true;
  315. focusEntity = NULL;
  316. focusTime = 0;
  317. alignHeadTime = 0;
  318. forceAlignHeadTime = 0;
  319. currentFocusPos.Zero();
  320. eyeAng.Zero();
  321. lookAng.Zero();
  322. destLookAng.Zero();
  323. lookMin.Zero();
  324. lookMax.Zero();
  325. eyeMin.Zero();
  326. eyeMax.Zero();
  327. muzzleFlashEnd = 0;
  328. flashTime = 0;
  329. flashJointWorld = INVALID_JOINT;
  330. focusJoint = INVALID_JOINT;
  331. orientationJoint = INVALID_JOINT;
  332. flyTiltJoint = INVALID_JOINT;
  333. eyeVerticalOffset = 0.0f;
  334. eyeHorizontalOffset = 0.0f;
  335. eyeFocusRate = 0.0f;
  336. headFocusRate = 0.0f;
  337. focusAlignTime = 0;
  338. }
  339. /*
  340. =====================
  341. idAI::~idAI
  342. =====================
  343. */
  344. idAI::~idAI() {
  345. delete projectileClipModel;
  346. DeconstructScriptObject();
  347. scriptObject.Free();
  348. if ( worldMuzzleFlashHandle != -1 ) {
  349. gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
  350. worldMuzzleFlashHandle = -1;
  351. }
  352. #ifdef _D3XP
  353. if ( harvestEnt.GetEntity() ) {
  354. harvestEnt.GetEntity()->PostEventMS( &EV_Remove, 0 );
  355. }
  356. #endif
  357. }
  358. /*
  359. =====================
  360. idAI::Save
  361. =====================
  362. */
  363. void idAI::Save( idSaveGame *savefile ) const {
  364. int i;
  365. savefile->WriteInt( travelFlags );
  366. move.Save( savefile );
  367. savedMove.Save( savefile );
  368. savefile->WriteFloat( kickForce );
  369. savefile->WriteBool( ignore_obstacles );
  370. savefile->WriteFloat( blockedRadius );
  371. savefile->WriteInt( blockedMoveTime );
  372. savefile->WriteInt( blockedAttackTime );
  373. savefile->WriteFloat( ideal_yaw );
  374. savefile->WriteFloat( current_yaw );
  375. savefile->WriteFloat( turnRate );
  376. savefile->WriteFloat( turnVel );
  377. savefile->WriteFloat( anim_turn_yaw );
  378. savefile->WriteFloat( anim_turn_amount );
  379. savefile->WriteFloat( anim_turn_angles );
  380. savefile->WriteStaticObject( physicsObj );
  381. savefile->WriteFloat( fly_speed );
  382. savefile->WriteFloat( fly_bob_strength );
  383. savefile->WriteFloat( fly_bob_vert );
  384. savefile->WriteFloat( fly_bob_horz );
  385. savefile->WriteInt( fly_offset );
  386. savefile->WriteFloat( fly_seek_scale );
  387. savefile->WriteFloat( fly_roll_scale );
  388. savefile->WriteFloat( fly_roll_max );
  389. savefile->WriteFloat( fly_roll );
  390. savefile->WriteFloat( fly_pitch_scale );
  391. savefile->WriteFloat( fly_pitch_max );
  392. savefile->WriteFloat( fly_pitch );
  393. savefile->WriteBool( allowMove );
  394. savefile->WriteBool( allowHiddenMovement );
  395. savefile->WriteBool( disableGravity );
  396. savefile->WriteBool( af_push_moveables );
  397. savefile->WriteBool( lastHitCheckResult );
  398. savefile->WriteInt( lastHitCheckTime );
  399. savefile->WriteInt( lastAttackTime );
  400. savefile->WriteFloat( melee_range );
  401. savefile->WriteFloat( projectile_height_to_distance_ratio );
  402. savefile->WriteInt( missileLaunchOffset.Num() );
  403. for( i = 0; i < missileLaunchOffset.Num(); i++ ) {
  404. savefile->WriteVec3( missileLaunchOffset[ i ] );
  405. }
  406. idStr projectileName;
  407. spawnArgs.GetString( "def_projectile", "", projectileName );
  408. savefile->WriteString( projectileName );
  409. savefile->WriteFloat( projectileRadius );
  410. savefile->WriteFloat( projectileSpeed );
  411. savefile->WriteVec3( projectileVelocity );
  412. savefile->WriteVec3( projectileGravity );
  413. projectile.Save( savefile );
  414. savefile->WriteString( attack );
  415. savefile->WriteSoundShader( chat_snd );
  416. savefile->WriteInt( chat_min );
  417. savefile->WriteInt( chat_max );
  418. savefile->WriteInt( chat_time );
  419. savefile->WriteInt( talk_state );
  420. talkTarget.Save( savefile );
  421. savefile->WriteInt( num_cinematics );
  422. savefile->WriteInt( current_cinematic );
  423. savefile->WriteBool( allowJointMod );
  424. focusEntity.Save( savefile );
  425. savefile->WriteVec3( currentFocusPos );
  426. savefile->WriteInt( focusTime );
  427. savefile->WriteInt( alignHeadTime );
  428. savefile->WriteInt( forceAlignHeadTime );
  429. savefile->WriteAngles( eyeAng );
  430. savefile->WriteAngles( lookAng );
  431. savefile->WriteAngles( destLookAng );
  432. savefile->WriteAngles( lookMin );
  433. savefile->WriteAngles( lookMax );
  434. savefile->WriteInt( lookJoints.Num() );
  435. for( i = 0; i < lookJoints.Num(); i++ ) {
  436. savefile->WriteJoint( lookJoints[ i ] );
  437. savefile->WriteAngles( lookJointAngles[ i ] );
  438. }
  439. savefile->WriteFloat( shrivel_rate );
  440. savefile->WriteInt( shrivel_start );
  441. savefile->WriteInt( particles.Num() );
  442. for ( i = 0; i < particles.Num(); i++ ) {
  443. savefile->WriteParticle( particles[i].particle );
  444. savefile->WriteInt( particles[i].time );
  445. savefile->WriteJoint( particles[i].joint );
  446. }
  447. savefile->WriteBool( restartParticles );
  448. savefile->WriteBool( useBoneAxis );
  449. enemy.Save( savefile );
  450. savefile->WriteVec3( lastVisibleEnemyPos );
  451. savefile->WriteVec3( lastVisibleEnemyEyeOffset );
  452. savefile->WriteVec3( lastVisibleReachableEnemyPos );
  453. savefile->WriteVec3( lastReachableEnemyPos );
  454. savefile->WriteBool( wakeOnFlashlight );
  455. savefile->WriteAngles( eyeMin );
  456. savefile->WriteAngles( eyeMax );
  457. savefile->WriteFloat( eyeVerticalOffset );
  458. savefile->WriteFloat( eyeHorizontalOffset );
  459. savefile->WriteFloat( eyeFocusRate );
  460. savefile->WriteFloat( headFocusRate );
  461. savefile->WriteInt( focusAlignTime );
  462. savefile->WriteJoint( flashJointWorld );
  463. savefile->WriteInt( muzzleFlashEnd );
  464. savefile->WriteJoint( focusJoint );
  465. savefile->WriteJoint( orientationJoint );
  466. savefile->WriteJoint( flyTiltJoint );
  467. savefile->WriteBool( GetPhysics() == static_cast<const idPhysics *>(&physicsObj) );
  468. #ifdef _D3XP
  469. savefile->WriteInt(funcEmitters.Num());
  470. for(int i = 0; i < funcEmitters.Num(); i++) {
  471. funcEmitter_t* emitter = funcEmitters.GetIndex(i);
  472. savefile->WriteString(emitter->name);
  473. savefile->WriteJoint(emitter->joint);
  474. savefile->WriteObject(emitter->particle);
  475. }
  476. harvestEnt.Save( savefile);
  477. #endif
  478. }
  479. /*
  480. =====================
  481. idAI::Restore
  482. =====================
  483. */
  484. void idAI::Restore( idRestoreGame *savefile ) {
  485. bool restorePhysics;
  486. int i;
  487. int num;
  488. idBounds bounds;
  489. savefile->ReadInt( travelFlags );
  490. move.Restore( savefile );
  491. savedMove.Restore( savefile );
  492. savefile->ReadFloat( kickForce );
  493. savefile->ReadBool( ignore_obstacles );
  494. savefile->ReadFloat( blockedRadius );
  495. savefile->ReadInt( blockedMoveTime );
  496. savefile->ReadInt( blockedAttackTime );
  497. savefile->ReadFloat( ideal_yaw );
  498. savefile->ReadFloat( current_yaw );
  499. savefile->ReadFloat( turnRate );
  500. savefile->ReadFloat( turnVel );
  501. savefile->ReadFloat( anim_turn_yaw );
  502. savefile->ReadFloat( anim_turn_amount );
  503. savefile->ReadFloat( anim_turn_angles );
  504. savefile->ReadStaticObject( physicsObj );
  505. savefile->ReadFloat( fly_speed );
  506. savefile->ReadFloat( fly_bob_strength );
  507. savefile->ReadFloat( fly_bob_vert );
  508. savefile->ReadFloat( fly_bob_horz );
  509. savefile->ReadInt( fly_offset );
  510. savefile->ReadFloat( fly_seek_scale );
  511. savefile->ReadFloat( fly_roll_scale );
  512. savefile->ReadFloat( fly_roll_max );
  513. savefile->ReadFloat( fly_roll );
  514. savefile->ReadFloat( fly_pitch_scale );
  515. savefile->ReadFloat( fly_pitch_max );
  516. savefile->ReadFloat( fly_pitch );
  517. savefile->ReadBool( allowMove );
  518. savefile->ReadBool( allowHiddenMovement );
  519. savefile->ReadBool( disableGravity );
  520. savefile->ReadBool( af_push_moveables );
  521. savefile->ReadBool( lastHitCheckResult );
  522. savefile->ReadInt( lastHitCheckTime );
  523. savefile->ReadInt( lastAttackTime );
  524. savefile->ReadFloat( melee_range );
  525. savefile->ReadFloat( projectile_height_to_distance_ratio );
  526. savefile->ReadInt( num );
  527. missileLaunchOffset.SetGranularity( 1 );
  528. missileLaunchOffset.SetNum( num );
  529. for( i = 0; i < num; i++ ) {
  530. savefile->ReadVec3( missileLaunchOffset[ i ] );
  531. }
  532. idStr projectileName;
  533. savefile->ReadString( projectileName );
  534. if ( projectileName.Length() ) {
  535. projectileDef = gameLocal.FindEntityDefDict( projectileName );
  536. } else {
  537. projectileDef = NULL;
  538. }
  539. savefile->ReadFloat( projectileRadius );
  540. savefile->ReadFloat( projectileSpeed );
  541. savefile->ReadVec3( projectileVelocity );
  542. savefile->ReadVec3( projectileGravity );
  543. projectile.Restore( savefile );
  544. savefile->ReadString( attack );
  545. savefile->ReadSoundShader( chat_snd );
  546. savefile->ReadInt( chat_min );
  547. savefile->ReadInt( chat_max );
  548. savefile->ReadInt( chat_time );
  549. savefile->ReadInt( i );
  550. talk_state = static_cast<talkState_t>( i );
  551. talkTarget.Restore( savefile );
  552. savefile->ReadInt( num_cinematics );
  553. savefile->ReadInt( current_cinematic );
  554. savefile->ReadBool( allowJointMod );
  555. focusEntity.Restore( savefile );
  556. savefile->ReadVec3( currentFocusPos );
  557. savefile->ReadInt( focusTime );
  558. savefile->ReadInt( alignHeadTime );
  559. savefile->ReadInt( forceAlignHeadTime );
  560. savefile->ReadAngles( eyeAng );
  561. savefile->ReadAngles( lookAng );
  562. savefile->ReadAngles( destLookAng );
  563. savefile->ReadAngles( lookMin );
  564. savefile->ReadAngles( lookMax );
  565. savefile->ReadInt( num );
  566. lookJoints.SetGranularity( 1 );
  567. lookJoints.SetNum( num );
  568. lookJointAngles.SetGranularity( 1 );
  569. lookJointAngles.SetNum( num );
  570. for( i = 0; i < num; i++ ) {
  571. savefile->ReadJoint( lookJoints[ i ] );
  572. savefile->ReadAngles( lookJointAngles[ i ] );
  573. }
  574. savefile->ReadFloat( shrivel_rate );
  575. savefile->ReadInt( shrivel_start );
  576. savefile->ReadInt( num );
  577. particles.SetNum( num );
  578. for ( i = 0; i < particles.Num(); i++ ) {
  579. savefile->ReadParticle( particles[i].particle );
  580. savefile->ReadInt( particles[i].time );
  581. savefile->ReadJoint( particles[i].joint );
  582. }
  583. savefile->ReadBool( restartParticles );
  584. savefile->ReadBool( useBoneAxis );
  585. enemy.Restore( savefile );
  586. savefile->ReadVec3( lastVisibleEnemyPos );
  587. savefile->ReadVec3( lastVisibleEnemyEyeOffset );
  588. savefile->ReadVec3( lastVisibleReachableEnemyPos );
  589. savefile->ReadVec3( lastReachableEnemyPos );
  590. savefile->ReadBool( wakeOnFlashlight );
  591. savefile->ReadAngles( eyeMin );
  592. savefile->ReadAngles( eyeMax );
  593. savefile->ReadFloat( eyeVerticalOffset );
  594. savefile->ReadFloat( eyeHorizontalOffset );
  595. savefile->ReadFloat( eyeFocusRate );
  596. savefile->ReadFloat( headFocusRate );
  597. savefile->ReadInt( focusAlignTime );
  598. savefile->ReadJoint( flashJointWorld );
  599. savefile->ReadInt( muzzleFlashEnd );
  600. savefile->ReadJoint( focusJoint );
  601. savefile->ReadJoint( orientationJoint );
  602. savefile->ReadJoint( flyTiltJoint );
  603. savefile->ReadBool( restorePhysics );
  604. // Set the AAS if the character has the correct gravity vector
  605. idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
  606. gravity *= g_gravity.GetFloat();
  607. if ( gravity == gameLocal.GetGravity() ) {
  608. SetAAS();
  609. }
  610. SetCombatModel();
  611. LinkCombat();
  612. InitMuzzleFlash();
  613. // Link the script variables back to the scriptobject
  614. LinkScriptVariables();
  615. if ( restorePhysics ) {
  616. RestorePhysics( &physicsObj );
  617. }
  618. #ifdef _D3XP
  619. //Clean up the emitters
  620. for(int i = 0; i < funcEmitters.Num(); i++) {
  621. funcEmitter_t* emitter = funcEmitters.GetIndex(i);
  622. if(emitter->particle) {
  623. //Destroy the emitters
  624. emitter->particle->PostEventMS(&EV_Remove, 0 );
  625. }
  626. }
  627. funcEmitters.Clear();
  628. int emitterCount;
  629. savefile->ReadInt( emitterCount );
  630. for(int i = 0; i < emitterCount; i++) {
  631. funcEmitter_t newEmitter;
  632. memset(&newEmitter, 0, sizeof(newEmitter));
  633. idStr name;
  634. savefile->ReadString( name );
  635. strcpy( newEmitter.name, name.c_str() );
  636. savefile->ReadJoint( newEmitter.joint );
  637. savefile->ReadObject(reinterpret_cast<idClass *&>(newEmitter.particle));
  638. funcEmitters.Set(newEmitter.name, newEmitter);
  639. }
  640. harvestEnt.Restore(savefile);
  641. //if(harvestEnt.GetEntity()) {
  642. // harvestEnt.GetEntity()->SetParent(this);
  643. //}
  644. #endif
  645. }
  646. /*
  647. =====================
  648. idAI::Spawn
  649. =====================
  650. */
  651. void idAI::Spawn( void ) {
  652. const char *jointname;
  653. const idKeyValue *kv;
  654. idStr jointName;
  655. idAngles jointScale;
  656. jointHandle_t joint;
  657. idVec3 local_dir;
  658. bool talks;
  659. if ( !g_monsters.GetBool() ) {
  660. PostEventMS( &EV_Remove, 0 );
  661. return;
  662. }
  663. spawnArgs.GetInt( "team", "1", team );
  664. spawnArgs.GetInt( "rank", "0", rank );
  665. spawnArgs.GetInt( "fly_offset", "0", fly_offset );
  666. spawnArgs.GetFloat( "fly_speed", "100", fly_speed );
  667. spawnArgs.GetFloat( "fly_bob_strength", "50", fly_bob_strength );
  668. spawnArgs.GetFloat( "fly_bob_vert", "2", fly_bob_horz );
  669. spawnArgs.GetFloat( "fly_bob_horz", "2.7", fly_bob_vert );
  670. spawnArgs.GetFloat( "fly_seek_scale", "4", fly_seek_scale );
  671. spawnArgs.GetFloat( "fly_roll_scale", "90", fly_roll_scale );
  672. spawnArgs.GetFloat( "fly_roll_max", "60", fly_roll_max );
  673. spawnArgs.GetFloat( "fly_pitch_scale", "45", fly_pitch_scale );
  674. spawnArgs.GetFloat( "fly_pitch_max", "30", fly_pitch_max );
  675. spawnArgs.GetFloat( "melee_range", "64", melee_range );
  676. spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio );
  677. spawnArgs.GetFloat( "turn_rate", "360", turnRate );
  678. spawnArgs.GetBool( "talks", "0", talks );
  679. if ( spawnArgs.GetString( "npc_name", NULL ) != NULL ) {
  680. if ( talks ) {
  681. talk_state = TALK_OK;
  682. } else {
  683. talk_state = TALK_BUSY;
  684. }
  685. } else {
  686. talk_state = TALK_NEVER;
  687. }
  688. spawnArgs.GetBool( "animate_z", "0", disableGravity );
  689. spawnArgs.GetBool( "af_push_moveables", "0", af_push_moveables );
  690. spawnArgs.GetFloat( "kick_force", "4096", kickForce );
  691. spawnArgs.GetBool( "ignore_obstacles", "0", ignore_obstacles );
  692. spawnArgs.GetFloat( "blockedRadius", "-1", blockedRadius );
  693. spawnArgs.GetInt( "blockedMoveTime", "750", blockedMoveTime );
  694. spawnArgs.GetInt( "blockedAttackTime", "750", blockedAttackTime );
  695. spawnArgs.GetInt( "num_cinematics", "0", num_cinematics );
  696. current_cinematic = 0;
  697. LinkScriptVariables();
  698. fl.takedamage = !spawnArgs.GetBool( "noDamage" );
  699. enemy = NULL;
  700. allowMove = true;
  701. allowHiddenMovement = false;
  702. animator.RemoveOriginOffset( true );
  703. // create combat collision hull for exact collision detection
  704. SetCombatModel();
  705. lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" );
  706. lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" );
  707. lookJoints.SetGranularity( 1 );
  708. lookJointAngles.SetGranularity( 1 );
  709. kv = spawnArgs.MatchPrefix( "look_joint", NULL );
  710. while( kv ) {
  711. jointName = kv->GetKey();
  712. jointName.StripLeadingOnce( "look_joint " );
  713. joint = animator.GetJointHandle( jointName );
  714. if ( joint == INVALID_JOINT ) {
  715. gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() );
  716. } else {
  717. jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" );
  718. jointScale.roll = 0.0f;
  719. // if no scale on any component, then don't bother adding it. this may be done to
  720. // zero out rotation from an inherited entitydef.
  721. if ( jointScale != ang_zero ) {
  722. lookJoints.Append( joint );
  723. lookJointAngles.Append( jointScale );
  724. }
  725. }
  726. kv = spawnArgs.MatchPrefix( "look_joint", kv );
  727. }
  728. // calculate joint positions on attack frames so we can do proper "can hit" tests
  729. CalculateAttackOffsets();
  730. eyeMin = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" );
  731. eyeMax = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" );
  732. eyeVerticalOffset = spawnArgs.GetFloat( "eye_verticle_offset", "5" );
  733. eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" );
  734. eyeFocusRate = spawnArgs.GetFloat( "eye_focus_rate", "0.5" );
  735. headFocusRate = spawnArgs.GetFloat( "head_focus_rate", "0.1" );
  736. focusAlignTime = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) );
  737. flashJointWorld = animator.GetJointHandle( "flash" );
  738. if ( head.GetEntity() ) {
  739. idAnimator *headAnimator = head.GetEntity()->GetAnimator();
  740. jointname = spawnArgs.GetString( "bone_focus" );
  741. if ( *jointname ) {
  742. focusJoint = headAnimator->GetJointHandle( jointname );
  743. if ( focusJoint == INVALID_JOINT ) {
  744. gameLocal.Warning( "Joint '%s' not found on head on '%s'", jointname, name.c_str() );
  745. }
  746. }
  747. } else {
  748. jointname = spawnArgs.GetString( "bone_focus" );
  749. if ( *jointname ) {
  750. focusJoint = animator.GetJointHandle( jointname );
  751. if ( focusJoint == INVALID_JOINT ) {
  752. gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
  753. }
  754. }
  755. }
  756. jointname = spawnArgs.GetString( "bone_orientation" );
  757. if ( *jointname ) {
  758. orientationJoint = animator.GetJointHandle( jointname );
  759. if ( orientationJoint == INVALID_JOINT ) {
  760. gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
  761. }
  762. }
  763. jointname = spawnArgs.GetString( "bone_flytilt" );
  764. if ( *jointname ) {
  765. flyTiltJoint = animator.GetJointHandle( jointname );
  766. if ( flyTiltJoint == INVALID_JOINT ) {
  767. gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
  768. }
  769. }
  770. InitMuzzleFlash();
  771. physicsObj.SetSelf( this );
  772. physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
  773. physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) );
  774. if ( spawnArgs.GetBool( "big_monster" ) ) {
  775. physicsObj.SetContents( 0 );
  776. physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY );
  777. } else {
  778. if ( use_combat_bbox ) {
  779. physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
  780. } else {
  781. physicsObj.SetContents( CONTENTS_BODY );
  782. }
  783. physicsObj.SetClipMask( MASK_MONSTERSOLID );
  784. }
  785. // move up to make sure the monster is at least an epsilon above the floor
  786. physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) );
  787. if ( num_cinematics ) {
  788. physicsObj.SetGravity( vec3_origin );
  789. } else {
  790. idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
  791. gravity *= g_gravity.GetFloat();
  792. physicsObj.SetGravity( gravity );
  793. }
  794. SetPhysics( &physicsObj );
  795. physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir );
  796. current_yaw = local_dir.ToYaw();
  797. ideal_yaw = idMath::AngleNormalize180( current_yaw );
  798. move.blockTime = 0;
  799. SetAAS();
  800. projectile = NULL;
  801. projectileDef = NULL;
  802. projectileClipModel = NULL;
  803. idStr projectileName;
  804. if ( spawnArgs.GetString( "def_projectile", "", projectileName ) && projectileName.Length() ) {
  805. projectileDef = gameLocal.FindEntityDefDict( projectileName );
  806. CreateProjectile( vec3_origin, viewAxis[ 0 ] );
  807. projectileRadius = projectile.GetEntity()->GetPhysics()->GetClipModel()->GetBounds().GetRadius();
  808. projectileVelocity = idProjectile::GetVelocity( projectileDef );
  809. projectileGravity = idProjectile::GetGravity( projectileDef );
  810. projectileSpeed = projectileVelocity.Length();
  811. delete projectile.GetEntity();
  812. projectile = NULL;
  813. }
  814. particles.Clear();
  815. restartParticles = true;
  816. useBoneAxis = spawnArgs.GetBool( "useBoneAxis" );
  817. SpawnParticles( "smokeParticleSystem" );
  818. if ( num_cinematics || spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "teleport" ) || spawnArgs.GetBool( "trigger_anim" ) ) {
  819. fl.takedamage = false;
  820. physicsObj.SetContents( 0 );
  821. physicsObj.GetClipModel()->Unlink();
  822. Hide();
  823. } else {
  824. // play a looping ambient sound if we have one
  825. StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
  826. }
  827. if ( health <= 0 ) {
  828. gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() );
  829. health = 1;
  830. }
  831. // set up monster chatter
  832. SetChatSound();
  833. BecomeActive( TH_THINK );
  834. if ( af_push_moveables ) {
  835. af.SetupPose( this, gameLocal.time );
  836. af.GetPhysics()->EnableClip();
  837. }
  838. // init the move variables
  839. StopMove( MOVE_STATUS_DONE );
  840. #ifdef _D3XP
  841. spawnArgs.GetBool( "spawnClearMoveables", "0", spawnClearMoveables );
  842. #endif
  843. }
  844. #ifdef _D3XP
  845. void idAI::Gib( const idVec3 &dir, const char *damageDefName ) {
  846. if(harvestEnt.GetEntity()) {
  847. //Let the harvest ent know that we gibbed
  848. harvestEnt.GetEntity()->Gib();
  849. }
  850. idActor::Gib(dir, damageDefName);
  851. }
  852. #endif
  853. /*
  854. ===================
  855. idAI::InitMuzzleFlash
  856. ===================
  857. */
  858. void idAI::InitMuzzleFlash( void ) {
  859. const char *shader;
  860. idVec3 flashColor;
  861. spawnArgs.GetString( "mtr_flashShader", "muzzleflash", &shader );
  862. spawnArgs.GetVector( "flashColor", "0 0 0", flashColor );
  863. float flashRadius = spawnArgs.GetFloat( "flashRadius" );
  864. flashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) );
  865. memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
  866. worldMuzzleFlash.pointLight = true;
  867. worldMuzzleFlash.shader = declManager->FindMaterial( shader, false );
  868. worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0];
  869. worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1];
  870. worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2];
  871. worldMuzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
  872. worldMuzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
  873. worldMuzzleFlash.lightRadius[0] = flashRadius;
  874. worldMuzzleFlash.lightRadius[1] = flashRadius;
  875. worldMuzzleFlash.lightRadius[2] = flashRadius;
  876. worldMuzzleFlashHandle = -1;
  877. }
  878. /*
  879. ===================
  880. idAI::List_f
  881. ===================
  882. */
  883. void idAI::List_f( const idCmdArgs &args ) {
  884. int e;
  885. idAI *check;
  886. int count;
  887. const char *statename;
  888. count = 0;
  889. gameLocal.Printf( "%-4s %-20s %s\n", " Num", "EntityDef", "Name" );
  890. gameLocal.Printf( "------------------------------------------------\n" );
  891. for( e = 0; e < MAX_GENTITIES; e++ ) {
  892. check = static_cast<idAI *>(gameLocal.entities[ e ]);
  893. if ( !check || !check->IsType( idAI::Type ) ) {
  894. continue;
  895. }
  896. if ( check->state ) {
  897. statename = check->state->Name();
  898. } else {
  899. statename = "NULL state";
  900. }
  901. gameLocal.Printf( "%4i: %-20s %-20s %s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), statename, check->allowMove );
  902. count++;
  903. }
  904. gameLocal.Printf( "...%d monsters\n", count );
  905. }
  906. /*
  907. ================
  908. idAI::DormantBegin
  909. called when entity becomes dormant
  910. ================
  911. */
  912. void idAI::DormantBegin( void ) {
  913. // since dormant happens on a timer, we wont get to update particles to
  914. // hidden through the think loop, but we need to hide them though.
  915. if ( particles.Num() ) {
  916. for ( int i = 0; i < particles.Num(); i++ ) {
  917. particles[i].time = 0;
  918. }
  919. }
  920. if ( enemyNode.InList() ) {
  921. // remove ourselves from the enemy's enemylist
  922. enemyNode.Remove();
  923. }
  924. idActor::DormantBegin();
  925. }
  926. /*
  927. ================
  928. idAI::DormantEnd
  929. called when entity wakes from being dormant
  930. ================
  931. */
  932. void idAI::DormantEnd( void ) {
  933. if ( enemy.GetEntity() && !enemyNode.InList() ) {
  934. // let our enemy know we're back on the trail
  935. enemyNode.AddToEnd( enemy.GetEntity()->enemyList );
  936. }
  937. if ( particles.Num() ) {
  938. for ( int i = 0; i < particles.Num(); i++ ) {
  939. particles[i].time = gameLocal.time;
  940. }
  941. }
  942. idActor::DormantEnd();
  943. }
  944. /*
  945. =====================
  946. idAI::Think
  947. =====================
  948. */
  949. void idAI::Think( void ) {
  950. // if we are completely closed off from the player, don't do anything at all
  951. if ( CheckDormant() ) {
  952. return;
  953. }
  954. if ( thinkFlags & TH_THINK ) {
  955. // clear out the enemy when he dies or is hidden
  956. idActor *enemyEnt = enemy.GetEntity();
  957. if ( enemyEnt ) {
  958. if ( enemyEnt->health <= 0 ) {
  959. EnemyDead();
  960. }
  961. }
  962. current_yaw += deltaViewAngles.yaw;
  963. ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw );
  964. deltaViewAngles.Zero();
  965. viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
  966. if ( num_cinematics ) {
  967. if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) {
  968. PlayCinematic();
  969. }
  970. RunPhysics();
  971. } else if ( !allowHiddenMovement && IsHidden() ) {
  972. // hidden monsters
  973. UpdateAIScript();
  974. } else {
  975. // clear the ik before we do anything else so the skeleton doesn't get updated twice
  976. walkIK.ClearJointMods();
  977. switch( move.moveType ) {
  978. case MOVETYPE_DEAD :
  979. // dead monsters
  980. UpdateAIScript();
  981. DeadMove();
  982. break;
  983. case MOVETYPE_FLY :
  984. // flying monsters
  985. UpdateEnemyPosition();
  986. UpdateAIScript();
  987. FlyMove();
  988. PlayChatter();
  989. CheckBlink();
  990. break;
  991. case MOVETYPE_STATIC :
  992. // static monsters
  993. UpdateEnemyPosition();
  994. UpdateAIScript();
  995. StaticMove();
  996. PlayChatter();
  997. CheckBlink();
  998. break;
  999. case MOVETYPE_ANIM :
  1000. // animation based movement
  1001. UpdateEnemyPosition();
  1002. UpdateAIScript();
  1003. AnimMove();
  1004. PlayChatter();
  1005. CheckBlink();
  1006. break;
  1007. case MOVETYPE_SLIDE :
  1008. // velocity based movement
  1009. UpdateEnemyPosition();
  1010. UpdateAIScript();
  1011. SlideMove();
  1012. PlayChatter();
  1013. CheckBlink();
  1014. break;
  1015. }
  1016. }
  1017. // clear pain flag so that we recieve any damage between now and the next time we run the script
  1018. AI_PAIN = false;
  1019. AI_SPECIAL_DAMAGE = 0;
  1020. AI_PUSHED = false;
  1021. } else if ( thinkFlags & TH_PHYSICS ) {
  1022. RunPhysics();
  1023. }
  1024. if ( af_push_moveables ) {
  1025. PushWithAF();
  1026. }
  1027. if ( fl.hidden && allowHiddenMovement ) {
  1028. // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement
  1029. animator.ServiceAnims( gameLocal.previousTime, gameLocal.time );
  1030. }
  1031. /* this still draws in retail builds.. not sure why.. don't care at this point.
  1032. if ( !aas && developer.GetBool() && !fl.hidden && !num_cinematics ) {
  1033. gameRenderWorld->DrawText( "No AAS", physicsObj.GetAbsBounds().GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, gameLocal.msec );
  1034. }
  1035. */
  1036. UpdateMuzzleFlash();
  1037. UpdateAnimation();
  1038. UpdateParticles();
  1039. Present();
  1040. UpdateDamageEffects();
  1041. LinkCombat();
  1042. #ifdef _D3XP
  1043. if(ai_showHealth.GetBool()) {
  1044. idVec3 aboveHead(0,0,20);
  1045. gameRenderWorld->DrawText( va( "%d", ( int )health), this->GetEyePosition()+aboveHead, 0.5f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
  1046. }
  1047. #endif
  1048. }
  1049. /***********************************************************************
  1050. AI script state management
  1051. ***********************************************************************/
  1052. /*
  1053. =====================
  1054. idAI::LinkScriptVariables
  1055. =====================
  1056. */
  1057. void idAI::LinkScriptVariables( void ) {
  1058. AI_TALK.LinkTo( scriptObject, "AI_TALK" );
  1059. AI_DAMAGE.LinkTo( scriptObject, "AI_DAMAGE" );
  1060. AI_PAIN.LinkTo( scriptObject, "AI_PAIN" );
  1061. AI_SPECIAL_DAMAGE.LinkTo( scriptObject, "AI_SPECIAL_DAMAGE" );
  1062. AI_DEAD.LinkTo( scriptObject, "AI_DEAD" );
  1063. AI_ENEMY_VISIBLE.LinkTo( scriptObject, "AI_ENEMY_VISIBLE" );
  1064. AI_ENEMY_IN_FOV.LinkTo( scriptObject, "AI_ENEMY_IN_FOV" );
  1065. AI_ENEMY_DEAD.LinkTo( scriptObject, "AI_ENEMY_DEAD" );
  1066. AI_MOVE_DONE.LinkTo( scriptObject, "AI_MOVE_DONE" );
  1067. AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" );
  1068. AI_ACTIVATED.LinkTo( scriptObject, "AI_ACTIVATED" );
  1069. AI_FORWARD.LinkTo( scriptObject, "AI_FORWARD" );
  1070. AI_JUMP.LinkTo( scriptObject, "AI_JUMP" );
  1071. AI_BLOCKED.LinkTo( scriptObject, "AI_BLOCKED" );
  1072. AI_DEST_UNREACHABLE.LinkTo( scriptObject, "AI_DEST_UNREACHABLE" );
  1073. AI_HIT_ENEMY.LinkTo( scriptObject, "AI_HIT_ENEMY" );
  1074. AI_OBSTACLE_IN_PATH.LinkTo( scriptObject, "AI_OBSTACLE_IN_PATH" );
  1075. AI_PUSHED.LinkTo( scriptObject, "AI_PUSHED" );
  1076. AI_CLAMBER.LinkTo( scriptObject, "AI_CLAMBER" );
  1077. }
  1078. /*
  1079. =====================
  1080. idAI::UpdateAIScript
  1081. =====================
  1082. */
  1083. void idAI::UpdateAIScript( void ) {
  1084. UpdateScript();
  1085. // clear the hit enemy flag so we catch the next time we hit someone
  1086. AI_HIT_ENEMY = false;
  1087. if ( allowHiddenMovement || !IsHidden() ) {
  1088. // update the animstate if we're not hidden
  1089. UpdateAnimState();
  1090. }
  1091. }
  1092. /***********************************************************************
  1093. navigation
  1094. ***********************************************************************/
  1095. /*
  1096. ============
  1097. idAI::KickObstacles
  1098. ============
  1099. */
  1100. void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) {
  1101. int i, numListedClipModels;
  1102. idBounds clipBounds;
  1103. idEntity *obEnt;
  1104. idClipModel *clipModel;
  1105. idClipModel *clipModelList[ MAX_GENTITIES ];
  1106. int clipmask;
  1107. idVec3 org;
  1108. idVec3 forceVec;
  1109. idVec3 delta;
  1110. idVec2 perpendicular;
  1111. org = physicsObj.GetOrigin();
  1112. // find all possible obstacles
  1113. clipBounds = physicsObj.GetAbsBounds();
  1114. clipBounds.TranslateSelf( dir * 32.0f );
  1115. clipBounds.ExpandSelf( 8.0f );
  1116. clipBounds.AddPoint( org );
  1117. clipmask = physicsObj.GetClipMask();
  1118. numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipmask, clipModelList, MAX_GENTITIES );
  1119. for ( i = 0; i < numListedClipModels; i++ ) {
  1120. clipModel = clipModelList[i];
  1121. obEnt = clipModel->GetEntity();
  1122. if ( obEnt == alwaysKick ) {
  1123. // we'll kick this one outside the loop
  1124. continue;
  1125. }
  1126. if ( !clipModel->IsTraceModel() ) {
  1127. continue;
  1128. }
  1129. if ( obEnt->IsType( idMoveable::Type ) && obEnt->GetPhysics()->IsPushable() ) {
  1130. delta = obEnt->GetPhysics()->GetOrigin() - org;
  1131. delta.NormalizeFast();
  1132. perpendicular.x = -delta.y;
  1133. perpendicular.y = delta.x;
  1134. delta.z += 0.5f;
  1135. delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f;
  1136. forceVec = delta * force * obEnt->GetPhysics()->GetMass();
  1137. obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec );
  1138. }
  1139. }
  1140. if ( alwaysKick ) {
  1141. delta = alwaysKick->GetPhysics()->GetOrigin() - org;
  1142. delta.NormalizeFast();
  1143. perpendicular.x = -delta.y;
  1144. perpendicular.y = delta.x;
  1145. delta.z += 0.5f;
  1146. delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f;
  1147. forceVec = delta * force * alwaysKick->GetPhysics()->GetMass();
  1148. alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec );
  1149. }
  1150. }
  1151. /*
  1152. ============
  1153. ValidForBounds
  1154. ============
  1155. */
  1156. bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) {
  1157. int i;
  1158. for ( i = 0; i < 3; i++ ) {
  1159. if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) {
  1160. return false;
  1161. }
  1162. if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) {
  1163. return false;
  1164. }
  1165. }
  1166. return true;
  1167. }
  1168. /*
  1169. =====================
  1170. idAI::SetAAS
  1171. =====================
  1172. */
  1173. void idAI::SetAAS( void ) {
  1174. idStr use_aas;
  1175. spawnArgs.GetString( "use_aas", NULL, use_aas );
  1176. aas = gameLocal.GetAAS( use_aas );
  1177. if ( aas ) {
  1178. const idAASSettings *settings = aas->GetSettings();
  1179. if ( settings ) {
  1180. if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) {
  1181. gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() );
  1182. }
  1183. float height = settings->maxStepHeight;
  1184. physicsObj.SetMaxStepHeight( height );
  1185. return;
  1186. } else {
  1187. aas = NULL;
  1188. }
  1189. }
  1190. gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() );
  1191. }
  1192. /*
  1193. =====================
  1194. idAI::DrawRoute
  1195. =====================
  1196. */
  1197. void idAI::DrawRoute( void ) const {
  1198. if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY && move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
  1199. if ( move.moveType == MOVETYPE_FLY ) {
  1200. aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
  1201. } else {
  1202. aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
  1203. }
  1204. }
  1205. }
  1206. /*
  1207. =====================
  1208. idAI::ReachedPos
  1209. =====================
  1210. */
  1211. bool idAI::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const {
  1212. if ( move.moveType == MOVETYPE_SLIDE ) {
  1213. idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) );
  1214. bnds.TranslateSelf( physicsObj.GetOrigin() );
  1215. if ( bnds.ContainsPoint( pos ) ) {
  1216. return true;
  1217. }
  1218. } else {
  1219. if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) {
  1220. if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) {
  1221. return true;
  1222. }
  1223. } else {
  1224. idBounds bnds( idVec3( -16.0, -16.0f, -8.0f ), idVec3( 16.0, 16.0f, 64.0f ) );
  1225. bnds.TranslateSelf( physicsObj.GetOrigin() );
  1226. if ( bnds.ContainsPoint( pos ) ) {
  1227. return true;
  1228. }
  1229. }
  1230. }
  1231. return false;
  1232. }
  1233. /*
  1234. =====================
  1235. idAI::PointReachableAreaNum
  1236. =====================
  1237. */
  1238. int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const {
  1239. int areaNum;
  1240. idVec3 size;
  1241. idBounds bounds;
  1242. if ( !aas ) {
  1243. return 0;
  1244. }
  1245. size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale;
  1246. bounds[0] = -size;
  1247. size.z = 32.0f;
  1248. bounds[1] = size;
  1249. if ( move.moveType == MOVETYPE_FLY ) {
  1250. areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY );
  1251. } else {
  1252. areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK );
  1253. }
  1254. return areaNum;
  1255. }
  1256. /*
  1257. =====================
  1258. idAI::PathToGoal
  1259. =====================
  1260. */
  1261. bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const {
  1262. idVec3 org;
  1263. idVec3 goal;
  1264. if ( !aas ) {
  1265. return false;
  1266. }
  1267. org = origin;
  1268. aas->PushPointIntoAreaNum( areaNum, org );
  1269. if ( !areaNum ) {
  1270. return false;
  1271. }
  1272. goal = goalOrigin;
  1273. aas->PushPointIntoAreaNum( goalAreaNum, goal );
  1274. if ( !goalAreaNum ) {
  1275. return false;
  1276. }
  1277. if ( move.moveType == MOVETYPE_FLY ) {
  1278. return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
  1279. } else {
  1280. return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
  1281. }
  1282. }
  1283. /*
  1284. =====================
  1285. idAI::TravelDistance
  1286. Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance.
  1287. This is feakin' slow, so it's not good to do it too many times per frame. It also is slower the further you
  1288. are from the goal, so try to break the goals up into shorter distances.
  1289. =====================
  1290. */
  1291. float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const {
  1292. int fromArea;
  1293. int toArea;
  1294. float dist;
  1295. idVec2 delta;
  1296. aasPath_t path;
  1297. if ( !aas ) {
  1298. // no aas, so just take the straight line distance
  1299. delta = end.ToVec2() - start.ToVec2();
  1300. dist = delta.LengthFast();
  1301. if ( ai_debugMove.GetBool() ) {
  1302. gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false );
  1303. gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
  1304. }
  1305. return dist;
  1306. }
  1307. fromArea = PointReachableAreaNum( start );
  1308. toArea = PointReachableAreaNum( end );
  1309. if ( !fromArea || !toArea ) {
  1310. // can't seem to get there
  1311. return -1;
  1312. }
  1313. if ( fromArea == toArea ) {
  1314. // same area, so just take the straight line distance
  1315. delta = end.ToVec2() - start.ToVec2();
  1316. dist = delta.LengthFast();
  1317. if ( ai_debugMove.GetBool() ) {
  1318. gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false );
  1319. gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
  1320. }
  1321. return dist;
  1322. }
  1323. idReachability *reach;
  1324. int travelTime;
  1325. if ( !aas->RouteToGoalArea( fromArea, start, toArea, travelFlags, travelTime, &reach ) ) {
  1326. return -1;
  1327. }
  1328. if ( ai_debugMove.GetBool() ) {
  1329. if ( move.moveType == MOVETYPE_FLY ) {
  1330. aas->ShowFlyPath( start, toArea, end );
  1331. } else {
  1332. aas->ShowWalkPath( start, toArea, end );
  1333. }
  1334. }
  1335. return travelTime;
  1336. }
  1337. /*
  1338. =====================
  1339. idAI::StopMove
  1340. =====================
  1341. */
  1342. void idAI::StopMove( moveStatus_t status ) {
  1343. AI_MOVE_DONE = true;
  1344. AI_FORWARD = false;
  1345. move.moveCommand = MOVE_NONE;
  1346. move.moveStatus = status;
  1347. move.toAreaNum = 0;
  1348. move.goalEntity = NULL;
  1349. move.moveDest = physicsObj.GetOrigin();
  1350. AI_DEST_UNREACHABLE = false;
  1351. AI_OBSTACLE_IN_PATH = false;
  1352. AI_BLOCKED = false;
  1353. move.startTime = gameLocal.time;
  1354. move.duration = 0;
  1355. move.range = 0.0f;
  1356. move.speed = 0.0f;
  1357. move.anim = 0;
  1358. move.moveDir.Zero();
  1359. move.lastMoveOrigin.Zero();
  1360. move.lastMoveTime = gameLocal.time;
  1361. }
  1362. /*
  1363. =====================
  1364. idAI::FaceEnemy
  1365. Continually face the enemy's last known position. MoveDone is always true in this case.
  1366. =====================
  1367. */
  1368. bool idAI::FaceEnemy( void ) {
  1369. idActor *enemyEnt = enemy.GetEntity();
  1370. if ( !enemyEnt ) {
  1371. StopMove( MOVE_STATUS_DEST_NOT_FOUND );
  1372. return false;
  1373. }
  1374. TurnToward( lastVisibleEnemyPos );
  1375. move.goalEntity = enemyEnt;
  1376. move.moveDest = physicsObj.GetOrigin();
  1377. move.moveCommand = MOVE_FACE_ENEMY;
  1378. move.moveStatus = MOVE_STATUS_WAITING;
  1379. move.startTime = gameLocal.time;
  1380. move.speed = 0.0f;
  1381. AI_MOVE_DONE = true;
  1382. AI_FORWARD = false;
  1383. AI_DEST_UNREACHABLE = false;
  1384. return true;
  1385. }
  1386. /*
  1387. =====================
  1388. idAI::FaceEntity
  1389. Continually face the entity position. MoveDone is always true in this case.
  1390. =====================
  1391. */
  1392. bool idAI::FaceEntity( idEntity *ent ) {
  1393. if ( !ent ) {
  1394. StopMove( MOVE_STATUS_DEST_NOT_FOUND );
  1395. return false;
  1396. }
  1397. idVec3 entityOrg = ent->GetPhysics()->GetOrigin();
  1398. TurnToward( entityOrg );
  1399. move.goalEntity = ent;
  1400. move.moveDest = physicsObj.GetOrigin();
  1401. move.moveCommand = MOVE_FACE_ENTITY;
  1402. move.moveStatus = MOVE_STATUS_WAITING;
  1403. move.startTime = gameLocal.time;
  1404. move.speed = 0.0f;
  1405. AI_MOVE_DONE = true;
  1406. AI_FORWARD = false;
  1407. AI_DEST_UNREACHABLE = false;
  1408. return true;
  1409. }
  1410. /*
  1411. =====================
  1412. idAI::DirectMoveToPosition
  1413. =====================
  1414. */
  1415. bool idAI::DirectMoveToPosition( const idVec3 &pos ) {
  1416. if ( ReachedPos( pos, move.moveCommand ) ) {
  1417. StopMove( MOVE_STATUS_DONE );
  1418. return true;
  1419. }
  1420. move.moveDest = pos;
  1421. move.goalEntity = NULL;
  1422. move.moveCommand = MOVE_TO_POSITION_DIRECT;
  1423. move.moveStatus = MOVE_STATUS_MOVING;
  1424. move.startTime = gameLocal.time;
  1425. move.speed = fly_speed;
  1426. AI_MOVE_DONE = false;
  1427. AI_DEST_UNREACHABLE = false;
  1428. AI_FORWARD = true;
  1429. if ( move.moveType == MOVETYPE_FLY ) {
  1430. idVec3 dir = pos - physicsObj.GetOrigin();
  1431. dir.Normalize();
  1432. dir *= fly_speed;
  1433. physicsObj.SetLinearVelocity( dir );
  1434. }
  1435. return true;
  1436. }
  1437. /*
  1438. =====================
  1439. idAI::MoveToEnemyHeight
  1440. =====================
  1441. */
  1442. bool idAI::MoveToEnemyHeight( void ) {
  1443. idActor *enemyEnt = enemy.GetEntity();
  1444. if ( !enemyEnt || ( move.moveType != MOVETYPE_FLY ) ) {
  1445. StopMove( MOVE_STATUS_DEST_NOT_FOUND );
  1446. return false;
  1447. }
  1448. move.moveDest.z = lastVisibleEnemyPos.z + enemyEnt->EyeOffset().z + fly_offset;
  1449. move.goalEntity = enemyEnt;
  1450. move.moveCommand = MOVE_TO_ENEMYHEIGHT;
  1451. move.moveStatus = MOVE_STATUS_MOVING;
  1452. move.startTime = gameLocal.time;
  1453. move.speed = 0.0f;
  1454. AI_MOVE_DONE = false;
  1455. AI_DEST_UNREACHABLE = false;
  1456. AI_FORWARD = false;
  1457. return true;
  1458. }
  1459. /*
  1460. =====================
  1461. idAI::MoveToEnemy
  1462. =====================
  1463. */
  1464. bool idAI::MoveToEnemy( void ) {
  1465. int areaNum;
  1466. aasPath_t path;
  1467. idActor *enemyEnt = enemy.GetEntity();
  1468. if ( !enemyEnt ) {
  1469. StopMove( MOVE_STATUS_DEST_NOT_FOUND );
  1470. return false;
  1471. }
  1472. if ( ReachedPos( lastVisibleReachableEnemyPos, MOVE_TO_ENEMY ) ) {
  1473. if ( !ReachedPos( lastVisibleEnemyPos, MOVE_TO_ENEMY ) || !AI_ENEMY_VISIBLE ) {
  1474. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1475. AI_DEST_UNREACHABLE = true;
  1476. return false;
  1477. }
  1478. StopMove( MOVE_STATUS_DONE );
  1479. return true;
  1480. }
  1481. idVec3 pos = lastVisibleReachableEnemyPos;
  1482. move.toAreaNum = 0;
  1483. if ( aas ) {
  1484. move.toAreaNum = PointReachableAreaNum( pos );
  1485. aas->PushPointIntoAreaNum( move.toAreaNum, pos );
  1486. areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
  1487. if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
  1488. AI_DEST_UNREACHABLE = true;
  1489. return false;
  1490. }
  1491. }
  1492. if ( !move.toAreaNum ) {
  1493. // if only trying to update the enemy position
  1494. if ( move.moveCommand == MOVE_TO_ENEMY ) {
  1495. if ( !aas ) {
  1496. // keep the move destination up to date for wandering
  1497. move.moveDest = pos;
  1498. }
  1499. return false;
  1500. }
  1501. if ( !NewWanderDir( pos ) ) {
  1502. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1503. AI_DEST_UNREACHABLE = true;
  1504. return false;
  1505. }
  1506. }
  1507. if ( move.moveCommand != MOVE_TO_ENEMY ) {
  1508. move.moveCommand = MOVE_TO_ENEMY;
  1509. move.startTime = gameLocal.time;
  1510. }
  1511. move.moveDest = pos;
  1512. move.goalEntity = enemyEnt;
  1513. move.speed = fly_speed;
  1514. move.moveStatus = MOVE_STATUS_MOVING;
  1515. AI_MOVE_DONE = false;
  1516. AI_DEST_UNREACHABLE = false;
  1517. AI_FORWARD = true;
  1518. return true;
  1519. }
  1520. /*
  1521. =====================
  1522. idAI::MoveToEntity
  1523. =====================
  1524. */
  1525. bool idAI::MoveToEntity( idEntity *ent ) {
  1526. int areaNum;
  1527. aasPath_t path;
  1528. idVec3 pos;
  1529. if ( !ent ) {
  1530. StopMove( MOVE_STATUS_DEST_NOT_FOUND );
  1531. return false;
  1532. }
  1533. pos = ent->GetPhysics()->GetOrigin();
  1534. if ( ( move.moveType != MOVETYPE_FLY ) && ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntityOrigin != pos ) ) ) {
  1535. ent->GetFloorPos( 64.0f, pos );
  1536. }
  1537. if ( ReachedPos( pos, MOVE_TO_ENTITY ) ) {
  1538. StopMove( MOVE_STATUS_DONE );
  1539. return true;
  1540. }
  1541. move.toAreaNum = 0;
  1542. if ( aas ) {
  1543. move.toAreaNum = PointReachableAreaNum( pos );
  1544. aas->PushPointIntoAreaNum( move.toAreaNum, pos );
  1545. areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
  1546. if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
  1547. AI_DEST_UNREACHABLE = true;
  1548. return false;
  1549. }
  1550. }
  1551. if ( !move.toAreaNum ) {
  1552. // if only trying to update the entity position
  1553. if ( move.moveCommand == MOVE_TO_ENTITY ) {
  1554. if ( !aas ) {
  1555. // keep the move destination up to date for wandering
  1556. move.moveDest = pos;
  1557. }
  1558. return false;
  1559. }
  1560. if ( !NewWanderDir( pos ) ) {
  1561. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1562. AI_DEST_UNREACHABLE = true;
  1563. return false;
  1564. }
  1565. }
  1566. if ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntity.GetEntity() != ent ) ) {
  1567. move.startTime = gameLocal.time;
  1568. move.goalEntity = ent;
  1569. move.moveCommand = MOVE_TO_ENTITY;
  1570. }
  1571. move.moveDest = pos;
  1572. move.goalEntityOrigin = ent->GetPhysics()->GetOrigin();
  1573. move.moveStatus = MOVE_STATUS_MOVING;
  1574. move.speed = fly_speed;
  1575. AI_MOVE_DONE = false;
  1576. AI_DEST_UNREACHABLE = false;
  1577. AI_FORWARD = true;
  1578. return true;
  1579. }
  1580. /*
  1581. =====================
  1582. idAI::MoveOutOfRange
  1583. =====================
  1584. */
  1585. bool idAI::MoveOutOfRange( idEntity *ent, float range ) {
  1586. int areaNum;
  1587. aasObstacle_t obstacle;
  1588. aasGoal_t goal;
  1589. idBounds bounds;
  1590. idVec3 pos;
  1591. if ( !aas || !ent ) {
  1592. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1593. AI_DEST_UNREACHABLE = true;
  1594. return false;
  1595. }
  1596. const idVec3 &org = physicsObj.GetOrigin();
  1597. areaNum = PointReachableAreaNum( org );
  1598. // consider the entity the monster is getting close to as an obstacle
  1599. obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
  1600. if ( ent == enemy.GetEntity() ) {
  1601. pos = lastVisibleEnemyPos;
  1602. } else {
  1603. pos = ent->GetPhysics()->GetOrigin();
  1604. }
  1605. idAASFindAreaOutOfRange findGoal( pos, range );
  1606. if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) {
  1607. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1608. AI_DEST_UNREACHABLE = true;
  1609. return false;
  1610. }
  1611. if ( ReachedPos( goal.origin, move.moveCommand ) ) {
  1612. StopMove( MOVE_STATUS_DONE );
  1613. return true;
  1614. }
  1615. move.moveDest = goal.origin;
  1616. move.toAreaNum = goal.areaNum;
  1617. move.goalEntity = ent;
  1618. move.moveCommand = MOVE_OUT_OF_RANGE;
  1619. move.moveStatus = MOVE_STATUS_MOVING;
  1620. move.range = range;
  1621. move.speed = fly_speed;
  1622. move.startTime = gameLocal.time;
  1623. AI_MOVE_DONE = false;
  1624. AI_DEST_UNREACHABLE = false;
  1625. AI_FORWARD = true;
  1626. return true;
  1627. }
  1628. /*
  1629. =====================
  1630. idAI::MoveToAttackPosition
  1631. =====================
  1632. */
  1633. bool idAI::MoveToAttackPosition( idEntity *ent, int attack_anim ) {
  1634. int areaNum;
  1635. aasObstacle_t obstacle;
  1636. aasGoal_t goal;
  1637. idBounds bounds;
  1638. idVec3 pos;
  1639. if ( !aas || !ent ) {
  1640. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1641. AI_DEST_UNREACHABLE = true;
  1642. return false;
  1643. }
  1644. const idVec3 &org = physicsObj.GetOrigin();
  1645. areaNum = PointReachableAreaNum( org );
  1646. // consider the entity the monster is getting close to as an obstacle
  1647. obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
  1648. if ( ent == enemy.GetEntity() ) {
  1649. pos = lastVisibleEnemyPos;
  1650. } else {
  1651. pos = ent->GetPhysics()->GetOrigin();
  1652. }
  1653. idAASFindAttackPosition findGoal( this, physicsObj.GetGravityAxis(), ent, pos, missileLaunchOffset[ attack_anim ] );
  1654. if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) {
  1655. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1656. AI_DEST_UNREACHABLE = true;
  1657. return false;
  1658. }
  1659. move.moveDest = goal.origin;
  1660. move.toAreaNum = goal.areaNum;
  1661. move.goalEntity = ent;
  1662. move.moveCommand = MOVE_TO_ATTACK_POSITION;
  1663. move.moveStatus = MOVE_STATUS_MOVING;
  1664. move.speed = fly_speed;
  1665. move.startTime = gameLocal.time;
  1666. move.anim = attack_anim;
  1667. AI_MOVE_DONE = false;
  1668. AI_DEST_UNREACHABLE = false;
  1669. AI_FORWARD = true;
  1670. return true;
  1671. }
  1672. /*
  1673. =====================
  1674. idAI::MoveToPosition
  1675. =====================
  1676. */
  1677. bool idAI::MoveToPosition( const idVec3 &pos ) {
  1678. idVec3 org;
  1679. int areaNum;
  1680. aasPath_t path;
  1681. if ( ReachedPos( pos, move.moveCommand ) ) {
  1682. StopMove( MOVE_STATUS_DONE );
  1683. return true;
  1684. }
  1685. org = pos;
  1686. move.toAreaNum = 0;
  1687. if ( aas ) {
  1688. move.toAreaNum = PointReachableAreaNum( org );
  1689. aas->PushPointIntoAreaNum( move.toAreaNum, org );
  1690. areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
  1691. if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, org ) ) {
  1692. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1693. AI_DEST_UNREACHABLE = true;
  1694. return false;
  1695. }
  1696. }
  1697. if ( !move.toAreaNum && !NewWanderDir( org ) ) {
  1698. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1699. AI_DEST_UNREACHABLE = true;
  1700. return false;
  1701. }
  1702. move.moveDest = org;
  1703. move.goalEntity = NULL;
  1704. move.moveCommand = MOVE_TO_POSITION;
  1705. move.moveStatus = MOVE_STATUS_MOVING;
  1706. move.startTime = gameLocal.time;
  1707. move.speed = fly_speed;
  1708. AI_MOVE_DONE = false;
  1709. AI_DEST_UNREACHABLE = false;
  1710. AI_FORWARD = true;
  1711. return true;
  1712. }
  1713. /*
  1714. =====================
  1715. idAI::MoveToCover
  1716. =====================
  1717. */
  1718. bool idAI::MoveToCover( idEntity *entity, const idVec3 &hideFromPos ) {
  1719. int areaNum;
  1720. aasObstacle_t obstacle;
  1721. aasGoal_t hideGoal;
  1722. idBounds bounds;
  1723. if ( !aas || !entity ) {
  1724. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1725. AI_DEST_UNREACHABLE = true;
  1726. return false;
  1727. }
  1728. const idVec3 &org = physicsObj.GetOrigin();
  1729. areaNum = PointReachableAreaNum( org );
  1730. // consider the entity the monster tries to hide from as an obstacle
  1731. obstacle.absBounds = entity->GetPhysics()->GetAbsBounds();
  1732. idAASFindCover findCover( hideFromPos );
  1733. if ( !aas->FindNearestGoal( hideGoal, areaNum, org, hideFromPos, travelFlags, &obstacle, 1, findCover ) ) {
  1734. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1735. AI_DEST_UNREACHABLE = true;
  1736. return false;
  1737. }
  1738. if ( ReachedPos( hideGoal.origin, move.moveCommand ) ) {
  1739. StopMove( MOVE_STATUS_DONE );
  1740. return true;
  1741. }
  1742. move.moveDest = hideGoal.origin;
  1743. move.toAreaNum = hideGoal.areaNum;
  1744. move.goalEntity = entity;
  1745. move.moveCommand = MOVE_TO_COVER;
  1746. move.moveStatus = MOVE_STATUS_MOVING;
  1747. move.startTime = gameLocal.time;
  1748. move.speed = fly_speed;
  1749. AI_MOVE_DONE = false;
  1750. AI_DEST_UNREACHABLE = false;
  1751. AI_FORWARD = true;
  1752. return true;
  1753. }
  1754. /*
  1755. =====================
  1756. idAI::SlideToPosition
  1757. =====================
  1758. */
  1759. bool idAI::SlideToPosition( const idVec3 &pos, float time ) {
  1760. StopMove( MOVE_STATUS_DONE );
  1761. move.moveDest = pos;
  1762. move.goalEntity = NULL;
  1763. move.moveCommand = MOVE_SLIDE_TO_POSITION;
  1764. move.moveStatus = MOVE_STATUS_MOVING;
  1765. move.startTime = gameLocal.time;
  1766. move.duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) );
  1767. AI_MOVE_DONE = false;
  1768. AI_DEST_UNREACHABLE = false;
  1769. AI_FORWARD = false;
  1770. if ( move.duration > 0 ) {
  1771. move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration );
  1772. if ( move.moveType != MOVETYPE_FLY ) {
  1773. move.moveDir.z = 0.0f;
  1774. }
  1775. move.speed = move.moveDir.LengthFast();
  1776. }
  1777. return true;
  1778. }
  1779. /*
  1780. =====================
  1781. idAI::WanderAround
  1782. =====================
  1783. */
  1784. bool idAI::WanderAround( void ) {
  1785. StopMove( MOVE_STATUS_DONE );
  1786. move.moveDest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
  1787. if ( !NewWanderDir( move.moveDest ) ) {
  1788. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1789. AI_DEST_UNREACHABLE = true;
  1790. return false;
  1791. }
  1792. move.moveCommand = MOVE_WANDER;
  1793. move.moveStatus = MOVE_STATUS_MOVING;
  1794. move.startTime = gameLocal.time;
  1795. move.speed = fly_speed;
  1796. AI_MOVE_DONE = false;
  1797. AI_FORWARD = true;
  1798. return true;
  1799. }
  1800. /*
  1801. =====================
  1802. idAI::MoveDone
  1803. =====================
  1804. */
  1805. bool idAI::MoveDone( void ) const {
  1806. return ( move.moveCommand == MOVE_NONE );
  1807. }
  1808. /*
  1809. ================
  1810. idAI::StepDirection
  1811. ================
  1812. */
  1813. bool idAI::StepDirection( float dir ) {
  1814. predictedPath_t path;
  1815. idVec3 org;
  1816. move.wanderYaw = dir;
  1817. move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward();
  1818. org = physicsObj.GetOrigin();
  1819. idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
  1820. if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) {
  1821. // don't report being blocked if we ran into our goal entity
  1822. return true;
  1823. }
  1824. if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) {
  1825. float z;
  1826. move.moveDir = path.endVelocity * 1.0f / 48.0f;
  1827. // trace down to the floor and see if we can go forward
  1828. idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path );
  1829. idVec3 floorPos = path.endPos;
  1830. idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path );
  1831. if ( !path.endEvent ) {
  1832. move.moveDir.z = -1.0f;
  1833. return true;
  1834. }
  1835. // trace up to see if we can go over something and go forward
  1836. idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path );
  1837. idVec3 ceilingPos = path.endPos;
  1838. for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) {
  1839. idVec3 start;
  1840. if ( z <= ceilingPos.z ) {
  1841. start.x = org.x;
  1842. start.y = org.y;
  1843. start.z = z;
  1844. } else {
  1845. start = ceilingPos;
  1846. }
  1847. idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path );
  1848. if ( !path.endEvent ) {
  1849. move.moveDir.z = 1.0f;
  1850. return true;
  1851. }
  1852. }
  1853. return false;
  1854. }
  1855. return ( path.endEvent == 0 );
  1856. }
  1857. /*
  1858. ================
  1859. idAI::NewWanderDir
  1860. ================
  1861. */
  1862. bool idAI::NewWanderDir( const idVec3 &dest ) {
  1863. float deltax, deltay;
  1864. float d[ 3 ];
  1865. float tdir, olddir, turnaround;
  1866. move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 );
  1867. olddir = idMath::AngleNormalize360( ( int )( current_yaw / 45 ) * 45 );
  1868. turnaround = idMath::AngleNormalize360( olddir - 180 );
  1869. idVec3 org = physicsObj.GetOrigin();
  1870. deltax = dest.x - org.x;
  1871. deltay = dest.y - org.y;
  1872. if ( deltax > 10 ) {
  1873. d[ 1 ]= 0;
  1874. } else if ( deltax < -10 ) {
  1875. d[ 1 ] = 180;
  1876. } else {
  1877. d[ 1 ] = DI_NODIR;
  1878. }
  1879. if ( deltay < -10 ) {
  1880. d[ 2 ] = 270;
  1881. } else if ( deltay > 10 ) {
  1882. d[ 2 ] = 90;
  1883. } else {
  1884. d[ 2 ] = DI_NODIR;
  1885. }
  1886. // try direct route
  1887. if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) {
  1888. if ( d[ 1 ] == 0 ) {
  1889. tdir = d[ 2 ] == 90 ? 45 : 315;
  1890. } else {
  1891. tdir = d[ 2 ] == 90 ? 135 : 215;
  1892. }
  1893. if ( tdir != turnaround && StepDirection( tdir ) ) {
  1894. return true;
  1895. }
  1896. }
  1897. // try other directions
  1898. if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) {
  1899. tdir = d[ 1 ];
  1900. d[ 1 ] = d[ 2 ];
  1901. d[ 2 ] = tdir;
  1902. }
  1903. if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) {
  1904. return true;
  1905. }
  1906. if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) {
  1907. return true;
  1908. }
  1909. // there is no direct path to the player, so pick another direction
  1910. if ( olddir != DI_NODIR && StepDirection( olddir ) ) {
  1911. return true;
  1912. }
  1913. // randomly determine direction of search
  1914. if ( gameLocal.random.RandomInt() & 1 ) {
  1915. for( tdir = 0; tdir <= 315; tdir += 45 ) {
  1916. if ( tdir != turnaround && StepDirection( tdir ) ) {
  1917. return true;
  1918. }
  1919. }
  1920. } else {
  1921. for ( tdir = 315; tdir >= 0; tdir -= 45 ) {
  1922. if ( tdir != turnaround && StepDirection( tdir ) ) {
  1923. return true;
  1924. }
  1925. }
  1926. }
  1927. if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) {
  1928. return true;
  1929. }
  1930. // can't move
  1931. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  1932. return false;
  1933. }
  1934. /*
  1935. =====================
  1936. idAI::GetMovePos
  1937. =====================
  1938. */
  1939. bool idAI::GetMovePos( idVec3 &seekPos ) {
  1940. int areaNum;
  1941. aasPath_t path;
  1942. bool result;
  1943. idVec3 org;
  1944. org = physicsObj.GetOrigin();
  1945. seekPos = org;
  1946. switch( move.moveCommand ) {
  1947. case MOVE_NONE :
  1948. seekPos = move.moveDest;
  1949. return false;
  1950. break;
  1951. case MOVE_FACE_ENEMY :
  1952. case MOVE_FACE_ENTITY :
  1953. seekPos = move.moveDest;
  1954. return false;
  1955. break;
  1956. case MOVE_TO_POSITION_DIRECT :
  1957. seekPos = move.moveDest;
  1958. if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
  1959. StopMove( MOVE_STATUS_DONE );
  1960. }
  1961. return false;
  1962. break;
  1963. case MOVE_SLIDE_TO_POSITION :
  1964. seekPos = org;
  1965. return false;
  1966. break;
  1967. }
  1968. if ( move.moveCommand == MOVE_TO_ENTITY ) {
  1969. MoveToEntity( move.goalEntity.GetEntity() );
  1970. }
  1971. move.moveStatus = MOVE_STATUS_MOVING;
  1972. result = false;
  1973. if ( gameLocal.time > move.blockTime ) {
  1974. if ( move.moveCommand == MOVE_WANDER ) {
  1975. move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
  1976. } else {
  1977. if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
  1978. StopMove( MOVE_STATUS_DONE );
  1979. seekPos = org;
  1980. return false;
  1981. }
  1982. }
  1983. if ( aas && move.toAreaNum ) {
  1984. areaNum = PointReachableAreaNum( org );
  1985. if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) {
  1986. seekPos = path.moveGoal;
  1987. result = true;
  1988. move.nextWanderTime = 0;
  1989. } else {
  1990. AI_DEST_UNREACHABLE = true;
  1991. }
  1992. }
  1993. }
  1994. if ( !result ) {
  1995. // wander around
  1996. if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) {
  1997. result = NewWanderDir( move.moveDest );
  1998. if ( !result ) {
  1999. StopMove( MOVE_STATUS_DEST_UNREACHABLE );
  2000. AI_DEST_UNREACHABLE = true;
  2001. seekPos = org;
  2002. return false;
  2003. }
  2004. } else {
  2005. result = true;
  2006. }
  2007. seekPos = org + move.moveDir * 2048.0f;
  2008. if ( ai_debugMove.GetBool() ) {
  2009. gameRenderWorld->DebugLine( colorYellow, org, seekPos, gameLocal.msec, true );
  2010. }
  2011. } else {
  2012. AI_DEST_UNREACHABLE = false;
  2013. }
  2014. if ( result && ( ai_debugMove.GetBool() ) ) {
  2015. gameRenderWorld->DebugLine( colorCyan, physicsObj.GetOrigin(), seekPos );
  2016. }
  2017. return result;
  2018. }
  2019. /*
  2020. =====================
  2021. idAI::EntityCanSeePos
  2022. =====================
  2023. */
  2024. bool idAI::EntityCanSeePos( idActor *actor, const idVec3 &actorOrigin, const idVec3 &pos ) {
  2025. idVec3 eye, point;
  2026. trace_t results;
  2027. pvsHandle_t handle;
  2028. handle = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() );
  2029. if ( !gameLocal.pvs.InCurrentPVS( handle, GetPVSAreas(), GetNumPVSAreas() ) ) {
  2030. gameLocal.pvs.FreeCurrentPVS( handle );
  2031. return false;
  2032. }
  2033. gameLocal.pvs.FreeCurrentPVS( handle );
  2034. eye = actorOrigin + actor->EyeOffset();
  2035. point = pos;
  2036. point[2] += 1.0f;
  2037. physicsObj.DisableClip();
  2038. gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
  2039. if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
  2040. physicsObj.EnableClip();
  2041. return true;
  2042. }
  2043. const idBounds &bounds = physicsObj.GetBounds();
  2044. point[2] += bounds[1][2] - bounds[0][2];
  2045. gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
  2046. physicsObj.EnableClip();
  2047. if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
  2048. return true;
  2049. }
  2050. return false;
  2051. }
  2052. /*
  2053. =====================
  2054. idAI::BlockedFailSafe
  2055. =====================
  2056. */
  2057. void idAI::BlockedFailSafe( void ) {
  2058. if ( !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f ) {
  2059. return;
  2060. }
  2061. if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL ||
  2062. ( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( blockedRadius ) ) {
  2063. move.lastMoveOrigin = physicsObj.GetOrigin();
  2064. move.lastMoveTime = gameLocal.time;
  2065. }
  2066. if ( move.lastMoveTime < gameLocal.time - blockedMoveTime ) {
  2067. if ( lastAttackTime < gameLocal.time - blockedAttackTime ) {
  2068. AI_BLOCKED = true;
  2069. move.lastMoveTime = gameLocal.time;
  2070. }
  2071. }
  2072. }
  2073. /***********************************************************************
  2074. turning
  2075. ***********************************************************************/
  2076. /*
  2077. =====================
  2078. idAI::Turn
  2079. =====================
  2080. */
  2081. void idAI::Turn( void ) {
  2082. float diff;
  2083. float diff2;
  2084. float turnAmount;
  2085. animFlags_t animflags;
  2086. if ( !turnRate ) {
  2087. return;
  2088. }
  2089. // check if the animator has marker this anim as non-turning
  2090. if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) {
  2091. animflags = legsAnim.GetAnimFlags();
  2092. } else {
  2093. animflags = torsoAnim.GetAnimFlags();
  2094. }
  2095. if ( animflags.ai_no_turn ) {
  2096. return;
  2097. }
  2098. if ( anim_turn_angles && animflags.anim_turn ) {
  2099. idMat3 rotateAxis;
  2100. // set the blend between no turn and full turn
  2101. float frac = anim_turn_amount / anim_turn_angles;
  2102. animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac );
  2103. animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac );
  2104. animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac );
  2105. animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac );
  2106. // get the total rotation from the start of the anim
  2107. animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis );
  2108. current_yaw = idMath::AngleNormalize180( anim_turn_yaw + rotateAxis[ 0 ].ToYaw() );
  2109. } else {
  2110. diff = idMath::AngleNormalize180( ideal_yaw - current_yaw );
  2111. turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec );
  2112. if ( turnVel > turnRate ) {
  2113. turnVel = turnRate;
  2114. } else if ( turnVel < -turnRate ) {
  2115. turnVel = -turnRate;
  2116. }
  2117. turnAmount = turnVel * MS2SEC( gameLocal.msec );
  2118. if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) {
  2119. turnVel = diff / MS2SEC( gameLocal.msec );
  2120. turnAmount = diff;
  2121. } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) {
  2122. turnVel = diff / MS2SEC( gameLocal.msec );
  2123. turnAmount = diff;
  2124. }
  2125. current_yaw += turnAmount;
  2126. current_yaw = idMath::AngleNormalize180( current_yaw );
  2127. diff2 = idMath::AngleNormalize180( ideal_yaw - current_yaw );
  2128. if ( idMath::Fabs( diff2 ) < 0.1f ) {
  2129. current_yaw = ideal_yaw;
  2130. }
  2131. }
  2132. viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
  2133. if ( ai_debugMove.GetBool() ) {
  2134. const idVec3 &org = physicsObj.GetOrigin();
  2135. gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, ideal_yaw, 0 ).ToForward() * 64, gameLocal.msec );
  2136. gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, current_yaw, 0 ).ToForward() * 48, gameLocal.msec );
  2137. gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, current_yaw + turnVel, 0 ).ToForward() * 32, gameLocal.msec );
  2138. }
  2139. }
  2140. /*
  2141. =====================
  2142. idAI::FacingIdeal
  2143. =====================
  2144. */
  2145. bool idAI::FacingIdeal( void ) {
  2146. float diff;
  2147. if ( !turnRate ) {
  2148. return true;
  2149. }
  2150. diff = idMath::AngleNormalize180( current_yaw - ideal_yaw );
  2151. if ( idMath::Fabs( diff ) < 0.01f ) {
  2152. // force it to be exact
  2153. current_yaw = ideal_yaw;
  2154. return true;
  2155. }
  2156. return false;
  2157. }
  2158. /*
  2159. =====================
  2160. idAI::TurnToward
  2161. =====================
  2162. */
  2163. bool idAI::TurnToward( float yaw ) {
  2164. ideal_yaw = idMath::AngleNormalize180( yaw );
  2165. bool result = FacingIdeal();
  2166. return result;
  2167. }
  2168. /*
  2169. =====================
  2170. idAI::TurnToward
  2171. =====================
  2172. */
  2173. bool idAI::TurnToward( const idVec3 &pos ) {
  2174. idVec3 dir;
  2175. idVec3 local_dir;
  2176. float lengthSqr;
  2177. dir = pos - physicsObj.GetOrigin();
  2178. physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
  2179. local_dir.z = 0.0f;
  2180. lengthSqr = local_dir.LengthSqr();
  2181. if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) {
  2182. ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() );
  2183. }
  2184. bool result = FacingIdeal();
  2185. return result;
  2186. }
  2187. /***********************************************************************
  2188. Movement
  2189. ***********************************************************************/
  2190. /*
  2191. ================
  2192. idAI::ApplyImpulse
  2193. ================
  2194. */
  2195. void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
  2196. // FIXME: Jim take a look at this and see if this is a reasonable thing to do
  2197. // instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 )
  2198. // and we don't want him taking physics impulses as it can knock him off the path
  2199. if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) {
  2200. idActor::ApplyImpulse( ent, id, point, impulse );
  2201. }
  2202. }
  2203. /*
  2204. =====================
  2205. idAI::GetMoveDelta
  2206. =====================
  2207. */
  2208. void idAI::GetMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) {
  2209. idVec3 oldModelOrigin;
  2210. idVec3 modelOrigin;
  2211. animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta );
  2212. delta = axis * delta;
  2213. if ( modelOffset != vec3_zero ) {
  2214. // the pivot of the monster's model is around its origin, and not around the bounding
  2215. // box's origin, so we have to compensate for this when the model is offset so that
  2216. // the monster still appears to rotate around it's origin.
  2217. oldModelOrigin = modelOffset * oldaxis;
  2218. modelOrigin = modelOffset * axis;
  2219. delta += oldModelOrigin - modelOrigin;
  2220. }
  2221. delta *= physicsObj.GetGravityAxis();
  2222. }
  2223. /*
  2224. =====================
  2225. idAI::CheckObstacleAvoidance
  2226. =====================
  2227. */
  2228. void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &newPos ) {
  2229. idEntity *obstacle;
  2230. obstaclePath_t path;
  2231. idVec3 dir;
  2232. float dist;
  2233. bool foundPath;
  2234. if ( ignore_obstacles ) {
  2235. newPos = goalPos;
  2236. move.obstacle = NULL;
  2237. return;
  2238. }
  2239. const idVec3 &origin = physicsObj.GetOrigin();
  2240. obstacle = NULL;
  2241. AI_OBSTACLE_IN_PATH = false;
  2242. foundPath = FindPathAroundObstacles( &physicsObj, aas, enemy.GetEntity(), origin, goalPos, path );
  2243. if ( ai_showObstacleAvoidance.GetBool() ) {
  2244. gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), gameLocal.msec );
  2245. gameRenderWorld->DebugLine( foundPath ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), gameLocal.msec );
  2246. }
  2247. if ( !foundPath ) {
  2248. // couldn't get around obstacles
  2249. if ( path.firstObstacle ) {
  2250. AI_OBSTACLE_IN_PATH = true;
  2251. if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.firstObstacle->GetPhysics()->GetAbsBounds() ) ) {
  2252. obstacle = path.firstObstacle;
  2253. }
  2254. } else if ( path.startPosObstacle ) {
  2255. AI_OBSTACLE_IN_PATH = true;
  2256. if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.startPosObstacle->GetPhysics()->GetAbsBounds() ) ) {
  2257. obstacle = path.startPosObstacle;
  2258. }
  2259. } else {
  2260. // Blocked by wall
  2261. move.moveStatus = MOVE_STATUS_BLOCKED_BY_WALL;
  2262. }
  2263. #if 0
  2264. } else if ( path.startPosObstacle ) {
  2265. // check if we're past where the our origin was pushed out of the obstacle
  2266. dir = goalPos - origin;
  2267. dir.Normalize();
  2268. dist = ( path.seekPos - origin ) * dir;
  2269. if ( dist < 1.0f ) {
  2270. AI_OBSTACLE_IN_PATH = true;
  2271. obstacle = path.startPosObstacle;
  2272. }
  2273. #endif
  2274. } else if ( path.seekPosObstacle ) {
  2275. // if the AI is very close to the path.seekPos already and path.seekPosObstacle != NULL
  2276. // then we want to push the path.seekPosObstacle entity out of the way
  2277. AI_OBSTACLE_IN_PATH = true;
  2278. // check if we're past where the goalPos was pushed out of the obstacle
  2279. dir = goalPos - origin;
  2280. dir.Normalize();
  2281. dist = ( path.seekPos - origin ) * dir;
  2282. if ( dist < 1.0f ) {
  2283. obstacle = path.seekPosObstacle;
  2284. }
  2285. }
  2286. // if we had an obstacle, set our move status based on the type, and kick it out of the way if it's a moveable
  2287. if ( obstacle ) {
  2288. if ( obstacle->IsType( idActor::Type ) ) {
  2289. // monsters aren't kickable
  2290. if ( obstacle == enemy.GetEntity() ) {
  2291. move.moveStatus = MOVE_STATUS_BLOCKED_BY_ENEMY;
  2292. } else {
  2293. move.moveStatus = MOVE_STATUS_BLOCKED_BY_MONSTER;
  2294. }
  2295. } else {
  2296. // try kicking the object out of the way
  2297. move.moveStatus = MOVE_STATUS_BLOCKED_BY_OBJECT;
  2298. }
  2299. newPos = obstacle->GetPhysics()->GetOrigin();
  2300. //newPos = path.seekPos;
  2301. move.obstacle = obstacle;
  2302. } else {
  2303. newPos = path.seekPos;
  2304. move.obstacle = NULL;
  2305. }
  2306. }
  2307. /*
  2308. =====================
  2309. idAI::DeadMove
  2310. =====================
  2311. */
  2312. void idAI::DeadMove( void ) {
  2313. idVec3 delta;
  2314. monsterMoveResult_t moveResult;
  2315. idVec3 org = physicsObj.GetOrigin();
  2316. GetMoveDelta( viewAxis, viewAxis, delta );
  2317. physicsObj.SetDelta( delta );
  2318. RunPhysics();
  2319. moveResult = physicsObj.GetMoveResult();
  2320. AI_ONGROUND = physicsObj.OnGround();
  2321. }
  2322. /*
  2323. =====================
  2324. idAI::AnimMove
  2325. =====================
  2326. */
  2327. void idAI::AnimMove( void ) {
  2328. idVec3 goalPos;
  2329. idVec3 delta;
  2330. idVec3 goalDelta;
  2331. float goalDist;
  2332. monsterMoveResult_t moveResult;
  2333. idVec3 newDest;
  2334. idVec3 oldorigin = physicsObj.GetOrigin();
  2335. idMat3 oldaxis = viewAxis;
  2336. AI_BLOCKED = false;
  2337. if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
  2338. move.lastMoveOrigin.Zero();
  2339. move.lastMoveTime = gameLocal.time;
  2340. }
  2341. move.obstacle = NULL;
  2342. if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
  2343. TurnToward( lastVisibleEnemyPos );
  2344. goalPos = oldorigin;
  2345. } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
  2346. TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
  2347. goalPos = oldorigin;
  2348. } else if ( GetMovePos( goalPos ) ) {
  2349. if ( move.moveCommand != MOVE_WANDER ) {
  2350. CheckObstacleAvoidance( goalPos, newDest );
  2351. TurnToward( newDest );
  2352. } else {
  2353. TurnToward( goalPos );
  2354. }
  2355. }
  2356. Turn();
  2357. if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
  2358. if ( gameLocal.time < move.startTime + move.duration ) {
  2359. goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
  2360. delta = goalPos - oldorigin;
  2361. delta.z = 0.0f;
  2362. } else {
  2363. delta = move.moveDest - oldorigin;
  2364. delta.z = 0.0f;
  2365. StopMove( MOVE_STATUS_DONE );
  2366. }
  2367. } else if ( allowMove ) {
  2368. GetMoveDelta( oldaxis, viewAxis, delta );
  2369. } else {
  2370. delta.Zero();
  2371. }
  2372. if ( move.moveCommand == MOVE_TO_POSITION ) {
  2373. goalDelta = move.moveDest - oldorigin;
  2374. goalDist = goalDelta.LengthFast();
  2375. if ( goalDist < delta.LengthFast() ) {
  2376. delta = goalDelta;
  2377. }
  2378. }
  2379. #ifdef _D3XP
  2380. physicsObj.UseFlyMove( false );
  2381. #endif
  2382. physicsObj.SetDelta( delta );
  2383. physicsObj.ForceDeltaMove( disableGravity );
  2384. RunPhysics();
  2385. if ( ai_debugMove.GetBool() ) {
  2386. gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
  2387. }
  2388. moveResult = physicsObj.GetMoveResult();
  2389. if ( !af_push_moveables && attack.Length() && TestMelee() ) {
  2390. DirectDamage( attack, enemy.GetEntity() );
  2391. } else {
  2392. idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
  2393. if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
  2394. KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
  2395. }
  2396. }
  2397. BlockedFailSafe();
  2398. AI_ONGROUND = physicsObj.OnGround();
  2399. idVec3 org = physicsObj.GetOrigin();
  2400. if ( oldorigin != org ) {
  2401. TouchTriggers();
  2402. }
  2403. if ( ai_debugMove.GetBool() ) {
  2404. gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
  2405. gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
  2406. gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
  2407. DrawRoute();
  2408. }
  2409. }
  2410. /*
  2411. =====================
  2412. Seek
  2413. =====================
  2414. */
  2415. idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) {
  2416. idVec3 predictedPos;
  2417. idVec3 goalDelta;
  2418. idVec3 seekVel;
  2419. // predict our position
  2420. predictedPos = org + vel * prediction;
  2421. goalDelta = goal - predictedPos;
  2422. seekVel = goalDelta * MS2SEC( gameLocal.msec );
  2423. return seekVel;
  2424. }
  2425. /*
  2426. =====================
  2427. idAI::SlideMove
  2428. =====================
  2429. */
  2430. void idAI::SlideMove( void ) {
  2431. idVec3 goalPos;
  2432. idVec3 delta;
  2433. idVec3 goalDelta;
  2434. float goalDist;
  2435. monsterMoveResult_t moveResult;
  2436. idVec3 newDest;
  2437. idVec3 oldorigin = physicsObj.GetOrigin();
  2438. idMat3 oldaxis = viewAxis;
  2439. AI_BLOCKED = false;
  2440. if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
  2441. move.lastMoveOrigin.Zero();
  2442. move.lastMoveTime = gameLocal.time;
  2443. }
  2444. move.obstacle = NULL;
  2445. if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
  2446. TurnToward( lastVisibleEnemyPos );
  2447. goalPos = move.moveDest;
  2448. } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
  2449. TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
  2450. goalPos = move.moveDest;
  2451. } else if ( GetMovePos( goalPos ) ) {
  2452. CheckObstacleAvoidance( goalPos, newDest );
  2453. TurnToward( newDest );
  2454. goalPos = newDest;
  2455. }
  2456. if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
  2457. if ( gameLocal.time < move.startTime + move.duration ) {
  2458. goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
  2459. } else {
  2460. goalPos = move.moveDest;
  2461. StopMove( MOVE_STATUS_DONE );
  2462. }
  2463. }
  2464. if ( move.moveCommand == MOVE_TO_POSITION ) {
  2465. goalDelta = move.moveDest - oldorigin;
  2466. goalDist = goalDelta.LengthFast();
  2467. if ( goalDist < delta.LengthFast() ) {
  2468. delta = goalDelta;
  2469. }
  2470. }
  2471. idVec3 vel = physicsObj.GetLinearVelocity();
  2472. float z = vel.z;
  2473. idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION;
  2474. // seek the goal position
  2475. goalDelta = goalPos - predictedPos;
  2476. vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
  2477. vel += goalDelta * MS2SEC( gameLocal.msec );
  2478. // cap our speed
  2479. vel.Truncate( fly_speed );
  2480. vel.z = z;
  2481. physicsObj.SetLinearVelocity( vel );
  2482. physicsObj.UseVelocityMove( true );
  2483. RunPhysics();
  2484. if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
  2485. TurnToward( lastVisibleEnemyPos );
  2486. } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
  2487. TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
  2488. } else if ( move.moveCommand != MOVE_NONE ) {
  2489. if ( vel.ToVec2().LengthSqr() > 0.1f ) {
  2490. TurnToward( vel.ToYaw() );
  2491. }
  2492. }
  2493. Turn();
  2494. if ( ai_debugMove.GetBool() ) {
  2495. gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
  2496. }
  2497. moveResult = physicsObj.GetMoveResult();
  2498. if ( !af_push_moveables && attack.Length() && TestMelee() ) {
  2499. DirectDamage( attack, enemy.GetEntity() );
  2500. } else {
  2501. idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
  2502. if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
  2503. KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
  2504. }
  2505. }
  2506. BlockedFailSafe();
  2507. AI_ONGROUND = physicsObj.OnGround();
  2508. idVec3 org = physicsObj.GetOrigin();
  2509. if ( oldorigin != org ) {
  2510. TouchTriggers();
  2511. }
  2512. if ( ai_debugMove.GetBool() ) {
  2513. gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
  2514. gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
  2515. gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
  2516. DrawRoute();
  2517. }
  2518. }
  2519. /*
  2520. =====================
  2521. idAI::AdjustFlyingAngles
  2522. =====================
  2523. */
  2524. void idAI::AdjustFlyingAngles( void ) {
  2525. idVec3 vel;
  2526. float speed;
  2527. float roll;
  2528. float pitch;
  2529. vel = physicsObj.GetLinearVelocity();
  2530. speed = vel.Length();
  2531. if ( speed < 5.0f ) {
  2532. roll = 0.0f;
  2533. pitch = 0.0f;
  2534. } else {
  2535. roll = vel * viewAxis[ 1 ] * -fly_roll_scale / fly_speed;
  2536. if ( roll > fly_roll_max ) {
  2537. roll = fly_roll_max;
  2538. } else if ( roll < -fly_roll_max ) {
  2539. roll = -fly_roll_max;
  2540. }
  2541. pitch = vel * viewAxis[ 2 ] * -fly_pitch_scale / fly_speed;
  2542. if ( pitch > fly_pitch_max ) {
  2543. pitch = fly_pitch_max;
  2544. } else if ( pitch < -fly_pitch_max ) {
  2545. pitch = -fly_pitch_max;
  2546. }
  2547. }
  2548. fly_roll = fly_roll * 0.95f + roll * 0.05f;
  2549. fly_pitch = fly_pitch * 0.95f + pitch * 0.05f;
  2550. if ( flyTiltJoint != INVALID_JOINT ) {
  2551. animator.SetJointAxis( flyTiltJoint, JOINTMOD_WORLD, idAngles( fly_pitch, 0.0f, fly_roll ).ToMat3() );
  2552. } else {
  2553. viewAxis = idAngles( fly_pitch, current_yaw, fly_roll ).ToMat3();
  2554. }
  2555. }
  2556. /*
  2557. =====================
  2558. idAI::AddFlyBob
  2559. =====================
  2560. */
  2561. void idAI::AddFlyBob( idVec3 &vel ) {
  2562. idVec3 fly_bob_add;
  2563. float t;
  2564. if ( fly_bob_strength ) {
  2565. t = MS2SEC( gameLocal.time + entityNumber * 497 );
  2566. fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * fly_bob_vert ) ) * fly_bob_strength;
  2567. vel += fly_bob_add * MS2SEC( gameLocal.msec );
  2568. if ( ai_debugMove.GetBool() ) {
  2569. const idVec3 &origin = physicsObj.GetOrigin();
  2570. gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 );
  2571. }
  2572. }
  2573. }
  2574. /*
  2575. =====================
  2576. idAI::AdjustFlyHeight
  2577. =====================
  2578. */
  2579. void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) {
  2580. const idVec3 &origin = physicsObj.GetOrigin();
  2581. predictedPath_t path;
  2582. idVec3 end;
  2583. idVec3 dest;
  2584. trace_t trace;
  2585. idActor *enemyEnt;
  2586. bool goLower;
  2587. // make sure we're not flying too high to get through doors
  2588. goLower = false;
  2589. if ( origin.z > goalPos.z ) {
  2590. dest = goalPos;
  2591. dest.z = origin.z + 128.0f;
  2592. idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path );
  2593. if ( path.endPos.z < origin.z ) {
  2594. idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION );
  2595. vel.z += addVel.z;
  2596. goLower = true;
  2597. }
  2598. if ( ai_debugMove.GetBool() ) {
  2599. gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec );
  2600. }
  2601. }
  2602. if ( !goLower ) {
  2603. // make sure we don't fly too low
  2604. end = origin;
  2605. enemyEnt = enemy.GetEntity();
  2606. if ( enemyEnt ) {
  2607. end.z = lastVisibleEnemyPos.z + lastVisibleEnemyEyeOffset.z + fly_offset;
  2608. } else {
  2609. // just use the default eye height for the player
  2610. end.z = goalPos.z + DEFAULT_FLY_OFFSET + fly_offset;
  2611. }
  2612. gameLocal.clip.Translation( trace, origin, end, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this );
  2613. vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION );
  2614. }
  2615. }
  2616. /*
  2617. =====================
  2618. idAI::FlySeekGoal
  2619. =====================
  2620. */
  2621. void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) {
  2622. idVec3 seekVel;
  2623. // seek the goal position
  2624. seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION );
  2625. seekVel *= fly_seek_scale;
  2626. vel += seekVel;
  2627. }
  2628. /*
  2629. =====================
  2630. idAI::AdjustFlySpeed
  2631. =====================
  2632. */
  2633. void idAI::AdjustFlySpeed( idVec3 &vel ) {
  2634. float speed;
  2635. // apply dampening
  2636. vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
  2637. // gradually speed up/slow down to desired speed
  2638. speed = vel.Normalize();
  2639. speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec );
  2640. if ( speed < 0.0f ) {
  2641. speed = 0.0f;
  2642. } else if ( move.speed && ( speed > move.speed ) ) {
  2643. speed = move.speed;
  2644. }
  2645. vel *= speed;
  2646. }
  2647. /*
  2648. =====================
  2649. idAI::FlyTurn
  2650. =====================
  2651. */
  2652. void idAI::FlyTurn( void ) {
  2653. if ( move.moveCommand == MOVE_FACE_ENEMY ) {
  2654. TurnToward( lastVisibleEnemyPos );
  2655. } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
  2656. TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
  2657. } else if ( move.speed > 0.0f ) {
  2658. const idVec3 &vel = physicsObj.GetLinearVelocity();
  2659. if ( vel.ToVec2().LengthSqr() > 0.1f ) {
  2660. TurnToward( vel.ToYaw() );
  2661. }
  2662. }
  2663. Turn();
  2664. }
  2665. /*
  2666. =====================
  2667. idAI::FlyMove
  2668. =====================
  2669. */
  2670. void idAI::FlyMove( void ) {
  2671. idVec3 goalPos;
  2672. idVec3 oldorigin;
  2673. idVec3 newDest;
  2674. AI_BLOCKED = false;
  2675. if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) {
  2676. StopMove( MOVE_STATUS_DONE );
  2677. }
  2678. if ( ai_debugMove.GetBool() ) {
  2679. gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), moveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, fly_speed );
  2680. }
  2681. if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
  2682. idVec3 vel = physicsObj.GetLinearVelocity();
  2683. if ( GetMovePos( goalPos ) ) {
  2684. CheckObstacleAvoidance( goalPos, newDest );
  2685. goalPos = newDest;
  2686. }
  2687. if ( move.speed ) {
  2688. FlySeekGoal( vel, goalPos );
  2689. }
  2690. // add in bobbing
  2691. AddFlyBob( vel );
  2692. if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) {
  2693. AdjustFlyHeight( vel, goalPos );
  2694. }
  2695. AdjustFlySpeed( vel );
  2696. physicsObj.SetLinearVelocity( vel );
  2697. }
  2698. // turn
  2699. FlyTurn();
  2700. // run the physics for this frame
  2701. oldorigin = physicsObj.GetOrigin();
  2702. physicsObj.UseFlyMove( true );
  2703. physicsObj.UseVelocityMove( false );
  2704. physicsObj.SetDelta( vec3_zero );
  2705. physicsObj.ForceDeltaMove( disableGravity );
  2706. RunPhysics();
  2707. monsterMoveResult_t moveResult = physicsObj.GetMoveResult();
  2708. if ( !af_push_moveables && attack.Length() && TestMelee() ) {
  2709. DirectDamage( attack, enemy.GetEntity() );
  2710. } else {
  2711. idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
  2712. if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
  2713. KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
  2714. } else if ( moveResult == MM_BLOCKED ) {
  2715. move.blockTime = gameLocal.time + 500;
  2716. AI_BLOCKED = true;
  2717. }
  2718. }
  2719. idVec3 org = physicsObj.GetOrigin();
  2720. if ( oldorigin != org ) {
  2721. TouchTriggers();
  2722. }
  2723. if ( ai_debugMove.GetBool() ) {
  2724. gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 );
  2725. gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec );
  2726. gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
  2727. gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true );
  2728. gameRenderWorld->DebugLine( colorBlue, org, goalPos, gameLocal.msec, true );
  2729. gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
  2730. DrawRoute();
  2731. }
  2732. }
  2733. /*
  2734. =====================
  2735. idAI::StaticMove
  2736. =====================
  2737. */
  2738. void idAI::StaticMove( void ) {
  2739. idActor *enemyEnt = enemy.GetEntity();
  2740. if ( AI_DEAD ) {
  2741. return;
  2742. }
  2743. if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemyEnt ) {
  2744. TurnToward( lastVisibleEnemyPos );
  2745. } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
  2746. TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
  2747. } else if ( move.moveCommand != MOVE_NONE ) {
  2748. TurnToward( move.moveDest );
  2749. }
  2750. Turn();
  2751. physicsObj.ForceDeltaMove( true ); // disable gravity
  2752. RunPhysics();
  2753. AI_ONGROUND = false;
  2754. if ( !af_push_moveables && attack.Length() && TestMelee() ) {
  2755. DirectDamage( attack, enemyEnt );
  2756. }
  2757. if ( ai_debugMove.GetBool() ) {
  2758. const idVec3 &org = physicsObj.GetOrigin();
  2759. gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
  2760. gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, gameLocal.msec, true );
  2761. gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
  2762. }
  2763. }
  2764. /***********************************************************************
  2765. Damage
  2766. ***********************************************************************/
  2767. /*
  2768. =====================
  2769. idAI::ReactionTo
  2770. =====================
  2771. */
  2772. int idAI::ReactionTo( const idEntity *ent ) {
  2773. if ( ent->fl.hidden ) {
  2774. // ignore hidden entities
  2775. return ATTACK_IGNORE;
  2776. }
  2777. if ( !ent->IsType( idActor::Type ) ) {
  2778. return ATTACK_IGNORE;
  2779. }
  2780. const idActor *actor = static_cast<const idActor *>( ent );
  2781. if ( actor->IsType( idPlayer::Type ) && static_cast<const idPlayer *>(actor)->noclip ) {
  2782. // ignore players in noclip mode
  2783. return ATTACK_IGNORE;
  2784. }
  2785. // actors on different teams will always fight each other
  2786. if ( actor->team != team ) {
  2787. if ( actor->fl.notarget ) {
  2788. // don't attack on sight when attacker is notargeted
  2789. return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
  2790. }
  2791. return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
  2792. }
  2793. // monsters will fight when attacked by lower ranked monsters. rank 0 never fights back.
  2794. if ( rank && ( actor->rank < rank ) ) {
  2795. return ATTACK_ON_DAMAGE;
  2796. }
  2797. // don't fight back
  2798. return ATTACK_IGNORE;
  2799. }
  2800. /*
  2801. =====================
  2802. idAI::Pain
  2803. =====================
  2804. */
  2805. bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
  2806. idActor *actor;
  2807. AI_PAIN = idActor::Pain( inflictor, attacker, damage, dir, location );
  2808. AI_DAMAGE = true;
  2809. // force a blink
  2810. blink_time = 0;
  2811. // ignore damage from self
  2812. if ( attacker != this ) {
  2813. if ( inflictor ) {
  2814. AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
  2815. } else {
  2816. AI_SPECIAL_DAMAGE = 0;
  2817. }
  2818. if ( enemy.GetEntity() != attacker && attacker->IsType( idActor::Type ) ) {
  2819. actor = ( idActor * )attacker;
  2820. if ( ReactionTo( actor ) & ATTACK_ON_DAMAGE ) {
  2821. gameLocal.AlertAI( actor );
  2822. SetEnemy( actor );
  2823. }
  2824. }
  2825. }
  2826. return ( AI_PAIN != 0 );
  2827. }
  2828. /*
  2829. =====================
  2830. idAI::SpawnParticles
  2831. =====================
  2832. */
  2833. void idAI::SpawnParticles( const char *keyName ) {
  2834. const idKeyValue *kv = spawnArgs.MatchPrefix( keyName, NULL );
  2835. while ( kv ) {
  2836. particleEmitter_t pe;
  2837. idStr particleName = kv->GetValue();
  2838. if ( particleName.Length() ) {
  2839. idStr jointName = kv->GetValue();
  2840. int dash = jointName.Find('-');
  2841. if ( dash > 0 ) {
  2842. particleName = particleName.Left( dash );
  2843. jointName = jointName.Right( jointName.Length() - dash - 1 );
  2844. }
  2845. SpawnParticlesOnJoint( pe, particleName, jointName );
  2846. particles.Append( pe );
  2847. }
  2848. kv = spawnArgs.MatchPrefix( keyName, kv );
  2849. }
  2850. }
  2851. /*
  2852. =====================
  2853. idAI::SpawnParticlesOnJoint
  2854. =====================
  2855. */
  2856. const idDeclParticle *idAI::SpawnParticlesOnJoint( particleEmitter_t &pe, const char *particleName, const char *jointName ) {
  2857. idVec3 origin;
  2858. idMat3 axis;
  2859. if ( *particleName == '\0' ) {
  2860. memset( &pe, 0, sizeof( pe ) );
  2861. return pe.particle;
  2862. }
  2863. pe.joint = animator.GetJointHandle( jointName );
  2864. if ( pe.joint == INVALID_JOINT ) {
  2865. gameLocal.Warning( "Unknown particleJoint '%s' on '%s'", jointName, name.c_str() );
  2866. pe.time = 0;
  2867. pe.particle = NULL;
  2868. } else {
  2869. animator.GetJointTransform( pe.joint, gameLocal.time, origin, axis );
  2870. origin = renderEntity.origin + origin * renderEntity.axis;
  2871. BecomeActive( TH_UPDATEPARTICLES );
  2872. if ( !gameLocal.time ) {
  2873. // particles with time of 0 don't show, so set the time differently on the first frame
  2874. pe.time = 1;
  2875. } else {
  2876. pe.time = gameLocal.time;
  2877. }
  2878. pe.particle = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, particleName ) );
  2879. gameLocal.smokeParticles->EmitSmoke( pe.particle, pe.time, gameLocal.random.CRandomFloat(), origin, axis, timeGroup /*_D3XP*/ );
  2880. }
  2881. return pe.particle;
  2882. }
  2883. /*
  2884. =====================
  2885. idAI::Killed
  2886. =====================
  2887. */
  2888. void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
  2889. idAngles ang;
  2890. const char *modelDeath;
  2891. // make sure the monster is activated
  2892. EndAttack();
  2893. if ( g_debugDamage.GetBool() ) {
  2894. gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ),
  2895. GetDamageGroup( location ) );
  2896. }
  2897. if ( inflictor ) {
  2898. AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
  2899. } else {
  2900. AI_SPECIAL_DAMAGE = 0;
  2901. }
  2902. if ( AI_DEAD ) {
  2903. AI_PAIN = true;
  2904. AI_DAMAGE = true;
  2905. return;
  2906. }
  2907. // stop all voice sounds
  2908. StopSound( SND_CHANNEL_VOICE, false );
  2909. if ( head.GetEntity() ) {
  2910. head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false );
  2911. head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 );
  2912. }
  2913. disableGravity = false;
  2914. move.moveType = MOVETYPE_DEAD;
  2915. af_push_moveables = false;
  2916. physicsObj.UseFlyMove( false );
  2917. physicsObj.ForceDeltaMove( false );
  2918. // end our looping ambient sound
  2919. StopSound( SND_CHANNEL_AMBIENT, false );
  2920. if ( attacker && attacker->IsType( idActor::Type ) ) {
  2921. gameLocal.AlertAI( ( idActor * )attacker );
  2922. }
  2923. // activate targets
  2924. ActivateTargets( attacker );
  2925. RemoveAttachments();
  2926. RemoveProjectile();
  2927. StopMove( MOVE_STATUS_DONE );
  2928. ClearEnemy();
  2929. AI_DEAD = true;
  2930. // make monster nonsolid
  2931. physicsObj.SetContents( 0 );
  2932. physicsObj.GetClipModel()->Unlink();
  2933. Unbind();
  2934. if ( StartRagdoll() ) {
  2935. StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
  2936. }
  2937. if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) {
  2938. // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here
  2939. StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
  2940. renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
  2941. SetModel( modelDeath );
  2942. physicsObj.SetLinearVelocity( vec3_zero );
  2943. physicsObj.PutToRest();
  2944. physicsObj.DisableImpact();
  2945. #ifdef _D3XP
  2946. // No grabbing if "model_death"
  2947. noGrab = true;
  2948. #endif
  2949. }
  2950. restartParticles = false;
  2951. state = GetScriptFunction( "state_Killed" );
  2952. SetState( state );
  2953. SetWaitState( "" );
  2954. const idKeyValue *kv = spawnArgs.MatchPrefix( "def_drops", NULL );
  2955. while( kv ) {
  2956. idDict args;
  2957. args.Set( "classname", kv->GetValue() );
  2958. args.Set( "origin", physicsObj.GetOrigin().ToString() );
  2959. gameLocal.SpawnEntityDef( args );
  2960. kv = spawnArgs.MatchPrefix( "def_drops", kv );
  2961. }
  2962. #ifndef _D3XP
  2963. if ( ( attacker && attacker->IsType( idPlayer::Type ) ) && ( inflictor && !inflictor->IsType( idSoulCubeMissile::Type ) ) ) {
  2964. static_cast< idPlayer* >( attacker )->AddAIKill();
  2965. }
  2966. #endif
  2967. #ifdef _D3XP
  2968. if(spawnArgs.GetBool("harvest_on_death")) {
  2969. const idDict *harvestDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_harvest_type"), false );
  2970. if ( harvestDef ) {
  2971. idEntity *temp;
  2972. gameLocal.SpawnEntityDef( *harvestDef, &temp, false );
  2973. harvestEnt = static_cast<idHarvestable *>(temp);
  2974. }
  2975. if(harvestEnt.GetEntity()) {
  2976. //Let the harvest entity set itself up
  2977. harvestEnt.GetEntity()->Init(this);
  2978. harvestEnt.GetEntity()->BecomeActive( TH_THINK );
  2979. }
  2980. }
  2981. #endif
  2982. }
  2983. /***********************************************************************
  2984. Targeting/Combat
  2985. ***********************************************************************/
  2986. /*
  2987. =====================
  2988. idAI::PlayCinematic
  2989. =====================
  2990. */
  2991. void idAI::PlayCinematic( void ) {
  2992. const char *animname;
  2993. if ( current_cinematic >= num_cinematics ) {
  2994. if ( g_debugCinematic.GetBool() ) {
  2995. gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() );
  2996. }
  2997. if ( !spawnArgs.GetBool( "cinematic_no_hide" ) ) {
  2998. Hide();
  2999. }
  3000. current_cinematic = 0;
  3001. ActivateTargets( gameLocal.GetLocalPlayer() );
  3002. fl.neverDormant = false;
  3003. return;
  3004. }
  3005. Show();
  3006. current_cinematic++;
  3007. allowJointMod = false;
  3008. allowEyeFocus = false;
  3009. spawnArgs.GetString( va( "anim%d", current_cinematic ), NULL, &animname );
  3010. if ( !animname ) {
  3011. gameLocal.Warning( "missing 'anim%d' key on %s", current_cinematic, name.c_str() );
  3012. return;
  3013. }
  3014. if ( g_debugCinematic.GetBool() ) {
  3015. gameLocal.Printf( "%d: '%s' start '%s'\n", gameLocal.framenum, GetName(), animname );
  3016. }
  3017. headAnim.animBlendFrames = 0;
  3018. headAnim.lastAnimBlendFrames = 0;
  3019. headAnim.BecomeIdle();
  3020. legsAnim.animBlendFrames = 0;
  3021. legsAnim.lastAnimBlendFrames = 0;
  3022. legsAnim.BecomeIdle();
  3023. torsoAnim.animBlendFrames = 0;
  3024. torsoAnim.lastAnimBlendFrames = 0;
  3025. ProcessEvent( &AI_PlayAnim, ANIMCHANNEL_TORSO, animname );
  3026. // make sure our model gets updated
  3027. animator.ForceUpdate();
  3028. // update the anim bounds
  3029. UpdateAnimation();
  3030. UpdateVisuals();
  3031. Present();
  3032. if ( head.GetEntity() ) {
  3033. // since the body anim was updated, we need to run physics to update the position of the head
  3034. RunPhysics();
  3035. // make sure our model gets updated
  3036. head.GetEntity()->GetAnimator()->ForceUpdate();
  3037. // update the anim bounds
  3038. head.GetEntity()->UpdateAnimation();
  3039. head.GetEntity()->UpdateVisuals();
  3040. head.GetEntity()->Present();
  3041. }
  3042. fl.neverDormant = true;
  3043. }
  3044. /*
  3045. =====================
  3046. idAI::Activate
  3047. Notifies the script that a monster has been activated by a trigger or flashlight
  3048. =====================
  3049. */
  3050. void idAI::Activate( idEntity *activator ) {
  3051. idPlayer *player;
  3052. if ( AI_DEAD ) {
  3053. // ignore it when they're dead
  3054. return;
  3055. }
  3056. // make sure he's not dormant
  3057. dormantStart = 0;
  3058. if ( num_cinematics ) {
  3059. PlayCinematic();
  3060. } else {
  3061. AI_ACTIVATED = true;
  3062. if ( !activator || !activator->IsType( idPlayer::Type ) ) {
  3063. player = gameLocal.GetLocalPlayer();
  3064. } else {
  3065. player = static_cast<idPlayer *>( activator );
  3066. }
  3067. if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) {
  3068. SetEnemy( player );
  3069. }
  3070. // update the script in cinematics so that entities don't start anims or show themselves a frame late.
  3071. if ( cinematic ) {
  3072. UpdateAIScript();
  3073. // make sure our model gets updated
  3074. animator.ForceUpdate();
  3075. // update the anim bounds
  3076. UpdateAnimation();
  3077. UpdateVisuals();
  3078. Present();
  3079. if ( head.GetEntity() ) {
  3080. // since the body anim was updated, we need to run physics to update the position of the head
  3081. RunPhysics();
  3082. // make sure our model gets updated
  3083. head.GetEntity()->GetAnimator()->ForceUpdate();
  3084. // update the anim bounds
  3085. head.GetEntity()->UpdateAnimation();
  3086. head.GetEntity()->UpdateVisuals();
  3087. head.GetEntity()->Present();
  3088. }
  3089. }
  3090. }
  3091. }
  3092. /*
  3093. =====================
  3094. idAI::EnemyDead
  3095. =====================
  3096. */
  3097. void idAI::EnemyDead( void ) {
  3098. ClearEnemy();
  3099. AI_ENEMY_DEAD = true;
  3100. }
  3101. /*
  3102. =====================
  3103. idAI::TalkTo
  3104. =====================
  3105. */
  3106. void idAI::TalkTo( idActor *actor ) {
  3107. if ( talk_state != TALK_OK ) {
  3108. return;
  3109. }
  3110. #ifdef _D3XP
  3111. // Wake up monsters that are pretending to be NPC's
  3112. if ( team == 1 && actor->team != team ) {
  3113. ProcessEvent( &EV_Activate, actor );
  3114. }
  3115. #endif
  3116. talkTarget = actor;
  3117. if ( actor ) {
  3118. AI_TALK = true;
  3119. } else {
  3120. AI_TALK = false;
  3121. }
  3122. }
  3123. /*
  3124. =====================
  3125. idAI::GetEnemy
  3126. =====================
  3127. */
  3128. idActor *idAI::GetEnemy( void ) const {
  3129. return enemy.GetEntity();
  3130. }
  3131. /*
  3132. =====================
  3133. idAI::GetTalkState
  3134. =====================
  3135. */
  3136. talkState_t idAI::GetTalkState( void ) const {
  3137. if ( ( talk_state != TALK_NEVER ) && AI_DEAD ) {
  3138. return TALK_DEAD;
  3139. }
  3140. if ( IsHidden() ) {
  3141. return TALK_NEVER;
  3142. }
  3143. return talk_state;
  3144. }
  3145. /*
  3146. =====================
  3147. idAI::TouchedByFlashlight
  3148. =====================
  3149. */
  3150. void idAI::TouchedByFlashlight( idActor *flashlight_owner ) {
  3151. if ( wakeOnFlashlight ) {
  3152. Activate( flashlight_owner );
  3153. }
  3154. }
  3155. /*
  3156. =====================
  3157. idAI::ClearEnemy
  3158. =====================
  3159. */
  3160. void idAI::ClearEnemy( void ) {
  3161. if ( move.moveCommand == MOVE_TO_ENEMY ) {
  3162. StopMove( MOVE_STATUS_DEST_NOT_FOUND );
  3163. }
  3164. enemyNode.Remove();
  3165. enemy = NULL;
  3166. AI_ENEMY_IN_FOV = false;
  3167. AI_ENEMY_VISIBLE = false;
  3168. AI_ENEMY_DEAD = true;
  3169. SetChatSound();
  3170. }
  3171. /*
  3172. =====================
  3173. idAI::EnemyPositionValid
  3174. =====================
  3175. */
  3176. bool idAI::EnemyPositionValid( void ) const {
  3177. trace_t tr;
  3178. idVec3 muzzle;
  3179. idMat3 axis;
  3180. if ( !enemy.GetEntity() ) {
  3181. return false;
  3182. }
  3183. if ( AI_ENEMY_VISIBLE ) {
  3184. return true;
  3185. }
  3186. gameLocal.clip.TracePoint( tr, GetEyePosition(), lastVisibleEnemyPos + lastVisibleEnemyEyeOffset, MASK_OPAQUE, this );
  3187. if ( tr.fraction < 1.0f ) {
  3188. // can't see the area yet, so don't know if he's there or not
  3189. return true;
  3190. }
  3191. return false;
  3192. }
  3193. /*
  3194. =====================
  3195. idAI::SetEnemyPosition
  3196. =====================
  3197. */
  3198. void idAI::SetEnemyPosition( void ) {
  3199. idActor *enemyEnt = enemy.GetEntity();
  3200. int enemyAreaNum;
  3201. int areaNum;
  3202. int lastVisibleReachableEnemyAreaNum;
  3203. aasPath_t path;
  3204. idVec3 pos;
  3205. bool onGround;
  3206. if ( !enemyEnt ) {
  3207. return;
  3208. }
  3209. lastVisibleReachableEnemyPos = lastReachableEnemyPos;
  3210. lastVisibleEnemyEyeOffset = enemyEnt->EyeOffset();
  3211. lastVisibleEnemyPos = enemyEnt->GetPhysics()->GetOrigin();
  3212. if ( move.moveType == MOVETYPE_FLY ) {
  3213. pos = lastVisibleEnemyPos;
  3214. onGround = true;
  3215. } else {
  3216. onGround = enemyEnt->GetFloorPos( 64.0f, pos );
  3217. if ( enemyEnt->OnLadder() ) {
  3218. onGround = false;
  3219. }
  3220. }
  3221. if ( !onGround ) {
  3222. if ( move.moveCommand == MOVE_TO_ENEMY ) {
  3223. AI_DEST_UNREACHABLE = true;
  3224. }
  3225. return;
  3226. }
  3227. // when we don't have an AAS, we can't tell if an enemy is reachable or not,
  3228. // so just assume that he is.
  3229. if ( !aas ) {
  3230. lastVisibleReachableEnemyPos = lastVisibleEnemyPos;
  3231. if ( move.moveCommand == MOVE_TO_ENEMY ) {
  3232. AI_DEST_UNREACHABLE = false;
  3233. }
  3234. enemyAreaNum = 0;
  3235. areaNum = 0;
  3236. } else {
  3237. lastVisibleReachableEnemyAreaNum = move.toAreaNum;
  3238. enemyAreaNum = PointReachableAreaNum( lastVisibleEnemyPos, 1.0f );
  3239. if ( !enemyAreaNum ) {
  3240. enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
  3241. pos = lastReachableEnemyPos;
  3242. }
  3243. if ( !enemyAreaNum ) {
  3244. if ( move.moveCommand == MOVE_TO_ENEMY ) {
  3245. AI_DEST_UNREACHABLE = true;
  3246. }
  3247. areaNum = 0;
  3248. } else {
  3249. const idVec3 &org = physicsObj.GetOrigin();
  3250. areaNum = PointReachableAreaNum( org );
  3251. if ( PathToGoal( path, areaNum, org, enemyAreaNum, pos ) ) {
  3252. lastVisibleReachableEnemyPos = pos;
  3253. lastVisibleReachableEnemyAreaNum = enemyAreaNum;
  3254. if ( move.moveCommand == MOVE_TO_ENEMY ) {
  3255. AI_DEST_UNREACHABLE = false;
  3256. }
  3257. } else if ( move.moveCommand == MOVE_TO_ENEMY ) {
  3258. AI_DEST_UNREACHABLE = true;
  3259. }
  3260. }
  3261. }
  3262. if ( move.moveCommand == MOVE_TO_ENEMY ) {
  3263. if ( !aas ) {
  3264. // keep the move destination up to date for wandering
  3265. move.moveDest = lastVisibleReachableEnemyPos;
  3266. } else if ( enemyAreaNum ) {
  3267. move.toAreaNum = lastVisibleReachableEnemyAreaNum;
  3268. move.moveDest = lastVisibleReachableEnemyPos;
  3269. }
  3270. if ( move.moveType == MOVETYPE_FLY ) {
  3271. predictedPath_t path;
  3272. idVec3 end = move.moveDest;
  3273. end.z += enemyEnt->EyeOffset().z + fly_offset;
  3274. idAI::PredictPath( this, aas, move.moveDest, end - move.moveDest, 1000, 1000, SE_BLOCKED, path );
  3275. move.moveDest = path.endPos;
  3276. move.toAreaNum = PointReachableAreaNum( move.moveDest, 1.0f );
  3277. }
  3278. }
  3279. }
  3280. /*
  3281. =====================
  3282. idAI::UpdateEnemyPosition
  3283. =====================
  3284. */
  3285. void idAI::UpdateEnemyPosition( void ) {
  3286. idActor *enemyEnt = enemy.GetEntity();
  3287. int enemyAreaNum;
  3288. int areaNum;
  3289. aasPath_t path;
  3290. predictedPath_t predictedPath;
  3291. idVec3 enemyPos;
  3292. bool onGround;
  3293. if ( !enemyEnt ) {
  3294. return;
  3295. }
  3296. const idVec3 &org = physicsObj.GetOrigin();
  3297. if ( move.moveType == MOVETYPE_FLY ) {
  3298. enemyPos = enemyEnt->GetPhysics()->GetOrigin();
  3299. onGround = true;
  3300. } else {
  3301. onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos );
  3302. if ( enemyEnt->OnLadder() ) {
  3303. onGround = false;
  3304. }
  3305. }
  3306. if ( onGround ) {
  3307. // when we don't have an AAS, we can't tell if an enemy is reachable or not,
  3308. // so just assume that he is.
  3309. if ( !aas ) {
  3310. enemyAreaNum = 0;
  3311. lastReachableEnemyPos = enemyPos;
  3312. } else {
  3313. enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f );
  3314. if ( enemyAreaNum ) {
  3315. areaNum = PointReachableAreaNum( org );
  3316. if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) {
  3317. lastReachableEnemyPos = enemyPos;
  3318. }
  3319. }
  3320. }
  3321. }
  3322. AI_ENEMY_IN_FOV = false;
  3323. AI_ENEMY_VISIBLE = false;
  3324. if ( CanSee( enemyEnt, false ) ) {
  3325. AI_ENEMY_VISIBLE = true;
  3326. if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) {
  3327. AI_ENEMY_IN_FOV = true;
  3328. }
  3329. SetEnemyPosition();
  3330. } else {
  3331. // check if we heard any sounds in the last frame
  3332. if ( enemyEnt == gameLocal.GetAlertEntity() ) {
  3333. float dist = ( enemyEnt->GetPhysics()->GetOrigin() - org ).LengthSqr();
  3334. if ( dist < Square( AI_HEARING_RANGE ) ) {
  3335. SetEnemyPosition();
  3336. }
  3337. }
  3338. }
  3339. if ( ai_debugMove.GetBool() ) {
  3340. gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec );
  3341. gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec );
  3342. }
  3343. }
  3344. /*
  3345. =====================
  3346. idAI::SetEnemy
  3347. =====================
  3348. */
  3349. void idAI::SetEnemy( idActor *newEnemy ) {
  3350. int enemyAreaNum;
  3351. if ( AI_DEAD ) {
  3352. ClearEnemy();
  3353. return;
  3354. }
  3355. AI_ENEMY_DEAD = false;
  3356. if ( !newEnemy ) {
  3357. ClearEnemy();
  3358. } else if ( enemy.GetEntity() != newEnemy ) {
  3359. enemy = newEnemy;
  3360. enemyNode.AddToEnd( newEnemy->enemyList );
  3361. if ( newEnemy->health <= 0 ) {
  3362. EnemyDead();
  3363. return;
  3364. }
  3365. // let the monster know where the enemy is
  3366. newEnemy->GetAASLocation( aas, lastReachableEnemyPos, enemyAreaNum );
  3367. SetEnemyPosition();
  3368. SetChatSound();
  3369. lastReachableEnemyPos = lastVisibleEnemyPos;
  3370. lastVisibleReachableEnemyPos = lastReachableEnemyPos;
  3371. enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
  3372. if ( aas && enemyAreaNum ) {
  3373. aas->PushPointIntoAreaNum( enemyAreaNum, lastReachableEnemyPos );
  3374. lastVisibleReachableEnemyPos = lastReachableEnemyPos;
  3375. }
  3376. }
  3377. }
  3378. /*
  3379. ============
  3380. idAI::FirstVisiblePointOnPath
  3381. ============
  3382. */
  3383. idVec3 idAI::FirstVisiblePointOnPath( const idVec3 origin, const idVec3 &target, int travelFlags ) const {
  3384. int i, areaNum, targetAreaNum, curAreaNum, travelTime;
  3385. idVec3 curOrigin;
  3386. idReachability *reach;
  3387. if ( !aas ) {
  3388. return origin;
  3389. }
  3390. areaNum = PointReachableAreaNum( origin );
  3391. targetAreaNum = PointReachableAreaNum( target );
  3392. if ( !areaNum || !targetAreaNum ) {
  3393. return origin;
  3394. }
  3395. if ( ( areaNum == targetAreaNum ) || PointVisible( origin ) ) {
  3396. return origin;
  3397. }
  3398. curAreaNum = areaNum;
  3399. curOrigin = origin;
  3400. for( i = 0; i < 10; i++ ) {
  3401. if ( !aas->RouteToGoalArea( curAreaNum, curOrigin, targetAreaNum, travelFlags, travelTime, &reach ) ) {
  3402. break;
  3403. }
  3404. if ( !reach ) {
  3405. return target;
  3406. }
  3407. curAreaNum = reach->toAreaNum;
  3408. curOrigin = reach->end;
  3409. if ( PointVisible( curOrigin ) ) {
  3410. return curOrigin;
  3411. }
  3412. }
  3413. return origin;
  3414. }
  3415. /*
  3416. ===================
  3417. idAI::CalculateAttackOffsets
  3418. calculate joint positions on attack frames so we can do proper "can hit" tests
  3419. ===================
  3420. */
  3421. void idAI::CalculateAttackOffsets( void ) {
  3422. const idDeclModelDef *modelDef;
  3423. int num;
  3424. int i;
  3425. int frame;
  3426. const frameCommand_t *command;
  3427. idMat3 axis;
  3428. const idAnim *anim;
  3429. jointHandle_t joint;
  3430. modelDef = animator.ModelDef();
  3431. if ( !modelDef ) {
  3432. return;
  3433. }
  3434. num = modelDef->NumAnims();
  3435. // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim
  3436. animator.RemoveOriginOffset( false );
  3437. // anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for
  3438. // launch offsets so that anim number can be used without subtracting 1.
  3439. missileLaunchOffset.SetGranularity( 1 );
  3440. missileLaunchOffset.SetNum( num + 1 );
  3441. missileLaunchOffset[ 0 ].Zero();
  3442. for( i = 1; i <= num; i++ ) {
  3443. missileLaunchOffset[ i ].Zero();
  3444. anim = modelDef->GetAnim( i );
  3445. if ( anim ) {
  3446. frame = anim->FindFrameForFrameCommand( FC_LAUNCHMISSILE, &command );
  3447. if ( frame >= 0 ) {
  3448. joint = animator.GetJointHandle( command->string->c_str() );
  3449. if ( joint == INVALID_JOINT ) {
  3450. gameLocal.Error( "Invalid joint '%s' on 'launch_missile' frame command on frame %d of model '%s'", command->string->c_str(), frame, modelDef->GetName() );
  3451. }
  3452. GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis );
  3453. }
  3454. }
  3455. }
  3456. animator.RemoveOriginOffset( true );
  3457. }
  3458. /*
  3459. =====================
  3460. idAI::CreateProjectileClipModel
  3461. =====================
  3462. */
  3463. void idAI::CreateProjectileClipModel( void ) const {
  3464. if ( projectileClipModel == NULL ) {
  3465. idBounds projectileBounds( vec3_origin );
  3466. projectileBounds.ExpandSelf( projectileRadius );
  3467. projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) );
  3468. }
  3469. }
  3470. /*
  3471. =====================
  3472. idAI::GetAimDir
  3473. =====================
  3474. */
  3475. bool idAI::GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const {
  3476. idVec3 targetPos1;
  3477. idVec3 targetPos2;
  3478. idVec3 delta;
  3479. float max_height;
  3480. bool result;
  3481. // if no aimAtEnt or projectile set
  3482. if ( !aimAtEnt || !projectileDef ) {
  3483. aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis();
  3484. return false;
  3485. }
  3486. if ( projectileClipModel == NULL ) {
  3487. CreateProjectileClipModel();
  3488. }
  3489. if ( aimAtEnt == enemy.GetEntity() ) {
  3490. static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( lastVisibleEnemyPos, targetPos1, targetPos2 );
  3491. } else if ( aimAtEnt->IsType( idActor::Type ) ) {
  3492. static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( aimAtEnt->GetPhysics()->GetOrigin(), targetPos1, targetPos2 );
  3493. } else {
  3494. targetPos1 = aimAtEnt->GetPhysics()->GetAbsBounds().GetCenter();
  3495. targetPos2 = targetPos1;
  3496. }
  3497. #ifdef _D3XP
  3498. if ( this->team == 0 && !idStr::Cmp( aimAtEnt->GetEntityDefName(), "monster_demon_vulgar" ) ) {
  3499. targetPos1.z -= 28.f;
  3500. targetPos2.z -= 12.f;
  3501. }
  3502. #endif
  3503. // try aiming for chest
  3504. delta = firePos - targetPos1;
  3505. max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
  3506. result = PredictTrajectory( firePos, targetPos1, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
  3507. if ( result || !aimAtEnt->IsType( idActor::Type ) ) {
  3508. return result;
  3509. }
  3510. // try aiming for head
  3511. delta = firePos - targetPos2;
  3512. max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
  3513. result = PredictTrajectory( firePos, targetPos2, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
  3514. return result;
  3515. }
  3516. /*
  3517. =====================
  3518. idAI::BeginAttack
  3519. =====================
  3520. */
  3521. void idAI::BeginAttack( const char *name ) {
  3522. attack = name;
  3523. lastAttackTime = gameLocal.time;
  3524. }
  3525. /*
  3526. =====================
  3527. idAI::EndAttack
  3528. =====================
  3529. */
  3530. void idAI::EndAttack( void ) {
  3531. attack = "";
  3532. }
  3533. /*
  3534. =====================
  3535. idAI::CreateProjectile
  3536. =====================
  3537. */
  3538. idProjectile *idAI::CreateProjectile( const idVec3 &pos, const idVec3 &dir ) {
  3539. idEntity *ent;
  3540. const char *clsname;
  3541. if ( !projectile.GetEntity() ) {
  3542. gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
  3543. if ( !ent ) {
  3544. clsname = projectileDef->GetString( "classname" );
  3545. gameLocal.Error( "Could not spawn entityDef '%s'", clsname );
  3546. }
  3547. if ( !ent->IsType( idProjectile::Type ) ) {
  3548. clsname = ent->GetClassname();
  3549. gameLocal.Error( "'%s' is not an idProjectile", clsname );
  3550. }
  3551. projectile = ( idProjectile * )ent;
  3552. }
  3553. projectile.GetEntity()->Create( this, pos, dir );
  3554. return projectile.GetEntity();
  3555. }
  3556. /*
  3557. =====================
  3558. idAI::RemoveProjectile
  3559. =====================
  3560. */
  3561. void idAI::RemoveProjectile( void ) {
  3562. if ( projectile.GetEntity() ) {
  3563. projectile.GetEntity()->PostEventMS( &EV_Remove, 0 );
  3564. projectile = NULL;
  3565. }
  3566. }
  3567. /*
  3568. =====================
  3569. idAI::LaunchProjectile
  3570. =====================
  3571. */
  3572. idProjectile *idAI::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ) {
  3573. idVec3 muzzle;
  3574. idVec3 dir;
  3575. idVec3 start;
  3576. trace_t tr;
  3577. idBounds projBounds;
  3578. float distance;
  3579. const idClipModel *projClip;
  3580. float attack_accuracy;
  3581. float attack_cone;
  3582. float projectile_spread;
  3583. float diff;
  3584. float angle;
  3585. float spin;
  3586. idAngles ang;
  3587. int num_projectiles;
  3588. int i;
  3589. idMat3 axis;
  3590. #ifdef _D3XP
  3591. idMat3 proj_axis;
  3592. bool forceMuzzle;
  3593. #endif
  3594. idVec3 tmp;
  3595. idProjectile *lastProjectile;
  3596. if ( !projectileDef ) {
  3597. gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
  3598. return NULL;
  3599. }
  3600. attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
  3601. attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
  3602. projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
  3603. num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
  3604. #ifdef _D3XP
  3605. forceMuzzle = spawnArgs.GetBool( "forceMuzzle", "0" );
  3606. #endif
  3607. GetMuzzle( jointname, muzzle, axis );
  3608. if ( !projectile.GetEntity() ) {
  3609. CreateProjectile( muzzle, axis[ 0 ] );
  3610. }
  3611. lastProjectile = projectile.GetEntity();
  3612. if ( target != NULL ) {
  3613. tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
  3614. tmp.Normalize();
  3615. axis = tmp.ToMat3();
  3616. } else {
  3617. axis = viewAxis;
  3618. }
  3619. // rotate it because the cone points up by default
  3620. tmp = axis[2];
  3621. axis[2] = axis[0];
  3622. axis[0] = -tmp;
  3623. #ifdef _D3XP
  3624. proj_axis = axis;
  3625. #endif
  3626. if ( !forceMuzzle ) { // _D3XP
  3627. // make sure the projectile starts inside the monster bounding box
  3628. const idBounds &ownerBounds = physicsObj.GetAbsBounds();
  3629. projClip = lastProjectile->GetPhysics()->GetClipModel();
  3630. projBounds = projClip->GetBounds().Rotate( axis );
  3631. // check if the owner bounds is bigger than the projectile bounds
  3632. if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
  3633. ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
  3634. ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
  3635. if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
  3636. start = muzzle + distance * viewAxis[ 0 ];
  3637. } else {
  3638. start = ownerBounds.GetCenter();
  3639. }
  3640. } else {
  3641. // projectile bounds bigger than the owner bounds, so just start it from the center
  3642. start = ownerBounds.GetCenter();
  3643. }
  3644. gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
  3645. muzzle = tr.endpos;
  3646. }
  3647. // set aiming direction
  3648. GetAimDir( muzzle, target, this, dir );
  3649. ang = dir.ToAngles();
  3650. // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
  3651. float t = MS2SEC( gameLocal.time + entityNumber * 497 );
  3652. ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
  3653. ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
  3654. if ( clampToAttackCone ) {
  3655. // clamp the attack direction to be within monster's attack cone so he doesn't do
  3656. // things like throw the missile backwards if you're behind him
  3657. diff = idMath::AngleDelta( ang.yaw, current_yaw );
  3658. if ( diff > attack_cone ) {
  3659. ang.yaw = current_yaw + attack_cone;
  3660. } else if ( diff < -attack_cone ) {
  3661. ang.yaw = current_yaw - attack_cone;
  3662. }
  3663. }
  3664. axis = ang.ToMat3();
  3665. float spreadRad = DEG2RAD( projectile_spread );
  3666. for( i = 0; i < num_projectiles; i++ ) {
  3667. // spread the projectiles out
  3668. angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
  3669. spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
  3670. dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
  3671. dir.Normalize();
  3672. // launch the projectile
  3673. if ( !projectile.GetEntity() ) {
  3674. CreateProjectile( muzzle, dir );
  3675. }
  3676. lastProjectile = projectile.GetEntity();
  3677. lastProjectile->Launch( muzzle, dir, vec3_origin );
  3678. projectile = NULL;
  3679. }
  3680. TriggerWeaponEffects( muzzle );
  3681. lastAttackTime = gameLocal.time;
  3682. return lastProjectile;
  3683. }
  3684. /*
  3685. ================
  3686. idAI::DamageFeedback
  3687. callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller.
  3688. FIXME: This gets called when we call idPlayer::CalcDamagePoints from idAI::AttackMelee, which then checks for a saving throw,
  3689. possibly forcing a miss. This is harmless behavior ATM, but is not intuitive.
  3690. ================
  3691. */
  3692. void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
  3693. if ( ( victim == this ) && inflictor->IsType( idProjectile::Type ) ) {
  3694. // monsters only get half damage from their own projectiles
  3695. damage = ( damage + 1 ) / 2; // round up so we don't do 0 damage
  3696. } else if ( victim == enemy.GetEntity() ) {
  3697. AI_HIT_ENEMY = true;
  3698. }
  3699. }
  3700. /*
  3701. =====================
  3702. idAI::DirectDamage
  3703. Causes direct damage to an entity
  3704. kickDir is specified in the monster's coordinate system, and gives the direction
  3705. that the view kick and knockback should go
  3706. =====================
  3707. */
  3708. void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) {
  3709. const idDict *meleeDef;
  3710. const char *p;
  3711. const idSoundShader *shader;
  3712. meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
  3713. if ( !meleeDef ) {
  3714. gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() );
  3715. }
  3716. if ( !ent->fl.takedamage ) {
  3717. const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" ));
  3718. StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
  3719. return;
  3720. }
  3721. //
  3722. // do the damage
  3723. //
  3724. p = meleeDef->GetString( "snd_hit" );
  3725. if ( p && *p ) {
  3726. shader = declManager->FindSound( p );
  3727. StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
  3728. }
  3729. idVec3 kickDir;
  3730. meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
  3731. idVec3 globalKickDir;
  3732. globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
  3733. ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
  3734. // end the attack if we're a multiframe attack
  3735. EndAttack();
  3736. }
  3737. /*
  3738. =====================
  3739. idAI::TestMelee
  3740. =====================
  3741. */
  3742. bool idAI::TestMelee( void ) const {
  3743. trace_t trace;
  3744. idActor *enemyEnt = enemy.GetEntity();
  3745. if ( !enemyEnt || !melee_range ) {
  3746. return false;
  3747. }
  3748. //FIXME: make work with gravity vector
  3749. idVec3 org = physicsObj.GetOrigin();
  3750. const idBounds &myBounds = physicsObj.GetBounds();
  3751. idBounds bounds;
  3752. // expand the bounds out by our melee range
  3753. bounds[0][0] = -melee_range;
  3754. bounds[0][1] = -melee_range;
  3755. bounds[0][2] = myBounds[0][2] - 4.0f;
  3756. bounds[1][0] = melee_range;
  3757. bounds[1][1] = melee_range;
  3758. bounds[1][2] = myBounds[1][2] + 4.0f;
  3759. bounds.TranslateSelf( org );
  3760. idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin();
  3761. idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds();
  3762. enemyBounds.TranslateSelf( enemyOrg );
  3763. if ( ai_debugMove.GetBool() ) {
  3764. gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec );
  3765. }
  3766. if ( !bounds.IntersectsBounds( enemyBounds ) ) {
  3767. return false;
  3768. }
  3769. idVec3 start = GetEyePosition();
  3770. idVec3 end = enemyEnt->GetEyePosition();
  3771. gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
  3772. if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) {
  3773. return true;
  3774. }
  3775. return false;
  3776. }
  3777. /*
  3778. =====================
  3779. idAI::AttackMelee
  3780. jointname allows the endpoint to be exactly specified in the model,
  3781. as for the commando tentacle. If not specified, it will be set to
  3782. the facing direction + melee_range.
  3783. kickDir is specified in the monster's coordinate system, and gives the direction
  3784. that the view kick and knockback should go
  3785. =====================
  3786. */
  3787. bool idAI::AttackMelee( const char *meleeDefName ) {
  3788. const idDict *meleeDef;
  3789. idActor *enemyEnt = enemy.GetEntity();
  3790. const char *p;
  3791. const idSoundShader *shader;
  3792. meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
  3793. if ( !meleeDef ) {
  3794. gameLocal.Error( "Unknown melee '%s'", meleeDefName );
  3795. }
  3796. if ( !enemyEnt ) {
  3797. p = meleeDef->GetString( "snd_miss" );
  3798. if ( p && *p ) {
  3799. shader = declManager->FindSound( p );
  3800. StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
  3801. }
  3802. return false;
  3803. }
  3804. // check for the "saving throw" automatic melee miss on lethal blow
  3805. // stupid place for this.
  3806. bool forceMiss = false;
  3807. if ( enemyEnt->IsType( idPlayer::Type ) && g_skill.GetInteger() < 2 ) {
  3808. int damage, armor;
  3809. idPlayer *player = static_cast<idPlayer*>( enemyEnt );
  3810. player->CalcDamagePoints( this, this, meleeDef, 1.0f, INVALID_JOINT, &damage, &armor );
  3811. if ( enemyEnt->health <= damage ) {
  3812. int t = gameLocal.time - player->lastSavingThrowTime;
  3813. if ( t > SAVING_THROW_TIME ) {
  3814. player->lastSavingThrowTime = gameLocal.time;
  3815. t = 0;
  3816. }
  3817. if ( t < 1000 ) {
  3818. gameLocal.Printf( "Saving throw.\n" );
  3819. forceMiss = true;
  3820. }
  3821. }
  3822. }
  3823. // make sure the trace can actually hit the enemy
  3824. if ( forceMiss || !TestMelee() ) {
  3825. // missed
  3826. p = meleeDef->GetString( "snd_miss" );
  3827. if ( p && *p ) {
  3828. shader = declManager->FindSound( p );
  3829. StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
  3830. }
  3831. return false;
  3832. }
  3833. //
  3834. // do the damage
  3835. //
  3836. p = meleeDef->GetString( "snd_hit" );
  3837. if ( p && *p ) {
  3838. shader = declManager->FindSound( p );
  3839. StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
  3840. }
  3841. idVec3 kickDir;
  3842. meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
  3843. idVec3 globalKickDir;
  3844. globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
  3845. enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
  3846. lastAttackTime = gameLocal.time;
  3847. return true;
  3848. }
  3849. /*
  3850. ================
  3851. idAI::PushWithAF
  3852. ================
  3853. */
  3854. void idAI::PushWithAF( void ) {
  3855. int i, j;
  3856. afTouch_t touchList[ MAX_GENTITIES ];
  3857. idEntity *pushed_ents[ MAX_GENTITIES ];
  3858. idEntity *ent;
  3859. idVec3 vel;
  3860. int num_pushed;
  3861. num_pushed = 0;
  3862. af.ChangePose( this, gameLocal.time );
  3863. int num = af.EntitiesTouchingAF( touchList );
  3864. for( i = 0; i < num; i++ ) {
  3865. if ( touchList[ i ].touchedEnt->IsType( idProjectile::Type ) ) {
  3866. // skip projectiles
  3867. continue;
  3868. }
  3869. // make sure we havent pushed this entity already. this avoids causing double damage
  3870. for( j = 0; j < num_pushed; j++ ) {
  3871. if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) {
  3872. break;
  3873. }
  3874. }
  3875. if ( j >= num_pushed ) {
  3876. ent = touchList[ i ].touchedEnt;
  3877. pushed_ents[num_pushed++] = ent;
  3878. vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin();
  3879. vel.Normalize();
  3880. if ( attack.Length() && ent->IsType( idActor::Type ) ) {
  3881. ent->Damage( this, this, vel, attack, 1.0f, INVALID_JOINT );
  3882. } else {
  3883. ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() );
  3884. }
  3885. }
  3886. }
  3887. }
  3888. /***********************************************************************
  3889. Misc
  3890. ***********************************************************************/
  3891. /*
  3892. ================
  3893. idAI::GetMuzzle
  3894. ================
  3895. */
  3896. void idAI::GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ) {
  3897. jointHandle_t joint;
  3898. if ( !jointname || !jointname[ 0 ] ) {
  3899. muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14;
  3900. muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f;
  3901. } else {
  3902. joint = animator.GetJointHandle( jointname );
  3903. if ( joint == INVALID_JOINT ) {
  3904. gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
  3905. }
  3906. GetJointWorldTransform( joint, gameLocal.time, muzzle, axis );
  3907. }
  3908. }
  3909. /*
  3910. ================
  3911. idAI::TriggerWeaponEffects
  3912. ================
  3913. */
  3914. void idAI::TriggerWeaponEffects( const idVec3 &muzzle ) {
  3915. idVec3 org;
  3916. idMat3 axis;
  3917. if ( !g_muzzleFlash.GetBool() ) {
  3918. return;
  3919. }
  3920. // muzzle flash
  3921. // offset the shader parms so muzzle flashes show up
  3922. renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
  3923. renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat();
  3924. if ( flashJointWorld != INVALID_JOINT ) {
  3925. GetJointWorldTransform( flashJointWorld, gameLocal.time, org, axis );
  3926. if ( worldMuzzleFlash.lightRadius.x > 0.0f ) {
  3927. worldMuzzleFlash.axis = axis;
  3928. worldMuzzleFlash.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
  3929. if ( worldMuzzleFlashHandle != - 1 ) {
  3930. gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
  3931. } else {
  3932. worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash );
  3933. }
  3934. muzzleFlashEnd = gameLocal.time + flashTime;
  3935. UpdateVisuals();
  3936. }
  3937. }
  3938. }
  3939. /*
  3940. ================
  3941. idAI::UpdateMuzzleFlash
  3942. ================
  3943. */
  3944. void idAI::UpdateMuzzleFlash( void ) {
  3945. if ( worldMuzzleFlashHandle != -1 ) {
  3946. if ( gameLocal.time >= muzzleFlashEnd ) {
  3947. gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
  3948. worldMuzzleFlashHandle = -1;
  3949. } else {
  3950. idVec3 muzzle;
  3951. animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
  3952. animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
  3953. muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis();
  3954. worldMuzzleFlash.origin = muzzle;
  3955. gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
  3956. }
  3957. }
  3958. }
  3959. /*
  3960. ================
  3961. idAI::Hide
  3962. ================
  3963. */
  3964. void idAI::Hide( void ) {
  3965. idActor::Hide();
  3966. fl.takedamage = false;
  3967. physicsObj.SetContents( 0 );
  3968. physicsObj.GetClipModel()->Unlink();
  3969. StopSound( SND_CHANNEL_AMBIENT, false );
  3970. SetChatSound();
  3971. AI_ENEMY_IN_FOV = false;
  3972. AI_ENEMY_VISIBLE = false;
  3973. StopMove( MOVE_STATUS_DONE );
  3974. }
  3975. /*
  3976. ================
  3977. idAI::Show
  3978. ================
  3979. */
  3980. void idAI::Show( void ) {
  3981. idActor::Show();
  3982. if ( spawnArgs.GetBool( "big_monster" ) ) {
  3983. physicsObj.SetContents( 0 );
  3984. } else if ( use_combat_bbox ) {
  3985. physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
  3986. } else {
  3987. physicsObj.SetContents( CONTENTS_BODY );
  3988. }
  3989. physicsObj.GetClipModel()->Link( gameLocal.clip );
  3990. fl.takedamage = !spawnArgs.GetBool( "noDamage" );
  3991. SetChatSound();
  3992. StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
  3993. }
  3994. /*
  3995. =====================
  3996. idAI::SetChatSound
  3997. =====================
  3998. */
  3999. void idAI::SetChatSound( void ) {
  4000. const char *snd;
  4001. if ( IsHidden() ) {
  4002. snd = NULL;
  4003. } else if ( enemy.GetEntity() ) {
  4004. snd = spawnArgs.GetString( "snd_chatter_combat", NULL );
  4005. chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_combat_min", "5" ) );
  4006. chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_combat_max", "10" ) );
  4007. } else if ( !spawnArgs.GetBool( "no_idle_chatter" ) ) {
  4008. snd = spawnArgs.GetString( "snd_chatter", NULL );
  4009. chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_min", "5" ) );
  4010. chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_max", "10" ) );
  4011. } else {
  4012. snd = NULL;
  4013. }
  4014. if ( snd && *snd ) {
  4015. chat_snd = declManager->FindSound( snd );
  4016. // set the next chat time
  4017. chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
  4018. } else {
  4019. chat_snd = NULL;
  4020. }
  4021. }
  4022. /*
  4023. ================
  4024. idAI::CanPlayChatterSounds
  4025. Used for playing chatter sounds on monsters.
  4026. ================
  4027. */
  4028. bool idAI::CanPlayChatterSounds( void ) const {
  4029. if ( AI_DEAD ) {
  4030. return false;
  4031. }
  4032. if ( IsHidden() ) {
  4033. return false;
  4034. }
  4035. if ( enemy.GetEntity() ) {
  4036. return true;
  4037. }
  4038. if ( spawnArgs.GetBool( "no_idle_chatter" ) ) {
  4039. return false;
  4040. }
  4041. return true;
  4042. }
  4043. /*
  4044. =====================
  4045. idAI::PlayChatter
  4046. =====================
  4047. */
  4048. void idAI::PlayChatter( void ) {
  4049. // check if it's time to play a chat sound
  4050. if ( AI_DEAD || !chat_snd || ( chat_time > gameLocal.time ) ) {
  4051. return;
  4052. }
  4053. StartSoundShader( chat_snd, SND_CHANNEL_VOICE, 0, false, NULL );
  4054. // set the next chat time
  4055. chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
  4056. }
  4057. /*
  4058. =====================
  4059. idAI::UpdateParticles
  4060. =====================
  4061. */
  4062. void idAI::UpdateParticles( void ) {
  4063. if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) {
  4064. idVec3 realVector;
  4065. idMat3 realAxis;
  4066. int particlesAlive = 0;
  4067. for ( int i = 0; i < particles.Num(); i++ ) {
  4068. #ifdef _D3XP
  4069. // Smoke particles on AI characters will always be "slow", even when held by grabber
  4070. SetTimeState ts(TIME_GROUP1);
  4071. #endif
  4072. if ( particles[i].particle && particles[i].time ) {
  4073. particlesAlive++;
  4074. if (af.IsActive()) {
  4075. realAxis = mat3_identity;
  4076. realVector = GetPhysics()->GetOrigin();
  4077. } else {
  4078. animator.GetJointTransform( particles[i].joint, gameLocal.time, realVector, realAxis );
  4079. realAxis *= renderEntity.axis;
  4080. realVector = physicsObj.GetOrigin() + ( realVector + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() );
  4081. }
  4082. if ( !gameLocal.smokeParticles->EmitSmoke( particles[i].particle, particles[i].time, gameLocal.random.CRandomFloat(), realVector, realAxis, timeGroup /*_D3XP*/ )) {
  4083. if ( restartParticles ) {
  4084. particles[i].time = gameLocal.time;
  4085. } else {
  4086. particles[i].time = 0;
  4087. particlesAlive--;
  4088. }
  4089. }
  4090. }
  4091. }
  4092. if ( particlesAlive == 0 ) {
  4093. BecomeInactive( TH_UPDATEPARTICLES );
  4094. }
  4095. }
  4096. }
  4097. /*
  4098. =====================
  4099. idAI::TriggerParticles
  4100. =====================
  4101. */
  4102. void idAI::TriggerParticles( const char *jointName ) {
  4103. jointHandle_t jointNum;
  4104. jointNum = animator.GetJointHandle( jointName );
  4105. for ( int i = 0; i < particles.Num(); i++ ) {
  4106. if ( particles[i].joint == jointNum ) {
  4107. particles[i].time = gameLocal.time;
  4108. BecomeActive( TH_UPDATEPARTICLES );
  4109. }
  4110. }
  4111. }
  4112. #ifdef _D3XP
  4113. void idAI::TriggerFX( const char* joint, const char* fx ) {
  4114. if( !strcmp(joint, "origin") ) {
  4115. idEntityFx::StartFx( fx, NULL, NULL, this, true );
  4116. } else {
  4117. idVec3 joint_origin;
  4118. idMat3 joint_axis;
  4119. jointHandle_t jointNum;
  4120. jointNum = animator.GetJointHandle( joint );
  4121. if ( jointNum == INVALID_JOINT ) {
  4122. gameLocal.Warning( "Unknown fx joint '%s' on entity %s", joint, name.c_str() );
  4123. return;
  4124. }
  4125. GetJointWorldTransform( jointNum, gameLocal.time, joint_origin, joint_axis );
  4126. idEntityFx::StartFx( fx, &joint_origin, &joint_axis, this, true );
  4127. }
  4128. }
  4129. idEntity* idAI::StartEmitter( const char* name, const char* joint, const char* particle ) {
  4130. idEntity* existing = GetEmitter(name);
  4131. if(existing) {
  4132. return existing;
  4133. }
  4134. jointHandle_t jointNum;
  4135. jointNum = animator.GetJointHandle( joint );
  4136. idVec3 offset;
  4137. idMat3 axis;
  4138. GetJointWorldTransform( jointNum, gameLocal.time, offset, axis );
  4139. /*animator.GetJointTransform( jointNum, gameLocal.time, offset, axis );
  4140. offset = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis();
  4141. axis = axis * GetPhysics()->GetAxis();*/
  4142. idDict args;
  4143. const idDeclEntityDef *emitterDef = gameLocal.FindEntityDef( "func_emitter", false );
  4144. args = emitterDef->dict;
  4145. args.Set("model", particle);
  4146. args.Set( "origin", offset.ToString() );
  4147. args.SetBool("start_off", true);
  4148. idEntity* ent;
  4149. gameLocal.SpawnEntityDef(args, &ent, false);
  4150. ent->GetPhysics()->SetOrigin(offset);
  4151. //ent->GetPhysics()->SetAxis(axis);
  4152. // align z-axis of model with the direction
  4153. /*idVec3 tmp;
  4154. axis = (viewAxis[ 0 ] * physicsObj.GetGravityAxis()).ToMat3();
  4155. tmp = axis[2];
  4156. axis[2] = axis[0];
  4157. axis[0] = -tmp;
  4158. ent->GetPhysics()->SetAxis(axis);*/
  4159. axis = physicsObj.GetGravityAxis();
  4160. ent->GetPhysics()->SetAxis(axis);
  4161. ent->GetPhysics()->GetClipModel()->SetOwner( this );
  4162. //Keep a reference to the emitter so we can track it
  4163. funcEmitter_t newEmitter;
  4164. strcpy(newEmitter.name, name);
  4165. newEmitter.particle = (idFuncEmitter*)ent;
  4166. newEmitter.joint = jointNum;
  4167. funcEmitters.Set(newEmitter.name, newEmitter);
  4168. //Bind it to the joint and make it active
  4169. newEmitter.particle->BindToJoint(this, jointNum, true);
  4170. newEmitter.particle->BecomeActive(TH_THINK);
  4171. newEmitter.particle->Show();
  4172. newEmitter.particle->PostEventMS(&EV_Activate, 0, this);
  4173. return newEmitter.particle;
  4174. }
  4175. idEntity* idAI::GetEmitter( const char* name ) {
  4176. funcEmitter_t* emitter;
  4177. funcEmitters.Get(name, &emitter);
  4178. if(emitter) {
  4179. return emitter->particle;
  4180. }
  4181. return NULL;
  4182. }
  4183. void idAI::StopEmitter( const char* name ) {
  4184. funcEmitter_t* emitter;
  4185. funcEmitters.Get(name, &emitter);
  4186. if(emitter) {
  4187. emitter->particle->Unbind();
  4188. emitter->particle->PostEventMS( &EV_Remove, 0 );
  4189. funcEmitters.Remove(name);
  4190. }
  4191. }
  4192. #endif
  4193. /***********************************************************************
  4194. Head & torso aiming
  4195. ***********************************************************************/
  4196. /*
  4197. ================
  4198. idAI::UpdateAnimationControllers
  4199. ================
  4200. */
  4201. bool idAI::UpdateAnimationControllers( void ) {
  4202. idVec3 local;
  4203. idVec3 focusPos;
  4204. idQuat jawQuat;
  4205. idVec3 left;
  4206. idVec3 dir;
  4207. idVec3 orientationJointPos;
  4208. idVec3 localDir;
  4209. idAngles newLookAng;
  4210. idAngles diff;
  4211. idMat3 mat;
  4212. idMat3 axis;
  4213. idMat3 orientationJointAxis;
  4214. idAFAttachment *headEnt = head.GetEntity();
  4215. idVec3 eyepos;
  4216. idVec3 pos;
  4217. int i;
  4218. idAngles jointAng;
  4219. float orientationJointYaw;
  4220. if ( AI_DEAD ) {
  4221. return idActor::UpdateAnimationControllers();
  4222. }
  4223. if ( orientationJoint == INVALID_JOINT ) {
  4224. orientationJointAxis = viewAxis;
  4225. orientationJointPos = physicsObj.GetOrigin();
  4226. orientationJointYaw = current_yaw;
  4227. } else {
  4228. GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis );
  4229. orientationJointYaw = orientationJointAxis[ 2 ].ToYaw();
  4230. orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3();
  4231. }
  4232. if ( focusJoint != INVALID_JOINT ) {
  4233. if ( headEnt ) {
  4234. headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
  4235. } else {
  4236. GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
  4237. }
  4238. eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z;
  4239. if ( ai_debugMove.GetBool() ) {
  4240. gameRenderWorld->DebugLine( colorRed, eyepos, eyepos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
  4241. }
  4242. } else {
  4243. eyepos = GetEyePosition();
  4244. }
  4245. if ( headEnt ) {
  4246. CopyJointsFromBodyToHead();
  4247. }
  4248. // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions.
  4249. // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which
  4250. // are already up to date because of getting the joints in this function) and then sets their positions, which
  4251. // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled,
  4252. // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no
  4253. // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number
  4254. // in order to see how many times an entity transforms the joints per frame.
  4255. idActor::UpdateAnimationControllers();
  4256. idEntity *focusEnt = focusEntity.GetEntity();
  4257. if ( !allowJointMod || !allowEyeFocus || ( gameLocal.time >= focusTime ) ) {
  4258. focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f;
  4259. } else if ( focusEnt == NULL ) {
  4260. // keep looking at last position until focusTime is up
  4261. focusPos = currentFocusPos;
  4262. } else if ( focusEnt == enemy.GetEntity() ) {
  4263. focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal();
  4264. } else if ( focusEnt->IsType( idActor::Type ) ) {
  4265. focusPos = static_cast<idActor *>( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal();
  4266. } else {
  4267. focusPos = focusEnt->GetPhysics()->GetOrigin();
  4268. }
  4269. //BC laser offset.
  4270. idVec3 offset = spawnArgs.GetVector("lookoffset", "0 0 0");
  4271. focusPos += offset;
  4272. currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate;
  4273. // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles
  4274. dir = focusPos - orientationJointPos;
  4275. newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw );
  4276. newLookAng.roll = 0.0f;
  4277. newLookAng.pitch = 0.0f;
  4278. #if 0
  4279. gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec );
  4280. gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
  4281. gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec );
  4282. #endif
  4283. // determine pitch from joint position
  4284. dir = focusPos - eyepos;
  4285. dir.NormalizeFast();
  4286. orientationJointAxis.ProjectVector( dir, localDir );
  4287. newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() );
  4288. newLookAng.roll = 0.0f;
  4289. diff = newLookAng - lookAng;
  4290. if ( eyeAng != diff ) {
  4291. eyeAng = diff;
  4292. eyeAng.Clamp( eyeMin, eyeMax );
  4293. idAngles angDelta = diff - eyeAng;
  4294. if ( !angDelta.Compare( ang_zero, 0.1f ) ) {
  4295. alignHeadTime = gameLocal.time;
  4296. } else {
  4297. alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
  4298. }
  4299. }
  4300. if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) {
  4301. alignHeadTime = gameLocal.time;
  4302. }
  4303. if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) {
  4304. alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
  4305. destLookAng = newLookAng;
  4306. destLookAng.Clamp( lookMin, lookMax );
  4307. }
  4308. diff = destLookAng - lookAng;
  4309. if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) {
  4310. if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) {
  4311. diff.pitch = 360.0f - diff.pitch;
  4312. }
  4313. }
  4314. if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) {
  4315. if ( diff.yaw > 180.0f ) {
  4316. diff.yaw -= 360.0f;
  4317. } else if ( diff.yaw <= -180.0f ) {
  4318. diff.yaw += 360.0f;
  4319. }
  4320. }
  4321. lookAng = lookAng + diff * headFocusRate;
  4322. lookAng.Normalize180();
  4323. jointAng.roll = 0.0f;
  4324. for( i = 0; i < lookJoints.Num(); i++ ) {
  4325. jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch;
  4326. jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw;
  4327. animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() );
  4328. }
  4329. if ( move.moveType == MOVETYPE_FLY ) {
  4330. // lean into turns
  4331. AdjustFlyingAngles();
  4332. }
  4333. if ( headEnt ) {
  4334. idAnimator *headAnimator = headEnt->GetAnimator();
  4335. if ( allowEyeFocus ) {
  4336. idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose();
  4337. axis = eyeAxis * orientationJointAxis;
  4338. left = axis[ 1 ] * eyeHorizontalOffset;
  4339. eyepos -= headEnt->GetPhysics()->GetOrigin();
  4340. headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose );
  4341. headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose );
  4342. } else {
  4343. headAnimator->ClearJoint( leftEyeJoint );
  4344. headAnimator->ClearJoint( rightEyeJoint );
  4345. }
  4346. } else {
  4347. if ( allowEyeFocus ) {
  4348. idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3();
  4349. axis = eyeAxis * orientationJointAxis;
  4350. left = axis[ 1 ] * eyeHorizontalOffset;
  4351. eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin();
  4352. animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left );
  4353. animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left );
  4354. } else {
  4355. animator.ClearJoint( leftEyeJoint );
  4356. animator.ClearJoint( rightEyeJoint );
  4357. }
  4358. }
  4359. return true;
  4360. }
  4361. /***********************************************************************
  4362. idCombatNode
  4363. ***********************************************************************/
  4364. const idEventDef EV_CombatNode_MarkUsed( "markUsed" );
  4365. CLASS_DECLARATION( idEntity, idCombatNode )
  4366. EVENT( EV_CombatNode_MarkUsed, idCombatNode::Event_MarkUsed )
  4367. EVENT( EV_Activate, idCombatNode::Event_Activate )
  4368. END_CLASS
  4369. /*
  4370. =====================
  4371. idCombatNode::idCombatNode
  4372. =====================
  4373. */
  4374. idCombatNode::idCombatNode( void ) {
  4375. min_dist = 0.0f;
  4376. max_dist = 0.0f;
  4377. cone_dist = 0.0f;
  4378. min_height = 0.0f;
  4379. max_height = 0.0f;
  4380. cone_left.Zero();
  4381. cone_right.Zero();
  4382. offset.Zero();
  4383. disabled = false;
  4384. }
  4385. /*
  4386. =====================
  4387. idCombatNode::Save
  4388. =====================
  4389. */
  4390. void idCombatNode::Save( idSaveGame *savefile ) const {
  4391. savefile->WriteFloat( min_dist );
  4392. savefile->WriteFloat( max_dist );
  4393. savefile->WriteFloat( cone_dist );
  4394. savefile->WriteFloat( min_height );
  4395. savefile->WriteFloat( max_height );
  4396. savefile->WriteVec3( cone_left );
  4397. savefile->WriteVec3( cone_right );
  4398. savefile->WriteVec3( offset );
  4399. savefile->WriteBool( disabled );
  4400. }
  4401. /*
  4402. =====================
  4403. idCombatNode::Restore
  4404. =====================
  4405. */
  4406. void idCombatNode::Restore( idRestoreGame *savefile ) {
  4407. savefile->ReadFloat( min_dist );
  4408. savefile->ReadFloat( max_dist );
  4409. savefile->ReadFloat( cone_dist );
  4410. savefile->ReadFloat( min_height );
  4411. savefile->ReadFloat( max_height );
  4412. savefile->ReadVec3( cone_left );
  4413. savefile->ReadVec3( cone_right );
  4414. savefile->ReadVec3( offset );
  4415. savefile->ReadBool( disabled );
  4416. }
  4417. /*
  4418. =====================
  4419. idCombatNode::Spawn
  4420. =====================
  4421. */
  4422. void idCombatNode::Spawn( void ) {
  4423. float fov;
  4424. float yaw;
  4425. float height;
  4426. min_dist = spawnArgs.GetFloat( "min" );
  4427. max_dist = spawnArgs.GetFloat( "max" );
  4428. height = spawnArgs.GetFloat( "height" );
  4429. fov = spawnArgs.GetFloat( "fov", "60" );
  4430. offset = spawnArgs.GetVector( "offset" );
  4431. const idVec3 &org = GetPhysics()->GetOrigin() + offset;
  4432. min_height = org.z - height * 0.5f;
  4433. max_height = min_height + height;
  4434. const idMat3 &axis = GetPhysics()->GetAxis();
  4435. yaw = axis[ 0 ].ToYaw();
  4436. idAngles leftang( 0.0f, yaw + fov * 0.5f - 90.0f, 0.0f );
  4437. cone_left = leftang.ToForward();
  4438. idAngles rightang( 0.0f, yaw - fov * 0.5f + 90.0f, 0.0f );
  4439. cone_right = rightang.ToForward();
  4440. disabled = spawnArgs.GetBool( "start_off" );
  4441. }
  4442. /*
  4443. =====================
  4444. idCombatNode::IsDisabled
  4445. =====================
  4446. */
  4447. bool idCombatNode::IsDisabled( void ) const {
  4448. return disabled;
  4449. }
  4450. /*
  4451. =====================
  4452. idCombatNode::DrawDebugInfo
  4453. =====================
  4454. */
  4455. void idCombatNode::DrawDebugInfo( void ) {
  4456. idEntity *ent;
  4457. idCombatNode *node;
  4458. idPlayer *player = gameLocal.GetLocalPlayer();
  4459. idVec4 color;
  4460. idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 0 ) );
  4461. for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
  4462. if ( !ent->IsType( idCombatNode::Type ) ) {
  4463. continue;
  4464. }
  4465. node = static_cast<idCombatNode *>( ent );
  4466. if ( node->disabled ) {
  4467. color = colorMdGrey;
  4468. } else if ( player && node->EntityInView( player, player->GetPhysics()->GetOrigin() ) ) {
  4469. color = colorYellow;
  4470. } else {
  4471. color = colorRed;
  4472. }
  4473. idVec3 leftDir( -node->cone_left.y, node->cone_left.x, 0.0f );
  4474. idVec3 rightDir( node->cone_right.y, -node->cone_right.x, 0.0f );
  4475. idVec3 org = node->GetPhysics()->GetOrigin() + node->offset;
  4476. bounds[ 1 ].z = node->max_height;
  4477. leftDir.NormalizeFast();
  4478. rightDir.NormalizeFast();
  4479. const idMat3 &axis = node->GetPhysics()->GetAxis();
  4480. float cone_dot = node->cone_right * axis[ 1 ];
  4481. if ( idMath::Fabs( cone_dot ) > 0.1 ) {
  4482. float cone_dist = node->max_dist / cone_dot;
  4483. idVec3 pos1 = org + leftDir * node->min_dist;
  4484. idVec3 pos2 = org + leftDir * cone_dist;
  4485. idVec3 pos3 = org + rightDir * node->min_dist;
  4486. idVec3 pos4 = org + rightDir * cone_dist;
  4487. gameRenderWorld->DebugLine( color, node->GetPhysics()->GetOrigin(), ( pos1 + pos3 ) * 0.5f, gameLocal.msec );
  4488. gameRenderWorld->DebugLine( color, pos1, pos2, gameLocal.msec );
  4489. gameRenderWorld->DebugLine( color, pos1, pos3, gameLocal.msec );
  4490. gameRenderWorld->DebugLine( color, pos3, pos4, gameLocal.msec );
  4491. gameRenderWorld->DebugLine( color, pos2, pos4, gameLocal.msec );
  4492. gameRenderWorld->DebugBounds( color, bounds, org, gameLocal.msec );
  4493. }
  4494. }
  4495. }
  4496. /*
  4497. =====================
  4498. idCombatNode::EntityInView
  4499. =====================
  4500. */
  4501. bool idCombatNode::EntityInView( idActor *actor, const idVec3 &pos ) {
  4502. if ( !actor || ( actor->health <= 0 ) ) {
  4503. return false;
  4504. }
  4505. const idBounds &bounds = actor->GetPhysics()->GetBounds();
  4506. if ( ( pos.z + bounds[ 1 ].z < min_height ) || ( pos.z + bounds[ 0 ].z >= max_height ) ) {
  4507. return false;
  4508. }
  4509. const idVec3 &org = GetPhysics()->GetOrigin() + offset;
  4510. const idMat3 &axis = GetPhysics()->GetAxis();
  4511. idVec3 dir = pos - org;
  4512. float dist = dir * axis[ 0 ];
  4513. if ( ( dist < min_dist ) || ( dist > max_dist ) ) {
  4514. return false;
  4515. }
  4516. float left_dot = dir * cone_left;
  4517. if ( left_dot < 0.0f ) {
  4518. return false;
  4519. }
  4520. float right_dot = dir * cone_right;
  4521. if ( right_dot < 0.0f ) {
  4522. return false;
  4523. }
  4524. return true;
  4525. }
  4526. /*
  4527. =====================
  4528. idCombatNode::Event_Activate
  4529. =====================
  4530. */
  4531. void idCombatNode::Event_Activate( idEntity *activator ) {
  4532. disabled = !disabled;
  4533. }
  4534. /*
  4535. =====================
  4536. idCombatNode::Event_MarkUsed
  4537. =====================
  4538. */
  4539. void idCombatNode::Event_MarkUsed( void ) {
  4540. if ( spawnArgs.GetBool( "use_once" ) ) {
  4541. disabled = true;
  4542. }
  4543. }