dm_ball.c 14 KB

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