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