triggers.qc 24 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. /* Copyright (C) 1996-2022 id Software LLC
  2. This program is free software; you can redistribute it and/or modify
  3. it under the terms of the GNU General Public License as published by
  4. the Free Software Foundation; either version 2 of the License, or
  5. (at your option) any later version.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License
  11. along with this program; if not, write to the Free Software
  12. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  13. See file, 'COPYING', for details.
  14. */
  15. // Yoder Sept 24 2021 Horde merge
  16. float COUNTER_LOOPS = 2; // so counters can loop back to the start again
  17. float MONSTER_ONLY = 8; // for trigger_hurt
  18. void() trigger_reactivate =
  19. {
  20. self.solid = SOLID_TRIGGER;
  21. };
  22. //=============================================================================
  23. // the wait time has passed, so set back up for another activation
  24. void() multi_wait =
  25. {
  26. if (self.max_health)
  27. {
  28. self.health = self.max_health;
  29. self.takedamage = DAMAGE_YES;
  30. self.solid = SOLID_BBOX;
  31. }
  32. };
  33. // the trigger was just touched/killed/used
  34. // self.enemy should be set to the activator so it can be held through a delay
  35. // so wait for the delay time before firing
  36. void() multi_trigger =
  37. {
  38. if (self.nextthink > time)
  39. {
  40. return; // allready been triggered
  41. }
  42. if (self.classname == "trigger_secret")
  43. {
  44. if (self.enemy.classname != "player")
  45. return;
  46. found_secrets = found_secrets + 1;
  47. WriteByte (MSG_ALL, SVC_FOUNDSECRET);
  48. msg_entity = self.enemy;
  49. WriteByte (MSG_ONE, SVC_ACHIEVEMENT);
  50. WriteString(MSG_ONE, "ACH_FIND_SECRET");
  51. }
  52. if (self.noise)
  53. sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
  54. // don't trigger again until reset
  55. self.takedamage = DAMAGE_NO;
  56. activator = self.enemy;
  57. SUB_UseTargets();
  58. if (self.wait > 0)
  59. {
  60. self.think = multi_wait;
  61. self.nextthink = time + self.wait;
  62. }
  63. else
  64. { // we can't just remove (self) here, because this is a touch function
  65. // called wheil C code is looping through area links...
  66. self.touch = SUB_Null;
  67. self.nextthink = time + 0.1;
  68. self.think = SUB_Remove;
  69. }
  70. };
  71. void() multi_killed =
  72. {
  73. self.enemy = damage_attacker;
  74. multi_trigger();
  75. };
  76. void() multi_use =
  77. {
  78. self.enemy = activator;
  79. multi_trigger();
  80. };
  81. void() multi_touch =
  82. {
  83. if (other.classname != "player")
  84. return;
  85. // if the trigger has an angles field, check player's facing direction
  86. if (self.movedir != '0 0 0')
  87. {
  88. makevectors (other.angles);
  89. if (v_forward * self.movedir < 0)
  90. return; // not facing the right way
  91. }
  92. self.enemy = other;
  93. multi_trigger ();
  94. };
  95. /*QUAKED trigger_multiple (.5 .5 .5) ? notouch
  96. Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
  97. If "delay" is set, the trigger waits some time after activating before firing.
  98. "wait" : Seconds between triggerings. (.2 default)
  99. If notouch is set, the trigger is only fired by other entities, not by touching.
  100. NOTOUCH has been obsoleted by trigger_relay!
  101. sounds
  102. 1) secret
  103. 2) beep beep
  104. 3) large switch
  105. 4)
  106. set "message" to text string
  107. */
  108. void() trigger_multiple =
  109. {
  110. INHIBIT_COOP
  111. self.netname = "trigger_multiple"; // for bot nav support.
  112. if (self.sounds == 1)
  113. {
  114. precache_sound ("misc/secret.wav");
  115. self.noise = "misc/secret.wav";
  116. }
  117. else if (self.sounds == 2)
  118. {
  119. precache_sound ("misc/talk.wav");
  120. self.noise = "misc/talk.wav";
  121. }
  122. else if (self.sounds == 3)
  123. {
  124. precache_sound ("misc/trigger1.wav");
  125. self.noise = "misc/trigger1.wav";
  126. }
  127. self.sounds = 0;
  128. if(self.spawnflags & SPAWNFLAG_TRIGGER_FIRST)
  129. {
  130. self.use = trigger_multiple;
  131. self.spawnflags &~= SPAWNFLAG_TRIGGER_FIRST;
  132. return;
  133. }
  134. if (!self.wait)
  135. self.wait = 0.2;
  136. self.use = multi_use;
  137. InitTrigger ();
  138. if (self.health)
  139. {
  140. if (self.spawnflags & SPAWNFLAG_NOTOUCH)
  141. objerror ("health and notouch don't make sense\n");
  142. self.max_health = self.health;
  143. self.th_die = multi_killed;
  144. self.takedamage = DAMAGE_YES;
  145. self.solid = SOLID_BBOX;
  146. setorigin (self, self.origin); // make sure it links into the world
  147. }
  148. else
  149. {
  150. if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
  151. {
  152. self.touch = multi_touch;
  153. }
  154. }
  155. };
  156. /*QUAKED trigger_once (.5 .5 .5) ? notouch
  157. Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
  158. "targetname". If "health" is set, the trigger must be killed to activate.
  159. If notouch is set, the trigger is only fired by other entities, not by touching.
  160. if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
  161. if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
  162. sounds
  163. 1) secret
  164. 2) beep beep
  165. 3) large switch
  166. 4)
  167. set "message" to text string
  168. */
  169. void() trigger_once =
  170. {
  171. self.wait = -1;
  172. trigger_multiple();
  173. self.netname = "trigger_once"; // for bot nav support.
  174. };
  175. //=============================================================================
  176. /*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
  177. This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
  178. */
  179. void() trigger_relay =
  180. {
  181. INHIBIT_COOP
  182. if (RemovedOutsideCoop()) return;
  183. self.use = SUB_UseTargets;
  184. };
  185. //=============================================================================
  186. /*QUAKED trigger_secret (.5 .5 .5) ?
  187. secret counter trigger
  188. sounds
  189. 1) secret
  190. 2) beep beep
  191. 3)
  192. 4)
  193. set "message" to text string
  194. */
  195. void() trigger_secret =
  196. {
  197. INHIBIT_COOP
  198. total_secrets = total_secrets + 1;
  199. self.wait = -1;
  200. if (!self.message)
  201. self.message = "$qc_found_secret";
  202. if (!self.sounds)
  203. self.sounds = 1;
  204. if (self.sounds == 1)
  205. {
  206. precache_sound ("misc/secret.wav");
  207. self.noise = "misc/secret.wav";
  208. }
  209. else if (self.sounds == 2)
  210. {
  211. precache_sound ("misc/talk.wav");
  212. self.noise = "misc/talk.wav";
  213. }
  214. trigger_multiple ();
  215. };
  216. //=============================================================================
  217. void() counter_use =
  218. {
  219. self.count = self.count - 1;
  220. if (self.count < 0)
  221. return;
  222. if (self.count != 0)
  223. {
  224. if (activator.classname == "player"
  225. && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
  226. {
  227. if (self.count >= 4)
  228. {
  229. centerprint_all ("$qc_more_go");
  230. }
  231. else if (self.count == 3)
  232. {
  233. centerprint_all ("$qc_three_more");
  234. }
  235. else if (self.count == 2)
  236. {
  237. centerprint_all ("$qc_two_more");
  238. }
  239. else
  240. {
  241. centerprint_all ("$qc_one_more");
  242. }
  243. }
  244. return;
  245. }
  246. if (activator.classname == "player" && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
  247. {
  248. centerprint_all("$qc_sequence_completed");
  249. }
  250. self.enemy = activator;
  251. // yoder Sept24, 2021, Horde Merge
  252. if (self.spawnflags & COUNTER_LOOPS)
  253. self.count = self.wait;
  254. multi_trigger ();
  255. };
  256. /*QUAKED trigger_counter (.5 .5 .5) ? nomessage message_all
  257. Acts as an intermediary for an action that takes multiple inputs.
  258. If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
  259. After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
  260. */
  261. void() trigger_counter =
  262. {
  263. INHIBIT_COOP
  264. if (RemovedOutsideCoop()) return;
  265. // Yoder Sept24, 2021 Horde Merge
  266. // .count is decremented each use and fires its targets on 0
  267. // .wait stores the original .count and restores to this if COUNTER_LOOPS
  268. if (!self.count)
  269. self.count = 2;
  270. if (!self.wait)
  271. self.wait = self.count;
  272. self.use = counter_use;
  273. };
  274. //=============================================================================
  275. void() counter_timed_think =
  276. {
  277. self.count = self.cnt;
  278. }
  279. void() counter_timed_use =
  280. {
  281. self.count = self.count - 1;
  282. if (self.count < 0)
  283. return;
  284. self.nextthink = time + self.delay;
  285. if (self.count != 0)
  286. {
  287. if (activator.classname == "player"
  288. && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
  289. {
  290. if (self.count >= 4)
  291. {
  292. centerprint_all ("$qc_more_go");
  293. }
  294. else if (self.count == 3)
  295. {
  296. centerprint_all ("$qc_three_more");
  297. }
  298. else if (self.count == 2)
  299. {
  300. centerprint_all ("$qc_two_more");
  301. }
  302. else
  303. {
  304. centerprint_all ("$qc_one_more");
  305. }
  306. }
  307. return;
  308. }
  309. if (activator.classname == "player" && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
  310. {
  311. centerprint_all("$qc_sequence_completed");
  312. }
  313. self.enemy = activator;
  314. self.delay = 0;
  315. self.nextthink = -1;
  316. self.think = SUB_Null;
  317. multi_trigger ();
  318. remove(self);
  319. };
  320. /*QUAKED trigger_counter_timed (.5 .5 .5) ? nomessage message_all
  321. Acts as an intermediary for an action that takes multiple inputs.
  322. If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
  323. After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
  324. */
  325. void() trigger_counter_timed =
  326. {
  327. INHIBIT_COOP
  328. if (RemovedOutsideCoop()) return;
  329. self.wait = -1;
  330. if (!self.count)
  331. self.count = 2;
  332. if (!self.delay)
  333. self.delay = 2;
  334. self.cnt = self.count;
  335. self.use = counter_timed_use;
  336. self.think = counter_timed_think;
  337. };
  338. /*
  339. ==============================================================================
  340. TELEPORT TRIGGERS
  341. ==============================================================================
  342. */
  343. float PLAYER_ONLY = 1;
  344. float SILENT = 2;
  345. float IGNORE_TARGETNAME = 4;
  346. void() play_teleport =
  347. {
  348. local float v;
  349. local string tmpstr;
  350. v = random() * 5;
  351. if (v < 1)
  352. tmpstr = "misc/r_tele1.wav";
  353. else if (v < 2)
  354. tmpstr = "misc/r_tele2.wav";
  355. else if (v < 3)
  356. tmpstr = "misc/r_tele3.wav";
  357. else if (v < 4)
  358. tmpstr = "misc/r_tele4.wav";
  359. else
  360. tmpstr = "misc/r_tele5.wav";
  361. sound (self, CHAN_VOICE, tmpstr, 1, ATTN_NORM);
  362. remove (self);
  363. };
  364. void(vector org) spawn_tfog =
  365. {
  366. entity s = spawn ();
  367. s.origin = org;
  368. s.nextthink = time + 0.2;
  369. s.think = play_teleport;
  370. WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
  371. WriteByte (MSG_BROADCAST, TE_TELEPORT);
  372. WriteCoord (MSG_BROADCAST, org_x);
  373. WriteCoord (MSG_BROADCAST, org_y);
  374. WriteCoord (MSG_BROADCAST, org_z);
  375. };
  376. void(vector org) spawn_tfog_silent =
  377. {
  378. WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
  379. WriteByte (MSG_BROADCAST, TE_TELEPORT);
  380. WriteCoord (MSG_BROADCAST, org_x);
  381. WriteCoord (MSG_BROADCAST, org_y);
  382. WriteCoord (MSG_BROADCAST, org_z);
  383. };
  384. void() tdeath_touch =
  385. {
  386. if (other == self.owner)
  387. return;
  388. // frag anyone who teleports in on top of an invincible player
  389. if (other.classname == "player")
  390. {
  391. if (other.invincible_finished > time)
  392. self.classname = "teledeath2";
  393. if (self.owner.classname != "player")
  394. { // other monsters explode themselves
  395. T_Damage (self.owner, self, self, 50000);
  396. return;
  397. }
  398. }
  399. if (other.health)
  400. {
  401. T_Damage (other, self, self, 50000);
  402. }
  403. };
  404. // Yoder sept24 2021 horde merge
  405. // a faster version of tdeath to minimize telefragging
  406. void(vector org, entity death_owner) spawn_tdeath_fast =
  407. {
  408. local entity death;
  409. death = spawn();
  410. death.classname = "teledeath";
  411. death.movetype = MOVETYPE_NONE;
  412. death.solid = SOLID_TRIGGER;
  413. death.angles = '0 0 0';
  414. setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1');
  415. setorigin (death, org);
  416. death.touch = tdeath_touch;
  417. death.nextthink = time + 0.01;
  418. death.think = SUB_Remove;
  419. death.owner = death_owner;
  420. force_retouch = 2; // make sure even still objects get hit
  421. };
  422. void(vector org, entity death_owner) spawn_tdeath =
  423. {
  424. local entity death;
  425. death = spawn();
  426. death.classname = "teledeath";
  427. death.movetype = MOVETYPE_NONE;
  428. death.solid = SOLID_TRIGGER;
  429. death.angles = '0 0 0';
  430. setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1');
  431. setorigin (death, org);
  432. death.touch = tdeath_touch;
  433. death.nextthink = time + 0.2;
  434. death.think = SUB_Remove;
  435. death.owner = death_owner;
  436. force_retouch = 2; // make sure even still objects get hit
  437. };
  438. void() teleport_touch =
  439. {
  440. local entity t;
  441. local vector org;
  442. if (self.targetname && (self.spawnflags & IGNORE_TARGETNAME) == 0)
  443. {
  444. if (self.nextthink < time)
  445. {
  446. return; // not fired yet
  447. }
  448. }
  449. if (self.spawnflags & PLAYER_ONLY)
  450. {
  451. if (other.classname != "player")
  452. return;
  453. }
  454. // only teleport living creatures
  455. if (other.health <= 0 || other.solid != SOLID_SLIDEBOX)
  456. return;
  457. SUB_UseTargets ();
  458. // put a tfog where the player was
  459. spawn_tfog (other.origin);
  460. t = find (world, targetname, self.target);
  461. if (!t)
  462. objerror ("couldn't find target");
  463. // spawn a tfog flash in front of the destination
  464. makevectors (t.mangle);
  465. org = t.origin + 32 * v_forward;
  466. spawn_tfog (org);
  467. spawn_tdeath(t.origin, other);
  468. // move the player and lock him down for a little while
  469. if (!other.health)
  470. {
  471. other.origin = t.origin;
  472. other.velocity = (v_forward * other.velocity_x) + (v_forward * other.velocity_y);
  473. return;
  474. }
  475. setorigin (other, t.origin);
  476. other.angles = t.mangle;
  477. if (other.classname == "player")
  478. {
  479. other.fixangle = 1; // turn this way immediately
  480. other.teleport_time = time + 0.7;
  481. if (other.flags & FL_ONGROUND)
  482. other.flags = other.flags - FL_ONGROUND;
  483. other.velocity = v_forward * 300;
  484. if(!coop && !deathmatch)
  485. {
  486. FogPushSettingsFrom(other, t, 0);
  487. }
  488. }
  489. other.flags = other.flags - other.flags & FL_ONGROUND;
  490. };
  491. /*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32)
  492. This is the destination marker for a teleporter. It should have a "targetname" field with the same value as a teleporter's "target" field.
  493. */
  494. void() info_teleport_destination =
  495. {
  496. self.netname = "info_teleport_destination"; // for bot nav support.
  497. // this does nothing, just serves as a target spot
  498. self.mangle = self.angles;
  499. self.angles = '0 0 0';
  500. self.model = "";
  501. self.origin = self.origin + '0 0 27';
  502. if (!self.targetname)
  503. objerror ("no targetname");
  504. };
  505. void() teleport_use =
  506. {
  507. self.nextthink = time + 0.2;
  508. force_retouch = 2; // make sure even still objects get hit
  509. self.think = SUB_Null;
  510. };
  511. /*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT
  512. Any object touching this will be transported to the corresponding info_teleport_destination entity. You must set the "target" field, and create an object with a "targetname" field that matches.
  513. If the trigger_teleport has a targetname, it will only teleport entities when it has been fired.
  514. */
  515. void() trigger_teleport =
  516. {
  517. self.netname = "trigger_teleport"; // for bot nav support.
  518. local vector o;
  519. InitTrigger ();
  520. self.touch = teleport_touch;
  521. // find the destination
  522. if (!self.target)
  523. objerror ("no target");
  524. self.use = teleport_use;
  525. if (!(self.spawnflags & SILENT))
  526. {
  527. precache_sound ("ambience/hum1.wav");
  528. o = (self.mins + self.maxs)*0.5;
  529. ambientsound (o, "ambience/hum1.wav",0.5 , ATTN_STATIC);
  530. }
  531. };
  532. /*
  533. ==============================================================================
  534. trigger_setskill
  535. ==============================================================================
  536. */
  537. void() trigger_skill_touch =
  538. {
  539. if (other.classname != "player")
  540. return;
  541. cvar_set ("skill", self.message);
  542. };
  543. /*QUAKED trigger_setskill (.5 .5 .5) ?
  544. sets skill level to the value of "message".
  545. Only used on start map.
  546. */
  547. void() trigger_setskill =
  548. {
  549. InitTrigger ();
  550. self.touch = trigger_skill_touch;
  551. };
  552. /*
  553. ==============================================================================
  554. ONLY REGISTERED TRIGGERS
  555. ==============================================================================
  556. */
  557. void() trigger_onlyregistered_touch =
  558. {
  559. if (other.classname != "player")
  560. return;
  561. if (self.attack_finished > time)
  562. return;
  563. self.attack_finished = time + 2;
  564. if (cvar("registered"))
  565. {
  566. self.message = "";
  567. SUB_UseTargets ();
  568. remove (self);
  569. }
  570. else
  571. {
  572. if (self.message != "")
  573. {
  574. centerprint (other, self.message); // Ingame message, localized
  575. sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
  576. }
  577. }
  578. };
  579. /*QUAKED trigger_onlyregistered (.5 .5 .5) ?
  580. Only fires if playing the registered version, otherwise prints the message
  581. */
  582. void() trigger_onlyregistered =
  583. {
  584. precache_sound ("misc/talk.wav");
  585. InitTrigger ();
  586. self.touch = trigger_onlyregistered_touch;
  587. };
  588. //============================================================================
  589. void hurt_use()
  590. {
  591. self.state = 1 - self.state;
  592. }
  593. void() hurt_on =
  594. {
  595. self.solid = SOLID_TRIGGER;
  596. self.nextthink = -1;
  597. };
  598. void() hurt_touch =
  599. {
  600. if(self.state) return;
  601. if (other.takedamage)
  602. {
  603. // Yoder Sept24 2021 Horde merge
  604. // allows for monster-only hurt volumes
  605. if ((self.spawnflags & MONSTER_ONLY) && !(other.flags & FL_MONSTER))
  606. return;
  607. self.solid = SOLID_NOT;
  608. T_Damage (other, self, self, self.dmg);
  609. self.think = hurt_on;
  610. self.nextthink = time + self.wait;
  611. }
  612. };
  613. /*QUAKED trigger_hurt (.5 .5 .5) ?
  614. Any object touching this will be hurt
  615. set dmg to damage amount
  616. defalt dmg = 5
  617. */
  618. void() trigger_hurt =
  619. {
  620. self.netname = "trigger_hurt"; // for bot nav support.
  621. InitTrigger ();
  622. self.touch = hurt_touch;
  623. if (!self.dmg)
  624. self.dmg = 5;
  625. if (!self.wait)
  626. self.wait = 1;
  627. if(self.spawnflags & START_OFF)
  628. {
  629. self.state = 1;
  630. }
  631. self.use = hurt_use;
  632. };
  633. //============================================================================
  634. float PUSH_ONCE = 1;
  635. float ADDITIVE_PUSH = 2;
  636. float PUSH_START_OFF = 4;
  637. void() trigger_push_touch =
  638. {
  639. // yoder Sept 24 2021 horde merge
  640. if (horde_ent)
  641. {
  642. if (other.spawnflags & PUSH_START_OFF) // ignore this spawnflag in horde mode
  643. other.spawnflags-= PUSH_START_OFF;
  644. if ((other.classname != "player"))
  645. {
  646. if ((other.classname == "item_artifact_invulnerability") || (other.classname == "item_artifact_super_damage"))
  647. {
  648. if (self.spawnflags & ADDITIVE_PUSH)
  649. other.velocity = other.velocity + (self.speed * self.movedir * 10 * frametime);
  650. else
  651. other.velocity = self.speed * self.movedir * 10;
  652. }
  653. return;
  654. }
  655. }
  656. if (other.health > 0 || other.classname == "grenade")
  657. {
  658. if(other.flags & FL_INSHELTER) return;
  659. if (self.spawnflags & ADDITIVE_PUSH) // yoder add, Jan 28 2021
  660. other.velocity = other.velocity + self.speed * self.movedir * 10 * frametime;
  661. else
  662. other.velocity = self.speed * self.movedir * 10;
  663. if (other.classname == "player")
  664. {
  665. if (other.fly_sound < time)
  666. {
  667. other.fly_sound = time + 1.5;
  668. sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM);
  669. }
  670. }
  671. if(other.flags & FL_MONSTER) other.flags (-) FL_ONGROUND; //If a monster walks inside a trigger_push, make it fly
  672. }
  673. if (self.spawnflags & PUSH_ONCE)
  674. remove(self);
  675. };
  676. void() trigger_push_use =
  677. {
  678. if(self.solid == SOLID_TRIGGER)
  679. {
  680. dprint("trigger_push: switched off\n");
  681. self.solid = SOLID_NOT;
  682. }
  683. else
  684. {
  685. dprint("trigger_push: switched on\n");
  686. self.solid = SOLID_TRIGGER;
  687. force_retouch = 1;
  688. }
  689. }
  690. /*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE ADDITIVE_PUSH START_OFF
  691. Pushes the player
  692. */
  693. void() trigger_push =
  694. {
  695. self.netname = "trigger_push"; // for bot nav support.
  696. InitTrigger ();
  697. precache_sound ("ambience/windfly.wav");
  698. self.touch = trigger_push_touch;
  699. self.use = trigger_push_use;
  700. if(self.spawnflags & PUSH_START_OFF) self.solid = SOLID_NOT;
  701. if (!self.speed)
  702. self.speed = 1000;
  703. };
  704. // =================================================================
  705. const float SHELTER_FLIPPED = 1;
  706. void() trigger_shelter_portal_touch =
  707. {
  708. if (other.health > 0 || other.classname == "grenade")
  709. {
  710. vector offs = other.origin - self.pos1;
  711. float dot = offs * self.pos2;
  712. if(dot >= 0)
  713. {
  714. other.flags (+) FL_INSHELTER;
  715. }
  716. else
  717. {
  718. other.flags (-) FL_INSHELTER;
  719. }
  720. }
  721. }
  722. /*QUAKED trigger_shelter (.5 .5 .5)
  723. Shelters the player from pushes
  724. */
  725. void() trigger_shelter_portal =
  726. {
  727. InitTrigger ();
  728. self.touch = trigger_shelter_portal_touch;
  729. self.pos1 = self.mins + (self.size * 0.5);
  730. if(self.size_x < self.size_y)
  731. {
  732. if(self.size_x < self.size_z)
  733. self.pos2 = '1 0 0';
  734. else
  735. self.pos2 = '0 0 1';
  736. }
  737. else
  738. {
  739. if(self.size_y < self.size_z)
  740. self.pos2 = '0 1 0';
  741. else
  742. self.pos2 = '0 0 1';
  743. }
  744. if(self.spawnflags & SHELTER_FLIPPED) self.pos2 *= -1;
  745. };
  746. //============================================================================
  747. void() trigger_monsterjump_touch =
  748. {
  749. if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER )
  750. return;
  751. // yoder add, July 10th 2020
  752. // making it so trigger_monsterjump's with spawnflag 8 only affect mosnters with spawnflag 8
  753. if (self.spawnflags & 64)
  754. {
  755. if (!(other.spawnflags & 64))
  756. {
  757. dprint("monster didn't have spawnflag 8!\n");
  758. return;
  759. }
  760. }
  761. // set XY even if not on ground, so the jump will clear lips
  762. other.velocity_x = self.movedir_x * self.speed;
  763. other.velocity_y = self.movedir_y * self.speed;
  764. if ( !(other.flags & FL_ONGROUND) )
  765. return;
  766. other.flags = other.flags - FL_ONGROUND;
  767. other.velocity_z = self.height;
  768. };
  769. /*QUAKED trigger_monsterjump (.5 .5 .5) ?
  770. Walking monsters that touch this will jump in the direction of the trigger's angle
  771. "speed" default to 200, the speed thrown forward
  772. "height" default to 200, the speed thrown upwards
  773. */
  774. void() trigger_monsterjump =
  775. {
  776. if (!self.speed)
  777. self.speed = 200;
  778. if (!self.height)
  779. self.height = 200;
  780. if (self.angles == '0 0 0')
  781. self.angles = '0 360 0';
  782. InitTrigger ();
  783. self.touch = trigger_monsterjump_touch;
  784. };
  785. //============================================================================
  786. const float NO_DAMAGE = 1;
  787. void() trigger_explosion_activate =
  788. {
  789. self.delay = 0;
  790. SUB_UseTargets ();
  791. if (!(self.spawnflags & NO_DAMAGE))
  792. T_RadiusDamage (self, self.owner, 120, self);
  793. WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
  794. WriteByte (MSG_BROADCAST, TE_EXPLOSION);
  795. WriteCoord (MSG_BROADCAST, self.origin_x);
  796. WriteCoord (MSG_BROADCAST, self.origin_y);
  797. WriteCoord (MSG_BROADCAST, self.origin_z);
  798. BecomeExplosion();
  799. }
  800. void() trigger_explosion_use =
  801. {
  802. if(self.delay > 0)
  803. {
  804. self.think = trigger_explosion_activate;
  805. self.nextthink = time + self.delay;
  806. }
  807. else
  808. {
  809. trigger_explosion_activate();
  810. }
  811. }
  812. void() trigger_explosion =
  813. {
  814. INHIBIT_COOP
  815. if (RemovedOutsideCoop()) return;
  816. self.use = trigger_explosion_use;
  817. };
  818. // ======================================================
  819. float REPEATER_ON = 1;
  820. void() repeater_think =
  821. {
  822. SUB_UseTargets();
  823. self.nextthink = time + self.wait + (self.pausetime * random());
  824. };
  825. void() repeater_use =
  826. {
  827. if (self.spawnflags & REPEATER_ON)
  828. {
  829. // turn off
  830. self.spawnflags = self.spawnflags - REPEATER_ON;
  831. self.nextthink = 0;
  832. self.think = SUB_Null;
  833. }
  834. else
  835. {
  836. // turn on
  837. self.spawnflags = self.spawnflags + REPEATER_ON;
  838. self.nextthink = time + self.wait + (self.pausetime * random());
  839. self.think = repeater_think;
  840. }
  841. };
  842. void() trigger_repeater =
  843. {
  844. INHIBIT_COOP
  845. if (RemovedOutsideCoop()) return;
  846. if (!self.wait)
  847. {
  848. self.wait = 1;
  849. }
  850. self.use = repeater_use;
  851. if (self.spawnflags & REPEATER_ON)
  852. {
  853. self.nextthink = time + self.wait + (self.pausetime * random());
  854. self.think = repeater_think;
  855. }
  856. else
  857. {
  858. self.nextthink = 0;
  859. self.think = SUB_Null;
  860. }
  861. };
  862. // ======================================================
  863. /*QUAKED trigger_multitouch (.5 .5 .5) ?
  864. A trigger that fires its targets on "first touch", "emptied" or both
  865. YODER Feb08, 2022
  866. */
  867. float IGNORE_FIRST_TOUCH = 16;
  868. float IGNORE_EMPTIED = 32;
  869. void() trigger_multitouch_think =
  870. {
  871. bprint("untouched\n");
  872. self.wait = 0;
  873. if (!(self.spawnflags & IGNORE_EMPTIED))
  874. SUB_UseTargets();
  875. };
  876. void() trigger_multitouch_touch =
  877. {
  878. if (other.classname != "player")
  879. return;
  880. if (other.health <= 0)
  881. return;
  882. if (self.wait == 0) // first touch?
  883. {
  884. self.wait = 1;
  885. bprint(" first touch\n");
  886. if (!(self.spawnflags & IGNORE_FIRST_TOUCH))
  887. SUB_UseTargets();
  888. }
  889. // think
  890. self.think = trigger_multitouch_think;
  891. self.nextthink = time + 0.2;
  892. };
  893. void() trigger_multitouch =
  894. {
  895. InitTrigger ();
  896. self.touch = trigger_multitouch_touch;
  897. self.wait = 0; // 0 is empty, 1 is touched
  898. };