g_xatrix_weapon.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "../g_local.h"
  4. // RAFAEL
  5. void fire_blueblaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, effects_t effect)
  6. {
  7. edict_t *bolt;
  8. trace_t tr;
  9. bolt = G_Spawn();
  10. bolt->s.origin = start;
  11. bolt->s.old_origin = start;
  12. bolt->s.angles = vectoangles(dir);
  13. bolt->velocity = dir * speed;
  14. bolt->svflags |= SVF_PROJECTILE;
  15. bolt->movetype = MOVETYPE_FLYMISSILE;
  16. bolt->flags |= FL_DODGE;
  17. bolt->clipmask = MASK_PROJECTILE;
  18. bolt->solid = SOLID_BBOX;
  19. bolt->s.effects |= effect;
  20. bolt->s.modelindex = gi.modelindex("models/objects/laser/tris.md2");
  21. bolt->s.skinnum = 1;
  22. bolt->s.sound = gi.soundindex("misc/lasfly.wav");
  23. bolt->owner = self;
  24. bolt->touch = blaster_touch;
  25. bolt->nextthink = level.time + 2_sec;
  26. bolt->think = G_FreeEdict;
  27. bolt->dmg = damage;
  28. bolt->classname = "bolt";
  29. bolt->style = MOD_BLUEBLASTER;
  30. gi.linkentity(bolt);
  31. tr = gi.traceline(self->s.origin, bolt->s.origin, bolt, bolt->clipmask);
  32. if (tr.fraction < 1.0f)
  33. {
  34. bolt->s.origin = tr.endpos + (tr.plane.normal * 1.f);
  35. bolt->touch(bolt, tr.ent, tr, false);
  36. }
  37. }
  38. // RAFAEL
  39. /*
  40. fire_ionripper
  41. */
  42. THINK(ionripper_sparks) (edict_t *self) -> void
  43. {
  44. gi.WriteByte(svc_temp_entity);
  45. gi.WriteByte(TE_WELDING_SPARKS);
  46. gi.WriteByte(0);
  47. gi.WritePosition(self->s.origin);
  48. gi.WriteDir(vec3_origin);
  49. gi.WriteByte(irandom(0xe4, 0xe8));
  50. gi.multicast(self->s.origin, MULTICAST_PVS, false);
  51. G_FreeEdict(self);
  52. }
  53. // RAFAEL
  54. TOUCH(ionripper_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  55. {
  56. if (other == self->owner)
  57. return;
  58. if (tr.surface && (tr.surface->flags & SURF_SKY))
  59. {
  60. G_FreeEdict(self);
  61. return;
  62. }
  63. if (self->owner->client)
  64. PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
  65. if (other->takedamage)
  66. {
  67. T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_ENERGY, MOD_RIPPER);
  68. }
  69. else
  70. {
  71. return;
  72. }
  73. G_FreeEdict(self);
  74. }
  75. // RAFAEL
  76. void fire_ionripper(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, effects_t effect)
  77. {
  78. edict_t *ion;
  79. trace_t tr;
  80. ion = G_Spawn();
  81. ion->s.origin = start;
  82. ion->s.old_origin = start;
  83. ion->s.angles = vectoangles(dir);
  84. ion->velocity = dir * speed;
  85. ion->movetype = MOVETYPE_WALLBOUNCE;
  86. ion->clipmask = MASK_PROJECTILE;
  87. // [Paril-KEX]
  88. if (self->client && !G_ShouldPlayersCollide(true))
  89. ion->clipmask &= ~CONTENTS_PLAYER;
  90. ion->solid = SOLID_BBOX;
  91. ion->s.effects |= effect;
  92. ion->svflags |= SVF_PROJECTILE;
  93. ion->flags |= FL_DODGE;
  94. ion->s.renderfx |= RF_FULLBRIGHT;
  95. ion->s.modelindex = gi.modelindex("models/objects/boomrang/tris.md2");
  96. ion->s.sound = gi.soundindex("misc/lasfly.wav");
  97. ion->owner = self;
  98. ion->touch = ionripper_touch;
  99. ion->nextthink = level.time + 3_sec;
  100. ion->think = ionripper_sparks;
  101. ion->dmg = damage;
  102. ion->dmg_radius = 100;
  103. gi.linkentity(ion);
  104. tr = gi.traceline(self->s.origin, ion->s.origin, ion, ion->clipmask);
  105. if (tr.fraction < 1.0f)
  106. {
  107. ion->s.origin = tr.endpos + (tr.plane.normal * 1.f);
  108. ion->touch(ion, tr.ent, tr, false);
  109. }
  110. }
  111. // RAFAEL
  112. /*
  113. fire_heat
  114. */
  115. THINK(heat_think) (edict_t *self) -> void
  116. {
  117. edict_t *target = nullptr;
  118. edict_t *acquire = nullptr;
  119. vec3_t vec;
  120. vec3_t oldang;
  121. float len;
  122. float oldlen = 0;
  123. float dot, olddot = 1;
  124. vec3_t fwd = AngleVectors(self->s.angles).forward;
  125. // acquire new target
  126. while ((target = findradius(target, self->s.origin, 1024)) != nullptr)
  127. {
  128. if (self->owner == target)
  129. continue;
  130. if (!target->client)
  131. continue;
  132. if (target->health <= 0)
  133. continue;
  134. if (!visible(self, target))
  135. continue;
  136. //if (!infront(self, target))
  137. // continue;
  138. vec = self->s.origin - target->s.origin;
  139. len = vec.length();
  140. dot = vec.normalized().dot(fwd);
  141. // targets that require us to turn less are preferred
  142. if (dot >= olddot)
  143. continue;
  144. if (acquire == nullptr || dot < olddot || len < oldlen)
  145. {
  146. acquire = target;
  147. oldlen = len;
  148. olddot = dot;
  149. }
  150. }
  151. if (acquire != nullptr)
  152. {
  153. oldang = self->s.angles;
  154. vec = (acquire->s.origin - self->s.origin).normalized();
  155. float t = self->accel;
  156. float d = self->movedir.dot(vec);
  157. if (d < 0.45f && d > -0.45f)
  158. vec = -vec;
  159. self->movedir = slerp(self->movedir, vec, t).normalized();
  160. self->s.angles = vectoangles(self->movedir);
  161. if (!self->enemy)
  162. {
  163. gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/railgr1a.wav"), 1.f, 0.25f, 0);
  164. self->enemy = acquire;
  165. }
  166. }
  167. else
  168. self->enemy = nullptr;
  169. self->velocity = self->movedir * self->speed;
  170. self->nextthink = level.time + FRAME_TIME_MS;
  171. }
  172. // RAFAEL
  173. void fire_heat(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, int radius_damage, float turn_fraction)
  174. {
  175. edict_t *heat;
  176. heat = G_Spawn();
  177. heat->s.origin = start;
  178. heat->movedir = dir;
  179. heat->s.angles = vectoangles(dir);
  180. heat->velocity = dir * speed;
  181. heat->flags |= FL_DODGE;
  182. heat->movetype = MOVETYPE_FLYMISSILE;
  183. heat->svflags |= SVF_PROJECTILE;
  184. heat->clipmask = MASK_PROJECTILE;
  185. heat->solid = SOLID_BBOX;
  186. heat->s.effects |= EF_ROCKET;
  187. heat->s.modelindex = gi.modelindex("models/objects/rocket/tris.md2");
  188. heat->owner = self;
  189. heat->touch = rocket_touch;
  190. heat->speed = speed;
  191. heat->accel = turn_fraction;
  192. heat->nextthink = level.time + FRAME_TIME_MS;
  193. heat->think = heat_think;
  194. heat->dmg = damage;
  195. heat->radius_dmg = radius_damage;
  196. heat->dmg_radius = damage_radius;
  197. heat->s.sound = gi.soundindex("weapons/rockfly.wav");
  198. gi.linkentity(heat);
  199. }
  200. // RAFAEL
  201. /*
  202. fire_plasma
  203. */
  204. TOUCH(plasma_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  205. {
  206. vec3_t origin;
  207. if (other == ent->owner)
  208. return;
  209. if (tr.surface && (tr.surface->flags & SURF_SKY))
  210. {
  211. G_FreeEdict(ent);
  212. return;
  213. }
  214. if (ent->owner->client)
  215. PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
  216. // calculate position for the explosion entity
  217. origin = ent->s.origin + (ent->velocity * -0.02f);
  218. if (other->takedamage)
  219. {
  220. T_Damage(other, ent, ent->owner, ent->velocity, ent->s.origin, tr.plane.normal, ent->dmg, 0, DAMAGE_ENERGY, MOD_PHALANX);
  221. }
  222. T_RadiusDamage(ent, ent->owner, (float) ent->radius_dmg, other, ent->dmg_radius, DAMAGE_ENERGY, MOD_PHALANX);
  223. gi.WriteByte(svc_temp_entity);
  224. gi.WriteByte(TE_PLASMA_EXPLOSION);
  225. gi.WritePosition(origin);
  226. gi.multicast(ent->s.origin, MULTICAST_PHS, false);
  227. G_FreeEdict(ent);
  228. }
  229. // RAFAEL
  230. void fire_plasma(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, int radius_damage)
  231. {
  232. edict_t *plasma;
  233. plasma = G_Spawn();
  234. plasma->s.origin = start;
  235. plasma->movedir = dir;
  236. plasma->s.angles = vectoangles(dir);
  237. plasma->velocity = dir * speed;
  238. plasma->movetype = MOVETYPE_FLYMISSILE;
  239. plasma->clipmask = MASK_PROJECTILE;
  240. // [Paril-KEX]
  241. if (self->client && !G_ShouldPlayersCollide(true))
  242. plasma->clipmask &= ~CONTENTS_PLAYER;
  243. plasma->solid = SOLID_BBOX;
  244. plasma->svflags |= SVF_PROJECTILE;
  245. plasma->flags |= FL_DODGE;
  246. plasma->owner = self;
  247. plasma->touch = plasma_touch;
  248. plasma->nextthink = level.time + gtime_t::from_sec(8000.f / speed);
  249. plasma->think = G_FreeEdict;
  250. plasma->dmg = damage;
  251. plasma->radius_dmg = radius_damage;
  252. plasma->dmg_radius = damage_radius;
  253. plasma->s.sound = gi.soundindex("weapons/rockfly.wav");
  254. plasma->s.modelindex = gi.modelindex("sprites/s_photon.sp2");
  255. plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST;
  256. gi.linkentity(plasma);
  257. }
  258. THINK(Trap_Gib_Think) (edict_t *ent) -> void
  259. {
  260. if (ent->owner->s.frame != 5)
  261. {
  262. G_FreeEdict(ent);
  263. return;
  264. }
  265. vec3_t forward, right, up;
  266. vec3_t vec;
  267. AngleVectors(ent->owner->s.angles, forward, right, up);
  268. // rotate us around the center
  269. float degrees = (150.f * gi.frame_time_s) + ent->owner->delay;
  270. vec3_t diff = ent->owner->s.origin - ent->s.origin;
  271. vec = RotatePointAroundVector(up, diff, degrees);
  272. ent->s.angles[1] += degrees;
  273. vec3_t new_origin = ent->owner->s.origin - vec;
  274. trace_t tr = gi.traceline(ent->s.origin, new_origin, ent, MASK_SOLID);
  275. ent->s.origin = tr.endpos;
  276. // pull us towards the trap's center
  277. diff.normalize();
  278. ent->s.origin += diff * (15.0f * gi.frame_time_s);
  279. ent->watertype = gi.pointcontents(ent->s.origin);
  280. if (ent->watertype & MASK_WATER)
  281. ent->waterlevel = WATER_FEET;
  282. ent->nextthink = level.time + FRAME_TIME_S;
  283. gi.linkentity(ent);
  284. }
  285. DIE(trap_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  286. {
  287. BecomeExplosion1(self);
  288. }
  289. // RAFAEL
  290. void SP_item_foodcube(edict_t *best);
  291. void SpawnDamage(int type, const vec3_t &origin, const vec3_t &normal, int damage);
  292. // RAFAEL
  293. THINK(Trap_Think) (edict_t *ent) -> void
  294. {
  295. edict_t *target = nullptr;
  296. edict_t *best = nullptr;
  297. vec3_t vec;
  298. float len;
  299. float oldlen = 8000;
  300. if (ent->timestamp < level.time)
  301. {
  302. BecomeExplosion1(ent);
  303. // note to self
  304. // cause explosion damage???
  305. return;
  306. }
  307. ent->nextthink = level.time + 10_hz;
  308. if (!ent->groundentity)
  309. return;
  310. // ok lets do the blood effect
  311. if (ent->s.frame > 4)
  312. {
  313. if (ent->s.frame == 5)
  314. {
  315. bool spawn = ent->wait == 64;
  316. ent->wait -= 2;
  317. if (spawn)
  318. gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/trapdown.wav"), 1, ATTN_IDLE, 0);
  319. ent->delay += 2.f;
  320. if (ent->wait < 19)
  321. ent->s.frame++;
  322. return;
  323. }
  324. ent->s.frame++;
  325. if (ent->s.frame == 8)
  326. {
  327. ent->nextthink = level.time + 1_sec;
  328. ent->think = G_FreeEdict;
  329. ent->s.effects &= ~EF_TRAP;
  330. best = G_Spawn();
  331. best->count = ent->mass;
  332. best->s.scale = 1.f + ((ent->accel - 100.f) / 300.f) * 1.0f;
  333. SP_item_foodcube(best);
  334. best->s.origin = ent->s.origin;
  335. best->s.origin[2] += 24 * best->s.scale;
  336. best->s.angles[YAW] = frandom() * 360;
  337. best->velocity[2] = 400;
  338. best->think(best);
  339. best->nextthink = 0_ms;
  340. best->s.old_origin = best->s.origin;
  341. gi.linkentity(best);
  342. gi.sound(best, CHAN_AUTO, gi.soundindex("misc/fhit3.wav"), 1.f, ATTN_NORM, 0.f);
  343. return;
  344. }
  345. return;
  346. }
  347. ent->s.effects &= ~EF_TRAP;
  348. if (ent->s.frame >= 4)
  349. {
  350. ent->s.effects |= EF_TRAP;
  351. // clear the owner if in deathmatch
  352. if (deathmatch->integer)
  353. ent->owner = nullptr;
  354. }
  355. if (ent->s.frame < 4)
  356. {
  357. ent->s.frame++;
  358. return;
  359. }
  360. while ((target = findradius(target, ent->s.origin, 256)) != nullptr)
  361. {
  362. if (target == ent)
  363. continue;
  364. // [Paril-KEX] don't allow traps to be placed near flags or teleporters
  365. // if it's a monster or player with health > 0
  366. // or it's a player start point
  367. // and we can see it
  368. // blow up
  369. if (target->classname && ((deathmatch->integer &&
  370. ((!strncmp(target->classname, "info_player_", 12)) ||
  371. (!strcmp(target->classname, "misc_teleporter_dest")) ||
  372. (!strncmp(target->classname, "item_flag_", 10))))) &&
  373. (visible(target, ent)))
  374. {
  375. BecomeExplosion1(ent);
  376. return;
  377. }
  378. if (!(target->svflags & SVF_MONSTER) && !target->client)
  379. continue;
  380. if (target != ent->teammaster && CheckTeamDamage(target, ent->teammaster))
  381. continue;
  382. // [Paril-KEX]
  383. if (!deathmatch->integer && target->client)
  384. continue;
  385. if (target->health <= 0)
  386. continue;
  387. if (!visible(ent, target))
  388. continue;
  389. vec = ent->s.origin - target->s.origin;
  390. len = vec.length();
  391. if (!best)
  392. {
  393. best = target;
  394. oldlen = len;
  395. continue;
  396. }
  397. if (len < oldlen)
  398. {
  399. oldlen = len;
  400. best = target;
  401. }
  402. }
  403. // pull the enemy in
  404. if (best)
  405. {
  406. if (best->groundentity)
  407. {
  408. best->s.origin[2] += 1;
  409. best->groundentity = nullptr;
  410. }
  411. vec = ent->s.origin - best->s.origin;
  412. len = vec.normalize();
  413. float max_speed = best->client ? 290.f : 150.f;
  414. best->velocity += (vec * clamp(max_speed - len, 64.f, max_speed));
  415. ent->s.sound = gi.soundindex("weapons/trapsuck.wav");
  416. if (len < 48)
  417. {
  418. if (best->mass < 400)
  419. {
  420. ent->takedamage = false;
  421. ent->solid = SOLID_NOT;
  422. ent->die = nullptr;
  423. T_Damage(best, ent, ent->teammaster, vec3_origin, best->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_TRAP);
  424. if (best->svflags & SVF_MONSTER)
  425. M_ProcessPain(best);
  426. ent->enemy = best;
  427. ent->wait = 64;
  428. ent->s.old_origin = ent->s.origin;
  429. ent->timestamp = level.time + 30_sec;
  430. ent->accel = best->mass;
  431. if (deathmatch->integer)
  432. ent->mass = best->mass / 4;
  433. else
  434. ent->mass = best->mass / 10;
  435. // ok spawn the food cube
  436. ent->s.frame = 5;
  437. // link up any gibs that this monster may have spawned
  438. for (uint32_t i = 0; i < globals.num_edicts; i++)
  439. {
  440. edict_t *e = &g_edicts[i];
  441. if (!e->inuse)
  442. continue;
  443. else if (strcmp(e->classname, "gib"))
  444. continue;
  445. else if ((e->s.origin - ent->s.origin).length() > 128.f)
  446. continue;
  447. e->movetype = MOVETYPE_NONE;
  448. e->nextthink = level.time + FRAME_TIME_S;
  449. e->think = Trap_Gib_Think;
  450. e->owner = ent;
  451. Trap_Gib_Think(e);
  452. }
  453. }
  454. else
  455. {
  456. BecomeExplosion1(ent);
  457. // note to self
  458. // cause explosion damage???
  459. return;
  460. }
  461. }
  462. }
  463. }
  464. // RAFAEL
  465. void fire_trap(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int speed)
  466. {
  467. edict_t *trap;
  468. vec3_t dir;
  469. vec3_t forward, right, up;
  470. dir = vectoangles(aimdir);
  471. AngleVectors(dir, forward, right, up);
  472. trap = G_Spawn();
  473. trap->s.origin = start;
  474. trap->velocity = aimdir * speed;
  475. float gravityAdjustment = level.gravity / 800.f;
  476. trap->velocity += up * (200 + crandom() * 10.0f) * gravityAdjustment;
  477. trap->velocity += right * (crandom() * 10.0f);
  478. trap->avelocity = { 0, 300, 0 };
  479. trap->movetype = MOVETYPE_BOUNCE;
  480. trap->solid = SOLID_BBOX;
  481. trap->takedamage = true;
  482. trap->mins = { -4, -4, 0 };
  483. trap->maxs = { 4, 4, 8 };
  484. trap->die = trap_die;
  485. trap->health = 20;
  486. trap->s.modelindex = gi.modelindex("models/weapons/z_trap/tris.md2");
  487. trap->owner = trap->teammaster = self;
  488. trap->nextthink = level.time + 1_sec;
  489. trap->think = Trap_Think;
  490. trap->classname = "food_cube_trap";
  491. // RAFAEL 16-APR-98
  492. trap->s.sound = gi.soundindex("weapons/traploop.wav");
  493. // END 16-APR-98
  494. trap->flags |= ( FL_DAMAGEABLE | FL_MECHANICAL | FL_TRAP );
  495. trap->clipmask = MASK_PROJECTILE & ~CONTENTS_DEADMONSTER;
  496. // [Paril-KEX]
  497. if (self->client && !G_ShouldPlayersCollide(true))
  498. trap->clipmask &= ~CONTENTS_PLAYER;
  499. gi.linkentity(trap);
  500. trap->timestamp = level.time + 30_sec;
  501. }