g_monster.cpp 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "g_local.h"
  4. #include "bots/bot_includes.h"
  5. //
  6. // monster weapons
  7. //
  8. void monster_muzzleflash(edict_t *self, const vec3_t &start, monster_muzzleflash_id_t id)
  9. {
  10. if (id <= 255)
  11. gi.WriteByte(svc_muzzleflash2);
  12. else
  13. gi.WriteByte(svc_muzzleflash3);
  14. gi.WriteEntity(self);
  15. if (id <= 255)
  16. gi.WriteByte(id);
  17. else
  18. gi.WriteShort(id);
  19. gi.multicast(start, MULTICAST_PHS, false);
  20. }
  21. void monster_fire_bullet(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int kick, int hspread,
  22. int vspread, monster_muzzleflash_id_t flashtype)
  23. {
  24. fire_bullet(self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
  25. monster_muzzleflash(self, start, flashtype);
  26. }
  27. void monster_fire_shotgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread,
  28. int vspread, int count, monster_muzzleflash_id_t flashtype)
  29. {
  30. fire_shotgun(self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
  31. monster_muzzleflash(self, start, flashtype);
  32. }
  33. void monster_fire_blaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed,
  34. monster_muzzleflash_id_t flashtype, effects_t effect)
  35. {
  36. fire_blaster(self, start, dir, damage, speed, effect, MOD_BLASTER);
  37. monster_muzzleflash(self, start, flashtype);
  38. }
  39. void monster_fire_flechette(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed,
  40. monster_muzzleflash_id_t flashtype)
  41. {
  42. fire_flechette(self, start, dir, damage, speed, damage / 2);
  43. monster_muzzleflash(self, start, flashtype);
  44. }
  45. void monster_fire_grenade(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed,
  46. monster_muzzleflash_id_t flashtype, float right_adjust, float up_adjust)
  47. {
  48. fire_grenade(self, start, aimdir, damage, speed, 2.5_sec, damage + 40.f, right_adjust, up_adjust, true);
  49. monster_muzzleflash(self, start, flashtype);
  50. }
  51. void monster_fire_rocket(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed,
  52. monster_muzzleflash_id_t flashtype)
  53. {
  54. fire_rocket(self, start, dir, damage, speed, (float) damage + 20, damage);
  55. monster_muzzleflash(self, start, flashtype);
  56. }
  57. void monster_fire_railgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick,
  58. monster_muzzleflash_id_t flashtype)
  59. {
  60. if (gi.pointcontents(start) & MASK_SOLID)
  61. return;
  62. fire_rail(self, start, aimdir, damage, kick);
  63. monster_muzzleflash(self, start, flashtype);
  64. }
  65. void monster_fire_bfg(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, int kick,
  66. float damage_radius, monster_muzzleflash_id_t flashtype)
  67. {
  68. fire_bfg(self, start, aimdir, damage, speed, damage_radius);
  69. monster_muzzleflash(self, start, flashtype);
  70. }
  71. // [Paril-KEX]
  72. vec3_t M_ProjectFlashSource(edict_t *self, const vec3_t &offset, const vec3_t &forward, const vec3_t &right)
  73. {
  74. return G_ProjectSource(self->s.origin, self->s.scale ? (offset * self->s.scale) : offset, forward, right);
  75. }
  76. // [Paril-KEX] check if shots fired from the given offset
  77. // might be blocked by something
  78. bool M_CheckClearShot(edict_t *self, const vec3_t &offset, vec3_t &start)
  79. {
  80. // no enemy, just do whatever
  81. if (!self->enemy)
  82. return false;
  83. vec3_t f, r;
  84. vec3_t real_angles = { self->s.angles[0], self->ideal_yaw, 0.f };
  85. AngleVectors(real_angles, f, r, nullptr);
  86. start = M_ProjectFlashSource(self, offset, f, r);
  87. vec3_t target;
  88. bool is_blind = self->monsterinfo.attack_state == AS_BLIND || (self->monsterinfo.aiflags & (AI_MANUAL_STEERING | AI_LOST_SIGHT));
  89. if (is_blind)
  90. target = self->monsterinfo.blind_fire_target;
  91. else
  92. target = self->enemy->s.origin + vec3_t{ 0, 0, (float) self->enemy->viewheight };
  93. trace_t tr = gi.traceline(start, target, self, MASK_PROJECTILE & ~CONTENTS_DEADMONSTER);
  94. if (tr.ent == self->enemy || tr.ent->client || (tr.fraction > 0.8f && !tr.startsolid))
  95. return true;
  96. if (!is_blind)
  97. {
  98. target = self->enemy->s.origin;
  99. trace_t tr = gi.traceline(start, target, self, MASK_PROJECTILE & ~CONTENTS_DEADMONSTER);
  100. if (tr.ent == self->enemy || tr.ent->client || (tr.fraction > 0.8f && !tr.startsolid))
  101. return true;
  102. }
  103. return false;
  104. }
  105. bool M_CheckClearShot(edict_t *self, const vec3_t &offset)
  106. {
  107. vec3_t start;
  108. return M_CheckClearShot(self, offset, start);
  109. }
  110. void M_CheckGround(edict_t *ent, contents_t mask)
  111. {
  112. vec3_t point;
  113. trace_t trace;
  114. if (ent->flags & (FL_SWIM | FL_FLY))
  115. return;
  116. if ((ent->velocity[2] * ent->gravityVector[2]) < -100) // PGM
  117. {
  118. ent->groundentity = nullptr;
  119. return;
  120. }
  121. // if the hull point one-quarter unit down is solid the entity is on ground
  122. point[0] = ent->s.origin[0];
  123. point[1] = ent->s.origin[1];
  124. point[2] = ent->s.origin[2] + (0.25f * ent->gravityVector[2]); // PGM
  125. trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, point, ent, mask);
  126. // check steepness
  127. // PGM
  128. if (ent->gravityVector[2] < 0) // normal gravity
  129. {
  130. if (trace.plane.normal[2] < 0.7f && !trace.startsolid)
  131. {
  132. ent->groundentity = nullptr;
  133. return;
  134. }
  135. }
  136. else // inverted gravity
  137. {
  138. if (trace.plane.normal[2] > -0.7f && !trace.startsolid)
  139. {
  140. ent->groundentity = nullptr;
  141. return;
  142. }
  143. }
  144. // PGM
  145. if (!trace.startsolid && !trace.allsolid)
  146. {
  147. ent->s.origin = trace.endpos;
  148. ent->groundentity = trace.ent;
  149. ent->groundentity_linkcount = trace.ent->linkcount;
  150. ent->velocity[2] = 0;
  151. }
  152. }
  153. void M_CatagorizePosition(edict_t *self, const vec3_t &in_point, water_level_t &waterlevel, contents_t &watertype)
  154. {
  155. vec3_t point;
  156. contents_t cont;
  157. //
  158. // get waterlevel
  159. //
  160. point[0] = in_point[0];
  161. point[1] = in_point[1];
  162. if (self->gravityVector[2] > 0)
  163. point[2] = in_point[2] + self->maxs[2] - 1;
  164. else
  165. point[2] = in_point[2] + self->mins[2] + 1;
  166. cont = gi.pointcontents(point);
  167. if (!(cont & MASK_WATER))
  168. {
  169. waterlevel = WATER_NONE;
  170. watertype = CONTENTS_NONE;
  171. return;
  172. }
  173. watertype = cont;
  174. waterlevel = WATER_FEET;
  175. point[2] += 26;
  176. cont = gi.pointcontents(point);
  177. if (!(cont & MASK_WATER))
  178. return;
  179. waterlevel = WATER_WAIST;
  180. point[2] += 22;
  181. cont = gi.pointcontents(point);
  182. if (cont & MASK_WATER)
  183. waterlevel = WATER_UNDER;
  184. }
  185. bool M_ShouldReactToPain(edict_t *self, const mod_t &mod)
  186. {
  187. if (self->monsterinfo.aiflags & (AI_DUCKED | AI_COMBAT_POINT))
  188. return false;
  189. return mod.id == MOD_CHAINFIST || skill->integer < 3;
  190. }
  191. void M_WorldEffects(edict_t *ent)
  192. {
  193. int dmg;
  194. if (ent->health > 0)
  195. {
  196. if (!(ent->flags & FL_SWIM))
  197. {
  198. if (ent->waterlevel < WATER_UNDER)
  199. {
  200. ent->air_finished = level.time + 12_sec;
  201. }
  202. else if (ent->air_finished < level.time)
  203. { // drown!
  204. if (ent->pain_debounce_time < level.time)
  205. {
  206. dmg = 2 + (int) (2 * floorf((level.time - ent->air_finished).seconds()));
  207. if (dmg > 15)
  208. dmg = 15;
  209. T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR,
  210. MOD_WATER);
  211. ent->pain_debounce_time = level.time + 1_sec;
  212. }
  213. }
  214. }
  215. else
  216. {
  217. if (ent->waterlevel > WATER_NONE)
  218. {
  219. ent->air_finished = level.time + 9_sec;
  220. }
  221. else if (ent->air_finished < level.time)
  222. { // suffocate!
  223. if (ent->pain_debounce_time < level.time)
  224. {
  225. dmg = 2 + (int) (2 * floorf((level.time - ent->air_finished).seconds()));
  226. if (dmg > 15)
  227. dmg = 15;
  228. T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR,
  229. MOD_WATER);
  230. ent->pain_debounce_time = level.time + 1_sec;
  231. }
  232. }
  233. }
  234. }
  235. if (ent->waterlevel == WATER_NONE)
  236. {
  237. if (ent->flags & FL_INWATER)
  238. {
  239. gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
  240. ent->flags &= ~FL_INWATER;
  241. }
  242. }
  243. else
  244. {
  245. if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
  246. {
  247. if (ent->damage_debounce_time < level.time)
  248. {
  249. ent->damage_debounce_time = level.time + 100_ms;
  250. T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10 * ent->waterlevel, 0, DAMAGE_NONE,
  251. MOD_LAVA);
  252. }
  253. }
  254. if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
  255. {
  256. if (ent->damage_debounce_time < level.time)
  257. {
  258. ent->damage_debounce_time = level.time + 100_ms;
  259. T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4 * ent->waterlevel, 0, DAMAGE_NONE,
  260. MOD_SLIME);
  261. }
  262. }
  263. if (!(ent->flags & FL_INWATER))
  264. {
  265. if (ent->watertype & CONTENTS_LAVA)
  266. {
  267. if ((ent->svflags & SVF_MONSTER) && ent->health > 0)
  268. {
  269. if (frandom() <= 0.5f)
  270. gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
  271. else
  272. gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
  273. }
  274. else
  275. gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
  276. }
  277. else if (ent->watertype & CONTENTS_SLIME)
  278. gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
  279. else if (ent->watertype & CONTENTS_WATER)
  280. gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
  281. ent->flags |= FL_INWATER;
  282. ent->damage_debounce_time = 0_ms;
  283. }
  284. }
  285. }
  286. bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, bool ceiling, edict_t *ignore, contents_t mask, bool allow_partial)
  287. {
  288. vec3_t end;
  289. trace_t trace;
  290. // PGM
  291. if (gi.trace(origin, mins, maxs, origin, ignore, mask).startsolid)
  292. {
  293. if (!ceiling)
  294. origin[2] += 1;
  295. else
  296. origin[2] -= 1;
  297. }
  298. if (!ceiling)
  299. {
  300. end = origin;
  301. end[2] -= 256;
  302. }
  303. else
  304. {
  305. end = origin;
  306. end[2] += 256;
  307. }
  308. // PGM
  309. trace = gi.trace(origin, mins, maxs, end, ignore, mask);
  310. if (trace.fraction == 1 || trace.allsolid || (!allow_partial && trace.startsolid))
  311. return false;
  312. origin = trace.endpos;
  313. return true;
  314. }
  315. bool M_droptofloor(edict_t *ent)
  316. {
  317. contents_t mask = G_GetClipMask(ent);
  318. if (!ent->spawnflags.has(SPAWNFLAG_MONSTER_NO_DROP))
  319. {
  320. if (!M_droptofloor_generic(ent->s.origin, ent->mins, ent->maxs, ent->gravityVector[2] > 0, ent, mask, true))
  321. return false;
  322. }
  323. else
  324. {
  325. if (gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask).startsolid)
  326. return false;
  327. }
  328. gi.linkentity(ent);
  329. M_CheckGround(ent, mask);
  330. M_CatagorizePosition(ent, ent->s.origin, ent->waterlevel, ent->watertype);
  331. return true;
  332. }
  333. void M_SetEffects(edict_t *ent)
  334. {
  335. ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN | EF_DOUBLE | EF_QUAD | EF_PENT | EF_FLIES);
  336. ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE | RF_SHELL_DOUBLE);
  337. ent->s.sound = 0;
  338. ent->s.loop_attenuation = 0;
  339. // we're gibbed
  340. if (ent->s.renderfx & RF_LOW_PRIORITY)
  341. return;
  342. if (ent->monsterinfo.weapon_sound && ent->health > 0)
  343. {
  344. ent->s.sound = ent->monsterinfo.weapon_sound;
  345. ent->s.loop_attenuation = ATTN_NORM;
  346. }
  347. else if (ent->monsterinfo.engine_sound)
  348. ent->s.sound = ent->monsterinfo.engine_sound;
  349. if (ent->monsterinfo.aiflags & AI_RESURRECTING)
  350. {
  351. ent->s.effects |= EF_COLOR_SHELL;
  352. ent->s.renderfx |= RF_SHELL_RED;
  353. }
  354. ent->s.renderfx |= RF_DOT_SHADOW;
  355. // no power armor/powerup effects if we died
  356. if (ent->health <= 0)
  357. return;
  358. if (ent->powerarmor_time > level.time)
  359. {
  360. if (ent->monsterinfo.power_armor_type == IT_ITEM_POWER_SCREEN)
  361. {
  362. ent->s.effects |= EF_POWERSCREEN;
  363. }
  364. else if (ent->monsterinfo.power_armor_type == IT_ITEM_POWER_SHIELD)
  365. {
  366. ent->s.effects |= EF_COLOR_SHELL;
  367. ent->s.renderfx |= RF_SHELL_GREEN;
  368. }
  369. }
  370. // PMM - new monster powerups
  371. if (ent->monsterinfo.quad_time > level.time)
  372. {
  373. if (G_PowerUpExpiring(ent->monsterinfo.quad_time))
  374. ent->s.effects |= EF_QUAD;
  375. }
  376. if (ent->monsterinfo.double_time > level.time)
  377. {
  378. if (G_PowerUpExpiring(ent->monsterinfo.double_time))
  379. ent->s.effects |= EF_DOUBLE;
  380. }
  381. if (ent->monsterinfo.invincible_time > level.time)
  382. {
  383. if (G_PowerUpExpiring(ent->monsterinfo.invincible_time))
  384. ent->s.effects |= EF_PENT;
  385. }
  386. }
  387. bool M_AllowSpawn( edict_t * self ) {
  388. if ( deathmatch->integer && !ai_allow_dm_spawn->integer ) {
  389. return false;
  390. }
  391. return true;
  392. }
  393. void M_SetAnimation(edict_t *self, const save_mmove_t &move, bool instant)
  394. {
  395. // [Paril-KEX] free the beams if we switch animations.
  396. if (self->beam)
  397. {
  398. G_FreeEdict(self->beam);
  399. self->beam = nullptr;
  400. }
  401. if (self->beam2)
  402. {
  403. G_FreeEdict(self->beam2);
  404. self->beam2 = nullptr;
  405. }
  406. // instant switches will cause active_move to change on the next frame
  407. if (instant)
  408. {
  409. self->monsterinfo.active_move = move;
  410. self->monsterinfo.next_move = nullptr;
  411. return;
  412. }
  413. // these wait until the frame is ready to be finished
  414. self->monsterinfo.next_move = move;
  415. }
  416. void M_MoveFrame(edict_t *self)
  417. {
  418. const mmove_t *move = self->monsterinfo.active_move.pointer();
  419. // [Paril-KEX] high tick rate adjustments;
  420. // monsters still only step frames and run thinkfunc's at
  421. // 10hz, but will run aifuncs at full speed with
  422. // distance spread over 10hz
  423. self->nextthink = level.time + FRAME_TIME_S;
  424. // time to run next 10hz move yet?
  425. bool run_frame = self->monsterinfo.next_move_time <= level.time;
  426. // we asked nicely to switch frames when the timer ran up
  427. if (run_frame && self->monsterinfo.next_move.pointer() && self->monsterinfo.active_move != self->monsterinfo.next_move)
  428. {
  429. M_SetAnimation(self, self->monsterinfo.next_move, true);
  430. move = self->monsterinfo.active_move.pointer();
  431. }
  432. if (!move)
  433. return;
  434. // no, but maybe we were explicitly forced into another move (pain,
  435. // death, etc)
  436. if (!run_frame)
  437. run_frame = (self->s.frame < move->firstframe || self->s.frame > move->lastframe);
  438. if (run_frame)
  439. {
  440. // [Paril-KEX] allow next_move and nextframe to work properly after an endfunc
  441. bool explicit_frame = false;
  442. if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) &&
  443. (self->monsterinfo.nextframe <= move->lastframe))
  444. {
  445. self->s.frame = self->monsterinfo.nextframe;
  446. self->monsterinfo.nextframe = 0;
  447. }
  448. else
  449. {
  450. if (self->s.frame == move->lastframe)
  451. {
  452. if (move->endfunc)
  453. {
  454. move->endfunc(self);
  455. if (self->monsterinfo.next_move)
  456. {
  457. M_SetAnimation(self, self->monsterinfo.next_move, true);
  458. if (self->monsterinfo.nextframe)
  459. {
  460. self->s.frame = self->monsterinfo.nextframe;
  461. self->monsterinfo.nextframe = 0;
  462. explicit_frame = true;
  463. }
  464. }
  465. // regrab move, endfunc is very likely to change it
  466. move = self->monsterinfo.active_move.pointer();
  467. // check for death
  468. if (self->svflags & SVF_DEADMONSTER)
  469. return;
  470. }
  471. }
  472. if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
  473. {
  474. self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
  475. self->s.frame = move->firstframe;
  476. }
  477. else if (!explicit_frame)
  478. {
  479. if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
  480. {
  481. self->s.frame++;
  482. if (self->s.frame > move->lastframe)
  483. self->s.frame = move->firstframe;
  484. }
  485. }
  486. }
  487. if (self->monsterinfo.aiflags & AI_HIGH_TICK_RATE)
  488. self->monsterinfo.next_move_time = level.time;
  489. else
  490. self->monsterinfo.next_move_time = level.time + 10_hz;
  491. if ((self->monsterinfo.nextframe) && !((self->monsterinfo.nextframe >= move->firstframe) &&
  492. (self->monsterinfo.nextframe <= move->lastframe)))
  493. self->monsterinfo.nextframe = 0;
  494. }
  495. // NB: frame thinkfunc can be called on the same frame
  496. // as the animation changing
  497. int32_t index = self->s.frame - move->firstframe;
  498. if (move->frame[index].aifunc)
  499. {
  500. if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
  501. {
  502. float dist = move->frame[index].dist * self->monsterinfo.scale;
  503. dist /= gi.tick_rate / 10;
  504. move->frame[index].aifunc(self, dist);
  505. }
  506. else
  507. move->frame[index].aifunc(self, 0);
  508. }
  509. if (run_frame && move->frame[index].thinkfunc)
  510. move->frame[index].thinkfunc(self);
  511. if (move->frame[index].lerp_frame != -1)
  512. {
  513. self->s.renderfx |= RF_OLD_FRAME_LERP;
  514. self->s.old_frame = move->frame[index].lerp_frame;
  515. }
  516. }
  517. void G_MonsterKilled(edict_t *self)
  518. {
  519. level.killed_monsters++;
  520. if (coop->integer && self->enemy && self->enemy->client)
  521. self->enemy->client->resp.score++;
  522. if (g_debug_monster_kills->integer)
  523. {
  524. bool found = false;
  525. for (auto &ent : level.monsters_registered)
  526. {
  527. if (ent == self)
  528. {
  529. ent = nullptr;
  530. found = true;
  531. break;
  532. }
  533. }
  534. if (!found)
  535. {
  536. #if defined(_DEBUG) && defined(KEX_PLATFORM_WINPC)
  537. __debugbreak();
  538. #endif
  539. gi.Center_Print(&g_edicts[1], "found missing monster?");
  540. }
  541. if (level.killed_monsters == level.total_monsters)
  542. {
  543. gi.Center_Print(&g_edicts[1], "all monsters dead");
  544. }
  545. }
  546. }
  547. void M_ProcessPain(edict_t *e)
  548. {
  549. if (!e->monsterinfo.damage_blood)
  550. return;
  551. if (e->health <= 0)
  552. {
  553. // ROGUE
  554. if (e->monsterinfo.aiflags & AI_MEDIC)
  555. {
  556. if (e->enemy && e->enemy->inuse && (e->enemy->svflags & SVF_MONSTER)) // god, I hope so
  557. {
  558. cleanupHealTarget(e->enemy);
  559. }
  560. // clean up self
  561. e->monsterinfo.aiflags &= ~AI_MEDIC;
  562. }
  563. // ROGUE
  564. if (!e->deadflag)
  565. {
  566. e->enemy = e->monsterinfo.damage_attacker;
  567. // ROGUE
  568. // ROGUE - free up slot for spawned monster if it's spawned
  569. if (e->monsterinfo.aiflags & AI_SPAWNED_CARRIER)
  570. {
  571. if (e->monsterinfo.commander && e->monsterinfo.commander->inuse &&
  572. !strcmp(e->monsterinfo.commander->classname, "monster_carrier"))
  573. e->monsterinfo.commander->monsterinfo.monster_slots++;
  574. e->monsterinfo.commander = nullptr;
  575. }
  576. if (e->monsterinfo.aiflags & AI_SPAWNED_WIDOW)
  577. {
  578. // need to check this because we can have variable numbers of coop players
  579. if (e->monsterinfo.commander && e->monsterinfo.commander->inuse &&
  580. !strncmp(e->monsterinfo.commander->classname, "monster_widow", 13))
  581. {
  582. if (e->monsterinfo.commander->monsterinfo.monster_used > 0)
  583. e->monsterinfo.commander->monsterinfo.monster_used--;
  584. e->monsterinfo.commander = nullptr;
  585. }
  586. }
  587. if (!(e->monsterinfo.aiflags & AI_DO_NOT_COUNT) && !(e->spawnflags & SPAWNFLAG_MONSTER_DEAD))
  588. G_MonsterKilled(e);
  589. e->touch = nullptr;
  590. monster_death_use(e);
  591. }
  592. e->die(e, e->monsterinfo.damage_inflictor, e->monsterinfo.damage_attacker, e->monsterinfo.damage_blood, e->monsterinfo.damage_from, e->monsterinfo.damage_mod);
  593. // [Paril-KEX] medic commander only gets his slots back after the monster is gibbed, since we can revive them
  594. if (e->health <= e->gib_health)
  595. {
  596. if (e->monsterinfo.aiflags & AI_SPAWNED_MEDIC_C)
  597. {
  598. if (e->monsterinfo.commander && e->monsterinfo.commander->inuse && !strcmp(e->monsterinfo.commander->classname, "monster_medic_commander"))
  599. e->monsterinfo.commander->monsterinfo.monster_used -= e->monsterinfo.monster_slots;
  600. e->monsterinfo.commander = nullptr;
  601. }
  602. }
  603. if (e->inuse && e->health > e->gib_health && e->s.frame == e->monsterinfo.active_move->lastframe)
  604. {
  605. e->s.frame -= irandom(1, 3);
  606. if (e->groundentity && e->movetype == MOVETYPE_TOSS && !(e->flags & FL_STATIONARY))
  607. e->s.angles[1] += brandom() ? 4.5f : -4.5f;
  608. }
  609. }
  610. else
  611. e->pain(e, e->monsterinfo.damage_attacker, (float) e->monsterinfo.damage_knockback, e->monsterinfo.damage_blood, e->monsterinfo.damage_mod);
  612. if (!e->inuse)
  613. return;
  614. if (e->monsterinfo.setskin)
  615. e->monsterinfo.setskin(e);
  616. e->monsterinfo.damage_blood = 0;
  617. e->monsterinfo.damage_knockback = 0;
  618. e->monsterinfo.damage_attacker = e->monsterinfo.damage_inflictor = nullptr;
  619. // [Paril-KEX] fire health target
  620. if (e->healthtarget)
  621. {
  622. const char *target = e->target;
  623. e->target = e->healthtarget;
  624. G_UseTargets(e, e->enemy);
  625. e->target = target;
  626. }
  627. }
  628. //
  629. // Monster utility functions
  630. //
  631. THINK(monster_dead_think) (edict_t *self) -> void
  632. {
  633. // flies
  634. if ((self->monsterinfo.aiflags & AI_STINKY) && !(self->monsterinfo.aiflags & AI_STUNK))
  635. {
  636. if (!self->fly_sound_debounce_time)
  637. self->fly_sound_debounce_time = level.time + random_time(5_sec, 15_sec);
  638. else if (self->fly_sound_debounce_time < level.time)
  639. {
  640. if (!self->s.sound)
  641. {
  642. self->s.effects |= EF_FLIES;
  643. self->s.sound = gi.soundindex("infantry/inflies1.wav");
  644. self->fly_sound_debounce_time = level.time + 60_sec;
  645. }
  646. else
  647. {
  648. self->s.effects &= ~EF_FLIES;
  649. self->s.sound = 0;
  650. self->monsterinfo.aiflags |= AI_STUNK;
  651. }
  652. }
  653. }
  654. if (!self->monsterinfo.damage_blood)
  655. {
  656. if (self->s.frame != self->monsterinfo.active_move->lastframe)
  657. self->s.frame++;
  658. }
  659. self->nextthink = level.time + 10_hz;
  660. }
  661. void monster_dead(edict_t *self)
  662. {
  663. self->think = monster_dead_think;
  664. self->nextthink = level.time + 10_hz;
  665. self->movetype = MOVETYPE_TOSS;
  666. self->svflags |= SVF_DEADMONSTER;
  667. self->monsterinfo.damage_blood = 0;
  668. self->fly_sound_debounce_time = 0_ms;
  669. self->monsterinfo.aiflags &= ~AI_STUNK;
  670. gi.linkentity(self);
  671. }
  672. /*
  673. =============
  674. infront
  675. returns 1 if the entity is in front (in sight) of self
  676. =============
  677. */
  678. static bool projectile_infront(edict_t *self, edict_t *other)
  679. {
  680. vec3_t vec;
  681. float dot;
  682. vec3_t forward;
  683. AngleVectors(self->s.angles, forward, nullptr, nullptr);
  684. vec = other->s.origin - self->s.origin;
  685. vec.normalize();
  686. dot = vec.dot(forward);
  687. return dot > 0.35f;
  688. }
  689. static BoxEdictsResult_t M_CheckDodge_BoxEdictsFilter(edict_t *ent, void *data)
  690. {
  691. edict_t *self = (edict_t *) data;
  692. // not a valid projectile
  693. if (!(ent->svflags & SVF_PROJECTILE) || !(ent->flags & FL_DODGE))
  694. return BoxEdictsResult_t::Skip;
  695. // not moving
  696. if (ent->velocity.lengthSquared() < 16.f)
  697. return BoxEdictsResult_t::Skip;
  698. // projectile is behind us, we can't see it
  699. if (!projectile_infront(self, ent))
  700. return BoxEdictsResult_t::Skip;
  701. // will it hit us within 1 second? gives us enough time to dodge
  702. trace_t tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin + ent->velocity, ent, ent->clipmask);
  703. if (tr.ent == self)
  704. {
  705. vec3_t v = tr.endpos - ent->s.origin;
  706. gtime_t eta = gtime_t::from_sec(v.length() / ent->velocity.length());
  707. self->monsterinfo.dodge(self, ent->owner, eta, &tr, (ent->movetype == MOVETYPE_BOUNCE || ent->movetype == MOVETYPE_TOSS));
  708. return BoxEdictsResult_t::End;
  709. }
  710. return BoxEdictsResult_t::Skip;
  711. }
  712. // [Paril-KEX] active checking for projectiles to dodge
  713. static void M_CheckDodge(edict_t *self)
  714. {
  715. // we recently made a valid dodge, don't try again for a bit
  716. if (self->monsterinfo.dodge_time > level.time)
  717. return;
  718. gi.BoxEdicts(self->absmin - vec3_t{512, 512, 512}, self->absmax + vec3_t{512, 512, 512}, nullptr, 0, AREA_SOLID, M_CheckDodge_BoxEdictsFilter, self);
  719. }
  720. static bool CheckPathVisibility(const vec3_t &start, const vec3_t &end)
  721. {
  722. trace_t tr = gi.traceline(start, end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP);
  723. bool valid = tr.fraction == 1.0f;
  724. if (!valid)
  725. {
  726. // try raising some of the points
  727. bool can_raise_start = false, can_raise_end = false;
  728. vec3_t raised_start = start + vec3_t{0.f, 0.f, 16.f};
  729. vec3_t raised_end = end + vec3_t{0.f, 0.f, 16.f};
  730. if (gi.traceline(start, raised_start, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP).fraction == 1.0f)
  731. can_raise_start = true;
  732. if (gi.traceline(end, raised_end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP).fraction == 1.0f)
  733. can_raise_end = true;
  734. // try raised start -> end
  735. if (can_raise_start)
  736. {
  737. tr = gi.traceline(raised_start, end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP);
  738. if (tr.fraction == 1.0f)
  739. return true;
  740. }
  741. // try start -> raised end
  742. if (can_raise_end)
  743. {
  744. tr = gi.traceline(start, raised_end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP);
  745. if (tr.fraction == 1.0f)
  746. return true;
  747. }
  748. // try both raised
  749. if (can_raise_start && can_raise_end)
  750. {
  751. tr = gi.traceline(raised_start, raised_end, nullptr, MASK_SOLID | CONTENTS_PROJECTILECLIP | CONTENTS_MONSTERCLIP | CONTENTS_PLAYERCLIP);
  752. if (tr.fraction == 1.0f)
  753. return true;
  754. }
  755. //gi.Draw_Line(start, end, rgba_red, 0.1f, false);
  756. }
  757. return valid;
  758. }
  759. THINK(monster_think) (edict_t *self) -> void
  760. {
  761. // [Paril-KEX] monster sniff testing; if we can make an unobstructed path to the player, murder ourselves.
  762. if (g_debug_monster_kills->integer)
  763. {
  764. if (g_edicts[1].inuse)
  765. {
  766. trace_t enemy_trace = gi.traceline(self->s.origin, g_edicts[1].s.origin, self, MASK_SHOT);
  767. if (enemy_trace.fraction < 1.0f && enemy_trace.ent == &g_edicts[1])
  768. T_Damage(self, &g_edicts[1], &g_edicts[1], { 0, 0, -1 }, self->s.origin, { 0, 0, -1 }, 9999, 9999, DAMAGE_NO_PROTECTION, MOD_BFG_BLAST);
  769. else
  770. {
  771. static vec3_t points[64];
  772. if (self->disintegrator_time <= level.time)
  773. {
  774. PathRequest request;
  775. request.goal = g_edicts[1].s.origin;
  776. request.moveDist = 4.0f;
  777. request.nodeSearch.ignoreNodeFlags = true;
  778. request.nodeSearch.radius = 9999;
  779. request.pathFlags = PathFlags::All;
  780. request.start = self->s.origin;
  781. request.traversals.dropHeight = 9999;
  782. request.traversals.jumpHeight = 9999;
  783. request.pathPoints.array = points;
  784. request.pathPoints.count = q_countof(points);
  785. PathInfo info;
  786. if (gi.GetPathToGoal(request, info))
  787. {
  788. if (info.returnCode != PathReturnCode::NoStartNode &&
  789. info.returnCode != PathReturnCode::NoGoalNode &&
  790. info.returnCode != PathReturnCode::NoPathFound &&
  791. info.returnCode != PathReturnCode::NoNavAvailable &&
  792. info.numPathPoints < q_countof(points))
  793. {
  794. if (CheckPathVisibility(g_edicts[1].s.origin + vec3_t { 0.f, 0.f, g_edicts[1].mins.z }, points[info.numPathPoints - 1]) &&
  795. CheckPathVisibility(self->s.origin + vec3_t { 0.f, 0.f, self->mins.z }, points[0]))
  796. {
  797. size_t i = 0;
  798. for (; i < info.numPathPoints - 1; i++)
  799. if (!CheckPathVisibility(points[i], points[i + 1]))
  800. break;
  801. if (i == info.numPathPoints - 1)
  802. T_Damage(self, &g_edicts[1], &g_edicts[1], { 0, 0, 1 }, self->s.origin, { 0, 0, 1 }, 9999, 9999, DAMAGE_NO_PROTECTION, MOD_BFG_BLAST);
  803. else
  804. self->disintegrator_time = level.time + 500_ms;
  805. }
  806. else
  807. self->disintegrator_time = level.time + 500_ms;
  808. }
  809. else
  810. {
  811. self->disintegrator_time = level.time + 1_sec;
  812. }
  813. }
  814. else
  815. {
  816. self->disintegrator_time = level.time + 1_sec;
  817. }
  818. }
  819. }
  820. if (!self->deadflag && !(self->monsterinfo.aiflags & AI_DO_NOT_COUNT))
  821. gi.Draw_Bounds(self->absmin, self->absmax, rgba_red, gi.frame_time_s, false);
  822. }
  823. }
  824. self->s.renderfx &= ~(RF_STAIR_STEP | RF_OLD_FRAME_LERP);
  825. M_ProcessPain(self);
  826. // pain/die above freed us
  827. if (!self->inuse || self->think != monster_think)
  828. return;
  829. if (self->hackflags & HACKFLAG_ATTACK_PLAYER)
  830. {
  831. if (!self->enemy && g_edicts[1].inuse)
  832. {
  833. self->enemy = &g_edicts[1];
  834. FoundTarget(self);
  835. }
  836. }
  837. if (self->health > 0 && self->monsterinfo.dodge && !(globals.server_flags & SERVER_FLAG_LOADING))
  838. M_CheckDodge(self);
  839. M_MoveFrame(self);
  840. if (self->linkcount != self->monsterinfo.linkcount)
  841. {
  842. self->monsterinfo.linkcount = self->linkcount;
  843. M_CheckGround(self, G_GetClipMask(self));
  844. }
  845. M_CatagorizePosition(self, self->s.origin, self->waterlevel, self->watertype);
  846. M_WorldEffects(self);
  847. M_SetEffects(self);
  848. }
  849. /*
  850. ================
  851. monster_use
  852. Using a monster makes it angry at the current activator
  853. ================
  854. */
  855. USE(monster_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  856. {
  857. if (self->enemy)
  858. return;
  859. if (self->health <= 0)
  860. return;
  861. if (!activator)
  862. return;
  863. if (activator->flags & FL_NOTARGET)
  864. return;
  865. if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
  866. return;
  867. if (activator->flags & FL_DISGUISED) // PGM
  868. return; // PGM
  869. // delay reaction so if the monster is teleported, its sound is still heard
  870. self->enemy = activator;
  871. FoundTarget(self);
  872. }
  873. void monster_start_go(edict_t *self);
  874. THINK(monster_triggered_spawn) (edict_t *self) -> void
  875. {
  876. self->s.origin[2] += 1;
  877. self->solid = SOLID_BBOX;
  878. self->movetype = MOVETYPE_STEP;
  879. self->svflags &= ~SVF_NOCLIENT;
  880. self->air_finished = level.time + 12_sec;
  881. gi.linkentity(self);
  882. KillBox(self, false);
  883. monster_start_go(self);
  884. // RAFAEL
  885. if (strcmp(self->classname, "monster_fixbot") == 0)
  886. {
  887. if (self->spawnflags.has(SPAWNFLAG_FIXBOT_LANDING | SPAWNFLAG_FIXBOT_TAKEOFF | SPAWNFLAG_FIXBOT_FIXIT))
  888. {
  889. self->enemy = nullptr;
  890. return;
  891. }
  892. }
  893. // RAFAEL
  894. if (self->enemy && !(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH) && !(self->enemy->flags & FL_NOTARGET) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
  895. {
  896. // ROGUE
  897. if (!(self->enemy->flags & FL_DISGUISED))
  898. // ROGUE
  899. FoundTarget(self);
  900. // ROGUE
  901. else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused
  902. self->enemy = nullptr;
  903. // ROGUE
  904. }
  905. else
  906. {
  907. self->enemy = nullptr;
  908. }
  909. }
  910. USE(monster_triggered_spawn_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  911. {
  912. // we have a one frame delay here so we don't telefrag the guy who activated us
  913. self->think = monster_triggered_spawn;
  914. self->nextthink = level.time + FRAME_TIME_S;
  915. if (activator && activator->client && !(self->hackflags & HACKFLAG_END_CUTSCENE))
  916. self->enemy = activator;
  917. self->use = monster_use;
  918. if (self->spawnflags.has(SPAWNFLAG_MONSTER_SCENIC))
  919. {
  920. M_droptofloor(self);
  921. self->nextthink = 0_ms;
  922. self->think(self);
  923. if (self->spawnflags.has(SPAWNFLAG_MONSTER_AMBUSH))
  924. monster_use(self, other, activator);
  925. for (int i = 0; i < 30; i++)
  926. {
  927. self->think(self);
  928. self->monsterinfo.next_move_time = 0_ms;
  929. }
  930. }
  931. }
  932. THINK(monster_triggered_think) (edict_t *self) -> void
  933. {
  934. if (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT))
  935. gi.Draw_Bounds(self->absmin, self->absmax, rgba_blue, gi.frame_time_s, false);
  936. self->nextthink = level.time + 1_ms;
  937. }
  938. void monster_triggered_start(edict_t *self)
  939. {
  940. self->solid = SOLID_NOT;
  941. self->movetype = MOVETYPE_NONE;
  942. self->svflags |= SVF_NOCLIENT;
  943. self->nextthink = 0_ms;
  944. self->use = monster_triggered_spawn_use;
  945. if (g_debug_monster_kills->integer)
  946. {
  947. self->think = monster_triggered_think;
  948. self->nextthink = level.time + 1_ms;
  949. }
  950. if (!self->targetname ||
  951. (G_FindByString<&edict_t::target>(nullptr, self->targetname) == nullptr &&
  952. G_FindByString<&edict_t::pathtarget>(nullptr, self->targetname) == nullptr &&
  953. G_FindByString<&edict_t::deathtarget>(nullptr, self->targetname) == nullptr &&
  954. G_FindByString<&edict_t::itemtarget>(nullptr, self->targetname) == nullptr &&
  955. G_FindByString<&edict_t::healthtarget>(nullptr, self->targetname) == nullptr &&
  956. G_FindByString<&edict_t::combattarget>(nullptr, self->targetname) == nullptr))
  957. {
  958. gi.Com_PrintFmt("{}: is trigger spawned, but has no targetname or no entity to spawn it\n", *self);
  959. }
  960. }
  961. /*
  962. ================
  963. monster_death_use
  964. When a monster dies, it fires all of its targets with the current
  965. enemy as activator.
  966. ================
  967. */
  968. void monster_death_use(edict_t *self)
  969. {
  970. self->flags &= ~(FL_FLY | FL_SWIM);
  971. self->monsterinfo.aiflags &= (AI_DOUBLE_TROUBLE | AI_GOOD_GUY | AI_STINKY | AI_SPAWNED_MASK);
  972. if (self->item)
  973. {
  974. edict_t *dropped = Drop_Item(self, self->item);
  975. if (self->itemtarget)
  976. {
  977. dropped->target = self->itemtarget;
  978. self->itemtarget = nullptr;
  979. }
  980. self->item = nullptr;
  981. }
  982. if (self->deathtarget)
  983. self->target = self->deathtarget;
  984. if (self->target)
  985. G_UseTargets(self, self->enemy);
  986. // [Paril-KEX] fire health target
  987. if (self->healthtarget)
  988. {
  989. self->target = self->healthtarget;
  990. G_UseTargets(self, self->enemy);
  991. }
  992. }
  993. // [Paril-KEX] adjust the monster's health from how
  994. // many active players we have
  995. void G_Monster_ScaleCoopHealth(edict_t *self)
  996. {
  997. // already scaled
  998. if (self->monsterinfo.health_scaling >= level.coop_scale_players)
  999. return;
  1000. // this is just to fix monsters that change health after spawning...
  1001. // looking at you, soldiers
  1002. if (!self->monsterinfo.base_health)
  1003. self->monsterinfo.base_health = self->max_health;
  1004. int32_t delta = level.coop_scale_players - self->monsterinfo.health_scaling;
  1005. int32_t additional_health = delta * (int32_t) (self->monsterinfo.base_health * level.coop_health_scaling);
  1006. self->health = max(1, self->health + additional_health);
  1007. self->max_health += additional_health;
  1008. self->monsterinfo.health_scaling = level.coop_scale_players;
  1009. }
  1010. struct monster_filter_t
  1011. {
  1012. inline bool operator()(edict_t *self) const
  1013. {
  1014. return self->inuse && (self->flags & FL_COOP_HEALTH_SCALE) && self->health > 0;
  1015. }
  1016. };
  1017. // check all active monsters' scaling
  1018. void G_Monster_CheckCoopHealthScaling()
  1019. {
  1020. for (auto monster : entity_iterable_t<monster_filter_t>())
  1021. G_Monster_ScaleCoopHealth(monster);
  1022. }
  1023. //============================================================================
  1024. constexpr spawnflags_t SPAWNFLAG_MONSTER_FUBAR = 4_spawnflag;
  1025. bool monster_start(edict_t *self)
  1026. {
  1027. if ( !M_AllowSpawn( self ) ) {
  1028. G_FreeEdict( self );
  1029. return false;
  1030. }
  1031. if (self->spawnflags.has(SPAWNFLAG_MONSTER_SCENIC))
  1032. self->monsterinfo.aiflags |= AI_GOOD_GUY;
  1033. // [Paril-KEX] n64
  1034. if (self->hackflags & (HACKFLAG_END_CUTSCENE | HACKFLAG_ATTACK_PLAYER))
  1035. self->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
  1036. if (self->spawnflags.has(SPAWNFLAG_MONSTER_FUBAR) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
  1037. {
  1038. self->spawnflags &= ~SPAWNFLAG_MONSTER_FUBAR;
  1039. self->spawnflags |= SPAWNFLAG_MONSTER_AMBUSH;
  1040. }
  1041. // [Paril-KEX] simplify other checks
  1042. if (self->monsterinfo.aiflags & AI_GOOD_GUY)
  1043. self->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
  1044. // ROGUE
  1045. if (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT) && !self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD))
  1046. {
  1047. if (g_debug_monster_kills->integer)
  1048. level.monsters_registered[level.total_monsters] = self;
  1049. // ROGUE
  1050. level.total_monsters++;
  1051. }
  1052. self->nextthink = level.time + FRAME_TIME_S;
  1053. self->svflags |= SVF_MONSTER;
  1054. self->takedamage = true;
  1055. self->air_finished = level.time + 12_sec;
  1056. self->use = monster_use;
  1057. self->max_health = self->health;
  1058. self->clipmask = MASK_MONSTERSOLID;
  1059. self->deadflag = false;
  1060. self->svflags &= ~SVF_DEADMONSTER;
  1061. self->flags &= ~FL_ALIVE_KNOCKBACK_ONLY;
  1062. self->flags |= FL_COOP_HEALTH_SCALE;
  1063. self->s.old_origin = self->s.origin;
  1064. self->monsterinfo.initial_power_armor_type = self->monsterinfo.power_armor_type;
  1065. self->monsterinfo.max_power_armor_power = self->monsterinfo.power_armor_power;
  1066. if (!self->monsterinfo.checkattack)
  1067. self->monsterinfo.checkattack = M_CheckAttack;
  1068. if ( ai_model_scale->value > 0 ) {
  1069. self->s.scale = ai_model_scale->value;
  1070. }
  1071. if (self->s.scale)
  1072. {
  1073. self->monsterinfo.scale *= self->s.scale;
  1074. self->mins *= self->s.scale;
  1075. self->maxs *= self->s.scale;
  1076. self->mass *= self->s.scale;
  1077. }
  1078. // set combat style if unset
  1079. if (self->monsterinfo.combat_style == COMBAT_UNKNOWN)
  1080. {
  1081. if (!self->monsterinfo.attack && self->monsterinfo.melee)
  1082. self->monsterinfo.combat_style = COMBAT_MELEE;
  1083. else
  1084. self->monsterinfo.combat_style = COMBAT_MIXED;
  1085. }
  1086. if (st.item)
  1087. {
  1088. self->item = FindItemByClassname(st.item);
  1089. if (!self->item)
  1090. gi.Com_PrintFmt("{}: bad item: {}\n", *self, st.item);
  1091. }
  1092. // randomize what frame they start on
  1093. if (self->monsterinfo.active_move)
  1094. self->s.frame =
  1095. irandom(self->monsterinfo.active_move->firstframe, self->monsterinfo.active_move->lastframe + 1);
  1096. // PMM - get this so I don't have to do it in all of the monsters
  1097. self->monsterinfo.base_height = self->maxs[2];
  1098. // Paril: monsters' old default viewheight (25)
  1099. // is all messed up for certain monsters. Calculate
  1100. // from maxs to make a bit more sense.
  1101. if (!self->viewheight)
  1102. self->viewheight = (int) (self->maxs[2] - 8.f);
  1103. // PMM - clear these
  1104. self->monsterinfo.quad_time = 0_ms;
  1105. self->monsterinfo.double_time = 0_ms;
  1106. self->monsterinfo.invincible_time = 0_ms;
  1107. // set base health & set base scaling to 1 player
  1108. self->monsterinfo.base_health = self->health;
  1109. self->monsterinfo.health_scaling = 1;
  1110. // [Paril-KEX] co-op health scale
  1111. G_Monster_ScaleCoopHealth(self);
  1112. return true;
  1113. }
  1114. stuck_result_t G_FixStuckObject(edict_t *self, vec3_t check)
  1115. {
  1116. contents_t mask = G_GetClipMask(self);
  1117. stuck_result_t result = G_FixStuckObject_Generic(check, self->mins, self->maxs, [self, mask] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) {
  1118. return gi.trace(start, mins, maxs, end, self, mask);
  1119. });
  1120. if (result == stuck_result_t::NO_GOOD_POSITION)
  1121. return result;
  1122. self->s.origin = check;
  1123. if (result == stuck_result_t::FIXED)
  1124. gi.Com_PrintFmt("fixed stuck {}\n", *self);
  1125. return result;
  1126. }
  1127. void monster_start_go(edict_t *self)
  1128. {
  1129. // Paril: moved here so this applies to swim/fly monsters too
  1130. if (!(self->flags & FL_STATIONARY))
  1131. {
  1132. const vec3_t check = self->s.origin;
  1133. // [Paril-KEX] different nudge method; see if any of the bbox sides are clear,
  1134. // if so we can see how much headroom we have in that direction and shift us.
  1135. // most of the monsters stuck in solids will only be stuck on one side, which
  1136. // conveniently leaves only one side not in a solid; this won't fix monsters
  1137. // stuck in a corner though.
  1138. bool is_stuck = false;
  1139. if ((self->monsterinfo.aiflags & AI_GOOD_GUY) || (self->flags & (FL_FLY | FL_SWIM)))
  1140. is_stuck = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID).startsolid;
  1141. else
  1142. is_stuck = !M_droptofloor(self) || !M_walkmove(self, 0, 0);
  1143. if (is_stuck)
  1144. {
  1145. if (G_FixStuckObject(self, check) != stuck_result_t::NO_GOOD_POSITION)
  1146. {
  1147. if (self->monsterinfo.aiflags & AI_GOOD_GUY)
  1148. is_stuck = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID).startsolid;
  1149. else if (!(self->flags & (FL_FLY | FL_SWIM)))
  1150. M_droptofloor(self);
  1151. is_stuck = false;
  1152. }
  1153. }
  1154. // last ditch effort: brute force
  1155. if (is_stuck)
  1156. {
  1157. // Paril: try nudging them out. this fixes monsters stuck
  1158. // in very shallow slopes.
  1159. constexpr const int32_t adjust[] = { 0, -1, 1, -2, 2, -4, 4, -8, 8 };
  1160. bool walked = false;
  1161. for (int32_t y = 0; !walked && y < 3; y++)
  1162. for (int32_t x = 0; !walked && x < 3; x++)
  1163. for (int32_t z = 0; !walked && z < 3; z++)
  1164. {
  1165. self->s.origin[0] = check[0] + adjust[x];
  1166. self->s.origin[1] = check[1] + adjust[y];
  1167. self->s.origin[2] = check[2] + adjust[z];
  1168. if (self->monsterinfo.aiflags & AI_GOOD_GUY)
  1169. {
  1170. is_stuck = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID).startsolid;
  1171. if (!is_stuck)
  1172. walked = true;
  1173. }
  1174. else if (!(self->flags & (FL_FLY | FL_SWIM)))
  1175. {
  1176. M_droptofloor(self);
  1177. walked = M_walkmove(self, 0, 0);
  1178. }
  1179. }
  1180. }
  1181. if (is_stuck)
  1182. gi.Com_PrintFmt("WARNING: {} stuck in solid\n", *self);
  1183. }
  1184. vec3_t v;
  1185. if (self->health <= 0)
  1186. return;
  1187. self->s.old_origin = self->s.origin;
  1188. // check for target to combat_point and change to combattarget
  1189. if (self->target)
  1190. {
  1191. bool notcombat;
  1192. bool fixup;
  1193. edict_t *target;
  1194. target = nullptr;
  1195. notcombat = false;
  1196. fixup = false;
  1197. while ((target = G_FindByString<&edict_t::targetname>(target, self->target)) != nullptr)
  1198. {
  1199. if (strcmp(target->classname, "point_combat") == 0)
  1200. {
  1201. self->combattarget = self->target;
  1202. fixup = true;
  1203. }
  1204. else
  1205. {
  1206. notcombat = true;
  1207. }
  1208. }
  1209. if (notcombat && self->combattarget)
  1210. gi.Com_PrintFmt("{}: has target with mixed types\n", *self);
  1211. if (fixup)
  1212. self->target = nullptr;
  1213. }
  1214. // validate combattarget
  1215. if (self->combattarget)
  1216. {
  1217. edict_t *target;
  1218. target = nullptr;
  1219. while ((target = G_FindByString<&edict_t::targetname>(target, self->combattarget)) != nullptr)
  1220. {
  1221. if (strcmp(target->classname, "point_combat") != 0)
  1222. {
  1223. gi.Com_PrintFmt("{} has a bad combattarget {} ({})\n", *self, self->combattarget, *target);
  1224. }
  1225. }
  1226. }
  1227. // allow spawning dead
  1228. bool spawn_dead = self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD);
  1229. if (self->target)
  1230. {
  1231. self->goalentity = self->movetarget = G_PickTarget(self->target);
  1232. if (!self->movetarget)
  1233. {
  1234. gi.Com_PrintFmt("{}: can't find target {}\n", *self, self->target);
  1235. self->target = nullptr;
  1236. self->monsterinfo.pausetime = HOLD_FOREVER;
  1237. if (!spawn_dead)
  1238. self->monsterinfo.stand(self);
  1239. }
  1240. else if (strcmp(self->movetarget->classname, "path_corner") == 0)
  1241. {
  1242. v = self->goalentity->s.origin - self->s.origin;
  1243. self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
  1244. if (!spawn_dead)
  1245. self->monsterinfo.walk(self);
  1246. self->target = nullptr;
  1247. }
  1248. else
  1249. {
  1250. self->goalentity = self->movetarget = nullptr;
  1251. self->monsterinfo.pausetime = HOLD_FOREVER;
  1252. if (!spawn_dead)
  1253. self->monsterinfo.stand(self);
  1254. }
  1255. }
  1256. else
  1257. {
  1258. self->monsterinfo.pausetime = HOLD_FOREVER;
  1259. if (!spawn_dead)
  1260. self->monsterinfo.stand(self);
  1261. }
  1262. if (spawn_dead)
  1263. {
  1264. // to spawn dead, we'll mimick them dying naturally
  1265. self->health = 0;
  1266. vec3_t f = self->s.origin;
  1267. if (self->die)
  1268. self->die(self, self, self, 0, vec3_origin, MOD_SUICIDE);
  1269. if (!self->inuse)
  1270. return;
  1271. if (self->monsterinfo.setskin)
  1272. self->monsterinfo.setskin(self);
  1273. self->monsterinfo.aiflags |= AI_SPAWNED_DEAD;
  1274. auto move = self->monsterinfo.active_move.pointer();
  1275. for (size_t i = move->firstframe; i < move->lastframe; i++)
  1276. {
  1277. self->s.frame = i;
  1278. if (move->frame[i - move->firstframe].thinkfunc)
  1279. move->frame[i - move->firstframe].thinkfunc(self);
  1280. if (!self->inuse)
  1281. return;
  1282. }
  1283. if (move->endfunc)
  1284. move->endfunc(self);
  1285. if (!self->inuse)
  1286. return;
  1287. if (self->monsterinfo.start_frame)
  1288. self->s.frame = self->monsterinfo.start_frame;
  1289. else
  1290. self->s.frame = move->lastframe;
  1291. self->s.origin = f;
  1292. gi.linkentity(self);
  1293. self->monsterinfo.aiflags &= ~AI_SPAWNED_DEAD;
  1294. }
  1295. else
  1296. {
  1297. self->think = monster_think;
  1298. self->nextthink = level.time + FRAME_TIME_S;
  1299. self->monsterinfo.aiflags |= AI_SPAWNED_ALIVE;
  1300. }
  1301. }
  1302. THINK(walkmonster_start_go) (edict_t *self) -> void
  1303. {
  1304. if (!self->yaw_speed)
  1305. self->yaw_speed = 20;
  1306. if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN))
  1307. monster_triggered_start(self);
  1308. else
  1309. monster_start_go(self);
  1310. }
  1311. void walkmonster_start(edict_t *self)
  1312. {
  1313. self->think = walkmonster_start_go;
  1314. monster_start(self);
  1315. }
  1316. THINK(flymonster_start_go) (edict_t *self) -> void
  1317. {
  1318. if (!self->yaw_speed)
  1319. self->yaw_speed = 30;
  1320. if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN))
  1321. monster_triggered_start(self);
  1322. else
  1323. monster_start_go(self);
  1324. }
  1325. void flymonster_start(edict_t *self)
  1326. {
  1327. self->flags |= FL_FLY;
  1328. self->think = flymonster_start_go;
  1329. monster_start(self);
  1330. }
  1331. THINK(swimmonster_start_go) (edict_t *self) -> void
  1332. {
  1333. if (!self->yaw_speed)
  1334. self->yaw_speed = 30;
  1335. if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN))
  1336. monster_triggered_start(self);
  1337. else
  1338. monster_start_go(self);
  1339. }
  1340. void swimmonster_start(edict_t *self)
  1341. {
  1342. self->flags |= FL_SWIM;
  1343. self->think = swimmonster_start_go;
  1344. monster_start(self);
  1345. }
  1346. USE(trigger_health_relay_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1347. {
  1348. float percent_health = clamp((float) (other->health) / (float) (other->max_health), 0.f, 1.f);
  1349. // not ready to trigger yet
  1350. if (percent_health > self->speed)
  1351. return;
  1352. // fire!
  1353. G_UseTargets(self, activator);
  1354. // kill self
  1355. G_FreeEdict(self);
  1356. }
  1357. /*QUAKED trigger_health_relay (1.0 1.0 0.0) (-8 -8 -8) (8 8 8)
  1358. Special type of relay that fires when a linked object is reduced
  1359. beyond a certain amount of health.
  1360. It will only fire once, and free itself afterwards.
  1361. */
  1362. void SP_trigger_health_relay(edict_t *self)
  1363. {
  1364. if (!self->targetname)
  1365. {
  1366. gi.Com_PrintFmt("{} missing targetname\n", *self);
  1367. G_FreeEdict(self);
  1368. return;
  1369. }
  1370. if (self->speed < 0 || self->speed > 100)
  1371. {
  1372. gi.Com_PrintFmt("{} has bad \"speed\" (health percentage); must be between 0 and 100, inclusive\n", *self);
  1373. G_FreeEdict(self);
  1374. return;
  1375. }
  1376. self->svflags |= SVF_NOCLIENT;
  1377. self->use = trigger_health_relay_use;
  1378. }