grapple.qc 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. // Rogue Grapple Implementation
  16. // Jan'97 by ZOID <zoid@threewave.com>
  17. // Under contract to id software for Rogue Entertainment
  18. // New entity fields
  19. .entity hook; // this is my hook
  20. .float on_hook; // we're on it
  21. .float hook_out; // it's out
  22. // prototypes for WEAPONS.QC functions
  23. float() crandom;
  24. void(vector org, vector vel, float damage) SpawnBlood;
  25. void(entity h, entity player) GrappleTrail =
  26. {
  27. // draw a line to the hook
  28. WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
  29. WriteByte (MSG_BROADCAST, TE_BEAM);
  30. WriteEntity (MSG_BROADCAST, h);
  31. WriteCoord (MSG_BROADCAST, h.origin_x);
  32. WriteCoord (MSG_BROADCAST, h.origin_y);
  33. WriteCoord (MSG_BROADCAST, h.origin_z);
  34. WriteCoord (MSG_BROADCAST, player.origin_x);
  35. WriteCoord (MSG_BROADCAST, player.origin_y);
  36. WriteCoord (MSG_BROADCAST, player.origin_z + 16);
  37. };
  38. void() GrappleReset =
  39. {
  40. self.owner.on_hook = FALSE;
  41. self.owner.hook_out = FALSE;
  42. self.owner.weaponframe = 0;
  43. self.owner.attack_finished = time +0.25;
  44. remove(self);
  45. };
  46. void() GrappleTrack =
  47. {
  48. local vector spray;
  49. // Release dead targets
  50. if (self.enemy.classname == "player" && self.enemy.health <= 0)
  51. self.owner.on_hook = FALSE;
  52. // drop the hook if owner is dead or has released the button
  53. if (!self.owner.on_hook || self.owner.health <= 0) {
  54. GrappleReset();
  55. return;
  56. }
  57. if (self.enemy.classname == "player")
  58. {
  59. if (self.enemy.teleport_time > time)
  60. {
  61. GrappleReset();
  62. return;
  63. }
  64. // move the hook along with the player. It's invisible, but
  65. // we need this to make the sound come from the right spot
  66. setorigin(self, self.enemy.origin);
  67. // sound (self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM);
  68. sound (self, CHAN_WEAPON, "pendulum/hit.wav", 1, ATTN_NORM);
  69. T_Damage (self.enemy, self, self.owner, 1);
  70. makevectors (self.v_angle);
  71. spray_x = 100 * crandom();
  72. spray_y = 100 * crandom();
  73. spray_z = 100 * crandom() + 50;
  74. SpawnBlood(self.origin, spray, 20);
  75. }
  76. if (self.enemy.solid == SOLID_SLIDEBOX) {
  77. self.velocity = '0 0 0';
  78. setorigin(self, self.enemy.origin + self.enemy.mins +
  79. self.enemy.size * 0.5);
  80. } else
  81. self.velocity = self.enemy.velocity;
  82. self.nextthink = time + 0.1;
  83. };
  84. /*
  85. entity(float head) GrappleMakeLink =
  86. {
  87. newmis = spawn ();
  88. newmis.movetype = MOVETYPE_FLYMISSILE;
  89. newmis.solid = SOLID_NOT;
  90. newmis.owner = self;// SELF is the hook!
  91. newmis.avelocity = '200 200 200';
  92. setmodel (newmis, "progs/bit.mdl");
  93. setorigin (newmis, self.origin);
  94. setsize (newmis, '0 0 0' , '0 0 0');
  95. return newmis;
  96. };
  97. */
  98. // Removes all chain link entities; this is a separate function because CLIENT
  99. // also needs to be able to remove the chain. Only one function required to
  100. // remove all links.
  101. /*
  102. void () GrappleRemoveChain =
  103. {
  104. self.think = SUB_Remove;
  105. self.nextthink = time;
  106. if (self.goalentity) {
  107. self.goalentity.think = SUB_Remove;
  108. self.goalentity.nextthink = time;
  109. if (self.goalentity.goalentity) {
  110. self.goalentity.goalentity.think = SUB_Remove;
  111. self.goalentity.goalentity.nextthink = time;
  112. }
  113. }
  114. };
  115. */
  116. // Repositions the chain links each frame. This single function maintains the
  117. // positions of all of the links. Only one link is thinking every frame.
  118. /*
  119. void() GrappleUpdateChain =
  120. {
  121. local vector temp;
  122. if (!self.owner.hook_out) {
  123. GrappleRemoveChain();
  124. return;
  125. }
  126. temp = (self.owner.hook.origin - self.owner.origin);
  127. // These numbers are correct assuming 3 links.
  128. // 4 links would be *20 *40 *60 and *80
  129. setorigin (self, self.owner.origin + temp * 0.25);
  130. setorigin (self.goalentity, self.owner.origin + temp * 0.5);
  131. setorigin (self.goalentity.goalentity, self.owner.origin + temp * 0.75);
  132. self.nextthink = time + 0.1;
  133. };
  134. */
  135. //
  136. // Build_Chain - Builds the chain (linked list)
  137. //
  138. /*
  139. void() GrappleBuildChain =
  140. {
  141. self.goalentity = GrappleMakeLink();
  142. self.goalentity.think = GrappleUpdateChain;
  143. self.goalentity.nextthink = time + 0.1;
  144. self.goalentity.owner = self.owner;
  145. self.goalentity.goalentity = GrappleMakeLink();
  146. self.goalentity.goalentity.goalentity = GrappleMakeLink();
  147. };
  148. */
  149. // Tries to anchor the grapple to whatever it touches
  150. void () GrappleAnchor =
  151. {
  152. if (other == self.owner) // don't hook the guy that fired it
  153. return;
  154. if (pointcontents(self.origin) == CONTENT_SKY) {
  155. GrappleReset();
  156. return;
  157. }
  158. if (other.classname == "player")
  159. {
  160. // glance off of teammates
  161. if (other.steam == self.owner.steam)
  162. {
  163. GrappleReset(); // PGM - fix drift after teammate hit 01/20/97
  164. return;
  165. }
  166. sound (self, CHAN_WEAPON, "player/axhit1.wav", 1, ATTN_NORM);
  167. T_Damage (other, self, self.owner, 10);
  168. } else {
  169. sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);
  170. // One point of damage inflicted upon impact. Subsequent
  171. // damage will only be done to PLAYERS... this way secret
  172. // doors and triggers will only be damaged once.
  173. if (other.takedamage)
  174. T_Damage (other, self, self.owner, 1);
  175. self.velocity = '0 0 0';
  176. self.avelocity = '0 0 0';
  177. }
  178. self.frame = 2; // anchored
  179. sound (self.owner, CHAN_WEAPON, "weapons/tink1.wav", 1, ATTN_NORM);
  180. if (!self.owner.button0) {
  181. GrappleReset();
  182. return;
  183. }
  184. self.owner.on_hook = TRUE;
  185. if (self.owner.flags & FL_ONGROUND)
  186. self.owner.flags = self.owner.flags - FL_ONGROUND;
  187. // CHAIN2 is a looping sample. Use LEFTY as a flag so that client.qc
  188. // will know to only play the tink sound ONCE to clear the weapons
  189. // sound channel. (Lefty is a leftover from AI.QC, so I reused it to
  190. // avoid adding a field)
  191. self.owner.lefty = TRUE;
  192. self.enemy = other;// remember this guy!
  193. self.think = GrappleTrack;
  194. self.nextthink = time;
  195. self.solid = SOLID_NOT;
  196. self.touch = SUB_Null;
  197. };
  198. void () W_FireGrapple =
  199. {
  200. if (self.hook_out)// reject subsequent calls from player.qc
  201. return;
  202. self.punchangle_x = -2; // bump him
  203. // chain out sound (loops)
  204. sound (self, CHAN_WEAPON, "weapons/chain1.wav", 1, ATTN_NORM);
  205. newmis = spawn();
  206. newmis.movetype = MOVETYPE_FLYMISSILE;
  207. newmis.solid = SOLID_BBOX;
  208. newmis.owner = self; // newmis belongs to me
  209. self.hook = newmis; // This is my newmis
  210. newmis.classname = "hook";
  211. makevectors (self.v_angle);
  212. newmis.velocity = v_forward * 800;
  213. // newmis.avelocity = '0 0 -500';
  214. newmis.angles = vectoangles(v_forward);
  215. newmis.touch = GrappleAnchor;
  216. newmis.think = GrappleReset;
  217. // grapple only lives for two seconds, this gives max range on it
  218. newmis.nextthink = time + 2;
  219. newmis.frame = 1; // hook spread
  220. setmodel (newmis,"progs/hook.mdl");
  221. setorigin (newmis, self.origin + v_forward * 16 + '0 0 16');
  222. setsize(newmis, '0 0 0' , '0 0 0 ');
  223. self.hook_out = TRUE;
  224. };
  225. // called each frame by CLIENT.QC if client has hook_out
  226. void() GrappleService =
  227. {
  228. local vector o, dist, vel;
  229. local float v;
  230. if (!self.on_hook) {
  231. // just draw a line to the hook
  232. if (vlen(self.hook.origin - self.origin) > 50)
  233. GrappleTrail(self.hook, self);
  234. return;
  235. }
  236. // drop the hook if player lets go of button
  237. if ((!self.button0 && self.weapon == IT_GRAPPLE) ||
  238. self.teleport_time > time)
  239. { // release when we get 'ported
  240. self = self.hook;
  241. GrappleReset();
  242. return;
  243. }
  244. makevectors (self.angles);
  245. dist = self.hook.origin - self.origin;
  246. vel = self.hook.origin - ( self.origin + (v_up * 16 *
  247. (!self.button2)) + (v_forward * 16));
  248. v = vlen (vel);
  249. if (v <= 100)
  250. vel = normalize(vel) * v * 10;
  251. else
  252. vel = normalize(vel) * 1000;
  253. self.velocity = vel;
  254. if ( vlen(dist) <= 50) {
  255. if (self.lefty) { // cancel chain sound
  256. // If there is a chain, ditch it now. We're
  257. // close enough. Having extra entities lying around
  258. // is never a good idea.
  259. // if (self.hook.goalentity) {
  260. // self.hook.goalentity.think = GrappleRemoveChain;
  261. // self.hook.goalentity.nextthink = time;
  262. // }
  263. self.lefty = FALSE;// we've reset the sound channel.
  264. }
  265. } else
  266. GrappleTrail(self.hook, self);
  267. };