g_trigger.cpp 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "g_local.h"
  4. // PGM - some of these are mine, some id's. I added the define's.
  5. constexpr spawnflags_t SPAWNFLAG_TRIGGER_MONSTER = 0x01_spawnflag;
  6. constexpr spawnflags_t SPAWNFLAG_TRIGGER_NOT_PLAYER = 0x02_spawnflag;
  7. constexpr spawnflags_t SPAWNFLAG_TRIGGER_TRIGGERED = 0x04_spawnflag;
  8. constexpr spawnflags_t SPAWNFLAG_TRIGGER_TOGGLE = 0x08_spawnflag;
  9. constexpr spawnflags_t SPAWNFLAG_TRIGGER_LATCHED = 0x10_spawnflag;
  10. constexpr spawnflags_t SPAWNFLAG_TRIGGER_CLIP = 0x20_spawnflag;
  11. // PGM
  12. void InitTrigger(edict_t *self)
  13. {
  14. if (st.was_key_specified("angle") || st.was_key_specified("angles") || self->s.angles)
  15. G_SetMovedir(self->s.angles, self->movedir);
  16. self->solid = SOLID_TRIGGER;
  17. self->movetype = MOVETYPE_NONE;
  18. // [Paril-KEX] adjusted to allow mins/maxs to be defined
  19. // by hand instead
  20. if (self->model)
  21. gi.setmodel(self, self->model);
  22. self->svflags = SVF_NOCLIENT;
  23. }
  24. // the wait time has passed, so set back up for another activation
  25. THINK(multi_wait) (edict_t *ent) -> void
  26. {
  27. ent->nextthink = 0_ms;
  28. }
  29. // the trigger was just activated
  30. // ent->activator should be set to the activator so it can be held through a delay
  31. // so wait for the delay time before firing
  32. void multi_trigger(edict_t *ent)
  33. {
  34. if (ent->nextthink)
  35. return; // already been triggered
  36. G_UseTargets(ent, ent->activator);
  37. if (ent->wait > 0)
  38. {
  39. ent->think = multi_wait;
  40. ent->nextthink = level.time + gtime_t::from_sec(ent->wait);
  41. }
  42. else
  43. { // we can't just remove (self) here, because this is a touch function
  44. // called while looping through area links...
  45. ent->touch = nullptr;
  46. ent->nextthink = level.time + FRAME_TIME_S;
  47. ent->think = G_FreeEdict;
  48. }
  49. }
  50. USE(Use_Multi) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  51. {
  52. // PGM
  53. if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_TOGGLE))
  54. {
  55. if (ent->solid == SOLID_TRIGGER)
  56. ent->solid = SOLID_NOT;
  57. else
  58. ent->solid = SOLID_TRIGGER;
  59. gi.linkentity(ent);
  60. }
  61. else
  62. {
  63. ent->activator = activator;
  64. multi_trigger(ent);
  65. }
  66. // PGM
  67. }
  68. TOUCH(Touch_Multi) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  69. {
  70. if (other->client)
  71. {
  72. if (self->spawnflags.has(SPAWNFLAG_TRIGGER_NOT_PLAYER))
  73. return;
  74. }
  75. else if (other->svflags & SVF_MONSTER)
  76. {
  77. if (!self->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER))
  78. return;
  79. }
  80. else
  81. return;
  82. if (self->spawnflags.has(SPAWNFLAG_TRIGGER_CLIP))
  83. {
  84. trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
  85. if (clip.fraction == 1.0f)
  86. return;
  87. }
  88. if (self->movedir)
  89. {
  90. vec3_t forward;
  91. AngleVectors(other->s.angles, forward, nullptr, nullptr);
  92. if (forward.dot(self->movedir) < 0)
  93. return;
  94. }
  95. self->activator = other;
  96. multi_trigger(self);
  97. }
  98. /*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE LATCHED
  99. Variable sized repeatable trigger. Must be targeted at one or more entities.
  100. If "delay" is set, the trigger waits some time after activating before firing.
  101. "wait" : Seconds between triggerings. (.2 default)
  102. TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive.
  103. sounds
  104. 1) secret
  105. 2) beep beep
  106. 3) large switch
  107. 4)
  108. set "message" to text string
  109. */
  110. USE(trigger_enable) (edict_t *self, edict_t *other, edict_t *activator) -> void
  111. {
  112. self->solid = SOLID_TRIGGER;
  113. self->use = Use_Multi;
  114. gi.linkentity(self);
  115. }
  116. static BoxEdictsResult_t latched_trigger_filter(edict_t *other, void *data)
  117. {
  118. edict_t *self = (edict_t *) data;
  119. if (other->client)
  120. {
  121. if (self->spawnflags.has(SPAWNFLAG_TRIGGER_NOT_PLAYER))
  122. return BoxEdictsResult_t::Skip;
  123. }
  124. else if (other->svflags & SVF_MONSTER)
  125. {
  126. if (!self->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER))
  127. return BoxEdictsResult_t::Skip;
  128. }
  129. else
  130. return BoxEdictsResult_t::Skip;
  131. if (self->movedir)
  132. {
  133. vec3_t forward;
  134. AngleVectors(other->s.angles, forward, nullptr, nullptr);
  135. if (forward.dot(self->movedir) < 0)
  136. return BoxEdictsResult_t::Skip;
  137. }
  138. self->activator = other;
  139. return BoxEdictsResult_t::Keep | BoxEdictsResult_t::End;
  140. }
  141. THINK(latched_trigger_think) (edict_t *self) -> void
  142. {
  143. self->nextthink = level.time + 1_ms;
  144. bool any_inside = !!gi.BoxEdicts(self->absmin, self->absmax, nullptr, 0, AREA_SOLID, latched_trigger_filter, self);
  145. if (!!self->count != any_inside)
  146. {
  147. G_UseTargets(self, self->activator);
  148. self->count = any_inside ? 1 : 0;
  149. }
  150. }
  151. void SP_trigger_multiple(edict_t *ent)
  152. {
  153. if (ent->sounds == 1)
  154. ent->noise_index = gi.soundindex("misc/secret.wav");
  155. else if (ent->sounds == 2)
  156. ent->noise_index = gi.soundindex("misc/talk.wav");
  157. else if (ent->sounds == 3)
  158. ent->noise_index = gi.soundindex("misc/trigger1.wav");
  159. if (!ent->wait)
  160. ent->wait = 0.2f;
  161. InitTrigger(ent);
  162. if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_LATCHED))
  163. {
  164. if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_TRIGGERED | SPAWNFLAG_TRIGGER_TOGGLE))
  165. gi.Com_PrintFmt("{}: latched and triggered/toggle are not supported\n", *ent);
  166. ent->think = latched_trigger_think;
  167. ent->nextthink = level.time + 1_ms;
  168. ent->use = Use_Multi;
  169. return;
  170. }
  171. else
  172. ent->touch = Touch_Multi;
  173. // PGM
  174. if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_TRIGGERED | SPAWNFLAG_TRIGGER_TOGGLE))
  175. // PGM
  176. {
  177. ent->solid = SOLID_NOT;
  178. ent->use = trigger_enable;
  179. }
  180. else
  181. {
  182. ent->solid = SOLID_TRIGGER;
  183. ent->use = Use_Multi;
  184. }
  185. gi.linkentity(ent);
  186. if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_CLIP))
  187. ent->svflags |= SVF_HULL;
  188. }
  189. /*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
  190. Triggers once, then removes itself.
  191. You must set the key "target" to the name of another object in the level that has a matching "targetname".
  192. If TRIGGERED, this trigger must be triggered before it is live.
  193. sounds
  194. 1) secret
  195. 2) beep beep
  196. 3) large switch
  197. 4)
  198. "message" string to be displayed when triggered
  199. */
  200. void SP_trigger_once(edict_t *ent)
  201. {
  202. // make old maps work because I messed up on flag assignments here
  203. // triggered was on bit 1 when it should have been on bit 4
  204. if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER))
  205. {
  206. ent->spawnflags &= ~SPAWNFLAG_TRIGGER_MONSTER;
  207. ent->spawnflags |= SPAWNFLAG_TRIGGER_TRIGGERED;
  208. gi.Com_PrintFmt("{}: fixed TRIGGERED flag\n", *ent);
  209. }
  210. ent->wait = -1;
  211. SP_trigger_multiple(ent);
  212. }
  213. /*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
  214. This fixed size trigger cannot be touched, it can only be fired by other events.
  215. */
  216. constexpr spawnflags_t SPAWNFLAGS_TRIGGER_RELAY_NO_SOUND = 1_spawnflag;
  217. USE(trigger_relay_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  218. {
  219. if (self->crosslevel_flags && !(self->crosslevel_flags == (game.cross_level_flags & SFL_CROSS_TRIGGER_MASK & self->crosslevel_flags)))
  220. return;
  221. G_UseTargets(self, activator);
  222. }
  223. void SP_trigger_relay(edict_t *self)
  224. {
  225. self->use = trigger_relay_use;
  226. if (self->spawnflags.has(SPAWNFLAGS_TRIGGER_RELAY_NO_SOUND))
  227. self->noise_index = -1;
  228. }
  229. /*
  230. ==============================================================================
  231. trigger_key
  232. ==============================================================================
  233. */
  234. /*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
  235. A relay trigger that only fires it's targets if player has the proper key.
  236. Use "item" to specify the required key, for example "key_data_cd"
  237. */
  238. USE(trigger_key_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  239. {
  240. item_id_t index;
  241. if (!self->item)
  242. return;
  243. if (!activator->client)
  244. return;
  245. index = self->item->id;
  246. if (!activator->client->pers.inventory[index])
  247. {
  248. if (level.time < self->touch_debounce_time)
  249. return;
  250. self->touch_debounce_time = level.time + 5_sec;
  251. gi.LocCenter_Print(activator, "$g_you_need", self->item->pickup_name_definite);
  252. gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0);
  253. return;
  254. }
  255. gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keyuse.wav"), 1, ATTN_NORM, 0);
  256. if (coop->integer)
  257. {
  258. edict_t *ent;
  259. if (self->item->id == IT_KEY_POWER_CUBE || self->item->id == IT_KEY_EXPLOSIVE_CHARGES)
  260. {
  261. int cube;
  262. for (cube = 0; cube < 8; cube++)
  263. if (activator->client->pers.power_cubes & (1 << cube))
  264. break;
  265. for (uint32_t player = 1; player <= game.maxclients; player++)
  266. {
  267. ent = &g_edicts[player];
  268. if (!ent->inuse)
  269. continue;
  270. if (!ent->client)
  271. continue;
  272. if (ent->client->pers.power_cubes & (1 << cube))
  273. {
  274. ent->client->pers.inventory[index]--;
  275. ent->client->pers.power_cubes &= ~(1 << cube);
  276. // [Paril-KEX] don't allow respawning players to keep
  277. // used keys
  278. if (!P_UseCoopInstancedItems())
  279. {
  280. ent->client->resp.coop_respawn.inventory[index] = 0;
  281. ent->client->resp.coop_respawn.power_cubes &= ~(1 << cube);
  282. }
  283. }
  284. }
  285. }
  286. else
  287. {
  288. for (uint32_t player = 1; player <= game.maxclients; player++)
  289. {
  290. ent = &g_edicts[player];
  291. if (!ent->inuse)
  292. continue;
  293. if (!ent->client)
  294. continue;
  295. ent->client->pers.inventory[index] = 0;
  296. // [Paril-KEX] don't allow respawning players to keep
  297. // used keys
  298. if (!P_UseCoopInstancedItems())
  299. ent->client->resp.coop_respawn.inventory[index] = 0;
  300. }
  301. }
  302. }
  303. else
  304. {
  305. activator->client->pers.inventory[index]--;
  306. }
  307. G_UseTargets(self, activator);
  308. self->use = nullptr;
  309. }
  310. void SP_trigger_key(edict_t *self)
  311. {
  312. if (!st.item)
  313. {
  314. gi.Com_PrintFmt("{}: no key item\n", *self);
  315. return;
  316. }
  317. self->item = FindItemByClassname(st.item);
  318. if (!self->item)
  319. {
  320. gi.Com_PrintFmt("{}: item {} not found\n", *self, st.item);
  321. return;
  322. }
  323. if (!self->target)
  324. {
  325. gi.Com_PrintFmt("{}: no target\n", *self);
  326. return;
  327. }
  328. gi.soundindex("misc/keytry.wav");
  329. gi.soundindex("misc/keyuse.wav");
  330. self->use = trigger_key_use;
  331. }
  332. /*
  333. ==============================================================================
  334. trigger_counter
  335. ==============================================================================
  336. */
  337. /*QUAKED trigger_counter (.5 .5 .5) ? nomessage
  338. Acts as an intermediary for an action that takes multiple inputs.
  339. If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
  340. After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
  341. */
  342. constexpr spawnflags_t SPAWNFLAG_COUNTER_NOMESSAGE = 1_spawnflag;
  343. USE(trigger_counter_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  344. {
  345. if (self->count == 0)
  346. return;
  347. self->count--;
  348. if (self->count)
  349. {
  350. if (!(self->spawnflags & SPAWNFLAG_COUNTER_NOMESSAGE))
  351. {
  352. gi.LocCenter_Print(activator, "$g_more_to_go", self->count);
  353. gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
  354. }
  355. return;
  356. }
  357. if (!(self->spawnflags & SPAWNFLAG_COUNTER_NOMESSAGE))
  358. {
  359. gi.LocCenter_Print(activator, "$g_sequence_completed");
  360. gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
  361. }
  362. self->activator = activator;
  363. multi_trigger(self);
  364. }
  365. void SP_trigger_counter(edict_t *self)
  366. {
  367. self->wait = -1;
  368. if (!self->count)
  369. self->count = 2;
  370. self->use = trigger_counter_use;
  371. }
  372. /*
  373. ==============================================================================
  374. trigger_always
  375. ==============================================================================
  376. */
  377. /*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
  378. This trigger will always fire. It is activated by the world.
  379. */
  380. void SP_trigger_always(edict_t *ent)
  381. {
  382. // we must have some delay to make sure our use targets are present
  383. if (!ent->delay)
  384. ent->delay = 0.2f;
  385. G_UseTargets(ent, ent);
  386. }
  387. /*
  388. ==============================================================================
  389. trigger_push
  390. ==============================================================================
  391. */
  392. // PGM
  393. constexpr spawnflags_t SPAWNFLAG_PUSH_ONCE = 0x01_spawnflag;
  394. constexpr spawnflags_t SPAWNFLAG_PUSH_PLUS = 0x02_spawnflag;
  395. constexpr spawnflags_t SPAWNFLAG_PUSH_SILENT = 0x04_spawnflag;
  396. constexpr spawnflags_t SPAWNFLAG_PUSH_START_OFF = 0x08_spawnflag;
  397. constexpr spawnflags_t SPAWNFLAG_PUSH_CLIP = 0x10_spawnflag;
  398. // PGM
  399. static cached_soundindex windsound;
  400. TOUCH(trigger_push_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  401. {
  402. if (self->spawnflags.has(SPAWNFLAG_PUSH_CLIP))
  403. {
  404. trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
  405. if (clip.fraction == 1.0f)
  406. return;
  407. }
  408. if (strcmp(other->classname, "grenade") == 0)
  409. {
  410. other->velocity = self->movedir * (self->speed * 10);
  411. }
  412. else if (other->health > 0)
  413. {
  414. other->velocity = self->movedir * (self->speed * 10);
  415. if (other->client)
  416. {
  417. // don't take falling damage immediately from this
  418. other->client->oldvelocity = other->velocity;
  419. other->client->oldgroundentity = other->groundentity;
  420. if (
  421. !(self->spawnflags & SPAWNFLAG_PUSH_SILENT) &&
  422. (other->fly_sound_debounce_time < level.time))
  423. {
  424. other->fly_sound_debounce_time = level.time + 1.5_sec;
  425. gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
  426. }
  427. }
  428. }
  429. if (self->spawnflags.has(SPAWNFLAG_PUSH_ONCE))
  430. G_FreeEdict(self);
  431. }
  432. //======
  433. // PGM
  434. USE(trigger_push_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  435. {
  436. if (self->solid == SOLID_NOT)
  437. self->solid = SOLID_TRIGGER;
  438. else
  439. self->solid = SOLID_NOT;
  440. gi.linkentity(self);
  441. }
  442. // PGM
  443. //======
  444. // RAFAEL
  445. void trigger_push_active(edict_t *self);
  446. void trigger_effect(edict_t *self)
  447. {
  448. vec3_t origin;
  449. int i;
  450. origin = (self->absmin + self->absmax) * 0.5f;
  451. for (i = 0; i < 10; i++)
  452. {
  453. origin[2] += (self->speed * 0.01f) * (i + frandom());
  454. gi.WriteByte(svc_temp_entity);
  455. gi.WriteByte(TE_TUNNEL_SPARKS);
  456. gi.WriteByte(1);
  457. gi.WritePosition(origin);
  458. gi.WriteDir(vec3_origin);
  459. gi.WriteByte(irandom(0x74, 0x7C));
  460. gi.multicast(self->s.origin, MULTICAST_PVS, false);
  461. }
  462. }
  463. THINK(trigger_push_inactive) (edict_t *self) -> void
  464. {
  465. if (self->delay > level.time.seconds())
  466. {
  467. self->nextthink = level.time + 100_ms;
  468. }
  469. else
  470. {
  471. self->touch = trigger_push_touch;
  472. self->think = trigger_push_active;
  473. self->nextthink = level.time + 100_ms;
  474. self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds();
  475. }
  476. }
  477. THINK(trigger_push_active) (edict_t *self) -> void
  478. {
  479. if (self->delay > level.time.seconds())
  480. {
  481. self->nextthink = level.time + 100_ms;
  482. trigger_effect(self);
  483. }
  484. else
  485. {
  486. self->touch = nullptr;
  487. self->think = trigger_push_inactive;
  488. self->nextthink = level.time + 100_ms;
  489. self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds();
  490. }
  491. }
  492. // RAFAEL
  493. /*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE PUSH_PLUS PUSH_SILENT START_OFF CLIP
  494. Pushes the player
  495. "speed" defaults to 1000
  496. "wait" defaults to 10, must use PUSH_PLUS
  497. If targeted, it will toggle on and off when used.
  498. START_OFF - toggled trigger_push begins in off setting
  499. SILENT - doesn't make wind noise
  500. */
  501. void SP_trigger_push(edict_t *self)
  502. {
  503. InitTrigger(self);
  504. if (!(self->spawnflags & SPAWNFLAG_PUSH_SILENT))
  505. windsound.assign("misc/windfly.wav");
  506. self->touch = trigger_push_touch;
  507. // RAFAEL
  508. if (self->spawnflags.has(SPAWNFLAG_PUSH_PLUS))
  509. {
  510. if (!self->wait)
  511. self->wait = 10;
  512. self->think = trigger_push_active;
  513. self->nextthink = level.time + 100_ms;
  514. self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds();
  515. }
  516. // RAFAEL
  517. if (!self->speed)
  518. self->speed = 1000;
  519. // PGM
  520. if (self->targetname) // toggleable
  521. {
  522. self->use = trigger_push_use;
  523. if (self->spawnflags.has(SPAWNFLAG_PUSH_START_OFF))
  524. self->solid = SOLID_NOT;
  525. }
  526. else if (self->spawnflags.has(SPAWNFLAG_PUSH_START_OFF))
  527. {
  528. gi.Com_Print("trigger_push is START_OFF but not targeted.\n");
  529. self->svflags = SVF_NONE;
  530. self->touch = nullptr;
  531. self->solid = SOLID_BSP;
  532. self->movetype = MOVETYPE_PUSH;
  533. }
  534. // PGM
  535. gi.linkentity(self);
  536. if (self->spawnflags.has(SPAWNFLAG_PUSH_CLIP))
  537. self->svflags |= SVF_HULL;
  538. }
  539. /*
  540. ==============================================================================
  541. trigger_hurt
  542. ==============================================================================
  543. */
  544. /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW NO_PLAYERS NO_MONSTERS
  545. Any entity that touches this will be hurt.
  546. It does dmg points of damage each server frame
  547. SILENT supresses playing the sound
  548. SLOW changes the damage rate to once per second
  549. NO_PROTECTION *nothing* stops the damage
  550. "dmg" default 5 (whole numbers only)
  551. */
  552. constexpr spawnflags_t SPAWNFLAG_HURT_START_OFF = 1_spawnflag;
  553. constexpr spawnflags_t SPAWNFLAG_HURT_TOGGLE = 2_spawnflag;
  554. constexpr spawnflags_t SPAWNFLAG_HURT_SILENT = 4_spawnflag;
  555. constexpr spawnflags_t SPAWNFLAG_HURT_NO_PROTECTION = 8_spawnflag;
  556. constexpr spawnflags_t SPAWNFLAG_HURT_SLOW = 16_spawnflag;
  557. constexpr spawnflags_t SPAWNFLAG_HURT_NO_PLAYERS = 32_spawnflag;
  558. constexpr spawnflags_t SPAWNFLAG_HURT_NO_MONSTERS = 64_spawnflag;
  559. constexpr spawnflags_t SPAWNFLAG_HURT_CLIPPED = 128_spawnflag;
  560. USE(hurt_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  561. {
  562. if (self->solid == SOLID_NOT)
  563. self->solid = SOLID_TRIGGER;
  564. else
  565. self->solid = SOLID_NOT;
  566. gi.linkentity(self);
  567. if (!(self->spawnflags & SPAWNFLAG_HURT_TOGGLE))
  568. self->use = nullptr;
  569. }
  570. TOUCH(hurt_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  571. {
  572. damageflags_t dflags;
  573. if (!other->takedamage)
  574. return;
  575. else if (!(other->svflags & SVF_MONSTER) && !(other->flags & FL_DAMAGEABLE) && (!other->client) && (strcmp(other->classname, "misc_explobox") != 0))
  576. return;
  577. else if (self->spawnflags.has(SPAWNFLAG_HURT_NO_MONSTERS) && (other->svflags & SVF_MONSTER))
  578. return;
  579. else if (self->spawnflags.has(SPAWNFLAG_HURT_NO_PLAYERS) && (other->client))
  580. return;
  581. if (self->timestamp > level.time)
  582. return;
  583. if (self->spawnflags.has(SPAWNFLAG_HURT_CLIPPED))
  584. {
  585. trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
  586. if (clip.fraction == 1.0f)
  587. return;
  588. }
  589. if (self->spawnflags.has(SPAWNFLAG_HURT_SLOW))
  590. self->timestamp = level.time + 1_sec;
  591. else
  592. self->timestamp = level.time + 10_hz;
  593. if (!(self->spawnflags & SPAWNFLAG_HURT_SILENT))
  594. {
  595. if (self->fly_sound_debounce_time < level.time)
  596. {
  597. gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
  598. self->fly_sound_debounce_time = level.time + 1_sec;
  599. }
  600. }
  601. if (self->spawnflags.has(SPAWNFLAG_HURT_NO_PROTECTION))
  602. dflags = DAMAGE_NO_PROTECTION;
  603. else
  604. dflags = DAMAGE_NONE;
  605. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
  606. }
  607. void SP_trigger_hurt(edict_t *self)
  608. {
  609. InitTrigger(self);
  610. self->noise_index = gi.soundindex("world/electro.wav");
  611. self->touch = hurt_touch;
  612. if (!self->dmg)
  613. self->dmg = 5;
  614. if (self->spawnflags.has(SPAWNFLAG_HURT_START_OFF))
  615. self->solid = SOLID_NOT;
  616. else
  617. self->solid = SOLID_TRIGGER;
  618. if (self->spawnflags.has(SPAWNFLAG_HURT_TOGGLE))
  619. self->use = hurt_use;
  620. gi.linkentity(self);
  621. if (self->spawnflags.has(SPAWNFLAG_HURT_CLIPPED))
  622. self->svflags |= SVF_HULL;
  623. }
  624. /*
  625. ==============================================================================
  626. trigger_gravity
  627. ==============================================================================
  628. */
  629. /*QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF
  630. Changes the touching entites gravity to the value of "gravity".
  631. 1.0 is standard gravity for the level.
  632. TOGGLE - trigger_gravity can be turned on and off
  633. START_OFF - trigger_gravity starts turned off (implies TOGGLE)
  634. */
  635. constexpr spawnflags_t SPAWNFLAG_GRAVITY_TOGGLE = 1_spawnflag;
  636. constexpr spawnflags_t SPAWNFLAG_GRAVITY_START_OFF = 2_spawnflag;
  637. constexpr spawnflags_t SPAWNFLAG_GRAVITY_CLIPPED = 4_spawnflag;
  638. // PGM
  639. USE(trigger_gravity_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  640. {
  641. if (self->solid == SOLID_NOT)
  642. self->solid = SOLID_TRIGGER;
  643. else
  644. self->solid = SOLID_NOT;
  645. gi.linkentity(self);
  646. }
  647. // PGM
  648. TOUCH(trigger_gravity_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  649. {
  650. if (self->spawnflags.has(SPAWNFLAG_GRAVITY_CLIPPED))
  651. {
  652. trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
  653. if (clip.fraction == 1.0f)
  654. return;
  655. }
  656. other->gravity = self->gravity;
  657. }
  658. void SP_trigger_gravity(edict_t *self)
  659. {
  660. if (!st.gravity || !*st.gravity)
  661. {
  662. gi.Com_PrintFmt("{}: no gravity set\n", *self);
  663. G_FreeEdict(self);
  664. return;
  665. }
  666. InitTrigger(self);
  667. // PGM
  668. self->gravity = (float) atof(st.gravity);
  669. if (self->spawnflags.has(SPAWNFLAG_GRAVITY_TOGGLE))
  670. self->use = trigger_gravity_use;
  671. if (self->spawnflags.has(SPAWNFLAG_GRAVITY_START_OFF))
  672. {
  673. self->use = trigger_gravity_use;
  674. self->solid = SOLID_NOT;
  675. }
  676. self->touch = trigger_gravity_touch;
  677. // PGM
  678. gi.linkentity(self);
  679. if (self->spawnflags.has(SPAWNFLAG_GRAVITY_CLIPPED))
  680. self->svflags |= SVF_HULL;
  681. }
  682. /*
  683. ==============================================================================
  684. trigger_monsterjump
  685. ==============================================================================
  686. */
  687. /*QUAKED trigger_monsterjump (.5 .5 .5) ?
  688. Walking monsters that touch this will jump in the direction of the trigger's angle
  689. "speed" default to 200, the speed thrown forward
  690. "height" default to 200, the speed thrown upwards
  691. TOGGLE - trigger_monsterjump can be turned on and off
  692. START_OFF - trigger_monsterjump starts turned off (implies TOGGLE)
  693. */
  694. constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_TOGGLE = 1_spawnflag;
  695. constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_START_OFF = 2_spawnflag;
  696. constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_CLIPPED = 4_spawnflag;
  697. USE(trigger_monsterjump_use) (edict_t* self, edict_t* other, edict_t* activator) -> void
  698. {
  699. if (self->solid == SOLID_NOT)
  700. self->solid = SOLID_TRIGGER;
  701. else
  702. self->solid = SOLID_NOT;
  703. gi.linkentity(self);
  704. }
  705. TOUCH(trigger_monsterjump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  706. {
  707. if (other->flags & (FL_FLY | FL_SWIM))
  708. return;
  709. if (other->svflags & SVF_DEADMONSTER)
  710. return;
  711. if (!(other->svflags & SVF_MONSTER))
  712. return;
  713. if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_CLIPPED))
  714. {
  715. trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
  716. if (clip.fraction == 1.0f)
  717. return;
  718. }
  719. // set XY even if not on ground, so the jump will clear lips
  720. other->velocity[0] = self->movedir[0] * self->speed;
  721. other->velocity[1] = self->movedir[1] * self->speed;
  722. if (!other->groundentity)
  723. return;
  724. other->groundentity = nullptr;
  725. other->velocity[2] = self->movedir[2];
  726. }
  727. void SP_trigger_monsterjump(edict_t *self)
  728. {
  729. if (!self->speed)
  730. self->speed = 200;
  731. if (!st.height)
  732. st.height = 200;
  733. if (self->s.angles[YAW] == 0)
  734. self->s.angles[YAW] = 360;
  735. InitTrigger(self);
  736. self->touch = trigger_monsterjump_touch;
  737. self->movedir[2] = (float) st.height;
  738. if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_TOGGLE))
  739. self->use = trigger_monsterjump_use;
  740. if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_START_OFF))
  741. {
  742. self->use = trigger_monsterjump_use;
  743. self->solid = SOLID_NOT;
  744. }
  745. gi.linkentity(self);
  746. if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_CLIPPED))
  747. self->svflags |= SVF_HULL;
  748. }
  749. /*
  750. ==============================================================================
  751. trigger_flashlight
  752. ==============================================================================
  753. */
  754. /*QUAKED trigger_flashlight (.5 .5 .5) ?
  755. Players moving against this trigger will have their flashlight turned on or off.
  756. "style" default to 0, set to 1 to always turn flashlight on, 2 to always turn off,
  757. otherwise "angles" are used to control on/off state
  758. */
  759. constexpr spawnflags_t SPAWNFLAG_FLASHLIGHT_CLIPPED = 1_spawnflag;
  760. TOUCH(trigger_flashlight_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  761. {
  762. if (!other->client)
  763. return;
  764. if (self->spawnflags.has(SPAWNFLAG_FLASHLIGHT_CLIPPED))
  765. {
  766. trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
  767. if (clip.fraction == 1.0f)
  768. return;
  769. }
  770. if (self->style == 1)
  771. {
  772. P_ToggleFlashlight(other, true);
  773. }
  774. else if (self->style == 2)
  775. {
  776. P_ToggleFlashlight(other, false);
  777. }
  778. else if (other->velocity.lengthSquared() > 32.f)
  779. {
  780. vec3_t forward = other->velocity.normalized();
  781. P_ToggleFlashlight(other, forward.dot(self->movedir) > 0);
  782. }
  783. }
  784. void SP_trigger_flashlight(edict_t *self)
  785. {
  786. if (self->s.angles[YAW] == 0)
  787. self->s.angles[YAW] = 360;
  788. InitTrigger(self);
  789. self->touch = trigger_flashlight_touch;
  790. self->movedir[2] = (float) st.height;
  791. if (self->spawnflags.has(SPAWNFLAG_FLASHLIGHT_CLIPPED))
  792. self->svflags |= SVF_HULL;
  793. gi.linkentity(self);
  794. }
  795. /*
  796. ==============================================================================
  797. trigger_fog
  798. ==============================================================================
  799. */
  800. /*QUAKED trigger_fog (.5 .5 .5) ? AFFECT_FOG AFFECT_HEIGHTFOG INSTANTANEOUS FORCE BLEND
  801. Players moving against this trigger will have their fog settings changed.
  802. Fog/heightfog will be adjusted if the spawnflags are set. Instantaneous
  803. ignores any delays. Force causes it to ignore movement dir and always use
  804. the "on" values. Blend causes it to change towards how far you are into the trigger
  805. with respect to angles.
  806. "target" can target an info_notnull to pull the keys below from.
  807. "delay" default to 0.5; time in seconds a change in fog will occur over
  808. "wait" default to 0.0; time in seconds before a re-trigger can be executed
  809. "fog_density"; density value of fog, 0-1
  810. "fog_color"; color value of fog, 3d vector with values between 0-1 (r g b)
  811. "fog_density_off"; transition density value of fog, 0-1
  812. "fog_color_off"; transition color value of fog, 3d vector with values between 0-1 (r g b)
  813. "fog_sky_factor"; sky factor value of fog, 0-1
  814. "fog_sky_factor_off"; transition sky factor value of fog, 0-1
  815. "heightfog_falloff"; falloff value of heightfog, 0-1
  816. "heightfog_density"; density value of heightfog, 0-1
  817. "heightfog_start_color"; the start color for the fog (r g b, 0-1)
  818. "heightfog_start_dist"; the start distance for the fog (units)
  819. "heightfog_end_color"; the start color for the fog (r g b, 0-1)
  820. "heightfog_end_dist"; the end distance for the fog (units)
  821. "heightfog_falloff_off"; transition falloff value of heightfog, 0-1
  822. "heightfog_density_off"; transition density value of heightfog, 0-1
  823. "heightfog_start_color_off"; transition the start color for the fog (r g b, 0-1)
  824. "heightfog_start_dist_off"; transition the start distance for the fog (units)
  825. "heightfog_end_color_off"; transition the start color for the fog (r g b, 0-1)
  826. "heightfog_end_dist_off"; transition the end distance for the fog (units)
  827. */
  828. constexpr spawnflags_t SPAWNFLAG_FOG_AFFECT_FOG = 1_spawnflag;
  829. constexpr spawnflags_t SPAWNFLAG_FOG_AFFECT_HEIGHTFOG = 2_spawnflag;
  830. constexpr spawnflags_t SPAWNFLAG_FOG_INSTANTANEOUS = 4_spawnflag;
  831. constexpr spawnflags_t SPAWNFLAG_FOG_FORCE = 8_spawnflag;
  832. constexpr spawnflags_t SPAWNFLAG_FOG_BLEND = 16_spawnflag;
  833. TOUCH(trigger_fog_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  834. {
  835. if (!other->client)
  836. return;
  837. if (self->timestamp > level.time)
  838. return;
  839. self->timestamp = level.time + gtime_t::from_sec(self->wait);
  840. edict_t *fog_value_storage = self;
  841. if (self->movetarget)
  842. fog_value_storage = self->movetarget;
  843. if (self->spawnflags.has(SPAWNFLAG_FOG_INSTANTANEOUS))
  844. other->client->pers.fog_transition_time = 0_ms;
  845. else
  846. other->client->pers.fog_transition_time = gtime_t::from_sec(fog_value_storage->delay);
  847. if (self->spawnflags.has(SPAWNFLAG_FOG_BLEND))
  848. {
  849. vec3_t center = (self->absmin + self->absmax) * 0.5f;
  850. vec3_t half_size = (self->size * 0.5f) + (other->size * 0.5f);
  851. vec3_t start = (-self->movedir).scaled(half_size);
  852. vec3_t end = (self->movedir).scaled(half_size);
  853. vec3_t player_dist = (other->s.origin - center).scaled(vec3_t{fabs(self->movedir[0]),fabs(self->movedir[1]),fabs(self->movedir[2])});
  854. float dist = (player_dist - start).length();
  855. dist /= (start - end).length();
  856. dist = clamp(dist, 0.f, 1.f);
  857. if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_FOG))
  858. {
  859. other->client->pers.wanted_fog = {
  860. lerp(fog_value_storage->fog.density_off, fog_value_storage->fog.density, dist),
  861. lerp(fog_value_storage->fog.color_off[0], fog_value_storage->fog.color[0], dist),
  862. lerp(fog_value_storage->fog.color_off[1], fog_value_storage->fog.color[1], dist),
  863. lerp(fog_value_storage->fog.color_off[2], fog_value_storage->fog.color[2], dist),
  864. lerp(fog_value_storage->fog.sky_factor_off, fog_value_storage->fog.sky_factor, dist)
  865. };
  866. }
  867. if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_HEIGHTFOG))
  868. {
  869. other->client->pers.wanted_heightfog = {
  870. {
  871. lerp(fog_value_storage->heightfog.start_color_off[0], fog_value_storage->heightfog.start_color[0], dist),
  872. lerp(fog_value_storage->heightfog.start_color_off[1], fog_value_storage->heightfog.start_color[1], dist),
  873. lerp(fog_value_storage->heightfog.start_color_off[2], fog_value_storage->heightfog.start_color[2], dist),
  874. lerp(fog_value_storage->heightfog.start_dist_off, fog_value_storage->heightfog.start_dist, dist)
  875. },
  876. {
  877. lerp(fog_value_storage->heightfog.end_color_off[0], fog_value_storage->heightfog.end_color[0], dist),
  878. lerp(fog_value_storage->heightfog.end_color_off[1], fog_value_storage->heightfog.end_color[1], dist),
  879. lerp(fog_value_storage->heightfog.end_color_off[2], fog_value_storage->heightfog.end_color[2], dist),
  880. lerp(fog_value_storage->heightfog.end_dist_off, fog_value_storage->heightfog.end_dist, dist)
  881. },
  882. lerp(fog_value_storage->heightfog.falloff_off, fog_value_storage->heightfog.falloff, dist),
  883. lerp(fog_value_storage->heightfog.density_off, fog_value_storage->heightfog.density, dist)
  884. };
  885. }
  886. return;
  887. }
  888. bool use_on = true;
  889. if (!self->spawnflags.has(SPAWNFLAG_FOG_FORCE))
  890. {
  891. float len;
  892. vec3_t forward = other->velocity.normalized(len);
  893. // not moving enough to trip; this is so we don't trip
  894. // the wrong direction when on an elevator, etc.
  895. if (len <= 0.0001f)
  896. return;
  897. use_on = forward.dot(self->movedir) > 0;
  898. }
  899. if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_FOG))
  900. {
  901. if (use_on)
  902. {
  903. other->client->pers.wanted_fog = {
  904. fog_value_storage->fog.density,
  905. fog_value_storage->fog.color[0],
  906. fog_value_storage->fog.color[1],
  907. fog_value_storage->fog.color[2],
  908. fog_value_storage->fog.sky_factor
  909. };
  910. }
  911. else
  912. {
  913. other->client->pers.wanted_fog = {
  914. fog_value_storage->fog.density_off,
  915. fog_value_storage->fog.color_off[0],
  916. fog_value_storage->fog.color_off[1],
  917. fog_value_storage->fog.color_off[2],
  918. fog_value_storage->fog.sky_factor_off
  919. };
  920. }
  921. }
  922. if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_HEIGHTFOG))
  923. {
  924. if (use_on)
  925. {
  926. other->client->pers.wanted_heightfog = {
  927. {
  928. fog_value_storage->heightfog.start_color[0],
  929. fog_value_storage->heightfog.start_color[1],
  930. fog_value_storage->heightfog.start_color[2],
  931. fog_value_storage->heightfog.start_dist
  932. },
  933. {
  934. fog_value_storage->heightfog.end_color[0],
  935. fog_value_storage->heightfog.end_color[1],
  936. fog_value_storage->heightfog.end_color[2],
  937. fog_value_storage->heightfog.end_dist
  938. },
  939. fog_value_storage->heightfog.falloff,
  940. fog_value_storage->heightfog.density
  941. };
  942. }
  943. else
  944. {
  945. other->client->pers.wanted_heightfog = {
  946. {
  947. fog_value_storage->heightfog.start_color_off[0],
  948. fog_value_storage->heightfog.start_color_off[1],
  949. fog_value_storage->heightfog.start_color_off[2],
  950. fog_value_storage->heightfog.start_dist_off
  951. },
  952. {
  953. fog_value_storage->heightfog.end_color_off[0],
  954. fog_value_storage->heightfog.end_color_off[1],
  955. fog_value_storage->heightfog.end_color_off[2],
  956. fog_value_storage->heightfog.end_dist_off
  957. },
  958. fog_value_storage->heightfog.falloff_off,
  959. fog_value_storage->heightfog.density_off
  960. };
  961. }
  962. }
  963. }
  964. void SP_trigger_fog(edict_t *self)
  965. {
  966. if (self->s.angles[YAW] == 0)
  967. self->s.angles[YAW] = 360;
  968. InitTrigger(self);
  969. if (!(self->spawnflags & (SPAWNFLAG_FOG_AFFECT_FOG | SPAWNFLAG_FOG_AFFECT_HEIGHTFOG)))
  970. gi.Com_PrintFmt("WARNING: {} with no fog spawnflags set\n", *self);
  971. if (self->target)
  972. {
  973. self->movetarget = G_PickTarget(self->target);
  974. if (self->movetarget)
  975. {
  976. if (!self->movetarget->delay)
  977. self->movetarget->delay = 0.5f;
  978. }
  979. }
  980. if (!self->delay)
  981. self->delay = 0.5f;
  982. self->touch = trigger_fog_touch;
  983. }
  984. /*QUAKED trigger_coop_relay (.5 .5 .5) ? AUTO_FIRE
  985. Like a trigger_relay, but all players must be touching its
  986. mins/maxs in order to fire, otherwise a message will be printed.
  987. AUTO_FIRE: check every `wait` seconds for containment instead of
  988. requiring to be fired by something else. Frees itself after firing.
  989. "message"; message to print to the one activating the relay if
  990. not all players are inside the bounds
  991. "message2"; message to print to players not inside the trigger
  992. if they aren't in the bounds
  993. */
  994. constexpr spawnflags_t SPAWNFLAG_COOP_RELAY_AUTO_FIRE = 1_spawnflag;
  995. inline bool trigger_coop_relay_filter(edict_t *player)
  996. {
  997. return (player->health <= 0 || player->deadflag || player->movetype == MOVETYPE_NOCLIP ||
  998. player->client->resp.spectator || player->s.modelindex != MODELINDEX_PLAYER);
  999. }
  1000. static bool trigger_coop_relay_can_use(edict_t *self, edict_t *activator)
  1001. {
  1002. // not coop, so act like a standard trigger_relay minus the message
  1003. if (!coop->integer)
  1004. return true;
  1005. // coop; scan for all alive players, print appropriate message
  1006. // to those in/out of range
  1007. bool can_use = true;
  1008. for (auto player : active_players())
  1009. {
  1010. // dead or spectator, don't count them
  1011. if (trigger_coop_relay_filter(player))
  1012. continue;
  1013. if (boxes_intersect(player->absmin, player->absmax, self->absmin, self->absmax))
  1014. continue;
  1015. if (self->timestamp < level.time)
  1016. gi.LocCenter_Print(player, self->map);
  1017. can_use = false;
  1018. }
  1019. return can_use;
  1020. }
  1021. USE(trigger_coop_relay_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1022. {
  1023. if (!trigger_coop_relay_can_use(self, activator))
  1024. {
  1025. if (self->timestamp < level.time)
  1026. gi.LocCenter_Print(activator, self->message);
  1027. self->timestamp = level.time + 5_sec;
  1028. return;
  1029. }
  1030. const char *msg = self->message;
  1031. self->message = nullptr;
  1032. G_UseTargets(self, activator);
  1033. self->message = msg;
  1034. }
  1035. static BoxEdictsResult_t trigger_coop_relay_player_filter(edict_t *ent, void *data)
  1036. {
  1037. if (!ent->client)
  1038. return BoxEdictsResult_t::Skip;
  1039. else if (trigger_coop_relay_filter(ent))
  1040. return BoxEdictsResult_t::Skip;
  1041. return BoxEdictsResult_t::Keep;
  1042. }
  1043. THINK(trigger_coop_relay_think) (edict_t *self) -> void
  1044. {
  1045. std::array<edict_t *, MAX_SPLIT_PLAYERS> players;
  1046. size_t num_active = 0;
  1047. for (auto player : active_players())
  1048. if (!trigger_coop_relay_filter(player))
  1049. num_active++;
  1050. size_t n = gi.BoxEdicts(self->absmin, self->absmax, players.data(), num_active, AREA_SOLID, trigger_coop_relay_player_filter, nullptr);
  1051. if (n == num_active)
  1052. {
  1053. const char *msg = self->message;
  1054. self->message = nullptr;
  1055. G_UseTargets(self, &globals.edicts[1]);
  1056. self->message = msg;
  1057. G_FreeEdict(self);
  1058. return;
  1059. }
  1060. else if (n && self->timestamp < level.time)
  1061. {
  1062. for (size_t i = 0; i < n; i++)
  1063. gi.LocCenter_Print(players[i], self->message);
  1064. for (auto player : active_players())
  1065. if (std::find(players.begin(), players.end(), player) == players.end())
  1066. gi.LocCenter_Print(player, self->map);
  1067. self->timestamp = level.time + 5_sec;
  1068. }
  1069. self->nextthink = level.time + gtime_t::from_sec(self->wait);
  1070. }
  1071. void SP_trigger_coop_relay(edict_t *self)
  1072. {
  1073. if (self->targetname && self->spawnflags.has(SPAWNFLAG_COOP_RELAY_AUTO_FIRE))
  1074. gi.Com_PrintFmt("{}: targetname and auto-fire are mutually exclusive\n", *self);
  1075. InitTrigger(self);
  1076. if (!self->message)
  1077. self->message = "$g_coop_wait_for_players";
  1078. if (!self->map)
  1079. self->map = "$g_coop_players_waiting_for_you";
  1080. if (!self->wait)
  1081. self->wait = 1;
  1082. if (self->spawnflags.has(SPAWNFLAG_COOP_RELAY_AUTO_FIRE))
  1083. {
  1084. self->think = trigger_coop_relay_think;
  1085. self->nextthink = level.time + gtime_t::from_sec(self->wait);
  1086. }
  1087. else
  1088. self->use = trigger_coop_relay_use;
  1089. self->svflags |= SVF_NOCLIENT;
  1090. gi.linkentity(self);
  1091. }