rogue_dm_ball.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. // dm_ball.c
  4. // pmack
  5. // june 98
  6. #include "../g_local.h"
  7. // defines
  8. [[maybe_unused]] constexpr spawnflags_t SPAWNFLAG_DBALL_GOAL_TEAM1 = 0x0001_spawnflag;
  9. // unused; assumed by not being team1
  10. // constexpr uint32_t SPAWNFLAG_DBALL_GOAL_TEAM2 = 0x0002;
  11. // globals
  12. edict_t *dball_ball_entity = nullptr;
  13. int dball_ball_startpt_count;
  14. int dball_team1_goalscore;
  15. int dball_team2_goalscore;
  16. cvar_t *dball_team1_skin;
  17. cvar_t *dball_team2_skin;
  18. cvar_t *goallimit;
  19. // prototypes
  20. void EndDMLevel();
  21. float PlayersRangeFromSpot(edict_t *spot);
  22. void DBall_BallDie(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod);
  23. void DBall_BallRespawn(edict_t *self);
  24. // **************************
  25. // Game rules
  26. // **************************
  27. int DBall_CheckDMRules()
  28. {
  29. if (goallimit->integer)
  30. {
  31. if (dball_team1_goalscore >= goallimit->integer)
  32. gi.LocBroadcast_Print(PRINT_HIGH, "Team 1 Wins.\n");
  33. else if (dball_team2_goalscore >= goallimit->integer)
  34. gi.LocBroadcast_Print(PRINT_HIGH, "Team 2 Wins.\n");
  35. else
  36. return 0;
  37. EndDMLevel();
  38. return 1;
  39. }
  40. return 0;
  41. }
  42. //==================
  43. //==================
  44. void DBall_ClientBegin(edict_t *ent)
  45. {
  46. #if 0
  47. uint32_t team1, team2, unassigned;
  48. edict_t *other;
  49. char *p;
  50. static char value[512];
  51. team1 = 0;
  52. team2 = 0;
  53. unassigned = 0;
  54. for (uint32_t j = 1; j <= game.maxclients; j++)
  55. {
  56. other = &g_edicts[j];
  57. if (!other->inuse)
  58. continue;
  59. if (!other->client)
  60. continue;
  61. if (other == ent) // don't count the new player
  62. continue;
  63. Q_strlcpy(value, Info_ValueForKey(other->client->pers.userinfo, "skin"), sizeof(value));
  64. p = strchr(value, '/');
  65. if (p)
  66. {
  67. if (!strcmp(dball_team1_skin->string, value))
  68. team1++;
  69. else if (!strcmp(dball_team2_skin->string, value))
  70. team2++;
  71. else
  72. unassigned++;
  73. }
  74. else
  75. unassigned++;
  76. }
  77. if (team1 > team2)
  78. {
  79. gi.Com_Print("assigned to team 2\n");
  80. Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team2_skin->string);
  81. }
  82. else
  83. {
  84. gi.Com_Print("assigned to team 1\n");
  85. Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team1_skin->string);
  86. }
  87. ClientUserinfoChanged(ent, ent->client->pers.userinfo);
  88. if (unassigned)
  89. gi.Com_PrintFmt("{} unassigned players present!\n", unassigned);
  90. #endif
  91. }
  92. //==================
  93. //==================
  94. bool DBall_SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn)
  95. {
  96. #if 0
  97. edict_t *bestspot;
  98. float bestdistance, bestplayerdistance;
  99. edict_t *spot;
  100. const char *spottype;
  101. static char skin[512];
  102. Q_strlcpy(skin, Info_ValueForKey(ent->client->pers.userinfo, "skin"), sizeof(skin));
  103. if (!strcmp(dball_team1_skin->string, skin))
  104. spottype = "dm_dball_team1_start";
  105. else if (!strcmp(dball_team2_skin->string, skin))
  106. spottype = "dm_dball_team2_start";
  107. else
  108. spottype = "info_player_deathmatch";
  109. spot = nullptr;
  110. bestspot = nullptr;
  111. bestdistance = 0;
  112. while ((spot = G_FindByString<&edict_t::classname>(spot, spottype)) != nullptr)
  113. {
  114. bestplayerdistance = PlayersRangeFromSpot(spot);
  115. if (bestplayerdistance > bestdistance)
  116. {
  117. bestspot = spot;
  118. bestdistance = bestplayerdistance;
  119. }
  120. }
  121. if (bestspot)
  122. {
  123. origin = bestspot->s.origin;
  124. origin[2] += 9;
  125. angles = bestspot->s.angles;
  126. return true;
  127. }
  128. // if we didn't find an appropriate spawnpoint, just
  129. // call the standard one.
  130. #endif
  131. bool lm = false;
  132. return SelectSpawnPoint(ent, origin, angles, force_spawn, lm);
  133. }
  134. //==================
  135. //==================
  136. void DBall_GameInit()
  137. {
  138. // we don't want a minimum speed for friction to take effect.
  139. // this will allow any knockback to move stuff.
  140. gi.cvar_forceset("sv_stopspeed", "0");
  141. dball_team1_goalscore = 0;
  142. dball_team2_goalscore = 0;
  143. gi.cvar_forceset(g_no_mines->name, "1");
  144. gi.cvar_forceset(g_no_nukes->name, "1");
  145. gi.cvar_forceset(g_dm_no_stack_double->name, "1");
  146. gi.cvar_forceset(g_friendly_fire->name, "0");
  147. //gi.cvar_forceset(g_no_mines->name, "1"); note: skin teams gone...
  148. dball_team1_skin = gi.cvar("dball_team1_skin", "male/ctf_r", CVAR_NOFLAGS);
  149. dball_team2_skin = gi.cvar("dball_team2_skin", "male/ctf_b", CVAR_NOFLAGS);
  150. goallimit = gi.cvar("goallimit", "0", CVAR_NOFLAGS);
  151. }
  152. //==================
  153. //==================
  154. void DBall_PostInitSetup()
  155. {
  156. edict_t *e;
  157. e = nullptr;
  158. // turn teleporter destinations nonsolid.
  159. while ((e = G_FindByString<&edict_t::classname>(e, "misc_teleporter_dest")))
  160. {
  161. e->solid = SOLID_NOT;
  162. gi.linkentity(e);
  163. }
  164. // count the ball start points
  165. dball_ball_startpt_count = 0;
  166. e = nullptr;
  167. while ((e = G_FindByString<&edict_t::classname>(e, "dm_dball_ball_start")))
  168. {
  169. dball_ball_startpt_count++;
  170. }
  171. if (dball_ball_startpt_count == 0)
  172. gi.Com_Print("No Deathball start points!\n");
  173. }
  174. //==================
  175. // DBall_ChangeDamage - half damage between players. full if it involves
  176. // the ball entity
  177. //==================
  178. int DBall_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod)
  179. {
  180. // cut player -> ball damage to 1
  181. if (targ == dball_ball_entity)
  182. return 1;
  183. // damage player -> player is halved
  184. if (attacker != dball_ball_entity)
  185. return damage / 2;
  186. return damage;
  187. }
  188. //==================
  189. //==================
  190. int DBall_ChangeKnockback(edict_t *targ, edict_t *attacker, int knockback, mod_t mod)
  191. {
  192. if (targ != dball_ball_entity)
  193. return knockback;
  194. if (knockback < 1)
  195. {
  196. // FIXME - these don't account for quad/double
  197. if (mod.id == MOD_ROCKET) // rocket
  198. knockback = 70;
  199. else if (mod.id == MOD_BFG_EFFECT) // bfg
  200. knockback = 90;
  201. else
  202. gi.Com_PrintFmt("zero knockback, mod {}\n", (int32_t) mod.id);
  203. }
  204. else
  205. {
  206. // FIXME - change this to an array?
  207. switch (mod.id)
  208. {
  209. case MOD_BLASTER:
  210. knockback *= 3;
  211. break;
  212. case MOD_SHOTGUN:
  213. knockback = (knockback * 3) / 8;
  214. break;
  215. case MOD_SSHOTGUN:
  216. knockback = knockback / 3;
  217. break;
  218. case MOD_MACHINEGUN:
  219. knockback = (knockback * 3) / 2;
  220. break;
  221. case MOD_HYPERBLASTER:
  222. knockback *= 4;
  223. break;
  224. case MOD_GRENADE:
  225. case MOD_HANDGRENADE:
  226. case MOD_PROX:
  227. case MOD_G_SPLASH:
  228. case MOD_HG_SPLASH:
  229. case MOD_HELD_GRENADE:
  230. case MOD_TRACKER:
  231. case MOD_DISINTEGRATOR:
  232. knockback /= 2;
  233. break;
  234. case MOD_R_SPLASH:
  235. knockback = (knockback * 3) / 2;
  236. break;
  237. case MOD_RAILGUN:
  238. case MOD_HEATBEAM:
  239. knockback /= 3;
  240. break;
  241. default:
  242. break;
  243. }
  244. }
  245. // gi.dprintf("mod: %d knockback: %d\n", mod, knockback);
  246. return knockback;
  247. }
  248. // **************************
  249. // Goals
  250. // **************************
  251. TOUCH(DBall_GoalTouch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  252. {
  253. #if 0
  254. static char value[512];
  255. int team_score;
  256. int scorechange;
  257. char *p;
  258. edict_t *ent;
  259. if (other != dball_ball_entity)
  260. return;
  261. self->health = self->max_health;
  262. // determine which team scored, and bump the team score
  263. if (self->spawnflags.has(SPAWNFLAG_DBALL_GOAL_TEAM1))
  264. {
  265. dball_team1_goalscore += (int) self->wait;
  266. team_score = 1;
  267. }
  268. else
  269. {
  270. dball_team2_goalscore += (int) self->wait;
  271. team_score = 2;
  272. }
  273. // bump the score for everyone on the correct team.
  274. for (uint32_t j = 1; j <= game.maxclients; j++)
  275. {
  276. ent = &g_edicts[j];
  277. if (!ent->inuse)
  278. continue;
  279. if (!ent->client)
  280. continue;
  281. if (ent == other->enemy)
  282. scorechange = (int) self->wait + 5;
  283. else
  284. scorechange = (int) self->wait;
  285. Q_strlcpy(value, Info_ValueForKey(ent->client->pers.userinfo, "skin"), sizeof(value));
  286. p = strchr(value, '/');
  287. if (p)
  288. {
  289. if (!strcmp(dball_team1_skin->string, value))
  290. {
  291. if (team_score == 1)
  292. ent->client->resp.score += scorechange;
  293. else if (other->enemy == ent)
  294. ent->client->resp.score -= scorechange;
  295. }
  296. else if (!strcmp(dball_team2_skin->string, value))
  297. {
  298. if (team_score == 2)
  299. ent->client->resp.score += scorechange;
  300. else if (other->enemy == ent)
  301. ent->client->resp.score -= scorechange;
  302. }
  303. else
  304. gi.Com_Print("unassigned player!!!!\n");
  305. }
  306. }
  307. if (other->enemy)
  308. gi.Com_PrintFmt("score for team {} by {}\n", team_score, other->enemy->client->pers.netname);
  309. else
  310. gi.Com_PrintFmt("score for team {} by someone\n", team_score);
  311. DBall_BallDie(other, other->enemy, other->enemy, 0, vec3_origin, MOD_SUICIDE);
  312. G_UseTargets(self, other);
  313. #endif
  314. }
  315. // **************************
  316. // Ball
  317. // **************************
  318. edict_t *PickBallStart(edict_t *ent)
  319. {
  320. int which, current;
  321. edict_t *e;
  322. which = irandom(dball_ball_startpt_count);
  323. e = nullptr;
  324. current = 0;
  325. while ((e = G_FindByString<&edict_t::classname>(e, "dm_dball_ball_start")))
  326. {
  327. current++;
  328. if (current == which)
  329. return e;
  330. }
  331. if (current == 0)
  332. gi.Com_Print("No ball start points found!\n");
  333. return G_FindByString<&edict_t::classname>(nullptr, "dm_dball_ball_start");
  334. }
  335. //==================
  336. // DBall_BallTouch - if the ball hit another player, hurt them
  337. //==================
  338. TOUCH(DBall_BallTouch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  339. {
  340. vec3_t dir;
  341. float dot;
  342. float speed;
  343. if (other->takedamage == false)
  344. return;
  345. // hit a player
  346. if (other->client)
  347. {
  348. if (ent->velocity[0] || ent->velocity[1] || ent->velocity[2])
  349. {
  350. speed = ent->velocity.length();
  351. dir = ent->s.origin - other->s.origin;
  352. dot = dir.dot(ent->velocity);
  353. if (dot > 0.7f)
  354. {
  355. T_Damage(other, ent, ent, vec3_origin, ent->s.origin, vec3_origin,
  356. (int) (speed / 10), (int) (speed / 10), DAMAGE_NONE, MOD_DBALL_CRUSH);
  357. }
  358. }
  359. }
  360. }
  361. //==================
  362. // DBall_BallPain
  363. //==================
  364. PAIN(DBall_BallPain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  365. {
  366. self->enemy = other;
  367. self->health = self->max_health;
  368. // if(other->classname)
  369. // gi.Com_PrintFmt("hurt by {} -- {}\n", other->classname, self->health);
  370. }
  371. DIE(DBall_BallDie) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  372. {
  373. // do the splash effect
  374. gi.WriteByte(svc_temp_entity);
  375. gi.WriteByte(TE_DBALL_GOAL);
  376. gi.WritePosition(self->s.origin);
  377. gi.multicast(self->s.origin, MULTICAST_PVS, false);
  378. self->s.angles = {};
  379. self->velocity = {};
  380. self->avelocity = {};
  381. // make it invisible and desolid until respawn time
  382. self->solid = SOLID_NOT;
  383. // self->s.modelindex = 0;
  384. self->think = DBall_BallRespawn;
  385. self->nextthink = level.time + 2_sec;
  386. gi.linkentity(self);
  387. }
  388. THINK(DBall_BallRespawn) (edict_t *self) -> void
  389. {
  390. edict_t *start;
  391. // do the splash effect
  392. gi.WriteByte(svc_temp_entity);
  393. gi.WriteByte(TE_DBALL_GOAL);
  394. gi.WritePosition(self->s.origin);
  395. gi.multicast(self->s.origin, MULTICAST_PVS, false);
  396. // move the ball and stop it
  397. start = PickBallStart(self);
  398. if (start)
  399. {
  400. self->s.origin = start->s.origin;
  401. self->s.old_origin = start->s.origin;
  402. }
  403. self->s.angles = {};
  404. self->velocity = {};
  405. self->avelocity = {};
  406. self->solid = SOLID_BBOX;
  407. self->s.modelindex = gi.modelindex("models/objects/dball/tris.md2");
  408. self->s.event = EV_PLAYER_TELEPORT;
  409. self->groundentity = nullptr;
  410. gi.linkentity(self);
  411. // kill anything at the destination
  412. KillBox(self, false);
  413. }
  414. // ************************
  415. // SPEED CHANGES
  416. // ************************
  417. constexpr spawnflags_t SPAWNFLAG_DBALL_SPEED_ONEWAY = 1_spawnflag;
  418. TOUCH(DBall_SpeedTouch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  419. {
  420. float dot;
  421. vec3_t vel;
  422. if (other != dball_ball_entity)
  423. return;
  424. if (self->timestamp >= level.time)
  425. return;
  426. if (other->velocity.length() < 1)
  427. return;
  428. if (self->spawnflags.has(SPAWNFLAG_DBALL_SPEED_ONEWAY))
  429. {
  430. vel = other->velocity;
  431. vel.normalize();
  432. dot = vel.dot(self->movedir);
  433. if (dot < 0.8f)
  434. return;
  435. }
  436. self->timestamp = level.time + gtime_t::from_sec(self->delay);
  437. other->velocity *= self->speed;
  438. }
  439. // ************************
  440. // SPAWN FUNCTIONS
  441. // ************************
  442. /*QUAKED dm_dball_ball (1 .5 .5) (-48 -48 -48) (48 48 48) ONEWAY
  443. Deathball Ball
  444. */
  445. void SP_dm_dball_ball(edict_t *self)
  446. {
  447. if (!deathmatch->integer)
  448. {
  449. G_FreeEdict(self);
  450. return;
  451. }
  452. if (gamerules->integer != RDM_DEATHBALL)
  453. {
  454. G_FreeEdict(self);
  455. return;
  456. }
  457. dball_ball_entity = self;
  458. // dball_ball_startpt = self->s.origin;
  459. self->s.modelindex = gi.modelindex("models/objects/dball/tris.md2");
  460. self->mins = { -32, -32, -32 };
  461. self->maxs = { 32, 32, 32 };
  462. self->solid = SOLID_BBOX;
  463. self->movetype = MOVETYPE_NEWTOSS;
  464. self->clipmask = MASK_MONSTERSOLID;
  465. self->takedamage = true;
  466. self->mass = 50;
  467. self->health = 50000;
  468. self->max_health = 50000;
  469. self->pain = DBall_BallPain;
  470. self->die = DBall_BallDie;
  471. self->touch = DBall_BallTouch;
  472. gi.linkentity(self);
  473. }
  474. /*QUAKED dm_dball_team1_start (1 .5 .5) (-16 -16 -24) (16 16 32)
  475. Deathball team 1 start point
  476. */
  477. void SP_dm_dball_team1_start(edict_t *self)
  478. {
  479. if (!deathmatch->integer)
  480. {
  481. G_FreeEdict(self);
  482. return;
  483. }
  484. if (gamerules->integer != RDM_DEATHBALL)
  485. {
  486. G_FreeEdict(self);
  487. return;
  488. }
  489. }
  490. /*QUAKED dm_dball_team2_start (1 .5 .5) (-16 -16 -24) (16 16 32)
  491. Deathball team 2 start point
  492. */
  493. void SP_dm_dball_team2_start(edict_t *self)
  494. {
  495. if (!deathmatch->integer)
  496. {
  497. G_FreeEdict(self);
  498. return;
  499. }
  500. if (gamerules->integer != RDM_DEATHBALL)
  501. {
  502. G_FreeEdict(self);
  503. return;
  504. }
  505. }
  506. /*QUAKED dm_dball_ball_start (1 .5 .5) (-48 -48 -48) (48 48 48)
  507. Deathball ball start point
  508. */
  509. void SP_dm_dball_ball_start(edict_t *self)
  510. {
  511. if (!deathmatch->integer)
  512. {
  513. G_FreeEdict(self);
  514. return;
  515. }
  516. if (gamerules->integer != RDM_DEATHBALL)
  517. {
  518. G_FreeEdict(self);
  519. return;
  520. }
  521. }
  522. /*QUAKED dm_dball_speed_change (1 .5 .5) ? ONEWAY
  523. Deathball ball speed changing field.
  524. speed: multiplier for speed (.5 = half, 2 = double, etc) (default = double)
  525. angle: used with ONEWAY so speed change is only one way.
  526. delay: time between speed changes (default: 0.2 sec)
  527. */
  528. void SP_dm_dball_speed_change(edict_t *self)
  529. {
  530. if (!deathmatch->integer)
  531. {
  532. G_FreeEdict(self);
  533. return;
  534. }
  535. if (gamerules->integer != RDM_DEATHBALL)
  536. {
  537. G_FreeEdict(self);
  538. return;
  539. }
  540. if (!self->speed)
  541. self->speed = 2;
  542. if (!self->delay)
  543. self->delay = 0.2f;
  544. self->touch = DBall_SpeedTouch;
  545. self->solid = SOLID_TRIGGER;
  546. self->movetype = MOVETYPE_NONE;
  547. self->svflags |= SVF_NOCLIENT;
  548. if (self->s.angles)
  549. G_SetMovedir(self->s.angles, self->movedir);
  550. else
  551. self->movedir = { 1, 0, 0 };
  552. gi.setmodel(self, self->model);
  553. gi.linkentity(self);
  554. }
  555. /*QUAKED dm_dball_goal (1 .5 .5) ? TEAM1 TEAM2
  556. Deathball goal
  557. Team1/Team2 - beneficiary of this goal. when the ball enters this goal, the beneficiary team will score.
  558. "wait": score to be given for this goal (default 10) player gets score+5.
  559. */
  560. void SP_dm_dball_goal(edict_t *self)
  561. {
  562. if (!deathmatch->integer)
  563. {
  564. G_FreeEdict(self);
  565. return;
  566. }
  567. if (gamerules->integer != RDM_DEATHBALL)
  568. {
  569. G_FreeEdict(self);
  570. return;
  571. }
  572. if (!self->wait)
  573. self->wait = 10;
  574. self->touch = DBall_GoalTouch;
  575. self->solid = SOLID_TRIGGER;
  576. self->movetype = MOVETYPE_NONE;
  577. self->svflags |= SVF_NOCLIENT;
  578. if (self->s.angles)
  579. G_SetMovedir(self->s.angles, self->movedir);
  580. gi.setmodel(self, self->model);
  581. gi.linkentity(self);
  582. }