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