g_rogue_sphere.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. // g_sphere.c
  4. // pmack
  5. // april 1998
  6. // defender - actively finds and shoots at enemies
  7. // hunter - waits until < 25% health and vore ball tracks person who hurt you
  8. // vengeance - kills person who killed you.
  9. #include "../g_local.h"
  10. constexpr gtime_t DEFENDER_LIFESPAN = 30_sec;
  11. constexpr gtime_t HUNTER_LIFESPAN = 30_sec;
  12. constexpr gtime_t VENGEANCE_LIFESPAN = 30_sec;
  13. constexpr gtime_t MINIMUM_FLY_TIME = 15_sec;
  14. void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker);
  15. void vengeance_touch(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self);
  16. void hunter_touch(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self);
  17. // *************************
  18. // General Sphere Code
  19. // *************************
  20. // =================
  21. // =================
  22. THINK(sphere_think_explode) (edict_t *self) -> void
  23. {
  24. if (self->owner && self->owner->client && !(self->spawnflags & SPHERE_DOPPLEGANGER))
  25. {
  26. self->owner->client->owned_sphere = nullptr;
  27. }
  28. BecomeExplosion1(self);
  29. }
  30. // =================
  31. // sphere_explode
  32. // =================
  33. DIE(sphere_explode) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  34. {
  35. sphere_think_explode(self);
  36. }
  37. // =================
  38. // sphere_if_idle_die - if the sphere is not currently attacking, blow up.
  39. // =================
  40. DIE(sphere_if_idle_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  41. {
  42. if (!self->enemy)
  43. sphere_think_explode(self);
  44. }
  45. // *************************
  46. // Sphere Movement
  47. // *************************
  48. // =================
  49. // =================
  50. void sphere_fly(edict_t *self)
  51. {
  52. vec3_t dest;
  53. vec3_t dir;
  54. if (level.time >= gtime_t::from_sec(self->wait))
  55. {
  56. sphere_think_explode(self);
  57. return;
  58. }
  59. dest = self->owner->s.origin;
  60. dest[2] = self->owner->absmax[2] + 4;
  61. if (level.time.seconds() == level.time.seconds<int>())
  62. {
  63. if (!visible(self, self->owner))
  64. {
  65. self->s.origin = dest;
  66. gi.linkentity(self);
  67. return;
  68. }
  69. }
  70. dir = dest - self->s.origin;
  71. self->velocity = dir * 5;
  72. }
  73. // =================
  74. // =================
  75. void sphere_chase(edict_t *self, int stupidChase)
  76. {
  77. vec3_t dest;
  78. vec3_t dir;
  79. float dist;
  80. if (level.time >= gtime_t::from_sec(self->wait) || (self->enemy && self->enemy->health < 1))
  81. {
  82. sphere_think_explode(self);
  83. return;
  84. }
  85. dest = self->enemy->s.origin;
  86. if (self->enemy->client)
  87. dest[2] += self->enemy->viewheight;
  88. if (visible(self, self->enemy) || stupidChase)
  89. {
  90. // if moving, hunter sphere uses active sound
  91. if (!stupidChase)
  92. self->s.sound = gi.soundindex("spheres/h_active.wav");
  93. dir = dest - self->s.origin;
  94. dir.normalize();
  95. self->s.angles = vectoangles(dir);
  96. self->velocity = dir * 500;
  97. self->monsterinfo.saved_goal = dest;
  98. }
  99. else if (!self->monsterinfo.saved_goal)
  100. {
  101. dir = self->enemy->s.origin - self->s.origin;
  102. dist = dir.normalize();
  103. self->s.angles = vectoangles(dir);
  104. // if lurking, hunter sphere uses lurking sound
  105. self->s.sound = gi.soundindex("spheres/h_lurk.wav");
  106. self->velocity = {};
  107. }
  108. else
  109. {
  110. dir = self->monsterinfo.saved_goal - self->s.origin;
  111. dist = dir.normalize();
  112. if (dist > 1)
  113. {
  114. self->s.angles = vectoangles(dir);
  115. if (dist > 500)
  116. self->velocity = dir * 500;
  117. else if (dist < 20)
  118. self->velocity = dir * (dist / gi.frame_time_s);
  119. else
  120. self->velocity = dir * dist;
  121. // if moving, hunter sphere uses active sound
  122. if (!stupidChase)
  123. self->s.sound = gi.soundindex("spheres/h_active.wav");
  124. }
  125. else
  126. {
  127. dir = self->enemy->s.origin - self->s.origin;
  128. dist = dir.normalize();
  129. self->s.angles = vectoangles(dir);
  130. // if not moving, hunter sphere uses lurk sound
  131. if (!stupidChase)
  132. self->s.sound = gi.soundindex("spheres/h_lurk.wav");
  133. self->velocity = {};
  134. }
  135. }
  136. }
  137. // *************************
  138. // Attack related stuff
  139. // *************************
  140. // =================
  141. // =================
  142. void sphere_fire(edict_t *self, edict_t *enemy)
  143. {
  144. vec3_t dest;
  145. vec3_t dir;
  146. if (!enemy || level.time >= gtime_t::from_sec(self->wait))
  147. {
  148. sphere_think_explode(self);
  149. return;
  150. }
  151. dest = enemy->s.origin;
  152. self->s.effects |= EF_ROCKET;
  153. dir = dest - self->s.origin;
  154. dir.normalize();
  155. self->s.angles = vectoangles(dir);
  156. self->velocity = dir * 1000;
  157. self->touch = vengeance_touch;
  158. self->think = sphere_think_explode;
  159. self->nextthink = gtime_t::from_sec(self->wait);
  160. }
  161. // =================
  162. // =================
  163. void sphere_touch(edict_t *self, edict_t *other, const trace_t &tr, mod_t mod)
  164. {
  165. if (self->spawnflags.has(SPHERE_DOPPLEGANGER))
  166. {
  167. if (other == self->teammaster)
  168. return;
  169. self->takedamage = false;
  170. self->owner = self->teammaster;
  171. self->teammaster = nullptr;
  172. }
  173. else
  174. {
  175. if (other == self->owner)
  176. return;
  177. // PMM - don't blow up on bodies
  178. if (!strcmp(other->classname, "bodyque"))
  179. return;
  180. }
  181. if (tr.surface && (tr.surface->flags & SURF_SKY))
  182. {
  183. G_FreeEdict(self);
  184. return;
  185. }
  186. if (self->owner)
  187. {
  188. if (other->takedamage)
  189. {
  190. T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal,
  191. 10000, 1, DAMAGE_DESTROY_ARMOR, mod);
  192. }
  193. else
  194. {
  195. T_RadiusDamage(self, self->owner, 512, self->owner, 256, DAMAGE_NONE, mod);
  196. }
  197. }
  198. sphere_think_explode(self);
  199. }
  200. // =================
  201. // =================
  202. TOUCH(vengeance_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  203. {
  204. if (self->spawnflags.has(SPHERE_DOPPLEGANGER))
  205. sphere_touch(self, other, tr, MOD_DOPPLE_VENGEANCE);
  206. else
  207. sphere_touch(self, other, tr, MOD_VENGEANCE_SPHERE);
  208. }
  209. // =================
  210. // =================
  211. TOUCH(hunter_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  212. {
  213. edict_t *owner;
  214. // don't blow up if you hit the world.... sheesh.
  215. if (other == world)
  216. return;
  217. if (self->owner)
  218. {
  219. // if owner is flying with us, make sure they stop too.
  220. owner = self->owner;
  221. if (owner->flags & FL_SAM_RAIMI)
  222. {
  223. owner->velocity = {};
  224. owner->movetype = MOVETYPE_NONE;
  225. gi.linkentity(owner);
  226. }
  227. }
  228. if (self->spawnflags.has(SPHERE_DOPPLEGANGER))
  229. sphere_touch(self, other, tr, MOD_DOPPLE_HUNTER);
  230. else
  231. sphere_touch(self, other, tr, MOD_HUNTER_SPHERE);
  232. }
  233. // =================
  234. // =================
  235. void defender_shoot(edict_t *self, edict_t *enemy)
  236. {
  237. vec3_t dir;
  238. vec3_t start;
  239. if (!(enemy->inuse) || enemy->health <= 0)
  240. return;
  241. if (enemy == self->owner)
  242. return;
  243. dir = enemy->s.origin - self->s.origin;
  244. dir.normalize();
  245. if (self->monsterinfo.attack_finished > level.time)
  246. return;
  247. if (!visible(self, self->enemy))
  248. return;
  249. start = self->s.origin;
  250. start[2] += 2;
  251. fire_blaster2(self->owner, start, dir, 10, 1000, EF_BLASTER, 0);
  252. self->monsterinfo.attack_finished = level.time + 400_ms;
  253. }
  254. // *************************
  255. // Activation Related Stuff
  256. // *************************
  257. // =================
  258. // =================
  259. void body_gib(edict_t *self)
  260. {
  261. gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  262. ThrowGibs(self, 50, {
  263. { 4, "models/objects/gibs/sm_meat/tris.md2" },
  264. { "models/objects/gibs/skull/tris.md2" }
  265. });
  266. }
  267. // =================
  268. // =================
  269. PAIN(hunter_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  270. {
  271. edict_t *owner;
  272. float dist;
  273. vec3_t dir;
  274. if (self->enemy)
  275. return;
  276. owner = self->owner;
  277. if (!(self->spawnflags & SPHERE_DOPPLEGANGER))
  278. {
  279. if (owner && (owner->health > 0))
  280. return;
  281. // PMM
  282. if (other == owner)
  283. return;
  284. // pmm
  285. }
  286. else
  287. {
  288. // if fired by a doppleganger, set it to 10 second timeout
  289. self->wait = (level.time + MINIMUM_FLY_TIME).seconds();
  290. }
  291. if ((gtime_t::from_sec(self->wait) - level.time) < MINIMUM_FLY_TIME)
  292. self->wait = (level.time + MINIMUM_FLY_TIME).seconds();
  293. self->s.effects |= EF_BLASTER | EF_TRACKER;
  294. self->touch = hunter_touch;
  295. self->enemy = other;
  296. // if we're not owned by a player, no sam raimi
  297. // if we're spawned by a doppleganger, no sam raimi
  298. if (self->spawnflags.has(SPHERE_DOPPLEGANGER) || !(owner && owner->client))
  299. return;
  300. // sam raimi cam is disabled if FORCE_RESPAWN is set.
  301. // sam raimi cam is also disabled if huntercam->value is 0.
  302. if (!g_dm_force_respawn->integer && huntercam->integer)
  303. {
  304. dir = other->s.origin - self->s.origin;
  305. dist = dir.length();
  306. if (owner && (dist >= 192))
  307. {
  308. // detach owner from body and send him flying
  309. owner->movetype = MOVETYPE_FLYMISSILE;
  310. // gib like we just died, even though we didn't, really.
  311. body_gib(owner);
  312. // move the sphere to the owner's current viewpoint.
  313. // we know it's a valid spot (or will be momentarily)
  314. self->s.origin = owner->s.origin;
  315. self->s.origin[2] += owner->viewheight;
  316. // move the player's origin to the sphere's new origin
  317. owner->s.origin = self->s.origin;
  318. owner->s.angles = self->s.angles;
  319. owner->client->v_angle = self->s.angles;
  320. owner->mins = { -5, -5, -5 };
  321. owner->maxs = { 5, 5, 5 };
  322. owner->client->ps.fov = 140;
  323. owner->s.modelindex = 0;
  324. owner->s.modelindex2 = 0;
  325. owner->viewheight = 8;
  326. owner->solid = SOLID_NOT;
  327. owner->flags |= FL_SAM_RAIMI;
  328. gi.linkentity(owner);
  329. self->solid = SOLID_BBOX;
  330. gi.linkentity(self);
  331. }
  332. }
  333. }
  334. // =================
  335. // =================
  336. PAIN(defender_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  337. {
  338. // PMM
  339. if (other == self->owner)
  340. return;
  341. // pmm
  342. self->enemy = other;
  343. }
  344. // =================
  345. // =================
  346. PAIN(vengeance_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  347. {
  348. if (self->enemy)
  349. return;
  350. if (!(self->spawnflags & SPHERE_DOPPLEGANGER))
  351. {
  352. if (self->owner && self->owner->health >= 25)
  353. return;
  354. // PMM
  355. if (other == self->owner)
  356. return;
  357. // pmm
  358. }
  359. else
  360. {
  361. self->wait = (level.time + MINIMUM_FLY_TIME).seconds();
  362. }
  363. if ((gtime_t::from_sec(self->wait) - level.time) < MINIMUM_FLY_TIME)
  364. self->wait = (level.time + MINIMUM_FLY_TIME).seconds();
  365. self->s.effects |= EF_ROCKET;
  366. self->touch = vengeance_touch;
  367. self->enemy = other;
  368. }
  369. // *************************
  370. // Think Functions
  371. // *************************
  372. // ===================
  373. // ===================
  374. THINK(defender_think) (edict_t *self) -> void
  375. {
  376. if (!self->owner)
  377. {
  378. G_FreeEdict(self);
  379. return;
  380. }
  381. // if we've exited the level, just remove ourselves.
  382. if (level.intermissiontime)
  383. {
  384. sphere_think_explode(self);
  385. return;
  386. }
  387. if (self->owner->health <= 0)
  388. {
  389. sphere_think_explode(self);
  390. return;
  391. }
  392. self->s.frame++;
  393. if (self->s.frame > 19)
  394. self->s.frame = 0;
  395. if (self->enemy)
  396. {
  397. if (self->enemy->health > 0)
  398. defender_shoot(self, self->enemy);
  399. else
  400. self->enemy = nullptr;
  401. }
  402. sphere_fly(self);
  403. if (self->inuse)
  404. self->nextthink = level.time + 10_hz;
  405. }
  406. // =================
  407. // =================
  408. THINK(hunter_think) (edict_t *self) -> void
  409. {
  410. // if we've exited the level, just remove ourselves.
  411. if (level.intermissiontime)
  412. {
  413. sphere_think_explode(self);
  414. return;
  415. }
  416. edict_t *owner = self->owner;
  417. if (!owner && !(self->spawnflags & SPHERE_DOPPLEGANGER))
  418. {
  419. G_FreeEdict(self);
  420. return;
  421. }
  422. if (owner)
  423. self->ideal_yaw = owner->s.angles[YAW];
  424. else if (self->enemy) // fired by doppleganger
  425. {
  426. vec3_t dir = self->enemy->s.origin - self->s.origin;
  427. self->ideal_yaw = vectoyaw(dir);
  428. }
  429. M_ChangeYaw(self);
  430. if (self->enemy)
  431. {
  432. sphere_chase(self, 0);
  433. // deal with sam raimi cam
  434. if (owner && (owner->flags & FL_SAM_RAIMI))
  435. {
  436. if (self->inuse)
  437. {
  438. owner->movetype = MOVETYPE_FLYMISSILE;
  439. LookAtKiller(owner, self, self->enemy);
  440. // owner is flying with us, move him too
  441. owner->movetype = MOVETYPE_FLYMISSILE;
  442. owner->viewheight = (int) (self->s.origin[2] - owner->s.origin[2]);
  443. owner->s.origin = self->s.origin;
  444. owner->velocity = self->velocity;
  445. owner->mins = {};
  446. owner->maxs = {};
  447. gi.linkentity(owner);
  448. }
  449. else // sphere timed out
  450. {
  451. owner->velocity = {};
  452. owner->movetype = MOVETYPE_NONE;
  453. gi.linkentity(owner);
  454. }
  455. }
  456. }
  457. else
  458. sphere_fly(self);
  459. if (self->inuse)
  460. self->nextthink = level.time + 10_hz;
  461. }
  462. // =================
  463. // =================
  464. THINK(vengeance_think) (edict_t *self) -> void
  465. {
  466. // if we've exited the level, just remove ourselves.
  467. if (level.intermissiontime)
  468. {
  469. sphere_think_explode(self);
  470. return;
  471. }
  472. if (!(self->owner) && !(self->spawnflags & SPHERE_DOPPLEGANGER))
  473. {
  474. G_FreeEdict(self);
  475. return;
  476. }
  477. if (self->enemy)
  478. sphere_chase(self, 1);
  479. else
  480. sphere_fly(self);
  481. if (self->inuse)
  482. self->nextthink = level.time + 10_hz;
  483. }
  484. // *************************
  485. // Spawning / Creation
  486. // *************************
  487. // monsterinfo_t
  488. // =================
  489. // =================
  490. edict_t *Sphere_Spawn(edict_t *owner, spawnflags_t spawnflags)
  491. {
  492. edict_t *sphere;
  493. sphere = G_Spawn();
  494. sphere->s.origin = owner->s.origin;
  495. sphere->s.origin[2] = owner->absmax[2];
  496. sphere->s.angles[YAW] = owner->s.angles[YAW];
  497. sphere->solid = SOLID_BBOX;
  498. sphere->clipmask = MASK_PROJECTILE;
  499. sphere->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE;
  500. sphere->movetype = MOVETYPE_FLYMISSILE;
  501. if (spawnflags.has(SPHERE_DOPPLEGANGER))
  502. sphere->teammaster = owner->teammaster;
  503. else
  504. sphere->owner = owner;
  505. sphere->classname = "sphere";
  506. sphere->yaw_speed = 40;
  507. sphere->monsterinfo.attack_finished = 0_ms;
  508. sphere->spawnflags = spawnflags; // need this for the HUD to recognize sphere
  509. // PMM
  510. sphere->takedamage = false;
  511. switch ((spawnflags & SPHERE_TYPE).value)
  512. {
  513. case SPHERE_DEFENDER.value:
  514. sphere->s.modelindex = gi.modelindex("models/items/defender/tris.md2");
  515. sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2");
  516. sphere->s.sound = gi.soundindex("spheres/d_idle.wav");
  517. sphere->pain = defender_pain;
  518. sphere->wait = (level.time + DEFENDER_LIFESPAN).seconds();
  519. sphere->die = sphere_explode;
  520. sphere->think = defender_think;
  521. break;
  522. case SPHERE_HUNTER.value:
  523. sphere->s.modelindex = gi.modelindex("models/items/hunter/tris.md2");
  524. sphere->s.sound = gi.soundindex("spheres/h_idle.wav");
  525. sphere->wait = (level.time + HUNTER_LIFESPAN).seconds();
  526. sphere->pain = hunter_pain;
  527. sphere->die = sphere_if_idle_die;
  528. sphere->think = hunter_think;
  529. break;
  530. case SPHERE_VENGEANCE.value:
  531. sphere->s.modelindex = gi.modelindex("models/items/vengnce/tris.md2");
  532. sphere->s.sound = gi.soundindex("spheres/v_idle.wav");
  533. sphere->wait = (level.time + VENGEANCE_LIFESPAN).seconds();
  534. sphere->pain = vengeance_pain;
  535. sphere->die = sphere_if_idle_die;
  536. sphere->think = vengeance_think;
  537. sphere->avelocity = { 30, 30, 0 };
  538. break;
  539. default:
  540. gi.Com_Print("Tried to create an invalid sphere\n");
  541. G_FreeEdict(sphere);
  542. return nullptr;
  543. }
  544. sphere->nextthink = level.time + 10_hz;
  545. gi.linkentity(sphere);
  546. return sphere;
  547. }
  548. // =================
  549. // Own_Sphere - attach the sphere to the client so we can
  550. // directly access it later
  551. // =================
  552. void Own_Sphere(edict_t *self, edict_t *sphere)
  553. {
  554. if (!sphere)
  555. return;
  556. // ownership only for players
  557. if (self->client)
  558. {
  559. // if they don't have one
  560. if (!(self->client->owned_sphere))
  561. {
  562. self->client->owned_sphere = sphere;
  563. }
  564. // they already have one, take care of the old one
  565. else
  566. {
  567. if (self->client->owned_sphere->inuse)
  568. {
  569. G_FreeEdict(self->client->owned_sphere);
  570. self->client->owned_sphere = sphere;
  571. }
  572. else
  573. {
  574. self->client->owned_sphere = sphere;
  575. }
  576. }
  577. }
  578. }
  579. // =================
  580. // =================
  581. void Defender_Launch(edict_t *self)
  582. {
  583. edict_t *sphere;
  584. sphere = Sphere_Spawn(self, SPHERE_DEFENDER);
  585. Own_Sphere(self, sphere);
  586. }
  587. // =================
  588. // =================
  589. void Hunter_Launch(edict_t *self)
  590. {
  591. edict_t *sphere;
  592. sphere = Sphere_Spawn(self, SPHERE_HUNTER);
  593. Own_Sphere(self, sphere);
  594. }
  595. // =================
  596. // =================
  597. void Vengeance_Launch(edict_t *self)
  598. {
  599. edict_t *sphere;
  600. sphere = Sphere_Spawn(self, SPHERE_VENGEANCE);
  601. Own_Sphere(self, sphere);
  602. }