p_client.cpp 94 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "g_local.h"
  4. #include "m_player.h"
  5. #include "bots/bot_includes.h"
  6. void SP_misc_teleporter_dest(edict_t *ent);
  7. THINK(info_player_start_drop) (edict_t *self) -> void
  8. {
  9. // allow them to drop
  10. self->solid = SOLID_TRIGGER;
  11. self->movetype = MOVETYPE_TOSS;
  12. self->mins = PLAYER_MINS;
  13. self->maxs = PLAYER_MAXS;
  14. gi.linkentity(self);
  15. }
  16. /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
  17. The normal starting point for a level.
  18. */
  19. void SP_info_player_start(edict_t *self)
  20. {
  21. // fix stuck spawn points
  22. if (gi.trace(self->s.origin, PLAYER_MINS, PLAYER_MAXS, self->s.origin, self, MASK_SOLID).startsolid)
  23. G_FixStuckObject(self, self->s.origin);
  24. // [Paril-KEX] on n64, since these can spawn riding elevators,
  25. // allow them to "ride" the elevators so respawning works
  26. if (level.is_n64)
  27. {
  28. self->think = info_player_start_drop;
  29. self->nextthink = level.time + FRAME_TIME_S;
  30. }
  31. }
  32. /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
  33. potential spawning position for deathmatch games
  34. */
  35. void SP_info_player_deathmatch(edict_t *self)
  36. {
  37. if (!deathmatch->integer)
  38. {
  39. G_FreeEdict(self);
  40. return;
  41. }
  42. SP_misc_teleporter_dest(self);
  43. }
  44. /*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
  45. potential spawning position for coop games
  46. */
  47. void SP_info_player_coop(edict_t *self)
  48. {
  49. if (!coop->integer)
  50. {
  51. G_FreeEdict(self);
  52. return;
  53. }
  54. SP_info_player_start(self);
  55. }
  56. /*QUAKED info_player_coop_lava (1 0 1) (-16 -16 -24) (16 16 32)
  57. potential spawning position for coop games on rmine2 where lava level
  58. needs to be checked
  59. */
  60. void SP_info_player_coop_lava(edict_t *self)
  61. {
  62. if (!coop->integer)
  63. {
  64. G_FreeEdict(self);
  65. return;
  66. }
  67. // fix stuck spawn points
  68. if (gi.trace(self->s.origin, PLAYER_MINS, PLAYER_MAXS, self->s.origin, self, MASK_SOLID).startsolid)
  69. G_FixStuckObject(self, self->s.origin);
  70. }
  71. /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
  72. The deathmatch intermission point will be at one of these
  73. Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll'
  74. */
  75. void SP_info_player_intermission(edict_t *ent)
  76. {
  77. }
  78. // [Paril-KEX] whether instanced items should be used or not
  79. bool P_UseCoopInstancedItems()
  80. {
  81. // squad respawn forces instanced items on, since we don't
  82. // want players to need to backtrack just to get their stuff.
  83. return g_coop_instanced_items->integer || g_coop_squad_respawn->integer;
  84. }
  85. //=======================================================================
  86. void ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker, mod_t mod)
  87. {
  88. const char *base = nullptr;
  89. if (coop->integer && attacker->client)
  90. mod.friendly_fire = true;
  91. switch (mod.id)
  92. {
  93. case MOD_SUICIDE:
  94. base = "$g_mod_generic_suicide";
  95. break;
  96. case MOD_FALLING:
  97. base = "$g_mod_generic_falling";
  98. break;
  99. case MOD_CRUSH:
  100. base = "$g_mod_generic_crush";
  101. break;
  102. case MOD_WATER:
  103. base = "$g_mod_generic_water";
  104. break;
  105. case MOD_SLIME:
  106. base = "$g_mod_generic_slime";
  107. break;
  108. case MOD_LAVA:
  109. base = "$g_mod_generic_lava";
  110. break;
  111. case MOD_EXPLOSIVE:
  112. case MOD_BARREL:
  113. base = "$g_mod_generic_explosive";
  114. break;
  115. case MOD_EXIT:
  116. base = "$g_mod_generic_exit";
  117. break;
  118. case MOD_TARGET_LASER:
  119. base = "$g_mod_generic_laser";
  120. break;
  121. case MOD_TARGET_BLASTER:
  122. base = "$g_mod_generic_blaster";
  123. break;
  124. case MOD_BOMB:
  125. case MOD_SPLASH:
  126. case MOD_TRIGGER_HURT:
  127. base = "$g_mod_generic_hurt";
  128. break;
  129. // RAFAEL
  130. case MOD_GEKK:
  131. case MOD_BRAINTENTACLE:
  132. base = "$g_mod_generic_gekk";
  133. break;
  134. // RAFAEL
  135. default:
  136. base = nullptr;
  137. break;
  138. }
  139. if (attacker == self)
  140. {
  141. switch (mod.id)
  142. {
  143. case MOD_HELD_GRENADE:
  144. base = "$g_mod_self_held_grenade";
  145. break;
  146. case MOD_HG_SPLASH:
  147. case MOD_G_SPLASH:
  148. base = "$g_mod_self_grenade_splash";
  149. break;
  150. case MOD_R_SPLASH:
  151. base = "$g_mod_self_rocket_splash";
  152. break;
  153. case MOD_BFG_BLAST:
  154. base = "$g_mod_self_bfg_blast";
  155. break;
  156. // RAFAEL 03-MAY-98
  157. case MOD_TRAP:
  158. base = "$g_mod_self_trap";
  159. break;
  160. // RAFAEL
  161. // ROGUE
  162. case MOD_DOPPLE_EXPLODE:
  163. base = "$g_mod_self_dopple_explode";
  164. break;
  165. // ROGUE
  166. default:
  167. base = "$g_mod_self_default";
  168. break;
  169. }
  170. }
  171. // send generic/self
  172. if (base)
  173. {
  174. gi.LocBroadcast_Print(PRINT_MEDIUM, base, self->client->pers.netname);
  175. if (deathmatch->integer && !mod.no_point_loss)
  176. {
  177. self->client->resp.score--;
  178. if (teamplay->integer)
  179. G_AdjustTeamScore(self->client->resp.ctf_team, -1);
  180. }
  181. self->enemy = nullptr;
  182. return;
  183. }
  184. // has a killer
  185. self->enemy = attacker;
  186. if (attacker && attacker->client)
  187. {
  188. switch (mod.id)
  189. {
  190. case MOD_BLASTER:
  191. base = "$g_mod_kill_blaster";
  192. break;
  193. case MOD_SHOTGUN:
  194. base = "$g_mod_kill_shotgun";
  195. break;
  196. case MOD_SSHOTGUN:
  197. base = "$g_mod_kill_sshotgun";
  198. break;
  199. case MOD_MACHINEGUN:
  200. base = "$g_mod_kill_machinegun";
  201. break;
  202. case MOD_CHAINGUN:
  203. base = "$g_mod_kill_chaingun";
  204. break;
  205. case MOD_GRENADE:
  206. base = "$g_mod_kill_grenade";
  207. break;
  208. case MOD_G_SPLASH:
  209. base = "$g_mod_kill_grenade_splash";
  210. break;
  211. case MOD_ROCKET:
  212. base = "$g_mod_kill_rocket";
  213. break;
  214. case MOD_R_SPLASH:
  215. base = "$g_mod_kill_rocket_splash";
  216. break;
  217. case MOD_HYPERBLASTER:
  218. base = "$g_mod_kill_hyperblaster";
  219. break;
  220. case MOD_RAILGUN:
  221. base = "$g_mod_kill_railgun";
  222. break;
  223. case MOD_BFG_LASER:
  224. base = "$g_mod_kill_bfg_laser";
  225. break;
  226. case MOD_BFG_BLAST:
  227. base = "$g_mod_kill_bfg_blast";
  228. break;
  229. case MOD_BFG_EFFECT:
  230. base = "$g_mod_kill_bfg_effect";
  231. break;
  232. case MOD_HANDGRENADE:
  233. base = "$g_mod_kill_handgrenade";
  234. break;
  235. case MOD_HG_SPLASH:
  236. base = "$g_mod_kill_handgrenade_splash";
  237. break;
  238. case MOD_HELD_GRENADE:
  239. base = "$g_mod_kill_held_grenade";
  240. break;
  241. case MOD_TELEFRAG:
  242. case MOD_TELEFRAG_SPAWN:
  243. base = "$g_mod_kill_telefrag";
  244. break;
  245. // RAFAEL 14-APR-98
  246. case MOD_RIPPER:
  247. base = "$g_mod_kill_ripper";
  248. break;
  249. case MOD_PHALANX:
  250. base = "$g_mod_kill_phalanx";
  251. break;
  252. case MOD_TRAP:
  253. base = "$g_mod_kill_trap";
  254. break;
  255. // RAFAEL
  256. //===============
  257. // ROGUE
  258. case MOD_CHAINFIST:
  259. base = "$g_mod_kill_chainfist";
  260. break;
  261. case MOD_DISINTEGRATOR:
  262. base = "$g_mod_kill_disintegrator";
  263. break;
  264. case MOD_ETF_RIFLE:
  265. base = "$g_mod_kill_etf_rifle";
  266. break;
  267. case MOD_HEATBEAM:
  268. base = "$g_mod_kill_heatbeam";
  269. break;
  270. case MOD_TESLA:
  271. base = "$g_mod_kill_tesla";
  272. break;
  273. case MOD_PROX:
  274. base = "$g_mod_kill_prox";
  275. break;
  276. case MOD_NUKE:
  277. base = "$g_mod_kill_nuke";
  278. break;
  279. case MOD_VENGEANCE_SPHERE:
  280. base = "$g_mod_kill_vengeance_sphere";
  281. break;
  282. case MOD_DEFENDER_SPHERE:
  283. base = "$g_mod_kill_defender_sphere";
  284. break;
  285. case MOD_HUNTER_SPHERE:
  286. base = "$g_mod_kill_hunter_sphere";
  287. break;
  288. case MOD_TRACKER:
  289. base = "$g_mod_kill_tracker";
  290. break;
  291. case MOD_DOPPLE_EXPLODE:
  292. base = "$g_mod_kill_dopple_explode";
  293. break;
  294. case MOD_DOPPLE_VENGEANCE:
  295. base = "$g_mod_kill_dopple_vengeance";
  296. break;
  297. case MOD_DOPPLE_HUNTER:
  298. base = "$g_mod_kill_dopple_hunter";
  299. break;
  300. // ROGUE
  301. //===============
  302. // ZOID
  303. case MOD_GRAPPLE:
  304. base = "$g_mod_kill_grapple";
  305. break;
  306. // ZOID
  307. default:
  308. base = "$g_mod_kill_generic";
  309. break;
  310. }
  311. gi.LocBroadcast_Print(PRINT_MEDIUM, base, self->client->pers.netname, attacker->client->pers.netname);
  312. if (G_TeamplayEnabled())
  313. {
  314. // ZOID
  315. // if at start and same team, clear.
  316. // [Paril-KEX] moved here so it's not an outlier in player_die.
  317. if (mod.id == MOD_TELEFRAG_SPAWN &&
  318. self->client->resp.ctf_state < 2 &&
  319. self->client->resp.ctf_team == attacker->client->resp.ctf_team)
  320. {
  321. self->client->resp.ctf_state = 0;
  322. return;
  323. }
  324. }
  325. // ROGUE
  326. if (gamerules->integer)
  327. {
  328. if (DMGame.Score)
  329. {
  330. if (mod.friendly_fire)
  331. {
  332. if (!mod.no_point_loss)
  333. DMGame.Score(attacker, self, -1, mod);
  334. }
  335. else
  336. DMGame.Score(attacker, self, 1, mod);
  337. }
  338. return;
  339. }
  340. // ROGUE
  341. if (deathmatch->integer)
  342. {
  343. if (mod.friendly_fire)
  344. {
  345. if (!mod.no_point_loss)
  346. {
  347. attacker->client->resp.score--;
  348. if (teamplay->integer)
  349. G_AdjustTeamScore(attacker->client->resp.ctf_team, -1);
  350. }
  351. }
  352. else
  353. {
  354. attacker->client->resp.score++;
  355. if (teamplay->integer)
  356. G_AdjustTeamScore(attacker->client->resp.ctf_team, 1);
  357. }
  358. }
  359. else if (!coop->integer)
  360. self->client->resp.score--;
  361. return;
  362. }
  363. gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_mod_generic_died", self->client->pers.netname);
  364. if (deathmatch->integer && !mod.no_point_loss)
  365. // ROGUE
  366. {
  367. if (gamerules->integer)
  368. {
  369. if (DMGame.Score)
  370. {
  371. DMGame.Score(self, self, -1, mod);
  372. }
  373. return;
  374. }
  375. else
  376. {
  377. self->client->resp.score--;
  378. if (teamplay->integer)
  379. G_AdjustTeamScore(attacker->client->resp.ctf_team, -1);
  380. }
  381. }
  382. // ROGUE
  383. }
  384. void TossClientWeapon(edict_t *self)
  385. {
  386. gitem_t *item;
  387. edict_t *drop;
  388. bool quad;
  389. // RAFAEL
  390. bool quadfire;
  391. // RAFAEL
  392. float spread;
  393. if (!deathmatch->integer)
  394. return;
  395. item = self->client->pers.weapon;
  396. if (item && g_instagib->integer)
  397. item = nullptr;
  398. if (item && !self->client->pers.inventory[self->client->pers.weapon->ammo])
  399. item = nullptr;
  400. if (item && !item->drop)
  401. item = nullptr;
  402. if (g_dm_no_quad_drop->integer)
  403. quad = false;
  404. else
  405. quad = (self->client->quad_time > (level.time + 1_sec));
  406. // RAFAEL
  407. if (g_dm_no_quadfire_drop->integer)
  408. quadfire = false;
  409. else
  410. quadfire = (self->client->quadfire_time > (level.time + 1_sec));
  411. // RAFAEL
  412. if (item && quad)
  413. spread = 22.5;
  414. // RAFAEL
  415. else if (item && quadfire)
  416. spread = 12.5;
  417. // RAFAEL
  418. else
  419. spread = 0.0;
  420. if (item)
  421. {
  422. self->client->v_angle[YAW] -= spread;
  423. drop = Drop_Item(self, item);
  424. self->client->v_angle[YAW] += spread;
  425. drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER;
  426. drop->spawnflags &= ~SPAWNFLAG_ITEM_DROPPED;
  427. drop->svflags &= ~SVF_INSTANCED;
  428. }
  429. if (quad)
  430. {
  431. self->client->v_angle[YAW] += spread;
  432. drop = Drop_Item(self, GetItemByIndex(IT_ITEM_QUAD));
  433. self->client->v_angle[YAW] -= spread;
  434. drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER;
  435. drop->spawnflags &= ~SPAWNFLAG_ITEM_DROPPED;
  436. drop->svflags &= ~SVF_INSTANCED;
  437. drop->touch = Touch_Item;
  438. drop->nextthink = self->client->quad_time;
  439. drop->think = G_FreeEdict;
  440. }
  441. // RAFAEL
  442. if (quadfire)
  443. {
  444. self->client->v_angle[YAW] += spread;
  445. drop = Drop_Item(self, GetItemByIndex(IT_ITEM_QUADFIRE));
  446. self->client->v_angle[YAW] -= spread;
  447. drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER;
  448. drop->spawnflags &= ~SPAWNFLAG_ITEM_DROPPED;
  449. drop->svflags &= ~SVF_INSTANCED;
  450. drop->touch = Touch_Item;
  451. drop->nextthink = self->client->quadfire_time;
  452. drop->think = G_FreeEdict;
  453. }
  454. // RAFAEL
  455. }
  456. /*
  457. ==================
  458. LookAtKiller
  459. ==================
  460. */
  461. void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker)
  462. {
  463. vec3_t dir;
  464. if (attacker && attacker != world && attacker != self)
  465. {
  466. dir = attacker->s.origin - self->s.origin;
  467. }
  468. else if (inflictor && inflictor != world && inflictor != self)
  469. {
  470. dir = inflictor->s.origin - self->s.origin;
  471. }
  472. else
  473. {
  474. self->client->killer_yaw = self->s.angles[YAW];
  475. return;
  476. }
  477. // PMM - fixed to correct for pitch of 0
  478. if (dir[0])
  479. self->client->killer_yaw = 180 / PIf * atan2f(dir[1], dir[0]);
  480. else if (dir[1] > 0)
  481. self->client->killer_yaw = 90;
  482. else if (dir[1] < 0)
  483. self->client->killer_yaw = 270;
  484. else
  485. self->client->killer_yaw = 0;
  486. }
  487. /*
  488. ==================
  489. player_die
  490. ==================
  491. */
  492. DIE(player_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  493. {
  494. PlayerTrail_Destroy(self);
  495. self->avelocity = {};
  496. self->takedamage = true;
  497. self->movetype = MOVETYPE_TOSS;
  498. self->s.modelindex2 = 0; // remove linked weapon model
  499. // ZOID
  500. self->s.modelindex3 = 0; // remove linked ctf flag
  501. // ZOID
  502. self->s.angles[0] = 0;
  503. self->s.angles[2] = 0;
  504. self->s.sound = 0;
  505. self->client->weapon_sound = 0;
  506. self->maxs[2] = -8;
  507. // self->solid = SOLID_NOT;
  508. self->svflags |= SVF_DEADMONSTER;
  509. if (!self->deadflag)
  510. {
  511. self->client->respawn_time = ( level.time + 1_sec );
  512. if ( deathmatch->integer && g_dm_force_respawn_time->integer ) {
  513. self->client->respawn_time = ( level.time + gtime_t::from_sec( g_dm_force_respawn_time->value ) );
  514. }
  515. LookAtKiller(self, inflictor, attacker);
  516. self->client->ps.pmove.pm_type = PM_DEAD;
  517. ClientObituary(self, inflictor, attacker, mod);
  518. CTFFragBonuses(self, inflictor, attacker);
  519. // ZOID
  520. TossClientWeapon(self);
  521. // ZOID
  522. CTFPlayerResetGrapple(self);
  523. CTFDeadDropFlag(self);
  524. CTFDeadDropTech(self);
  525. // ZOID
  526. if (deathmatch->integer && !self->client->showscores)
  527. Cmd_Help_f(self); // show scores
  528. if (coop->integer && !P_UseCoopInstancedItems())
  529. {
  530. // clear inventory
  531. // this is kind of ugly, but it's how we want to handle keys in coop
  532. for (int n = 0; n < IT_TOTAL; n++)
  533. {
  534. if (coop->integer && (itemlist[n].flags & IF_KEY))
  535. self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n];
  536. self->client->pers.inventory[n] = 0;
  537. }
  538. }
  539. }
  540. if (gamerules->integer) // if we're in a dm game, alert the game
  541. {
  542. if (DMGame.PlayerDeath)
  543. DMGame.PlayerDeath(self, inflictor, attacker);
  544. }
  545. // remove powerups
  546. self->client->quad_time = 0_ms;
  547. self->client->invincible_time = 0_ms;
  548. self->client->breather_time = 0_ms;
  549. self->client->enviro_time = 0_ms;
  550. self->client->invisible_time = 0_ms;
  551. self->flags &= ~FL_POWER_ARMOR;
  552. // clear inventory
  553. if (G_TeamplayEnabled())
  554. self->client->pers.inventory.fill(0);
  555. // RAFAEL
  556. self->client->quadfire_time = 0_ms;
  557. // RAFAEL
  558. //==============
  559. // ROGUE stuff
  560. self->client->double_time = 0_ms;
  561. // if there's a sphere around, let it know the player died.
  562. // vengeance and hunter will die if they're not attacking,
  563. // defender should always die
  564. if (self->client->owned_sphere)
  565. {
  566. edict_t *sphere;
  567. sphere = self->client->owned_sphere;
  568. sphere->die(sphere, self, self, 0, vec3_origin, mod);
  569. }
  570. // if we've been killed by the tracker, GIB!
  571. if (mod.id == MOD_TRACKER)
  572. {
  573. self->health = -100;
  574. damage = 400;
  575. }
  576. // make sure no trackers are still hurting us.
  577. if (self->client->tracker_pain_time)
  578. {
  579. RemoveAttackingPainDaemons(self);
  580. }
  581. // if we got obliterated by the nuke, don't gib
  582. if ((self->health < -80) && (mod.id == MOD_NUKE))
  583. self->flags |= FL_NOGIB;
  584. // ROGUE
  585. //==============
  586. if (self->health < -40)
  587. {
  588. // PMM
  589. // don't toss gibs if we got vaped by the nuke
  590. if (!(self->flags & FL_NOGIB))
  591. {
  592. // pmm
  593. // gib
  594. gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  595. // more meaty gibs for your dollar!
  596. if (deathmatch->integer && (self->health < -80))
  597. ThrowGibs(self, damage, { { 4, "models/objects/gibs/sm_meat/tris.md2" } });
  598. ThrowGibs(self, damage, { { 4, "models/objects/gibs/sm_meat/tris.md2" } });
  599. // PMM
  600. }
  601. self->flags &= ~FL_NOGIB;
  602. // pmm
  603. ThrowClientHead(self, damage);
  604. // ZOID
  605. self->client->anim_priority = ANIM_DEATH;
  606. self->client->anim_end = 0;
  607. // ZOID
  608. self->takedamage = false;
  609. }
  610. else
  611. { // normal death
  612. if (!self->deadflag)
  613. {
  614. // start a death animation
  615. self->client->anim_priority = ANIM_DEATH;
  616. if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
  617. {
  618. self->s.frame = FRAME_crdeath1 - 1;
  619. self->client->anim_end = FRAME_crdeath5;
  620. }
  621. else
  622. {
  623. switch (irandom(3))
  624. {
  625. case 0:
  626. self->s.frame = FRAME_death101 - 1;
  627. self->client->anim_end = FRAME_death106;
  628. break;
  629. case 1:
  630. self->s.frame = FRAME_death201 - 1;
  631. self->client->anim_end = FRAME_death206;
  632. break;
  633. case 2:
  634. self->s.frame = FRAME_death301 - 1;
  635. self->client->anim_end = FRAME_death308;
  636. break;
  637. }
  638. }
  639. static constexpr const char *death_sounds[] = {
  640. "*death1.wav",
  641. "*death2.wav",
  642. "*death3.wav",
  643. "*death4.wav"
  644. };
  645. gi.sound(self, CHAN_VOICE, gi.soundindex(random_element(death_sounds)), 1, ATTN_NORM, 0);
  646. self->client->anim_time = 0_ms;
  647. }
  648. }
  649. if (!self->deadflag)
  650. {
  651. if (coop->integer && (g_coop_squad_respawn->integer || g_coop_enable_lives->integer))
  652. {
  653. if (g_coop_enable_lives->integer && self->client->pers.lives)
  654. {
  655. self->client->pers.lives--;
  656. self->client->resp.coop_respawn.lives--;
  657. }
  658. bool allPlayersDead = true;
  659. for (auto player : active_players())
  660. if (player->health > 0 || (!level.deadly_kill_box && g_coop_enable_lives->integer && player->client->pers.lives > 0))
  661. {
  662. allPlayersDead = false;
  663. break;
  664. }
  665. if (allPlayersDead) // allow respawns for telefrags and weird shit
  666. {
  667. level.coop_level_restart_time = level.time + 5_sec;
  668. for (auto player : active_players())
  669. gi.LocCenter_Print(player, "$g_coop_lose");
  670. }
  671. // in 3 seconds, attempt a respawn or put us into
  672. // spectator mode
  673. if (!level.coop_level_restart_time)
  674. self->client->respawn_time = level.time + 3_sec;
  675. }
  676. }
  677. self->deadflag = true;
  678. gi.linkentity(self);
  679. }
  680. //=======================================================================
  681. #include <string>
  682. #include <sstream>
  683. // [Paril-KEX]
  684. static void Player_GiveStartItems(edict_t *ent, const char *ptr)
  685. {
  686. char token_copy[MAX_TOKEN_CHARS];
  687. const char *token;
  688. while (*(token = COM_ParseEx(&ptr, ";")))
  689. {
  690. Q_strlcpy(token_copy, token, sizeof(token_copy));
  691. const char *ptr_copy = token_copy;
  692. const char *item_name = COM_Parse(&ptr_copy);
  693. gitem_t *item = FindItemByClassname(item_name);
  694. if (!item || !item->pickup)
  695. gi.Com_ErrorFmt("Invalid g_start_item entry: {}\n", item_name);
  696. int32_t count = 1;
  697. if (*ptr_copy)
  698. count = atoi(COM_Parse(&ptr_copy));
  699. if (count == 0)
  700. {
  701. ent->client->pers.inventory[item->id] = 0;
  702. continue;
  703. }
  704. edict_t *dummy = G_Spawn();
  705. dummy->item = item;
  706. dummy->count = count;
  707. dummy->spawnflags |= SPAWNFLAG_ITEM_DROPPED;
  708. item->pickup(dummy, ent);
  709. G_FreeEdict(dummy);
  710. }
  711. }
  712. /*
  713. ==============
  714. InitClientPersistant
  715. This is only called when the game first initializes in single player,
  716. but is called after each death and level change in deathmatch
  717. ==============
  718. */
  719. void InitClientPersistant(edict_t *ent, gclient_t *client)
  720. {
  721. // backup & restore userinfo
  722. char userinfo[MAX_INFO_STRING];
  723. Q_strlcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
  724. memset(&client->pers, 0, sizeof(client->pers));
  725. ClientUserinfoChanged(ent, userinfo);
  726. client->pers.health = 100;
  727. client->pers.max_health = 100;
  728. // don't give us weapons if we shouldn't have any
  729. if ((G_TeamplayEnabled() && client->resp.ctf_team != CTF_NOTEAM) ||
  730. (!G_TeamplayEnabled() && !client->resp.spectator))
  731. {
  732. // in coop, if there's already a player in the game and we're new,
  733. // steal their loadout. this would fix a potential softlock where a new
  734. // player may not have weapons at all.
  735. bool taken_loadout = false;
  736. if (coop->integer)
  737. {
  738. for (auto player : active_players())
  739. {
  740. if (player == ent || !player->client->pers.spawned ||
  741. player->client->resp.spectator || player->movetype == MOVETYPE_NOCLIP)
  742. continue;
  743. client->pers.inventory = player->client->pers.inventory;
  744. client->pers.max_ammo = player->client->pers.max_ammo;
  745. client->pers.power_cubes = player->client->pers.power_cubes;
  746. taken_loadout = true;
  747. break;
  748. }
  749. }
  750. if (!taken_loadout)
  751. {
  752. // fill with 50s, since it's our most common value
  753. client->pers.max_ammo.fill(50);
  754. client->pers.max_ammo[AMMO_BULLETS] = 200;
  755. client->pers.max_ammo[AMMO_SHELLS] = 100;
  756. client->pers.max_ammo[AMMO_CELLS] = 200;
  757. // RAFAEL
  758. client->pers.max_ammo[AMMO_TRAP] = 5;
  759. // RAFAEL
  760. // ROGUE
  761. client->pers.max_ammo[AMMO_FLECHETTES] = 200;
  762. client->pers.max_ammo[AMMO_DISRUPTOR] = 12;
  763. client->pers.max_ammo[AMMO_TESLA] = 5;
  764. // ROGUE
  765. if (!deathmatch->integer || !g_instagib->integer)
  766. client->pers.inventory[IT_WEAPON_BLASTER] = 1;
  767. // [Kex]
  768. // start items!
  769. if (*g_start_items->string)
  770. Player_GiveStartItems(ent, g_start_items->string);
  771. else if (deathmatch->integer && g_instagib->integer)
  772. {
  773. client->pers.inventory[IT_WEAPON_RAILGUN] = 1;
  774. client->pers.inventory[IT_AMMO_SLUGS] = 99;
  775. }
  776. if (level.start_items && *level.start_items)
  777. Player_GiveStartItems(ent, level.start_items);
  778. if (!deathmatch->integer)
  779. client->pers.inventory[IT_ITEM_COMPASS] = 1;
  780. // ZOID
  781. bool give_grapple = (!strcmp(g_allow_grapple->string, "auto")) ?
  782. (ctf->integer ? !level.no_grapple : 0) :
  783. g_allow_grapple->integer;
  784. if (give_grapple)
  785. client->pers.inventory[IT_WEAPON_GRAPPLE] = 1;
  786. // ZOID
  787. }
  788. NoAmmoWeaponChange(ent, false);
  789. client->pers.weapon = client->newweapon;
  790. if (client->newweapon)
  791. client->pers.selected_item = client->newweapon->id;
  792. client->newweapon = nullptr;
  793. // ZOID
  794. client->pers.lastweapon = client->pers.weapon;
  795. // ZOID
  796. }
  797. if (coop->value && g_coop_enable_lives->integer)
  798. client->pers.lives = g_coop_num_lives->integer + 1;
  799. if (ent->client->pers.autoshield >= AUTO_SHIELD_AUTO)
  800. ent->flags |= FL_WANTS_POWER_ARMOR;
  801. client->pers.connected = true;
  802. client->pers.spawned = true;
  803. }
  804. void InitClientResp(gclient_t *client)
  805. {
  806. // ZOID
  807. ctfteam_t ctf_team = client->resp.ctf_team;
  808. bool id_state = client->resp.id_state;
  809. // ZOID
  810. memset(&client->resp, 0, sizeof(client->resp));
  811. // ZOID
  812. client->resp.ctf_team = ctf_team;
  813. client->resp.id_state = id_state;
  814. // ZOID
  815. client->resp.entertime = level.time;
  816. client->resp.coop_respawn = client->pers;
  817. }
  818. /*
  819. ==================
  820. SaveClientData
  821. Some information that should be persistant, like health,
  822. is still stored in the edict structure, so it needs to
  823. be mirrored out to the client structure before all the
  824. edicts are wiped.
  825. ==================
  826. */
  827. void SaveClientData()
  828. {
  829. edict_t *ent;
  830. for (uint32_t i = 0; i < game.maxclients; i++)
  831. {
  832. ent = &g_edicts[1 + i];
  833. if (!ent->inuse)
  834. continue;
  835. game.clients[i].pers.health = ent->health;
  836. game.clients[i].pers.max_health = ent->max_health;
  837. game.clients[i].pers.savedFlags = (ent->flags & (FL_FLASHLIGHT | FL_GODMODE | FL_NOTARGET | FL_POWER_ARMOR | FL_WANTS_POWER_ARMOR));
  838. if (coop->integer)
  839. game.clients[i].pers.score = ent->client->resp.score;
  840. }
  841. }
  842. void FetchClientEntData(edict_t *ent)
  843. {
  844. ent->health = ent->client->pers.health;
  845. ent->max_health = ent->client->pers.max_health;
  846. ent->flags |= ent->client->pers.savedFlags;
  847. if (coop->integer)
  848. ent->client->resp.score = ent->client->pers.score;
  849. }
  850. /*
  851. =======================================================================
  852. SelectSpawnPoint
  853. =======================================================================
  854. */
  855. /*
  856. ================
  857. PlayersRangeFromSpot
  858. Returns the distance to the nearest player from the given spot
  859. ================
  860. */
  861. float PlayersRangeFromSpot(edict_t *spot)
  862. {
  863. edict_t *player;
  864. float bestplayerdistance;
  865. vec3_t v;
  866. float playerdistance;
  867. bestplayerdistance = 9999999;
  868. for (uint32_t n = 1; n <= game.maxclients; n++)
  869. {
  870. player = &g_edicts[n];
  871. if (!player->inuse)
  872. continue;
  873. if (player->health <= 0)
  874. continue;
  875. v = spot->s.origin - player->s.origin;
  876. playerdistance = v.length();
  877. if (playerdistance < bestplayerdistance)
  878. bestplayerdistance = playerdistance;
  879. }
  880. return bestplayerdistance;
  881. }
  882. bool SpawnPointClear(edict_t *spot)
  883. {
  884. vec3_t p = spot->s.origin + vec3_t{0, 0, 9.f};
  885. return !gi.trace(p, PLAYER_MINS, PLAYER_MAXS, p, spot, CONTENTS_PLAYER | CONTENTS_MONSTER).startsolid;
  886. }
  887. select_spawn_result_t SelectDeathmatchSpawnPoint(bool farthest, bool force_spawn, bool fallback_to_ctf_or_start)
  888. {
  889. struct spawn_point_t
  890. {
  891. edict_t *point;
  892. float dist;
  893. };
  894. static std::vector<spawn_point_t> spawn_points;
  895. spawn_points.clear();
  896. // gather all spawn points
  897. edict_t *spot = nullptr;
  898. while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_deathmatch")) != nullptr)
  899. spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) });
  900. // no points
  901. if (spawn_points.size() == 0)
  902. {
  903. // try CTF spawns...
  904. if (fallback_to_ctf_or_start)
  905. {
  906. spot = nullptr;
  907. while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_team1")) != nullptr)
  908. spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) });
  909. spot = nullptr;
  910. while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_team2")) != nullptr)
  911. spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) });
  912. // we only have an info_player_start then
  913. if (spawn_points.size() == 0)
  914. {
  915. spot = G_FindByString<&edict_t::classname>(nullptr, "info_player_start");
  916. if (spot)
  917. spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) });
  918. // map is malformed
  919. if (spawn_points.size() == 0)
  920. return { nullptr, false };
  921. }
  922. }
  923. else
  924. return { nullptr, false };
  925. }
  926. // if there's only one spawn point, that's the one.
  927. if (spawn_points.size() == 1)
  928. {
  929. if (force_spawn || SpawnPointClear(spawn_points[0].point))
  930. return { spawn_points[0].point, true };
  931. return { nullptr, true };
  932. }
  933. // order by distances ascending (top of list has closest players to point)
  934. std::sort(spawn_points.begin(), spawn_points.end(), [](const spawn_point_t &a, const spawn_point_t &b) { return a.dist < b.dist; });
  935. // farthest spawn is simple
  936. if (farthest)
  937. {
  938. for (int32_t i = spawn_points.size() - 1; i >= 0; --i)
  939. {
  940. if (SpawnPointClear(spawn_points[i].point))
  941. return { spawn_points[i].point, true };
  942. }
  943. // none clear
  944. }
  945. else
  946. {
  947. // for random, select a random point other than the two
  948. // that are closest to the player if possible.
  949. // shuffle the non-distance-related spawn points
  950. std::shuffle(spawn_points.begin() + 2, spawn_points.end(), mt_rand);
  951. // run down the list and pick the first one that we can use
  952. for (auto it = spawn_points.begin() + 2; it != spawn_points.end(); ++it)
  953. {
  954. auto spot = it->point;
  955. if (SpawnPointClear(spot))
  956. return { spot, true };
  957. }
  958. // none clear, so we have to pick one of the other two
  959. if (SpawnPointClear(spawn_points[1].point))
  960. return { spawn_points[1].point, true };
  961. else if (SpawnPointClear(spawn_points[0].point))
  962. return { spawn_points[0].point, true };
  963. }
  964. if (force_spawn)
  965. return { random_element(spawn_points).point, true };
  966. return { nullptr, true };
  967. }
  968. //===============
  969. // ROGUE
  970. edict_t *SelectLavaCoopSpawnPoint(edict_t *ent)
  971. {
  972. int index;
  973. edict_t *spot = nullptr;
  974. float lavatop;
  975. edict_t *lava;
  976. edict_t *pointWithLeastLava;
  977. float lowest;
  978. edict_t *spawnPoints[64];
  979. vec3_t center;
  980. int numPoints;
  981. edict_t *highestlava;
  982. lavatop = -99999;
  983. highestlava = nullptr;
  984. // first, find the highest lava
  985. // remember that some will stop moving when they've filled their
  986. // areas...
  987. lava = nullptr;
  988. while (1)
  989. {
  990. lava = G_FindByString<&edict_t::classname>(lava, "func_water");
  991. if (!lava)
  992. break;
  993. center = lava->absmax + lava->absmin;
  994. center *= 0.5f;
  995. if (lava->spawnflags.has(SPAWNFLAG_WATER_SMART) && (gi.pointcontents(center) & MASK_WATER))
  996. {
  997. if (lava->absmax[2] > lavatop)
  998. {
  999. lavatop = lava->absmax[2];
  1000. highestlava = lava;
  1001. }
  1002. }
  1003. }
  1004. // if we didn't find ANY lava, then return nullptr
  1005. if (!highestlava)
  1006. return nullptr;
  1007. // find the top of the lava and include a small margin of error (plus bbox size)
  1008. lavatop = highestlava->absmax[2] + 64;
  1009. // find all the lava spawn points and store them in spawnPoints[]
  1010. spot = nullptr;
  1011. numPoints = 0;
  1012. while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_coop_lava")))
  1013. {
  1014. if (numPoints == 64)
  1015. break;
  1016. spawnPoints[numPoints++] = spot;
  1017. }
  1018. // walk up the sorted list and return the lowest, open, non-lava spawn point
  1019. spot = nullptr;
  1020. lowest = 999999;
  1021. pointWithLeastLava = nullptr;
  1022. for (index = 0; index < numPoints; index++)
  1023. {
  1024. if (spawnPoints[index]->s.origin[2] < lavatop)
  1025. continue;
  1026. if (PlayersRangeFromSpot(spawnPoints[index]) > 32)
  1027. {
  1028. if (spawnPoints[index]->s.origin[2] < lowest)
  1029. {
  1030. // save the last point
  1031. pointWithLeastLava = spawnPoints[index];
  1032. lowest = spawnPoints[index]->s.origin[2];
  1033. }
  1034. }
  1035. }
  1036. return pointWithLeastLava;
  1037. }
  1038. // ROGUE
  1039. //===============
  1040. // [Paril-KEX]
  1041. static edict_t *SelectSingleSpawnPoint(edict_t *ent)
  1042. {
  1043. edict_t *spot = nullptr;
  1044. while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_start")) != nullptr)
  1045. {
  1046. if (!game.spawnpoint[0] && !spot->targetname)
  1047. break;
  1048. if (!game.spawnpoint[0] || !spot->targetname)
  1049. continue;
  1050. if (Q_strcasecmp(game.spawnpoint, spot->targetname) == 0)
  1051. break;
  1052. }
  1053. if (!spot)
  1054. {
  1055. // there wasn't a matching targeted spawnpoint, use one that has no targetname
  1056. while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_start")) != nullptr)
  1057. if (!spot->targetname)
  1058. return spot;
  1059. }
  1060. // none at all, so just pick any
  1061. if (!spot)
  1062. return G_FindByString<&edict_t::classname>(spot, "info_player_start");
  1063. return spot;
  1064. }
  1065. // [Paril-KEX]
  1066. static edict_t *G_UnsafeSpawnPosition(vec3_t spot, bool check_players)
  1067. {
  1068. contents_t mask = MASK_PLAYERSOLID;
  1069. if (!check_players)
  1070. mask &= ~CONTENTS_PLAYER;
  1071. trace_t tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, mask);
  1072. // sometimes the spot is too close to the ground, give it a bit of slack
  1073. if (tr.startsolid && !tr.ent->client)
  1074. {
  1075. spot[2] += 1;
  1076. tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, mask);
  1077. }
  1078. // no idea why this happens in some maps..
  1079. if (tr.startsolid && !tr.ent->client)
  1080. {
  1081. // try a nudge
  1082. if (G_FixStuckObject_Generic(spot, PLAYER_MINS, PLAYER_MAXS, [mask] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) {
  1083. return gi.trace(start, mins, maxs, end, nullptr, mask);
  1084. }) == stuck_result_t::NO_GOOD_POSITION)
  1085. return tr.ent; // what do we do here...?
  1086. trace_t tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, mask);
  1087. if (tr.startsolid && !tr.ent->client)
  1088. return tr.ent; // what do we do here...?
  1089. }
  1090. if (tr.fraction == 1.f)
  1091. return nullptr;
  1092. else if (check_players && tr.ent && tr.ent->client)
  1093. return tr.ent;
  1094. return nullptr;
  1095. }
  1096. edict_t *SelectCoopSpawnPoint(edict_t *ent, bool force_spawn, bool check_players)
  1097. {
  1098. edict_t *spot = nullptr;
  1099. const char *target;
  1100. // ROGUE
  1101. // rogue hack, but not too gross...
  1102. if (!Q_strcasecmp(level.mapname, "rmine2"))
  1103. return SelectLavaCoopSpawnPoint(ent);
  1104. // ROGUE
  1105. // try the main spawn point first
  1106. spot = SelectSingleSpawnPoint(ent);
  1107. if (spot && !G_UnsafeSpawnPosition(spot->s.origin, check_players))
  1108. return spot;
  1109. spot = nullptr;
  1110. // assume there are four coop spots at each spawnpoint
  1111. int32_t num_valid_spots = 0;
  1112. while (1)
  1113. {
  1114. spot = G_FindByString<&edict_t::classname>(spot, "info_player_coop");
  1115. if (!spot)
  1116. break; // we didn't have enough...
  1117. target = spot->targetname;
  1118. if (!target)
  1119. target = "";
  1120. if (Q_strcasecmp(game.spawnpoint, target) == 0)
  1121. { // this is a coop spawn point for one of the clients here
  1122. num_valid_spots++;
  1123. if (!G_UnsafeSpawnPosition(spot->s.origin, check_players))
  1124. return spot; // this is it
  1125. }
  1126. }
  1127. bool use_targetname = true;
  1128. // if we didn't find any spots, map is probably set up wrong.
  1129. // use empty targetname ones.
  1130. if (!num_valid_spots)
  1131. {
  1132. use_targetname = false;
  1133. while (1)
  1134. {
  1135. spot = G_FindByString<&edict_t::classname>(spot, "info_player_coop");
  1136. if (!spot)
  1137. break; // we didn't have enough...
  1138. target = spot->targetname;
  1139. if (!target)
  1140. {
  1141. // this is a coop spawn point for one of the clients here
  1142. num_valid_spots++;
  1143. if (!G_UnsafeSpawnPosition(spot->s.origin, check_players))
  1144. return spot; // this is it
  1145. }
  1146. }
  1147. }
  1148. // if player collision is disabled, just pick a random spot
  1149. if (!g_coop_player_collision->integer)
  1150. {
  1151. spot = nullptr;
  1152. num_valid_spots = irandom(num_valid_spots);
  1153. while (1)
  1154. {
  1155. spot = G_FindByString<&edict_t::classname>(spot, "info_player_coop");
  1156. if (!spot)
  1157. break; // we didn't have enough...
  1158. target = spot->targetname;
  1159. if (use_targetname && !target)
  1160. target = "";
  1161. if (use_targetname ? (Q_strcasecmp(game.spawnpoint, target) == 0) : !target)
  1162. { // this is a coop spawn point for one of the clients here
  1163. num_valid_spots++;
  1164. if (!num_valid_spots)
  1165. return spot;
  1166. --num_valid_spots;
  1167. }
  1168. }
  1169. // if this fails, just fall through to some other spawn.
  1170. }
  1171. // no safe spots..?
  1172. if (force_spawn || !g_coop_player_collision->integer)
  1173. return SelectSingleSpawnPoint(spot);
  1174. return nullptr;
  1175. }
  1176. bool TryLandmarkSpawn(edict_t* ent, vec3_t& origin, vec3_t& angles)
  1177. {
  1178. // if transitioning from another level with a landmark seamless transition
  1179. // just set the location here
  1180. if (!ent->client->landmark_name || !strlen(ent->client->landmark_name))
  1181. {
  1182. return false;
  1183. }
  1184. edict_t* landmark = G_PickTarget(ent->client->landmark_name);
  1185. if (!landmark)
  1186. {
  1187. return false;
  1188. }
  1189. vec3_t old_origin = origin;
  1190. vec3_t spot_origin = origin;
  1191. origin = ent->client->landmark_rel_pos;
  1192. // rotate our relative landmark into our new landmark's frame of reference
  1193. origin = RotatePointAroundVector({ 1, 0, 0 }, origin, landmark->s.angles[0]);
  1194. origin = RotatePointAroundVector({ 0, 1, 0 }, origin, landmark->s.angles[2]);
  1195. origin = RotatePointAroundVector({ 0, 0, 1 }, origin, landmark->s.angles[1]);
  1196. origin += landmark->s.origin;
  1197. angles = ent->client->oldviewangles + landmark->s.angles;
  1198. if (landmark->spawnflags.has(SPAWNFLAG_LANDMARK_KEEP_Z))
  1199. origin[2] = spot_origin[2];
  1200. // sometimes, landmark spawns can cause slight inconsistencies in collision;
  1201. // we'll do a bit of tracing to make sure the bbox is clear
  1202. if (G_FixStuckObject_Generic(origin, PLAYER_MINS, PLAYER_MAXS, [ent] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) {
  1203. return gi.trace(start, mins, maxs, end, ent, MASK_PLAYERSOLID & ~CONTENTS_PLAYER);
  1204. }) == stuck_result_t::NO_GOOD_POSITION)
  1205. {
  1206. origin = old_origin;
  1207. return false;
  1208. }
  1209. ent->s.origin = origin;
  1210. // rotate the velocity that we grabbed from the map
  1211. if (ent->velocity)
  1212. {
  1213. ent->velocity = RotatePointAroundVector({ 1, 0, 0 }, ent->velocity, landmark->s.angles[0]);
  1214. ent->velocity = RotatePointAroundVector({ 0, 1, 0 }, ent->velocity, landmark->s.angles[2]);
  1215. ent->velocity = RotatePointAroundVector({ 0, 0, 1 }, ent->velocity, landmark->s.angles[1]);
  1216. }
  1217. return true;
  1218. }
  1219. /*
  1220. ===========
  1221. SelectSpawnPoint
  1222. Chooses a player start, deathmatch start, coop start, etc
  1223. ============
  1224. */
  1225. bool SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn, bool &landmark)
  1226. {
  1227. edict_t *spot = nullptr;
  1228. // DM spots are simple
  1229. if (deathmatch->integer)
  1230. {
  1231. if (G_TeamplayEnabled())
  1232. spot = SelectCTFSpawnPoint(ent, force_spawn);
  1233. else
  1234. {
  1235. select_spawn_result_t result = SelectDeathmatchSpawnPoint(g_dm_spawn_farthest->integer, force_spawn, true);
  1236. if (!result.any_valid)
  1237. gi.Com_Error("no valid spawn points found");
  1238. spot = result.spot;
  1239. }
  1240. if (spot)
  1241. {
  1242. origin = spot->s.origin + vec3_t{ 0, 0, 9 };
  1243. angles = spot->s.angles;
  1244. return true;
  1245. }
  1246. return false;
  1247. }
  1248. if (coop->integer)
  1249. {
  1250. spot = SelectCoopSpawnPoint(ent, force_spawn, true);
  1251. if (!spot)
  1252. spot = SelectCoopSpawnPoint(ent, force_spawn, false);
  1253. // no open spot yet
  1254. if (!spot)
  1255. {
  1256. // in worst case scenario in coop during intermission, just spawn us at intermission
  1257. // spot. this only happens for a single frame, and won't break
  1258. // anything if they come back.
  1259. if (level.intermissiontime)
  1260. {
  1261. origin = level.intermission_origin;
  1262. angles = level.intermission_angle;
  1263. return true;
  1264. }
  1265. return false;
  1266. }
  1267. }
  1268. else
  1269. {
  1270. spot = SelectSingleSpawnPoint(ent);
  1271. // in SP, just put us at the origin if spawn fails
  1272. if (!spot)
  1273. {
  1274. gi.Com_PrintFmt("Couldn't find spawn point {}\n", game.spawnpoint);
  1275. origin = { 0, 0, 0 };
  1276. angles = { 0, 0, 0 };
  1277. return true;
  1278. }
  1279. }
  1280. // spot should always be non-null here
  1281. origin = spot->s.origin;
  1282. angles = spot->s.angles;
  1283. // check landmark
  1284. if (TryLandmarkSpawn(ent, origin, angles))
  1285. landmark = true;
  1286. return true;
  1287. }
  1288. //======================================================================
  1289. void InitBodyQue()
  1290. {
  1291. int i;
  1292. edict_t *ent;
  1293. level.body_que = 0;
  1294. for (i = 0; i < BODY_QUEUE_SIZE; i++)
  1295. {
  1296. ent = G_Spawn();
  1297. ent->classname = "bodyque";
  1298. }
  1299. }
  1300. DIE(body_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  1301. {
  1302. if (self->s.modelindex == MODELINDEX_PLAYER &&
  1303. self->health < self->gib_health)
  1304. {
  1305. gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  1306. ThrowGibs(self, damage, { { 4, "models/objects/gibs/sm_meat/tris.md2" } });
  1307. self->s.origin[2] -= 48;
  1308. ThrowClientHead(self, damage);
  1309. }
  1310. if (mod.id == MOD_CRUSH)
  1311. {
  1312. // prevent explosion singularities
  1313. self->svflags = SVF_NOCLIENT;
  1314. self->takedamage = false;
  1315. self->solid = SOLID_NOT;
  1316. self->movetype = MOVETYPE_NOCLIP;
  1317. gi.linkentity(self);
  1318. }
  1319. }
  1320. void CopyToBodyQue(edict_t *ent)
  1321. {
  1322. // if we were completely removed, don't bother with a body
  1323. if (!ent->s.modelindex)
  1324. return;
  1325. edict_t *body;
  1326. // grab a body que and cycle to the next one
  1327. body = &g_edicts[game.maxclients + level.body_que + 1];
  1328. level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
  1329. // FIXME: send an effect on the removed body
  1330. gi.unlinkentity(ent);
  1331. gi.unlinkentity(body);
  1332. body->s = ent->s;
  1333. body->s.number = body - g_edicts;
  1334. body->s.skinnum = ent->s.skinnum & 0xFF; // only copy the client #
  1335. body->s.effects = EF_NONE;
  1336. body->s.renderfx = RF_NONE;
  1337. body->svflags = ent->svflags;
  1338. body->absmin = ent->absmin;
  1339. body->absmax = ent->absmax;
  1340. body->size = ent->size;
  1341. body->solid = ent->solid;
  1342. body->clipmask = ent->clipmask;
  1343. body->owner = ent->owner;
  1344. body->movetype = ent->movetype;
  1345. body->health = ent->health;
  1346. body->gib_health = ent->gib_health;
  1347. body->s.event = EV_OTHER_TELEPORT;
  1348. body->velocity = ent->velocity;
  1349. body->avelocity = ent->avelocity;
  1350. body->groundentity = ent->groundentity;
  1351. body->groundentity_linkcount = ent->groundentity_linkcount;
  1352. if (ent->takedamage)
  1353. {
  1354. body->mins = ent->mins;
  1355. body->maxs = ent->maxs;
  1356. }
  1357. else
  1358. body->mins = body->maxs = {};
  1359. body->die = body_die;
  1360. body->takedamage = true;
  1361. gi.linkentity(body);
  1362. }
  1363. void G_PostRespawn(edict_t *self)
  1364. {
  1365. if (self->svflags & SVF_NOCLIENT)
  1366. return;
  1367. // add a teleportation effect
  1368. self->s.event = EV_PLAYER_TELEPORT;
  1369. // hold in place briefly
  1370. self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
  1371. self->client->ps.pmove.pm_time = 112;
  1372. self->client->respawn_time = level.time;
  1373. }
  1374. void respawn(edict_t *self)
  1375. {
  1376. if (deathmatch->integer || coop->integer)
  1377. {
  1378. // spectators don't leave bodies
  1379. if (!self->client->resp.spectator)
  1380. CopyToBodyQue(self);
  1381. self->svflags &= ~SVF_NOCLIENT;
  1382. PutClientInServer(self);
  1383. G_PostRespawn(self);
  1384. return;
  1385. }
  1386. // restart the entire server
  1387. gi.AddCommandString("menu_loadgame\n");
  1388. }
  1389. /*
  1390. * only called when pers.spectator changes
  1391. * note that resp.spectator should be the opposite of pers.spectator here
  1392. */
  1393. void spectator_respawn(edict_t *ent)
  1394. {
  1395. uint32_t i, numspec;
  1396. // if the user wants to become a spectator, make sure he doesn't
  1397. // exceed max_spectators
  1398. if (ent->client->pers.spectator)
  1399. {
  1400. char value[MAX_INFO_VALUE] = { 0 };
  1401. gi.Info_ValueForKey(ent->client->pers.userinfo, "spectator", value, sizeof(value));
  1402. if (*spectator_password->string &&
  1403. strcmp(spectator_password->string, "none") &&
  1404. strcmp(spectator_password->string, value))
  1405. {
  1406. gi.LocClient_Print(ent, PRINT_HIGH, "Spectator password incorrect.\n");
  1407. ent->client->pers.spectator = false;
  1408. gi.WriteByte(svc_stufftext);
  1409. gi.WriteString("spectator 0\n");
  1410. gi.unicast(ent, true);
  1411. return;
  1412. }
  1413. // count spectators
  1414. for (i = 1, numspec = 0; i <= game.maxclients; i++)
  1415. if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
  1416. numspec++;
  1417. if (numspec >= (uint32_t) maxspectators->integer)
  1418. {
  1419. gi.LocClient_Print(ent, PRINT_HIGH, "Server spectator limit is full.");
  1420. ent->client->pers.spectator = false;
  1421. // reset his spectator var
  1422. gi.WriteByte(svc_stufftext);
  1423. gi.WriteString("spectator 0\n");
  1424. gi.unicast(ent, true);
  1425. return;
  1426. }
  1427. }
  1428. else
  1429. {
  1430. // he was a spectator and wants to join the game
  1431. // he must have the right password
  1432. char value[MAX_INFO_VALUE] = { 0 };
  1433. gi.Info_ValueForKey(ent->client->pers.userinfo, "password", value, sizeof(value));
  1434. if (*password->string && strcmp(password->string, "none") &&
  1435. strcmp(password->string, value))
  1436. {
  1437. gi.LocClient_Print(ent, PRINT_HIGH, "Password incorrect.\n");
  1438. ent->client->pers.spectator = true;
  1439. gi.WriteByte(svc_stufftext);
  1440. gi.WriteString("spectator 1\n");
  1441. gi.unicast(ent, true);
  1442. return;
  1443. }
  1444. }
  1445. // clear score on respawn
  1446. ent->client->resp.score = ent->client->pers.score = 0;
  1447. // move us to no team
  1448. ent->client->resp.ctf_team = CTF_NOTEAM;
  1449. // change spectator mode
  1450. ent->client->resp.spectator = ent->client->pers.spectator;
  1451. ent->svflags &= ~SVF_NOCLIENT;
  1452. PutClientInServer(ent);
  1453. // add a teleportation effect
  1454. if (!ent->client->pers.spectator)
  1455. {
  1456. // send effect
  1457. gi.WriteByte(svc_muzzleflash);
  1458. gi.WriteEntity(ent);
  1459. gi.WriteByte(MZ_LOGIN);
  1460. gi.multicast(ent->s.origin, MULTICAST_PVS, false);
  1461. // hold in place briefly
  1462. ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
  1463. ent->client->ps.pmove.pm_time = 112;
  1464. }
  1465. ent->client->respawn_time = level.time;
  1466. if (ent->client->pers.spectator)
  1467. gi.LocBroadcast_Print(PRINT_HIGH, "$g_observing", ent->client->pers.netname);
  1468. else
  1469. gi.LocBroadcast_Print(PRINT_HIGH, "$g_joined_game", ent->client->pers.netname);
  1470. }
  1471. //==============================================================
  1472. // [Paril-KEX]
  1473. // skinnum was historically used to pack data
  1474. // so we're going to build onto that.
  1475. void P_AssignClientSkinnum(edict_t *ent)
  1476. {
  1477. if (ent->s.modelindex != 255)
  1478. return;
  1479. player_skinnum_t packed;
  1480. packed.client_num = ent->client - game.clients;
  1481. if (ent->client->pers.weapon)
  1482. packed.vwep_index = ent->client->pers.weapon->vwep_index - level.vwep_offset + 1;
  1483. else
  1484. packed.vwep_index = 0;
  1485. packed.viewheight = ent->client->ps.viewoffset.z + ent->client->ps.pmove.viewheight;
  1486. if (coop->value)
  1487. packed.team_index = 1; // all players are teamed in coop
  1488. else if (G_TeamplayEnabled())
  1489. packed.team_index = ent->client->resp.ctf_team;
  1490. else
  1491. packed.team_index = 0;
  1492. if (ent->deadflag)
  1493. packed.poi_icon = 1;
  1494. else
  1495. packed.poi_icon = 0;
  1496. ent->s.skinnum = packed.skinnum;
  1497. }
  1498. // [Paril-KEX] send player level POI
  1499. void P_SendLevelPOI(edict_t *ent)
  1500. {
  1501. if (!level.valid_poi)
  1502. return;
  1503. gi.WriteByte(svc_poi);
  1504. gi.WriteShort(POI_OBJECTIVE);
  1505. gi.WriteShort(10000);
  1506. gi.WritePosition(ent->client->help_poi_location);
  1507. gi.WriteShort(ent->client->help_poi_image);
  1508. gi.WriteByte(208);
  1509. gi.WriteByte(POI_FLAG_NONE);
  1510. gi.unicast(ent, true);
  1511. }
  1512. // [Paril-KEX] force the fog transition on the given player,
  1513. // optionally instantaneously (ignore any transition time)
  1514. void P_ForceFogTransition(edict_t *ent, bool instant)
  1515. {
  1516. // sanity check; if we're not changing the values, don't bother
  1517. if (ent->client->fog == ent->client->pers.wanted_fog &&
  1518. ent->client->heightfog == ent->client->pers.wanted_heightfog)
  1519. return;
  1520. svc_fog_data_t fog {};
  1521. // check regular fog
  1522. if (ent->client->pers.wanted_fog[0] != ent->client->fog[0] ||
  1523. ent->client->pers.wanted_fog[4] != ent->client->fog[4])
  1524. {
  1525. fog.bits |= svc_fog_data_t::BIT_DENSITY;
  1526. fog.density = ent->client->pers.wanted_fog[0];
  1527. fog.skyfactor = ent->client->pers.wanted_fog[4] * 255.f;
  1528. }
  1529. if (ent->client->pers.wanted_fog[1] != ent->client->fog[1])
  1530. {
  1531. fog.bits |= svc_fog_data_t::BIT_R;
  1532. fog.red = ent->client->pers.wanted_fog[1] * 255.f;
  1533. }
  1534. if (ent->client->pers.wanted_fog[2] != ent->client->fog[2])
  1535. {
  1536. fog.bits |= svc_fog_data_t::BIT_G;
  1537. fog.green = ent->client->pers.wanted_fog[2] * 255.f;
  1538. }
  1539. if (ent->client->pers.wanted_fog[3] != ent->client->fog[3])
  1540. {
  1541. fog.bits |= svc_fog_data_t::BIT_B;
  1542. fog.blue = ent->client->pers.wanted_fog[3] * 255.f;
  1543. }
  1544. if (!instant && ent->client->pers.fog_transition_time)
  1545. {
  1546. fog.bits |= svc_fog_data_t::BIT_TIME;
  1547. fog.time = clamp(ent->client->pers.fog_transition_time.milliseconds(), (int64_t) 0, (int64_t) std::numeric_limits<uint16_t>::max());
  1548. }
  1549. // check heightfog stuff
  1550. auto &hf = ent->client->heightfog;
  1551. const auto &wanted_hf = ent->client->pers.wanted_heightfog;
  1552. if (hf.falloff != wanted_hf.falloff)
  1553. {
  1554. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_FALLOFF;
  1555. if (!wanted_hf.falloff)
  1556. fog.hf_falloff = 0;
  1557. else
  1558. fog.hf_falloff = wanted_hf.falloff;
  1559. }
  1560. if (hf.density != wanted_hf.density)
  1561. {
  1562. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_DENSITY;
  1563. if (!wanted_hf.density)
  1564. fog.hf_density = 0;
  1565. else
  1566. fog.hf_density = wanted_hf.density;
  1567. }
  1568. if (hf.start[0] != wanted_hf.start[0])
  1569. {
  1570. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_START_R;
  1571. fog.hf_start_r = wanted_hf.start[0] * 255.f;
  1572. }
  1573. if (hf.start[1] != wanted_hf.start[1])
  1574. {
  1575. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_START_G;
  1576. fog.hf_start_g = wanted_hf.start[1] * 255.f;
  1577. }
  1578. if (hf.start[2] != wanted_hf.start[2])
  1579. {
  1580. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_START_B;
  1581. fog.hf_start_b = wanted_hf.start[2] * 255.f;
  1582. }
  1583. if (hf.start[3] != wanted_hf.start[3])
  1584. {
  1585. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_START_DIST;
  1586. fog.hf_start_dist = wanted_hf.start[3];
  1587. }
  1588. if (hf.end[0] != wanted_hf.end[0])
  1589. {
  1590. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_END_R;
  1591. fog.hf_end_r = wanted_hf.end[0] * 255.f;
  1592. }
  1593. if (hf.end[1] != wanted_hf.end[1])
  1594. {
  1595. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_END_G;
  1596. fog.hf_end_g = wanted_hf.end[1] * 255.f;
  1597. }
  1598. if (hf.end[2] != wanted_hf.end[2])
  1599. {
  1600. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_END_B;
  1601. fog.hf_end_b = wanted_hf.end[2] * 255.f;
  1602. }
  1603. if (hf.end[3] != wanted_hf.end[3])
  1604. {
  1605. fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_END_DIST;
  1606. fog.hf_end_dist = wanted_hf.end[3];
  1607. }
  1608. if (fog.bits & 0xFF00)
  1609. fog.bits |= svc_fog_data_t::BIT_MORE_BITS;
  1610. gi.WriteByte(svc_fog);
  1611. if (fog.bits & svc_fog_data_t::BIT_MORE_BITS)
  1612. gi.WriteShort(fog.bits);
  1613. else
  1614. gi.WriteByte(fog.bits);
  1615. if (fog.bits & svc_fog_data_t::BIT_DENSITY)
  1616. {
  1617. gi.WriteFloat(fog.density);
  1618. gi.WriteByte(fog.skyfactor);
  1619. }
  1620. if (fog.bits & svc_fog_data_t::BIT_R)
  1621. gi.WriteByte(fog.red);
  1622. if (fog.bits & svc_fog_data_t::BIT_G)
  1623. gi.WriteByte(fog.green);
  1624. if (fog.bits & svc_fog_data_t::BIT_B)
  1625. gi.WriteByte(fog.blue);
  1626. if (fog.bits & svc_fog_data_t::BIT_TIME)
  1627. gi.WriteShort(fog.time);
  1628. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_FALLOFF)
  1629. gi.WriteFloat(fog.hf_falloff);
  1630. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_DENSITY)
  1631. gi.WriteFloat(fog.hf_density);
  1632. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_START_R)
  1633. gi.WriteByte(fog.hf_start_r);
  1634. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_START_G)
  1635. gi.WriteByte(fog.hf_start_g);
  1636. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_START_B)
  1637. gi.WriteByte(fog.hf_start_b);
  1638. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_START_DIST)
  1639. gi.WriteLong(fog.hf_start_dist);
  1640. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_END_R)
  1641. gi.WriteByte(fog.hf_end_r);
  1642. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_END_G)
  1643. gi.WriteByte(fog.hf_end_g);
  1644. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_END_B)
  1645. gi.WriteByte(fog.hf_end_b);
  1646. if (fog.bits & svc_fog_data_t::BIT_HEIGHTFOG_END_DIST)
  1647. gi.WriteLong(fog.hf_end_dist);
  1648. gi.unicast(ent, true);
  1649. ent->client->fog = ent->client->pers.wanted_fog;
  1650. hf = wanted_hf;
  1651. }
  1652. // [Paril-KEX] ugly global to handle squad respawn origin
  1653. static bool use_squad_respawn = false;
  1654. static bool spawn_from_begin = false;
  1655. static vec3_t squad_respawn_position, squad_respawn_angles;
  1656. inline void PutClientOnSpawnPoint(edict_t *ent, const vec3_t &spawn_origin, const vec3_t &spawn_angles)
  1657. {
  1658. gclient_t *client = ent->client;
  1659. client->ps.pmove.origin = spawn_origin;
  1660. ent->s.origin = spawn_origin;
  1661. if (!use_squad_respawn)
  1662. ent->s.origin[2] += 1; // make sure off ground
  1663. ent->s.old_origin = ent->s.origin;
  1664. // set the delta angle
  1665. client->ps.pmove.delta_angles = spawn_angles - client->resp.cmd_angles;
  1666. ent->s.angles = spawn_angles;
  1667. ent->s.angles[PITCH] /= 3;
  1668. client->ps.viewangles = ent->s.angles;
  1669. client->v_angle = ent->s.angles;
  1670. AngleVectors(client->v_angle, client->v_forward, nullptr, nullptr);
  1671. }
  1672. /*
  1673. ===========
  1674. PutClientInServer
  1675. Called when a player connects to a server or respawns in
  1676. a deathmatch.
  1677. ============
  1678. */
  1679. void PutClientInServer(edict_t *ent)
  1680. {
  1681. int index;
  1682. vec3_t spawn_origin, spawn_angles;
  1683. gclient_t *client;
  1684. client_persistant_t saved;
  1685. client_respawn_t resp;
  1686. index = ent - g_edicts - 1;
  1687. client = ent->client;
  1688. // clear velocity now, since landmark may change it
  1689. ent->velocity = {};
  1690. bool keepVelocity = client->landmark_name != nullptr;
  1691. if (keepVelocity)
  1692. ent->velocity = client->oldvelocity;
  1693. // find a spawn point
  1694. // do it before setting health back up, so farthest
  1695. // ranging doesn't count this client
  1696. bool valid_spawn = false;
  1697. bool force_spawn = client->awaiting_respawn && level.time > client->respawn_timeout;
  1698. bool is_landmark = false;
  1699. if (use_squad_respawn)
  1700. {
  1701. spawn_origin = squad_respawn_position;
  1702. spawn_angles = squad_respawn_angles;
  1703. valid_spawn = true;
  1704. }
  1705. else if (gamerules->integer && DMGame.SelectSpawnPoint) // PGM
  1706. valid_spawn = DMGame.SelectSpawnPoint(ent, spawn_origin, spawn_angles, force_spawn); // PGM
  1707. else // PGM
  1708. valid_spawn = SelectSpawnPoint(ent, spawn_origin, spawn_angles, force_spawn, is_landmark);
  1709. // [Paril-KEX] if we didn't get a valid spawn, hold us in
  1710. // limbo for a while until we do get one
  1711. if (!valid_spawn)
  1712. {
  1713. // only do this once per spawn
  1714. if (!client->awaiting_respawn)
  1715. {
  1716. char userinfo[MAX_INFO_STRING];
  1717. memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
  1718. ClientUserinfoChanged(ent, userinfo);
  1719. client->respawn_timeout = level.time + 3_sec;
  1720. }
  1721. // find a spot to place us
  1722. if (!level.respawn_intermission)
  1723. {
  1724. // find an intermission spot
  1725. edict_t *pt = G_FindByString<&edict_t::classname>(nullptr, "info_player_intermission");
  1726. if (!pt)
  1727. { // the map creator forgot to put in an intermission point...
  1728. pt = G_FindByString<&edict_t::classname>(nullptr, "info_player_start");
  1729. if (!pt)
  1730. pt = G_FindByString<&edict_t::classname>(nullptr, "info_player_deathmatch");
  1731. }
  1732. else
  1733. { // choose one of four spots
  1734. int32_t i = irandom(4);
  1735. while (i--)
  1736. {
  1737. pt = G_FindByString<&edict_t::classname>(pt, "info_player_intermission");
  1738. if (!pt) // wrap around the list
  1739. pt = G_FindByString<&edict_t::classname>(pt, "info_player_intermission");
  1740. }
  1741. }
  1742. level.intermission_origin = pt->s.origin;
  1743. level.intermission_angle = pt->s.angles;
  1744. level.respawn_intermission = true;
  1745. }
  1746. ent->s.origin = level.intermission_origin;
  1747. ent->client->ps.pmove.origin = level.intermission_origin;
  1748. ent->client->ps.viewangles = level.intermission_angle;
  1749. client->awaiting_respawn = true;
  1750. client->ps.pmove.pm_type = PM_FREEZE;
  1751. client->ps.rdflags = RDF_NONE;
  1752. ent->deadflag = false;
  1753. ent->solid = SOLID_NOT;
  1754. ent->movetype = MOVETYPE_NOCLIP;
  1755. ent->s.modelindex = 0;
  1756. ent->svflags |= SVF_NOCLIENT;
  1757. ent->client->ps.team_id = ent->client->resp.ctf_team;
  1758. gi.linkentity(ent);
  1759. return;
  1760. }
  1761. client->resp.ctf_state++;
  1762. bool was_waiting_for_respawn = client->awaiting_respawn;
  1763. if (client->awaiting_respawn)
  1764. ent->svflags &= ~SVF_NOCLIENT;
  1765. client->awaiting_respawn = false;
  1766. client->respawn_timeout = 0_ms;
  1767. char social_id[MAX_INFO_VALUE];
  1768. Q_strlcpy(social_id, ent->client->pers.social_id, sizeof(social_id));
  1769. // deathmatch wipes most client data every spawn
  1770. if (deathmatch->integer)
  1771. {
  1772. client->pers.health = 0;
  1773. resp = client->resp;
  1774. }
  1775. else
  1776. {
  1777. // [Kex] Maintain user info in singleplayer to keep the player skin.
  1778. char userinfo[MAX_INFO_STRING];
  1779. memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
  1780. if (coop->integer)
  1781. {
  1782. resp = client->resp;
  1783. if (!P_UseCoopInstancedItems())
  1784. {
  1785. resp.coop_respawn.game_help1changed = client->pers.game_help1changed;
  1786. resp.coop_respawn.game_help2changed = client->pers.game_help2changed;
  1787. resp.coop_respawn.helpchanged = client->pers.helpchanged;
  1788. client->pers = resp.coop_respawn;
  1789. }
  1790. else
  1791. {
  1792. // fix weapon
  1793. if (!client->pers.weapon)
  1794. client->pers.weapon = client->pers.lastweapon;
  1795. }
  1796. }
  1797. ClientUserinfoChanged(ent, userinfo);
  1798. if (coop->integer)
  1799. {
  1800. if (resp.score > client->pers.score)
  1801. client->pers.score = resp.score;
  1802. }
  1803. else
  1804. memset(&resp, 0, sizeof(resp));
  1805. }
  1806. // clear everything but the persistant data
  1807. saved = client->pers;
  1808. memset(client, 0, sizeof(*client));
  1809. client->pers = saved;
  1810. client->resp = resp;
  1811. // on a new, fresh spawn (always in DM, clear inventory
  1812. // or new spawns in SP/coop)
  1813. if (client->pers.health <= 0)
  1814. InitClientPersistant(ent, client);
  1815. // restore social ID
  1816. Q_strlcpy(ent->client->pers.social_id, social_id, sizeof(social_id));
  1817. // fix level switch issue
  1818. ent->client->pers.connected = true;
  1819. // slow time will be unset here
  1820. globals.server_flags &= ~SERVER_FLAG_SLOW_TIME;
  1821. // copy some data from the client to the entity
  1822. FetchClientEntData(ent);
  1823. // clear entity values
  1824. ent->groundentity = nullptr;
  1825. ent->client = &game.clients[index];
  1826. ent->takedamage = true;
  1827. ent->movetype = MOVETYPE_WALK;
  1828. ent->viewheight = 22;
  1829. ent->inuse = true;
  1830. ent->classname = "player";
  1831. ent->mass = 200;
  1832. ent->solid = SOLID_BBOX;
  1833. ent->deadflag = false;
  1834. ent->air_finished = level.time + 12_sec;
  1835. ent->clipmask = MASK_PLAYERSOLID;
  1836. ent->model = "players/male/tris.md2";
  1837. ent->die = player_die;
  1838. ent->waterlevel = WATER_NONE;
  1839. ent->watertype = CONTENTS_NONE;
  1840. ent->flags &= ~( FL_NO_KNOCKBACK | FL_ALIVE_KNOCKBACK_ONLY | FL_NO_DAMAGE_EFFECTS );
  1841. ent->svflags &= ~SVF_DEADMONSTER;
  1842. ent->svflags |= SVF_PLAYER;
  1843. ent->flags &= ~FL_SAM_RAIMI; // PGM - turn off sam raimi flag
  1844. ent->mins = PLAYER_MINS;
  1845. ent->maxs = PLAYER_MAXS;
  1846. // clear playerstate values
  1847. memset(&ent->client->ps, 0, sizeof(client->ps));
  1848. char val[MAX_INFO_VALUE];
  1849. gi.Info_ValueForKey(ent->client->pers.userinfo, "fov", val, sizeof(val));
  1850. ent->client->ps.fov = clamp((float) atoi(val), 1.f, 160.f);
  1851. ent->client->ps.pmove.viewheight = ent->viewheight;
  1852. ent->client->ps.team_id = ent->client->resp.ctf_team;
  1853. if (!G_ShouldPlayersCollide(false))
  1854. ent->clipmask &= ~CONTENTS_PLAYER;
  1855. // PGM
  1856. if (client->pers.weapon)
  1857. client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
  1858. else
  1859. client->ps.gunindex = 0;
  1860. client->ps.gunskin = 0;
  1861. // PGM
  1862. // clear entity state values
  1863. ent->s.effects = EF_NONE;
  1864. ent->s.modelindex = MODELINDEX_PLAYER; // will use the skin specified model
  1865. ent->s.modelindex2 = MODELINDEX_PLAYER; // custom gun model
  1866. // sknum is player num and weapon number
  1867. // weapon number will be added in changeweapon
  1868. P_AssignClientSkinnum(ent);
  1869. ent->s.frame = 0;
  1870. PutClientOnSpawnPoint(ent, spawn_origin, spawn_angles);
  1871. // [Paril-KEX] set up world fog & send it instantly
  1872. ent->client->pers.wanted_fog = {
  1873. world->fog.density,
  1874. world->fog.color[0],
  1875. world->fog.color[1],
  1876. world->fog.color[2],
  1877. world->fog.sky_factor
  1878. };
  1879. ent->client->pers.wanted_heightfog = {
  1880. { world->heightfog.start_color[0], world->heightfog.start_color[1], world->heightfog.start_color[2], world->heightfog.start_dist },
  1881. { world->heightfog.end_color[0], world->heightfog.end_color[1], world->heightfog.end_color[2], world->heightfog.end_dist },
  1882. world->heightfog.falloff,
  1883. world->heightfog.density
  1884. };
  1885. P_ForceFogTransition(ent, true);
  1886. // ZOID
  1887. if (CTFStartClient(ent))
  1888. return;
  1889. // ZOID
  1890. // spawn a spectator
  1891. if (client->pers.spectator)
  1892. {
  1893. client->chase_target = nullptr;
  1894. client->resp.spectator = true;
  1895. ent->movetype = MOVETYPE_NOCLIP;
  1896. ent->solid = SOLID_NOT;
  1897. ent->svflags |= SVF_NOCLIENT;
  1898. ent->client->ps.gunindex = 0;
  1899. ent->client->ps.gunskin = 0;
  1900. gi.linkentity(ent);
  1901. return;
  1902. }
  1903. client->resp.spectator = false;
  1904. // [Paril-KEX] a bit of a hack, but landmark spawns can sometimes cause
  1905. // intersecting spawns, so we'll do a sanity check here...
  1906. if (spawn_from_begin)
  1907. {
  1908. if (coop->integer)
  1909. {
  1910. edict_t *collision = G_UnsafeSpawnPosition(ent->s.origin, true);
  1911. if (collision)
  1912. {
  1913. gi.linkentity(ent);
  1914. if (collision->client)
  1915. {
  1916. // we spawned in somebody else, so we're going to change their spawn position
  1917. bool lm = false;
  1918. SelectSpawnPoint(collision, spawn_origin, spawn_angles, true, lm);
  1919. PutClientOnSpawnPoint(collision, spawn_origin, spawn_angles);
  1920. }
  1921. // else, no choice but to accept where ever we spawned :(
  1922. }
  1923. }
  1924. // give us one (1) free fall ticket even if
  1925. // we didn't spawn from landmark
  1926. ent->client->landmark_free_fall = true;
  1927. }
  1928. gi.linkentity(ent);
  1929. if (!KillBox(ent, true, MOD_TELEFRAG_SPAWN))
  1930. { // could't spawn in?
  1931. }
  1932. // my tribute to cash's level-specific hacks. I hope I live
  1933. // up to his trailblazing cheese.
  1934. if (Q_strcasecmp(level.mapname, "rboss") == 0)
  1935. {
  1936. // if you get on to rboss in single player or coop, ensure
  1937. // the player has the nuke key. (not in DM)
  1938. if (!deathmatch->integer)
  1939. client->pers.inventory[IT_KEY_NUKE] = 1;
  1940. }
  1941. // force the current weapon up
  1942. client->newweapon = client->pers.weapon;
  1943. ChangeWeapon(ent);
  1944. if (was_waiting_for_respawn)
  1945. G_PostRespawn(ent);
  1946. }
  1947. /*
  1948. =====================
  1949. ClientBeginDeathmatch
  1950. A client has just connected to the server in
  1951. deathmatch mode, so clear everything out before starting them.
  1952. =====================
  1953. */
  1954. void ClientBeginDeathmatch(edict_t *ent)
  1955. {
  1956. G_InitEdict(ent);
  1957. // make sure we have a known default
  1958. ent->svflags |= SVF_PLAYER;
  1959. InitClientResp(ent->client);
  1960. // ZOID
  1961. if (G_TeamplayEnabled() && ent->client->resp.ctf_team < CTF_TEAM1)
  1962. CTFAssignTeam(ent->client);
  1963. // ZOID
  1964. // PGM
  1965. if (gamerules->integer && DMGame.ClientBegin)
  1966. {
  1967. DMGame.ClientBegin(ent);
  1968. }
  1969. // PGM
  1970. // locate ent at a spawn point
  1971. PutClientInServer(ent);
  1972. if (level.intermissiontime)
  1973. {
  1974. MoveClientToIntermission(ent);
  1975. }
  1976. else
  1977. {
  1978. if (!(ent->svflags & SVF_NOCLIENT))
  1979. {
  1980. // send effect
  1981. gi.WriteByte(svc_muzzleflash);
  1982. gi.WriteEntity(ent);
  1983. gi.WriteByte(MZ_LOGIN);
  1984. gi.multicast(ent->s.origin, MULTICAST_PVS, false);
  1985. }
  1986. }
  1987. gi.LocBroadcast_Print(PRINT_HIGH, "$g_entered_game", ent->client->pers.netname);
  1988. // make sure all view stuff is valid
  1989. ClientEndServerFrame(ent);
  1990. }
  1991. static void G_SetLevelEntry()
  1992. {
  1993. if (deathmatch->integer)
  1994. return;
  1995. // map is a hub map, so we shouldn't bother tracking any of this.
  1996. // the next map will pick up as the start.
  1997. else if (level.hub_map)
  1998. return;
  1999. level_entry_t *found_entry = nullptr;
  2000. int32_t highest_order = 0;
  2001. for (size_t i = 0; i < MAX_LEVELS_PER_UNIT; i++)
  2002. {
  2003. level_entry_t *entry = &game.level_entries[i];
  2004. highest_order = max(highest_order, entry->visit_order);
  2005. if (!strcmp(entry->map_name, level.mapname) || !*entry->map_name)
  2006. {
  2007. found_entry = entry;
  2008. break;
  2009. }
  2010. }
  2011. if (!found_entry)
  2012. {
  2013. gi.Com_PrintFmt("WARNING: more than {} maps in unit, can't track the rest\n", MAX_LEVELS_PER_UNIT);
  2014. return;
  2015. }
  2016. level.entry = found_entry;
  2017. Q_strlcpy(level.entry->map_name, level.mapname, sizeof(level.entry->map_name));
  2018. // we're visiting this map for the first time, so
  2019. // mark it in our order as being recent
  2020. if (!*level.entry->pretty_name)
  2021. {
  2022. Q_strlcpy(level.entry->pretty_name, level.level_name, sizeof(level.entry->pretty_name));
  2023. level.entry->visit_order = highest_order + 1;
  2024. // give all of the clients an extra life back
  2025. if (g_coop_enable_lives->integer)
  2026. for (size_t i = 0; i < game.maxclients; i++)
  2027. game.clients[i].pers.lives = min(g_coop_num_lives->integer + 1, game.clients[i].pers.lives + 1);
  2028. }
  2029. // scan for all new maps we can go to, for secret levels
  2030. edict_t *changelevel = nullptr;
  2031. while ((changelevel = G_FindByString<&edict_t::classname>(changelevel, "target_changelevel")))
  2032. {
  2033. if (!changelevel->map || !*changelevel->map)
  2034. continue;
  2035. // next unit map, don't count it
  2036. if (strchr(changelevel->map, '*'))
  2037. continue;
  2038. const char *level = strchr(changelevel->map, '+');
  2039. if (level)
  2040. level++;
  2041. else
  2042. level = changelevel->map;
  2043. // don't include end screen levels
  2044. if (strstr(level, ".cin") || strstr(level, ".pcx"))
  2045. continue;
  2046. size_t level_length;
  2047. const char *spawnpoint = strchr(level, '$');
  2048. if (spawnpoint)
  2049. level_length = spawnpoint - level;
  2050. else
  2051. level_length = strlen(level);
  2052. // make an entry for this level that we may or may not visit
  2053. level_entry_t *found_entry = nullptr;
  2054. for (size_t i = 0; i < MAX_LEVELS_PER_UNIT; i++)
  2055. {
  2056. level_entry_t *entry = &game.level_entries[i];
  2057. if (!*entry->map_name || !strncmp(entry->map_name, level, level_length))
  2058. {
  2059. found_entry = entry;
  2060. break;
  2061. }
  2062. }
  2063. if (!found_entry)
  2064. {
  2065. gi.Com_PrintFmt("WARNING: more than {} maps in unit, can't track the rest\n", MAX_LEVELS_PER_UNIT);
  2066. return;
  2067. }
  2068. Q_strlcpy(found_entry->map_name, level, min(level_length + 1, sizeof(found_entry->map_name)));
  2069. }
  2070. }
  2071. /*
  2072. ===========
  2073. ClientBegin
  2074. called when a client has finished connecting, and is ready
  2075. to be placed into the game. This will happen every level load.
  2076. ============
  2077. */
  2078. void ClientBegin(edict_t *ent)
  2079. {
  2080. ent->client = game.clients + (ent - g_edicts - 1);
  2081. ent->client->awaiting_respawn = false;
  2082. ent->client->respawn_timeout = 0_ms;
  2083. // [Paril-KEX] we're always connected by this point...
  2084. ent->client->pers.connected = true;
  2085. if (deathmatch->integer)
  2086. {
  2087. ClientBeginDeathmatch(ent);
  2088. return;
  2089. }
  2090. // [Paril-KEX] set enter time now, so we can send messages slightly
  2091. // after somebody first joins
  2092. ent->client->resp.entertime = level.time;
  2093. ent->client->pers.spawned = true;
  2094. // if there is already a body waiting for us (a loadgame), just
  2095. // take it, otherwise spawn one from scratch
  2096. if (ent->inuse)
  2097. {
  2098. // the client has cleared the client side viewangles upon
  2099. // connecting to the server, which is different than the
  2100. // state when the game is saved, so we need to compensate
  2101. // with deltaangles
  2102. ent->client->ps.pmove.delta_angles = ent->client->ps.viewangles;
  2103. }
  2104. else
  2105. {
  2106. // a spawn point will completely reinitialize the entity
  2107. // except for the persistant data that was initialized at
  2108. // ClientConnect() time
  2109. G_InitEdict(ent);
  2110. ent->classname = "player";
  2111. InitClientResp(ent->client);
  2112. spawn_from_begin = true;
  2113. PutClientInServer(ent);
  2114. spawn_from_begin = false;
  2115. }
  2116. // make sure we have a known default
  2117. ent->svflags |= SVF_PLAYER;
  2118. if (level.intermissiontime)
  2119. {
  2120. MoveClientToIntermission(ent);
  2121. }
  2122. else
  2123. {
  2124. // send effect if in a multiplayer game
  2125. if (game.maxclients > 1 && !(ent->svflags & SVF_NOCLIENT))
  2126. gi.LocBroadcast_Print(PRINT_HIGH, "$g_entered_game", ent->client->pers.netname);
  2127. }
  2128. level.coop_scale_players++;
  2129. G_Monster_CheckCoopHealthScaling();
  2130. // make sure all view stuff is valid
  2131. ClientEndServerFrame(ent);
  2132. // [Paril-KEX] send them goal, if needed
  2133. G_PlayerNotifyGoal(ent);
  2134. // [Paril-KEX] we're going to set this here just to be certain
  2135. // that the level entry timer only starts when a player is actually
  2136. // *in* the level
  2137. G_SetLevelEntry();
  2138. }
  2139. /*
  2140. ================
  2141. P_GetLobbyUserNum
  2142. ================
  2143. */
  2144. unsigned int P_GetLobbyUserNum( const edict_t * player ) {
  2145. unsigned int playerNum = 0;
  2146. if ( player > g_edicts && player < g_edicts + MAX_EDICTS ) {
  2147. playerNum = ( player - g_edicts ) - 1;
  2148. if ( playerNum >= MAX_CLIENTS ) {
  2149. playerNum = 0;
  2150. }
  2151. }
  2152. return playerNum;
  2153. }
  2154. /*
  2155. ================
  2156. G_EncodedPlayerName
  2157. Gets a token version of the players "name" to be decoded on the client.
  2158. ================
  2159. */
  2160. std::string G_EncodedPlayerName(edict_t* player)
  2161. {
  2162. unsigned int playernum = P_GetLobbyUserNum( player );
  2163. return std::string("##P") + std::to_string(playernum);
  2164. }
  2165. /*
  2166. ===========
  2167. ClientUserInfoChanged
  2168. called whenever the player updates a userinfo variable.
  2169. ============
  2170. */
  2171. void ClientUserinfoChanged(edict_t *ent, const char *userinfo)
  2172. {
  2173. // set name
  2174. if (!gi.Info_ValueForKey(userinfo, "name", ent->client->pers.netname, sizeof(ent->client->pers.netname)))
  2175. Q_strlcpy(ent->client->pers.netname, "badinfo", sizeof(ent->client->pers.netname));
  2176. // set spectator
  2177. char val[MAX_INFO_VALUE] = { 0 };
  2178. gi.Info_ValueForKey(userinfo, "spectator", val, sizeof(val));
  2179. // spectators are only supported in deathmatch
  2180. if (deathmatch->integer && !G_TeamplayEnabled() && *val && strcmp(val, "0"))
  2181. ent->client->pers.spectator = true;
  2182. else
  2183. ent->client->pers.spectator = false;
  2184. // set skin
  2185. if (!gi.Info_ValueForKey(userinfo, "skin", val, sizeof(val)))
  2186. Q_strlcpy(val, "male/grunt", sizeof(val));
  2187. int playernum = ent - g_edicts - 1;
  2188. // combine name and skin into a configstring
  2189. // ZOID
  2190. if (G_TeamplayEnabled())
  2191. CTFAssignSkin(ent, val);
  2192. else
  2193. {
  2194. // set dogtag
  2195. char dogtag[MAX_INFO_VALUE] = { 0 };
  2196. gi.Info_ValueForKey(userinfo, "dogtag", dogtag, sizeof(dogtag));
  2197. // ZOID
  2198. gi.configstring(CS_PLAYERSKINS + playernum, G_Fmt("{}\\{}\\{}", ent->client->pers.netname, val, dogtag).data());
  2199. }
  2200. // ZOID
  2201. // set player name field (used in id_state view)
  2202. gi.configstring(CONFIG_CTF_PLAYER_NAME + playernum, ent->client->pers.netname);
  2203. // ZOID
  2204. // [Kex] netname is used for a couple of other things, so we update this after those.
  2205. if ( ( ent->svflags & SVF_BOT ) == 0 ) {
  2206. Q_strlcpy( ent->client->pers.netname, G_EncodedPlayerName( ent ).c_str(), sizeof( ent->client->pers.netname ) );
  2207. }
  2208. // fov
  2209. gi.Info_ValueForKey(userinfo, "fov", val, sizeof(val));
  2210. ent->client->ps.fov = clamp((float) atoi(val), 1.f, 160.f);
  2211. // handedness
  2212. if (gi.Info_ValueForKey(userinfo, "hand", val, sizeof(val)))
  2213. {
  2214. ent->client->pers.hand = static_cast<handedness_t>(clamp(atoi(val), (int32_t) RIGHT_HANDED, (int32_t) CENTER_HANDED));
  2215. }
  2216. else
  2217. {
  2218. ent->client->pers.hand = RIGHT_HANDED;
  2219. }
  2220. // [Paril-KEX] auto-switch
  2221. if (gi.Info_ValueForKey(userinfo, "autoswitch", val, sizeof(val)))
  2222. {
  2223. ent->client->pers.autoswitch = static_cast<auto_switch_t>(clamp(atoi(val), (int32_t)auto_switch_t::SMART, (int32_t)auto_switch_t::NEVER));
  2224. }
  2225. else
  2226. {
  2227. ent->client->pers.autoswitch = auto_switch_t::SMART;
  2228. }
  2229. if (gi.Info_ValueForKey(userinfo, "autoshield", val, sizeof(val)))
  2230. {
  2231. ent->client->pers.autoshield = atoi(val);
  2232. }
  2233. else
  2234. {
  2235. ent->client->pers.autoshield = -1;
  2236. }
  2237. // [Paril-KEX] wants bob
  2238. if (gi.Info_ValueForKey(userinfo, "bobskip", val, sizeof(val)))
  2239. {
  2240. ent->client->pers.bob_skip = val[0] == '1';
  2241. }
  2242. else
  2243. {
  2244. ent->client->pers.bob_skip = false;
  2245. }
  2246. // save off the userinfo in case we want to check something later
  2247. Q_strlcpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo));
  2248. }
  2249. inline bool IsSlotIgnored(edict_t *slot, edict_t **ignore, size_t num_ignore)
  2250. {
  2251. for (size_t i = 0; i < num_ignore; i++)
  2252. if (slot == ignore[i])
  2253. return true;
  2254. return false;
  2255. }
  2256. inline edict_t *ClientChooseSlot_Any(edict_t **ignore, size_t num_ignore)
  2257. {
  2258. for (size_t i = 0; i < game.maxclients; i++)
  2259. if (!IsSlotIgnored(globals.edicts + i + 1, ignore, num_ignore) && !game.clients[i].pers.connected)
  2260. return globals.edicts + i + 1;
  2261. return nullptr;
  2262. }
  2263. inline edict_t *ClientChooseSlot_Coop(const char *userinfo, const char *social_id, bool isBot, edict_t **ignore, size_t num_ignore)
  2264. {
  2265. char name[MAX_INFO_VALUE] = { 0 };
  2266. gi.Info_ValueForKey(userinfo, "name", name, sizeof(name));
  2267. // the host should always occupy slot 0, some systems rely on this
  2268. // (CHECK: is this true? is it just bots?)
  2269. {
  2270. size_t num_players = 0;
  2271. for (size_t i = 0; i < game.maxclients; i++)
  2272. if (IsSlotIgnored(globals.edicts + i + 1, ignore, num_ignore) || game.clients[i].pers.connected)
  2273. num_players++;
  2274. if (!num_players)
  2275. {
  2276. gi.Com_PrintFmt("coop slot {} is host {}+{}\n", 1, name, social_id);
  2277. return globals.edicts + 1;
  2278. }
  2279. }
  2280. // grab matches from players that we have connected
  2281. using match_type_t = int32_t;
  2282. enum {
  2283. MATCH_USERNAME,
  2284. MATCH_SOCIAL,
  2285. MATCH_BOTH,
  2286. MATCH_TYPES
  2287. };
  2288. struct {
  2289. edict_t *slot = nullptr;
  2290. size_t total = 0;
  2291. } matches[MATCH_TYPES];
  2292. for (size_t i = 0; i < game.maxclients; i++)
  2293. {
  2294. if (IsSlotIgnored(globals.edicts + i + 1, ignore, num_ignore) || game.clients[i].pers.connected)
  2295. continue;
  2296. char check_name[MAX_INFO_VALUE] = { 0 };
  2297. gi.Info_ValueForKey(game.clients[i].pers.userinfo, "name", check_name, sizeof(check_name));
  2298. bool username_match = game.clients[i].pers.userinfo[0] &&
  2299. !strcmp(check_name, name);
  2300. bool social_match = social_id && game.clients[i].pers.social_id[0] &&
  2301. !strcmp(game.clients[i].pers.social_id, social_id);
  2302. match_type_t type = (match_type_t) 0;
  2303. if (username_match)
  2304. type |= MATCH_USERNAME;
  2305. if (social_match)
  2306. type |= MATCH_SOCIAL;
  2307. if (!type)
  2308. continue;
  2309. matches[type].slot = globals.edicts + i + 1;
  2310. matches[type].total++;
  2311. }
  2312. // pick matches in descending order, only if the total matches
  2313. // is 1 in the particular set; this will prefer to pick
  2314. // social+username matches first, then social, then username last.
  2315. for (int32_t i = 2; i >= 0; i--)
  2316. {
  2317. if (matches[i].total == 1)
  2318. {
  2319. gi.Com_PrintFmt("coop slot {} restored for {}+{}\n", (ptrdiff_t) (matches[i].slot - globals.edicts), name, social_id);
  2320. // spawn us a ghost now since we're gonna spawn eventually
  2321. if (!matches[i].slot->inuse)
  2322. {
  2323. matches[i].slot->s.modelindex = MODELINDEX_PLAYER;
  2324. matches[i].slot->solid = SOLID_BBOX;
  2325. G_InitEdict(matches[i].slot);
  2326. matches[i].slot->classname = "player";
  2327. InitClientResp(matches[i].slot->client);
  2328. spawn_from_begin = true;
  2329. PutClientInServer(matches[i].slot);
  2330. spawn_from_begin = false;
  2331. // make sure we have a known default
  2332. matches[i].slot->svflags |= SVF_PLAYER;
  2333. matches[i].slot->sv.init = false;
  2334. matches[i].slot->classname = "player";
  2335. matches[i].slot->client->pers.connected = true;
  2336. matches[i].slot->client->pers.spawned = true;
  2337. P_AssignClientSkinnum(matches[i].slot);
  2338. gi.linkentity(matches[i].slot);
  2339. }
  2340. return matches[i].slot;
  2341. }
  2342. }
  2343. // in the case where we can't find a match, we're probably a new
  2344. // player, so pick a slot that hasn't been occupied yet
  2345. for (size_t i = 0; i < game.maxclients; i++)
  2346. if (!IsSlotIgnored(globals.edicts + i + 1, ignore, num_ignore) && !game.clients[i].pers.userinfo[0])
  2347. {
  2348. gi.Com_PrintFmt("coop slot {} issuing new for {}+{}\n", i + 1, name, social_id);
  2349. return globals.edicts + i + 1;
  2350. }
  2351. // all slots have some player data in them, we're forced to replace one.
  2352. edict_t *any_slot = ClientChooseSlot_Any(ignore, num_ignore);
  2353. gi.Com_PrintFmt("coop slot {} any slot for {}+{}\n", !any_slot ? -1 : (ptrdiff_t) (any_slot - globals.edicts), name, social_id);
  2354. return any_slot;
  2355. }
  2356. // [Paril-KEX] for coop, we want to try to ensure that players will always get their
  2357. // proper slot back when they connect.
  2358. edict_t *ClientChooseSlot(const char *userinfo, const char *social_id, bool isBot, edict_t **ignore, size_t num_ignore, bool cinematic)
  2359. {
  2360. // coop and non-bots is the only thing that we need to do special behavior on
  2361. if (!cinematic && coop->integer && !isBot)
  2362. return ClientChooseSlot_Coop(userinfo, social_id, isBot, ignore, num_ignore);
  2363. // just find any free slot
  2364. return ClientChooseSlot_Any(ignore, num_ignore);
  2365. }
  2366. /*
  2367. ===========
  2368. ClientConnect
  2369. Called when a player begins connecting to the server.
  2370. The game can refuse entrance to a client by returning false.
  2371. If the client is allowed, the connection process will continue
  2372. and eventually get to ClientBegin()
  2373. Changing levels will NOT cause this to be called again, but
  2374. loadgames will.
  2375. ============
  2376. */
  2377. bool ClientConnect(edict_t *ent, char *userinfo, const char *social_id, bool isBot)
  2378. {
  2379. // check to see if they are on the banned IP list
  2380. #if 0
  2381. value = Info_ValueForKey(userinfo, "ip");
  2382. if (SV_FilterPacket(value))
  2383. {
  2384. Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
  2385. return false;
  2386. }
  2387. #endif
  2388. // check for a spectator
  2389. char value[MAX_INFO_VALUE] = { 0 };
  2390. gi.Info_ValueForKey(userinfo, "spectator", value, sizeof(value));
  2391. if (deathmatch->integer && *value && strcmp(value, "0"))
  2392. {
  2393. uint32_t i, numspec;
  2394. if (*spectator_password->string &&
  2395. strcmp(spectator_password->string, "none") &&
  2396. strcmp(spectator_password->string, value))
  2397. {
  2398. gi.Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
  2399. return false;
  2400. }
  2401. // count spectators
  2402. for (i = numspec = 0; i < game.maxclients; i++)
  2403. if (g_edicts[i + 1].inuse && g_edicts[i + 1].client->pers.spectator)
  2404. numspec++;
  2405. if (numspec >= (uint32_t) maxspectators->integer)
  2406. {
  2407. gi.Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
  2408. return false;
  2409. }
  2410. }
  2411. else
  2412. {
  2413. // check for a password ( if not a bot! )
  2414. gi.Info_ValueForKey(userinfo, "password", value, sizeof(value));
  2415. if ( !isBot && *password->string && strcmp(password->string, "none") &&
  2416. strcmp(password->string, value))
  2417. {
  2418. gi.Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
  2419. return false;
  2420. }
  2421. }
  2422. // they can connect
  2423. ent->client = game.clients + (ent - g_edicts - 1);
  2424. // set up userinfo early
  2425. ClientUserinfoChanged(ent, userinfo);
  2426. // if there is already a body waiting for us (a loadgame), just
  2427. // take it, otherwise spawn one from scratch
  2428. if (ent->inuse == false)
  2429. {
  2430. // clear the respawning variables
  2431. // ZOID -- force team join
  2432. ent->client->resp.ctf_team = CTF_NOTEAM;
  2433. ent->client->resp.id_state = true;
  2434. // ZOID
  2435. InitClientResp(ent->client);
  2436. if (!game.autosaved || !ent->client->pers.weapon)
  2437. InitClientPersistant(ent, ent->client);
  2438. }
  2439. // make sure we start with known default(s)
  2440. ent->svflags = SVF_PLAYER;
  2441. if ( isBot ) {
  2442. ent->svflags |= SVF_BOT;
  2443. }
  2444. Q_strlcpy(ent->client->pers.social_id, social_id, sizeof(ent->client->pers.social_id));
  2445. if (game.maxclients > 1)
  2446. {
  2447. // [Paril-KEX] fetch name because now netname is kinda unsuitable
  2448. gi.Info_ValueForKey(userinfo, "name", value, sizeof(value));
  2449. gi.LocClient_Print(nullptr, PRINT_HIGH, "$g_player_connected", value);
  2450. }
  2451. ent->client->pers.connected = true;
  2452. // [Paril-KEX] force a state update
  2453. ent->sv.init = false;
  2454. return true;
  2455. }
  2456. /*
  2457. ===========
  2458. ClientDisconnect
  2459. Called when a player drops from the server.
  2460. Will not be called between levels.
  2461. ============
  2462. */
  2463. void ClientDisconnect(edict_t *ent)
  2464. {
  2465. if (!ent->client)
  2466. return;
  2467. // ZOID
  2468. CTFDeadDropFlag(ent);
  2469. CTFDeadDropTech(ent);
  2470. // ZOID
  2471. PlayerTrail_Destroy(ent);
  2472. //============
  2473. // ROGUE
  2474. // make sure no trackers are still hurting us.
  2475. if (ent->client->tracker_pain_time)
  2476. RemoveAttackingPainDaemons(ent);
  2477. if (ent->client->owned_sphere)
  2478. {
  2479. if (ent->client->owned_sphere->inuse)
  2480. G_FreeEdict(ent->client->owned_sphere);
  2481. ent->client->owned_sphere = nullptr;
  2482. }
  2483. if (gamerules->integer)
  2484. {
  2485. if (DMGame.PlayerDisconnect)
  2486. DMGame.PlayerDisconnect(ent);
  2487. }
  2488. // ROGUE
  2489. //============
  2490. // send effect
  2491. if (!(ent->svflags & SVF_NOCLIENT))
  2492. {
  2493. gi.WriteByte(svc_muzzleflash);
  2494. gi.WriteEntity(ent);
  2495. gi.WriteByte(MZ_LOGOUT);
  2496. gi.multicast(ent->s.origin, MULTICAST_PVS, false);
  2497. }
  2498. gi.unlinkentity(ent);
  2499. ent->s.modelindex = 0;
  2500. ent->solid = SOLID_NOT;
  2501. ent->inuse = false;
  2502. ent->sv.init = false;
  2503. ent->classname = "disconnected";
  2504. ent->client->pers.connected = false;
  2505. ent->client->pers.spawned = false;
  2506. ent->timestamp = level.time + 1_sec;
  2507. // update active scoreboards
  2508. if (deathmatch->integer)
  2509. for (auto player : active_players())
  2510. if (player->client->showscores)
  2511. player->client->menutime = level.time;
  2512. }
  2513. //==============================================================
  2514. trace_t SV_PM_Clip(const vec3_t &start, const vec3_t *mins, const vec3_t *maxs, const vec3_t &end, contents_t mask)
  2515. {
  2516. return gi.game_import_t::clip(world, start, mins, maxs, end, mask);
  2517. }
  2518. bool G_ShouldPlayersCollide(bool weaponry)
  2519. {
  2520. if (g_disable_player_collision->integer)
  2521. return false; // only for debugging.
  2522. // always collide on dm
  2523. if (!coop->integer)
  2524. return true;
  2525. // weaponry collides if friendly fire is enabled
  2526. if (weaponry && g_friendly_fire->integer)
  2527. return true;
  2528. // check collision cvar
  2529. return g_coop_player_collision->integer;
  2530. }
  2531. /*
  2532. =================
  2533. P_FallingDamage
  2534. Paril-KEX: this is moved here and now reacts directly
  2535. to ClientThink rather than being delayed.
  2536. =================
  2537. */
  2538. void P_FallingDamage(edict_t *ent, const pmove_t &pm)
  2539. {
  2540. int damage;
  2541. vec3_t dir;
  2542. // dead stuff can't crater
  2543. if (ent->health <= 0 || ent->deadflag)
  2544. return;
  2545. if (ent->s.modelindex != MODELINDEX_PLAYER)
  2546. return; // not in the player model
  2547. if (ent->movetype == MOVETYPE_NOCLIP)
  2548. return;
  2549. // never take falling damage if completely underwater
  2550. if (pm.waterlevel == WATER_UNDER)
  2551. return;
  2552. // ZOID
  2553. // never take damage if just release grapple or on grapple
  2554. if (ent->client->ctf_grapplereleasetime >= level.time ||
  2555. (ent->client->ctf_grapple &&
  2556. ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY))
  2557. return;
  2558. // ZOID
  2559. float delta = pm.impact_delta;
  2560. delta = delta * delta * 0.0001f;
  2561. if (pm.waterlevel == WATER_WAIST)
  2562. delta *= 0.25f;
  2563. if (pm.waterlevel == WATER_FEET)
  2564. delta *= 0.5f;
  2565. if (delta < 1)
  2566. return;
  2567. // restart footstep timer
  2568. ent->client->bobtime = 0;
  2569. if (ent->client->landmark_free_fall)
  2570. {
  2571. delta = min(30.f, delta);
  2572. ent->client->landmark_free_fall = false;
  2573. ent->client->landmark_noise_time = level.time + 100_ms;
  2574. }
  2575. if (delta < 15)
  2576. {
  2577. if (!(pm.s.pm_flags & PMF_ON_LADDER))
  2578. ent->s.event = EV_FOOTSTEP;
  2579. return;
  2580. }
  2581. ent->client->fall_value = delta * 0.5f;
  2582. if (ent->client->fall_value > 40)
  2583. ent->client->fall_value = 40;
  2584. ent->client->fall_time = level.time + FALL_TIME();
  2585. if (delta > 30)
  2586. {
  2587. if (delta >= 55)
  2588. ent->s.event = EV_FALLFAR;
  2589. else
  2590. ent->s.event = EV_FALL;
  2591. ent->pain_debounce_time = level.time + FRAME_TIME_S; // no normal pain sound
  2592. damage = (int) ((delta - 30) / 2);
  2593. if (damage < 1)
  2594. damage = 1;
  2595. dir = { 0, 0, 1 };
  2596. if (!deathmatch->integer || !g_dm_no_fall_damage->integer)
  2597. T_Damage(ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, DAMAGE_NONE, MOD_FALLING);
  2598. }
  2599. else
  2600. ent->s.event = EV_FALLSHORT;
  2601. // Paril: falling damage noises alert monsters
  2602. if (ent->health)
  2603. PlayerNoise(ent, pm.s.origin, PNOISE_SELF);
  2604. }
  2605. bool HandleMenuMovement(edict_t *ent, usercmd_t *ucmd)
  2606. {
  2607. if (!ent->client->menu)
  2608. return false;
  2609. // [Paril-KEX] handle menu movement
  2610. int32_t menu_sign = ucmd->forwardmove > 0 ? 1 : ucmd->forwardmove < 0 ? -1 : 0;
  2611. if (ent->client->menu_sign != menu_sign)
  2612. {
  2613. ent->client->menu_sign = menu_sign;
  2614. if (menu_sign > 0)
  2615. {
  2616. PMenu_Prev(ent);
  2617. return true;
  2618. }
  2619. else if (menu_sign < 0)
  2620. {
  2621. PMenu_Next(ent);
  2622. return true;
  2623. }
  2624. }
  2625. if (ent->client->latched_buttons & (BUTTON_ATTACK | BUTTON_JUMP))
  2626. {
  2627. PMenu_Select(ent);
  2628. return true;
  2629. }
  2630. return false;
  2631. }
  2632. /*
  2633. ==============
  2634. ClientThink
  2635. This will be called once for each client frame, which will
  2636. usually be a couple times for each server frame.
  2637. ==============
  2638. */
  2639. void ClientThink(edict_t *ent, usercmd_t *ucmd)
  2640. {
  2641. gclient_t *client;
  2642. edict_t *other;
  2643. uint32_t i;
  2644. pmove_t pm;
  2645. level.current_entity = ent;
  2646. client = ent->client;
  2647. // [Paril-KEX] pass buttons through even if we are in intermission or
  2648. // chasing.
  2649. client->oldbuttons = client->buttons;
  2650. client->buttons = ucmd->buttons;
  2651. client->latched_buttons |= client->buttons & ~client->oldbuttons;
  2652. client->cmd = *ucmd;
  2653. if ((ucmd->buttons & BUTTON_CROUCH) && pm_config.n64_physics)
  2654. {
  2655. if (client->pers.n64_crouch_warn_times < 12 &&
  2656. client->pers.n64_crouch_warning < level.time &&
  2657. (++client->pers.n64_crouch_warn_times % 3) == 0)
  2658. {
  2659. client->pers.n64_crouch_warning = level.time + 10_sec;
  2660. gi.LocClient_Print(ent, PRINT_CENTER, "$g_n64_crouching");
  2661. }
  2662. }
  2663. if (level.intermissiontime || ent->client->awaiting_respawn)
  2664. {
  2665. client->ps.pmove.pm_type = PM_FREEZE;
  2666. bool n64_sp = false;
  2667. if (level.intermissiontime)
  2668. {
  2669. n64_sp = !deathmatch->integer && level.is_n64;
  2670. // can exit intermission after five seconds
  2671. // Paril: except in N64. the camera handles it.
  2672. // Paril again: except on unit exits, we can leave immediately after camera finishes
  2673. if (level.changemap && (!n64_sp || level.level_intermission_set) && level.time > level.intermissiontime + 5_sec && (ucmd->buttons & BUTTON_ANY))
  2674. level.exitintermission = true;
  2675. }
  2676. if (!n64_sp)
  2677. client->ps.pmove.viewheight = ent->viewheight = 22;
  2678. else
  2679. client->ps.pmove.viewheight = ent->viewheight = 0;
  2680. ent->movetype = MOVETYPE_NOCLIP;
  2681. return;
  2682. }
  2683. if (ent->client->chase_target)
  2684. {
  2685. client->resp.cmd_angles = ucmd->angles;
  2686. ent->movetype = MOVETYPE_NOCLIP;
  2687. }
  2688. else
  2689. {
  2690. // set up for pmove
  2691. memset(&pm, 0, sizeof(pm));
  2692. if (ent->movetype == MOVETYPE_NOCLIP)
  2693. {
  2694. if (ent->client->menu)
  2695. {
  2696. client->ps.pmove.pm_type = PM_FREEZE;
  2697. // [Paril-KEX] handle menu movement
  2698. HandleMenuMovement(ent, ucmd);
  2699. }
  2700. else if (ent->client->awaiting_respawn)
  2701. client->ps.pmove.pm_type = PM_FREEZE;
  2702. else if (ent->client->resp.spectator || (G_TeamplayEnabled() && ent->client->resp.ctf_team == CTF_NOTEAM))
  2703. client->ps.pmove.pm_type = PM_SPECTATOR;
  2704. else
  2705. client->ps.pmove.pm_type = PM_NOCLIP;
  2706. }
  2707. else if (ent->s.modelindex != MODELINDEX_PLAYER)
  2708. client->ps.pmove.pm_type = PM_GIB;
  2709. else if (ent->deadflag)
  2710. client->ps.pmove.pm_type = PM_DEAD;
  2711. else if (ent->client->ctf_grapplestate >= CTF_GRAPPLE_STATE_PULL)
  2712. client->ps.pmove.pm_type = PM_GRAPPLE;
  2713. else
  2714. client->ps.pmove.pm_type = PM_NORMAL;
  2715. // [Paril-KEX]
  2716. if (!G_ShouldPlayersCollide(false) ||
  2717. (coop->integer && !(ent->clipmask & CONTENTS_PLAYER)) // if player collision is on and we're temporarily ghostly...
  2718. )
  2719. client->ps.pmove.pm_flags |= PMF_IGNORE_PLAYER_COLLISION;
  2720. else
  2721. client->ps.pmove.pm_flags &= ~PMF_IGNORE_PLAYER_COLLISION;
  2722. // PGM trigger_gravity support
  2723. client->ps.pmove.gravity = (short) (level.gravity * ent->gravity);
  2724. pm.s = client->ps.pmove;
  2725. pm.s.origin = ent->s.origin;
  2726. pm.s.velocity = ent->velocity;
  2727. if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
  2728. pm.snapinitial = true;
  2729. pm.cmd = *ucmd;
  2730. pm.player = ent;
  2731. pm.trace = gi.game_import_t::trace;
  2732. pm.clip = SV_PM_Clip;
  2733. pm.pointcontents = gi.pointcontents;
  2734. pm.viewoffset = ent->client->ps.viewoffset;
  2735. // perform a pmove
  2736. Pmove(&pm);
  2737. if (pm.groundentity && ent->groundentity)
  2738. {
  2739. float stepsize = fabs(ent->s.origin[2] - pm.s.origin[2]);
  2740. if (stepsize > 4.f && stepsize < STEPSIZE)
  2741. {
  2742. ent->s.renderfx |= RF_STAIR_STEP;
  2743. ent->client->step_frame = gi.ServerFrame() + 1;
  2744. }
  2745. }
  2746. P_FallingDamage(ent, pm);
  2747. if (ent->client->landmark_free_fall && pm.groundentity)
  2748. {
  2749. ent->client->landmark_free_fall = false;
  2750. ent->client->landmark_noise_time = level.time + 100_ms;
  2751. }
  2752. // [Paril-KEX] save old position for G_TouchProjectiles
  2753. vec3_t old_origin = ent->s.origin;
  2754. ent->s.origin = pm.s.origin;
  2755. ent->velocity = pm.s.velocity;
  2756. // [Paril-KEX] if we stepped onto/off of a ladder, reset the
  2757. // last ladder pos
  2758. if ((pm.s.pm_flags & PMF_ON_LADDER) != (client->ps.pmove.pm_flags & PMF_ON_LADDER))
  2759. {
  2760. client->last_ladder_pos = ent->s.origin;
  2761. if (pm.s.pm_flags & PMF_ON_LADDER)
  2762. {
  2763. if (!deathmatch->integer &&
  2764. client->last_ladder_sound < level.time)
  2765. {
  2766. ent->s.event = EV_LADDER_STEP;
  2767. client->last_ladder_sound = level.time + LADDER_SOUND_TIME;
  2768. }
  2769. }
  2770. }
  2771. // save results of pmove
  2772. client->ps.pmove = pm.s;
  2773. client->old_pmove = pm.s;
  2774. ent->mins = pm.mins;
  2775. ent->maxs = pm.maxs;
  2776. if (!ent->client->menu)
  2777. client->resp.cmd_angles = ucmd->angles;
  2778. if (pm.jump_sound && !(pm.s.pm_flags & PMF_ON_LADDER))
  2779. {
  2780. gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
  2781. // Paril: removed to make ambushes more effective and to
  2782. // not have monsters around corners come to jumps
  2783. // PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
  2784. }
  2785. // ROGUE sam raimi cam support
  2786. if (ent->flags & FL_SAM_RAIMI)
  2787. ent->viewheight = 8;
  2788. else
  2789. ent->viewheight = (int) pm.s.viewheight;
  2790. // ROGUE
  2791. ent->waterlevel = pm.waterlevel;
  2792. ent->watertype = pm.watertype;
  2793. ent->groundentity = pm.groundentity;
  2794. if (pm.groundentity)
  2795. ent->groundentity_linkcount = pm.groundentity->linkcount;
  2796. if (ent->deadflag)
  2797. {
  2798. client->ps.viewangles[ROLL] = 40;
  2799. client->ps.viewangles[PITCH] = -15;
  2800. client->ps.viewangles[YAW] = client->killer_yaw;
  2801. }
  2802. else if (!ent->client->menu)
  2803. {
  2804. client->v_angle = pm.viewangles;
  2805. client->ps.viewangles = pm.viewangles;
  2806. AngleVectors(client->v_angle, client->v_forward, nullptr, nullptr);
  2807. }
  2808. // ZOID
  2809. if (client->ctf_grapple)
  2810. CTFGrapplePull(client->ctf_grapple);
  2811. // ZOID
  2812. gi.linkentity(ent);
  2813. // PGM trigger_gravity support
  2814. ent->gravity = 1.0;
  2815. // PGM
  2816. if (ent->movetype != MOVETYPE_NOCLIP)
  2817. {
  2818. G_TouchTriggers(ent);
  2819. G_TouchProjectiles(ent, old_origin);
  2820. }
  2821. // touch other objects
  2822. for (i = 0; i < pm.touch.num; i++)
  2823. {
  2824. trace_t &tr = pm.touch.traces[i];
  2825. other = tr.ent;
  2826. if (other->touch)
  2827. other->touch(other, ent, tr, true);
  2828. }
  2829. }
  2830. // fire weapon from final position if needed
  2831. if (client->latched_buttons & BUTTON_ATTACK)
  2832. {
  2833. if (client->resp.spectator)
  2834. {
  2835. client->latched_buttons = BUTTON_NONE;
  2836. if (client->chase_target)
  2837. {
  2838. client->chase_target = nullptr;
  2839. client->ps.pmove.pm_flags &= ~(PMF_NO_POSITIONAL_PREDICTION | PMF_NO_ANGULAR_PREDICTION);
  2840. }
  2841. else
  2842. GetChaseTarget(ent);
  2843. }
  2844. else if (!ent->client->weapon_thunk)
  2845. {
  2846. // we can only do this during a ready state and
  2847. // if enough time has passed from last fire
  2848. if (ent->client->weaponstate == WEAPON_READY)
  2849. {
  2850. ent->client->weapon_fire_buffered = true;
  2851. if (ent->client->weapon_fire_finished <= level.time)
  2852. {
  2853. ent->client->weapon_thunk = true;
  2854. Think_Weapon(ent);
  2855. }
  2856. }
  2857. }
  2858. }
  2859. if (client->resp.spectator)
  2860. {
  2861. if (!HandleMenuMovement(ent, ucmd))
  2862. {
  2863. if (ucmd->buttons & BUTTON_JUMP)
  2864. {
  2865. if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD))
  2866. {
  2867. client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
  2868. if (client->chase_target)
  2869. ChaseNext(ent);
  2870. else
  2871. GetChaseTarget(ent);
  2872. }
  2873. }
  2874. else
  2875. client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
  2876. }
  2877. }
  2878. // update chase cam if being followed
  2879. for (i = 1; i <= game.maxclients; i++)
  2880. {
  2881. other = g_edicts + i;
  2882. if (other->inuse && other->client->chase_target == ent)
  2883. UpdateChaseCam(other);
  2884. }
  2885. }
  2886. // active monsters
  2887. struct active_monsters_filter_t
  2888. {
  2889. inline bool operator()(edict_t *ent) const
  2890. {
  2891. return (ent->inuse && (ent->svflags & SVF_MONSTER) && ent->health > 0);
  2892. }
  2893. };
  2894. inline entity_iterable_t<active_monsters_filter_t> active_monsters()
  2895. {
  2896. return entity_iterable_t<active_monsters_filter_t> { game.maxclients + (uint32_t)BODY_QUEUE_SIZE + 1U };
  2897. }
  2898. inline bool G_MonstersSearchingFor(edict_t *player)
  2899. {
  2900. for (auto ent : active_monsters())
  2901. {
  2902. // check for *any* player target
  2903. if (player == nullptr && ent->enemy && !ent->enemy->client)
  2904. continue;
  2905. // they're not targeting us, so who cares
  2906. else if (player != nullptr && ent->enemy != player)
  2907. continue;
  2908. // they lost sight of us
  2909. if ((ent->monsterinfo.aiflags & AI_LOST_SIGHT) && level.time > ent->monsterinfo.trail_time + 5_sec)
  2910. continue;
  2911. // no sir
  2912. return true;
  2913. }
  2914. // yes sir
  2915. return false;
  2916. }
  2917. // [Paril-KEX] from the given player, find a good spot to
  2918. // spawn a player
  2919. inline bool G_FindRespawnSpot(edict_t *player, vec3_t &spot)
  2920. {
  2921. // sanity check; make sure there's enough room for ourselves.
  2922. // (crouching in a small area, etc)
  2923. trace_t tr = gi.trace(player->s.origin, PLAYER_MINS, PLAYER_MAXS, player->s.origin, player, MASK_PLAYERSOLID);
  2924. if (tr.startsolid || tr.allsolid)
  2925. return false;
  2926. // throw five boxes a short-ish distance from the player and see if they land in a good, visible spot
  2927. constexpr float yaw_spread[] = { 0, 90, 45, -45, -90 };
  2928. constexpr float back_distance = 128.f;
  2929. constexpr float up_distance = 128.f;
  2930. constexpr float player_viewheight = 22.f;
  2931. // we don't want to spawn inside of these
  2932. contents_t mask = MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME;
  2933. for (auto &yaw : yaw_spread)
  2934. {
  2935. vec3_t angles = { 0, (player->s.angles[YAW] + 180) + yaw, 0 };
  2936. // throw the box three times:
  2937. // one up & back
  2938. // one back
  2939. // one up, then back
  2940. // pick the one that went the farthest
  2941. vec3_t start = player->s.origin;
  2942. vec3_t end = start + vec3_t { 0, 0, up_distance };
  2943. tr = gi.trace(start, PLAYER_MINS, PLAYER_MAXS, end, player, mask);
  2944. // stuck
  2945. if (tr.startsolid || tr.allsolid || (tr.contents & (CONTENTS_LAVA | CONTENTS_SLIME)))
  2946. continue;
  2947. vec3_t fwd;
  2948. AngleVectors(angles, fwd, nullptr, nullptr);
  2949. start = tr.endpos;
  2950. end = start + fwd * back_distance;
  2951. tr = gi.trace(start, PLAYER_MINS, PLAYER_MAXS, end, player, mask);
  2952. // stuck
  2953. if (tr.startsolid || tr.allsolid || (tr.contents & (CONTENTS_LAVA | CONTENTS_SLIME)))
  2954. continue;
  2955. // plop us down now
  2956. start = tr.endpos;
  2957. end = tr.endpos - vec3_t { 0, 0, up_distance * 4 };
  2958. tr = gi.trace(start, PLAYER_MINS, PLAYER_MAXS, end, player, mask);
  2959. // stuck, or floating, or touching some other entity
  2960. if (tr.startsolid || tr.allsolid || (tr.contents & (CONTENTS_LAVA | CONTENTS_SLIME)) || tr.fraction == 1.0f || tr.ent != world)
  2961. continue;
  2962. // don't spawn us *inside* liquids
  2963. if (gi.pointcontents(tr.endpos + vec3_t{0, 0, player_viewheight}) & MASK_WATER)
  2964. continue;
  2965. // don't spawn us on steep slopes
  2966. if (tr.plane.normal.z < 0.7f)
  2967. continue;
  2968. spot = tr.endpos;
  2969. float z_diff = fabsf(player->s.origin[2] - tr.endpos[2]);
  2970. // 5 steps is way too many steps
  2971. if (z_diff > STEPSIZE * 4.f)
  2972. continue;
  2973. // if we went up or down 1 step, make sure we can still see their origin and their head
  2974. if (z_diff > STEPSIZE)
  2975. {
  2976. tr = gi.traceline(player->s.origin, tr.endpos, player, mask);
  2977. if (tr.fraction != 1.0f)
  2978. continue;
  2979. tr = gi.traceline(player->s.origin + vec3_t{0, 0, player_viewheight}, tr.endpos + vec3_t{0, 0, player_viewheight}, player, mask);
  2980. if (tr.fraction != 1.0f)
  2981. continue;
  2982. }
  2983. // good spot!
  2984. return true;
  2985. }
  2986. return false;
  2987. }
  2988. // [Paril-KEX] check each player to find a good
  2989. // respawn target & position
  2990. inline std::tuple<edict_t *, vec3_t> G_FindSquadRespawnTarget()
  2991. {
  2992. bool monsters_searching_for_anybody = G_MonstersSearchingFor(nullptr);
  2993. for (auto player : active_players())
  2994. {
  2995. // no dead players
  2996. if (player->deadflag)
  2997. continue;
  2998. // check combat state; we can't have taken damage recently
  2999. if (player->client->last_damage_time >= level.time)
  3000. {
  3001. player->client->coop_respawn_state = COOP_RESPAWN_IN_COMBAT;
  3002. continue;
  3003. }
  3004. // check if any monsters are currently targeting us
  3005. // or searching for us
  3006. if (G_MonstersSearchingFor(player))
  3007. {
  3008. player->client->coop_respawn_state = COOP_RESPAWN_IN_COMBAT;
  3009. continue;
  3010. }
  3011. // check firing state; if any enemies are mad at any players,
  3012. // don't respawn until everybody has cooled down
  3013. if (monsters_searching_for_anybody && player->client->last_firing_time >= level.time)
  3014. {
  3015. player->client->coop_respawn_state = COOP_RESPAWN_IN_COMBAT;
  3016. continue;
  3017. }
  3018. // check positioning; we must be on world ground
  3019. if (player->groundentity != world)
  3020. {
  3021. player->client->coop_respawn_state = COOP_RESPAWN_BAD_AREA;
  3022. continue;
  3023. }
  3024. // can't be in liquid
  3025. if (player->waterlevel >= WATER_UNDER)
  3026. {
  3027. player->client->coop_respawn_state = COOP_RESPAWN_BAD_AREA;
  3028. continue;
  3029. }
  3030. // good player; pick a spot
  3031. vec3_t spot;
  3032. if (!G_FindRespawnSpot(player, spot))
  3033. {
  3034. player->client->coop_respawn_state = COOP_RESPAWN_BLOCKED;
  3035. continue;
  3036. }
  3037. // good player most likely
  3038. return { player, spot };
  3039. }
  3040. // no good player
  3041. return { nullptr, {} };
  3042. }
  3043. enum respawn_state_t
  3044. {
  3045. RESPAWN_NONE, // invalid state
  3046. RESPAWN_SPECTATE, // move to spectator
  3047. RESPAWN_SQUAD, // move to good squad point
  3048. RESPAWN_START // move to start of map
  3049. };
  3050. // [Paril-KEX] return false to fall back to click-to-respawn behavior.
  3051. // note that this is only called if they are allowed to respawn (not
  3052. // restarting the level due to all being dead)
  3053. static bool G_CoopRespawn(edict_t *ent)
  3054. {
  3055. // don't do this in non-coop
  3056. if (!coop->integer)
  3057. return false;
  3058. // if we don't have squad or lives, it doesn't matter
  3059. else if (!g_coop_squad_respawn->integer && !g_coop_enable_lives->integer)
  3060. return false;
  3061. respawn_state_t state = RESPAWN_NONE;
  3062. // first pass: if we have no lives left, just move to spectator
  3063. if (g_coop_enable_lives->integer)
  3064. {
  3065. if (ent->client->pers.lives == 0)
  3066. {
  3067. state = RESPAWN_SPECTATE;
  3068. ent->client->coop_respawn_state = COOP_RESPAWN_NO_LIVES;
  3069. }
  3070. }
  3071. // second pass: check for where to spawn
  3072. if (state == RESPAWN_NONE)
  3073. {
  3074. // if squad respawn, don't respawn until we can find a good player to spawn on.
  3075. if (g_coop_squad_respawn->integer)
  3076. {
  3077. bool allDead = true;
  3078. for (auto player : active_players())
  3079. {
  3080. if (player->health > 0)
  3081. {
  3082. allDead = false;
  3083. break;
  3084. }
  3085. }
  3086. // all dead, so if we ever get here we have lives enabled;
  3087. // we should just respawn at the start of the level
  3088. if (allDead)
  3089. state = RESPAWN_START;
  3090. else
  3091. {
  3092. auto [ good_player, good_spot ] = G_FindSquadRespawnTarget();
  3093. if (good_player) {
  3094. state = RESPAWN_SQUAD;
  3095. squad_respawn_position = good_spot;
  3096. squad_respawn_angles = good_player->s.angles;
  3097. squad_respawn_angles[2] = 0;
  3098. use_squad_respawn = true;
  3099. } else {
  3100. state = RESPAWN_SPECTATE;
  3101. }
  3102. }
  3103. }
  3104. else
  3105. state = RESPAWN_START;
  3106. }
  3107. if (state == RESPAWN_SQUAD || state == RESPAWN_START)
  3108. {
  3109. // give us our max health back since it will reset
  3110. // to pers.health; in instanced items we'd lose the items
  3111. // we touched so we always want to respawn with our max.
  3112. if (P_UseCoopInstancedItems())
  3113. ent->client->pers.health = ent->client->pers.max_health = ent->max_health;
  3114. respawn(ent);
  3115. ent->client->latched_buttons = BUTTON_NONE;
  3116. use_squad_respawn = false;
  3117. }
  3118. else if (state == RESPAWN_SPECTATE)
  3119. {
  3120. if (!ent->client->coop_respawn_state)
  3121. ent->client->coop_respawn_state = COOP_RESPAWN_WAITING;
  3122. if (!ent->client->resp.spectator)
  3123. {
  3124. // move us to spectate just so we don't have to twiddle
  3125. // our thumbs forever
  3126. CopyToBodyQue(ent);
  3127. ent->client->resp.spectator = true;
  3128. ent->solid = SOLID_NOT;
  3129. ent->takedamage = false;
  3130. ent->s.modelindex = 0;
  3131. ent->svflags |= SVF_NOCLIENT;
  3132. ent->client->ps.damage_blend[3] = ent->client->ps.screen_blend[3] = 0;
  3133. ent->client->ps.rdflags = RDF_NONE;
  3134. ent->movetype = MOVETYPE_NOCLIP;
  3135. // TODO: check if anything else needs to be reset
  3136. gi.linkentity(ent);
  3137. GetChaseTarget(ent);
  3138. }
  3139. }
  3140. return true;
  3141. }
  3142. /*
  3143. ==============
  3144. ClientBeginServerFrame
  3145. This will be called once for each server frame, before running
  3146. any other entities in the world.
  3147. ==============
  3148. */
  3149. void ClientBeginServerFrame(edict_t *ent)
  3150. {
  3151. gclient_t *client;
  3152. int buttonMask;
  3153. if (gi.ServerFrame() != ent->client->step_frame)
  3154. ent->s.renderfx &= ~RF_STAIR_STEP;
  3155. if (level.intermissiontime)
  3156. return;
  3157. client = ent->client;
  3158. if (client->awaiting_respawn)
  3159. {
  3160. if ((level.time.milliseconds() % 500) == 0)
  3161. PutClientInServer(ent);
  3162. return;
  3163. }
  3164. if ( ( ent->svflags & SVF_BOT ) != 0 ) {
  3165. Bot_BeginFrame( ent );
  3166. }
  3167. if (deathmatch->integer && !G_TeamplayEnabled() &&
  3168. client->pers.spectator != client->resp.spectator &&
  3169. (level.time - client->respawn_time) >= 5_sec)
  3170. {
  3171. spectator_respawn(ent);
  3172. return;
  3173. }
  3174. // run weapon animations if it hasn't been done by a ucmd_t
  3175. if (!client->weapon_thunk && !client->resp.spectator)
  3176. Think_Weapon(ent);
  3177. else
  3178. client->weapon_thunk = false;
  3179. if (ent->deadflag)
  3180. {
  3181. // don't respawn if level is waiting to restart
  3182. if (level.time > client->respawn_time && !level.coop_level_restart_time)
  3183. {
  3184. // check for coop handling
  3185. if (!G_CoopRespawn(ent))
  3186. {
  3187. // in deathmatch, only wait for attack button
  3188. if (deathmatch->integer)
  3189. buttonMask = BUTTON_ATTACK;
  3190. else
  3191. buttonMask = -1;
  3192. if ((client->latched_buttons & buttonMask) ||
  3193. (deathmatch->integer && g_dm_force_respawn->integer))
  3194. {
  3195. respawn(ent);
  3196. client->latched_buttons = BUTTON_NONE;
  3197. }
  3198. }
  3199. }
  3200. return;
  3201. }
  3202. // add player trail so monsters can follow
  3203. if (!deathmatch->integer)
  3204. PlayerTrail_Add(ent);
  3205. client->latched_buttons = BUTTON_NONE;
  3206. }
  3207. /*
  3208. ==============
  3209. RemoveAttackingPainDaemons
  3210. This is called to clean up the pain daemons that the disruptor attaches
  3211. to clients to damage them.
  3212. ==============
  3213. */
  3214. void RemoveAttackingPainDaemons(edict_t *self)
  3215. {
  3216. edict_t *tracker;
  3217. tracker = G_FindByString<&edict_t::classname>(nullptr, "pain daemon");
  3218. while (tracker)
  3219. {
  3220. if (tracker->enemy == self)
  3221. G_FreeEdict(tracker);
  3222. tracker = G_FindByString<&edict_t::classname>(tracker, "pain daemon");
  3223. }
  3224. if (self->client)
  3225. self->client->tracker_pain_time = 0_ms;
  3226. }