678 lines
14 KiB
C
678 lines
14 KiB
C
// dm_ball.c
|
|
// pmack
|
|
// june 98
|
|
|
|
#include "g_local.h"
|
|
|
|
// defines
|
|
|
|
#define DBALL_GOAL_TEAM1 0x0001
|
|
#define DBALL_GOAL_TEAM2 0x0002
|
|
|
|
// globals
|
|
|
|
edict_t *dball_ball_entity = NULL;
|
|
int dball_ball_startpt_count;
|
|
int dball_team1_goalscore;
|
|
int dball_team2_goalscore;
|
|
|
|
cvar_t *dball_team1_skin;
|
|
cvar_t *dball_team2_skin;
|
|
cvar_t *goallimit;
|
|
|
|
// prototypes
|
|
|
|
extern void EndDMLevel (void);
|
|
extern void ClientUserinfoChanged (edict_t *ent, char *userinfo);
|
|
extern void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
|
|
extern float PlayersRangeFromSpot (edict_t *spot);
|
|
|
|
void DBall_BallDie (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
|
|
void DBall_BallRespawn (edict_t *self);
|
|
|
|
// **************************
|
|
// Game rules
|
|
// **************************
|
|
|
|
int DBall_CheckDMRules (void)
|
|
{
|
|
if(goallimit && goallimit->value)
|
|
{
|
|
if(dball_team1_goalscore >= goallimit->value)
|
|
gi.bprintf (PRINT_HIGH, "Team 1 Wins.\n");
|
|
else if(dball_team2_goalscore >= goallimit->value)
|
|
gi.bprintf (PRINT_HIGH, "Team 2 Wins.\n");
|
|
else
|
|
return 0;
|
|
|
|
EndDMLevel ();
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//==================
|
|
//==================
|
|
void DBall_ClientBegin (edict_t *ent)
|
|
{
|
|
int team1, team2, unassigned;
|
|
edict_t *other;
|
|
char *p;
|
|
static char value[512];
|
|
int j;
|
|
|
|
team1 = 0;
|
|
team2 = 0;
|
|
unassigned = 0;
|
|
|
|
for (j = 1; j <= game.maxclients; j++)
|
|
{
|
|
other = &g_edicts[j];
|
|
if (!other->inuse)
|
|
continue;
|
|
if (!other->client)
|
|
continue;
|
|
if (other == ent) // don't count the new player
|
|
continue;
|
|
|
|
strcpy(value, Info_ValueForKey (other->client->pers.userinfo, "skin"));
|
|
p = strchr(value, '/');
|
|
if (p)
|
|
{
|
|
if(!strcmp(dball_team1_skin->string, value))
|
|
team1++;
|
|
else if(!strcmp(dball_team2_skin->string, value))
|
|
team2++;
|
|
else
|
|
unassigned++;
|
|
}
|
|
else
|
|
unassigned++;
|
|
}
|
|
|
|
if(team1 > team2)
|
|
{
|
|
gi.dprintf("assigned to team 2\n");
|
|
Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team2_skin->string);
|
|
}
|
|
else
|
|
{
|
|
gi.dprintf("assigned to team 1\n");
|
|
Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team1_skin->string);
|
|
}
|
|
|
|
ClientUserinfoChanged(ent, ent->client->pers.userinfo);
|
|
|
|
if(unassigned)
|
|
gi.dprintf("%d unassigned players present!\n", unassigned);
|
|
}
|
|
|
|
//==================
|
|
//==================
|
|
void DBall_SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
|
|
{
|
|
edict_t *bestspot;
|
|
float bestdistance, bestplayerdistance;
|
|
edict_t *spot;
|
|
char *spottype;
|
|
char skin[512];
|
|
|
|
strcpy(skin, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
|
|
if(!strcmp(dball_team1_skin->string, skin))
|
|
spottype = "dm_dball_team1_start";
|
|
else if(!strcmp(dball_team2_skin->string, skin))
|
|
spottype = "dm_dball_team2_start";
|
|
else
|
|
spottype = "info_player_deathmatch";
|
|
|
|
spot = NULL;
|
|
bestspot = NULL;
|
|
bestdistance = 0;
|
|
while ((spot = G_Find (spot, FOFS(classname), spottype)) != NULL)
|
|
{
|
|
bestplayerdistance = PlayersRangeFromSpot (spot);
|
|
|
|
if (bestplayerdistance > bestdistance)
|
|
{
|
|
bestspot = spot;
|
|
bestdistance = bestplayerdistance;
|
|
}
|
|
}
|
|
|
|
if (bestspot)
|
|
{
|
|
VectorCopy (bestspot->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy (bestspot->s.angles, angles);
|
|
return;
|
|
}
|
|
|
|
// if we didn't find an appropriate spawnpoint, just
|
|
// call the standard one.
|
|
SelectSpawnPoint(ent, origin, angles);
|
|
}
|
|
|
|
//==================
|
|
//==================
|
|
void DBall_GameInit (void)
|
|
{
|
|
// we don't want a minimum speed for friction to take effect.
|
|
// this will allow any knockback to move stuff.
|
|
sv_stopspeed->value = 0;
|
|
dball_team1_goalscore = 0;
|
|
dball_team2_goalscore = 0;
|
|
|
|
dmflags->value = (int)dmflags->value | DF_NO_MINES | DF_NO_NUKES | DF_NO_STACK_DOUBLE |
|
|
DF_NO_FRIENDLY_FIRE | DF_SKINTEAMS;
|
|
|
|
dball_team1_skin = gi.cvar ("dball_team1_skin", "male/ctf_r", 0);
|
|
dball_team2_skin = gi.cvar ("dball_team2_skin", "male/ctf_b", 0);
|
|
goallimit = gi.cvar ("goallimit", "0", 0);
|
|
}
|
|
|
|
//==================
|
|
//==================
|
|
void DBall_PostInitSetup (void)
|
|
{
|
|
edict_t *e;
|
|
|
|
e=NULL;
|
|
// turn teleporter destinations nonsolid.
|
|
while(e = G_Find (e, FOFS(classname), "misc_teleporter_dest"))
|
|
{
|
|
e->solid = SOLID_NOT;
|
|
gi.linkentity (e);
|
|
}
|
|
|
|
// count the ball start points
|
|
dball_ball_startpt_count = 0;
|
|
e=NULL;
|
|
while(e = G_Find (e, FOFS(classname), "dm_dball_ball_start"))
|
|
{
|
|
dball_ball_startpt_count++;
|
|
}
|
|
|
|
if(dball_ball_startpt_count == 0)
|
|
gi.dprintf("No Deathball start points!\n");
|
|
}
|
|
|
|
//==================
|
|
// DBall_ChangeDamage - half damage between players. full if it involves
|
|
// the ball entity
|
|
//==================
|
|
int DBall_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod)
|
|
{
|
|
// cut player -> ball damage to 1
|
|
if (targ == dball_ball_entity)
|
|
return 1;
|
|
|
|
// damage player -> player is halved
|
|
if (attacker != dball_ball_entity)
|
|
return damage / 2;
|
|
|
|
return damage;
|
|
}
|
|
|
|
//==================
|
|
//==================
|
|
int DBall_ChangeKnockback (edict_t *targ, edict_t *attacker, int knockback, int mod)
|
|
{
|
|
if(targ != dball_ball_entity)
|
|
return knockback;
|
|
|
|
if(knockback < 1)
|
|
{
|
|
// FIXME - these don't account for quad/double
|
|
if(mod == MOD_ROCKET) // rocket
|
|
knockback = 70;
|
|
else if(mod == MOD_BFG_EFFECT) // bfg
|
|
knockback = 90;
|
|
else
|
|
gi.dprintf ("zero knockback, mod %d\n", mod);
|
|
}
|
|
else
|
|
{
|
|
// FIXME - change this to an array?
|
|
switch(mod)
|
|
{
|
|
case MOD_BLASTER:
|
|
knockback *= 3;
|
|
break;
|
|
case MOD_SHOTGUN:
|
|
knockback = (knockback * 3) / 8;
|
|
break;
|
|
case MOD_SSHOTGUN:
|
|
knockback = knockback / 3;
|
|
break;
|
|
case MOD_MACHINEGUN:
|
|
knockback = (knockback * 3) / 2;
|
|
break;
|
|
case MOD_HYPERBLASTER:
|
|
knockback *= 4;
|
|
break;
|
|
case MOD_GRENADE:
|
|
case MOD_HANDGRENADE:
|
|
case MOD_PROX:
|
|
case MOD_G_SPLASH:
|
|
case MOD_HG_SPLASH:
|
|
case MOD_HELD_GRENADE:
|
|
case MOD_TRACKER:
|
|
case MOD_DISINTEGRATOR:
|
|
knockback /= 2;
|
|
break;
|
|
case MOD_R_SPLASH:
|
|
knockback = (knockback * 3) / 2;
|
|
break;
|
|
case MOD_RAILGUN:
|
|
case MOD_HEATBEAM:
|
|
knockback /= 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// gi.dprintf("mod: %d knockback: %d\n", mod, knockback);
|
|
return knockback;
|
|
}
|
|
|
|
// **************************
|
|
// Goals
|
|
// **************************
|
|
|
|
void DBall_GoalTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
int team_score;
|
|
int scorechange;
|
|
int j;
|
|
char value[512];
|
|
char *p;
|
|
edict_t *ent;
|
|
|
|
if(other != dball_ball_entity)
|
|
return;
|
|
|
|
self->health = self->max_health;
|
|
|
|
// determine which team scored, and bump the team score
|
|
if(self->spawnflags & DBALL_GOAL_TEAM1)
|
|
{
|
|
dball_team1_goalscore += self->wait;
|
|
team_score = 1;
|
|
}
|
|
else
|
|
{
|
|
dball_team2_goalscore += self->wait;
|
|
team_score = 2;
|
|
}
|
|
|
|
// bump the score for everyone on the correct team.
|
|
for (j = 1; j <= game.maxclients; j++)
|
|
{
|
|
ent = &g_edicts[j];
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (!ent->client)
|
|
continue;
|
|
|
|
if (ent == other->enemy)
|
|
scorechange = self->wait + 5;
|
|
else
|
|
scorechange = self->wait;
|
|
|
|
strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
|
|
p = strchr(value, '/');
|
|
if (p)
|
|
{
|
|
if(!strcmp(dball_team1_skin->string, value))
|
|
{
|
|
if(team_score == 1)
|
|
ent->client->resp.score += scorechange;
|
|
else if(other->enemy == ent)
|
|
ent->client->resp.score -= scorechange;
|
|
}
|
|
else if(!strcmp(dball_team2_skin->string, value))
|
|
{
|
|
if(team_score == 2)
|
|
ent->client->resp.score += scorechange;
|
|
else if(other->enemy == ent)
|
|
ent->client->resp.score -= scorechange;
|
|
}
|
|
else
|
|
gi.dprintf("unassigned player!!!!\n");
|
|
}
|
|
}
|
|
|
|
if(other->enemy)
|
|
gi.dprintf("score for team %d by %s\n", team_score, other->enemy->client->pers.netname);
|
|
else
|
|
gi.dprintf("score for team %d by someone\n", team_score);
|
|
|
|
DBall_BallDie (other, other->enemy, other->enemy, 0, vec3_origin);
|
|
|
|
G_UseTargets (self, other);
|
|
}
|
|
|
|
// **************************
|
|
// Ball
|
|
// **************************
|
|
|
|
edict_t *PickBallStart (edict_t *ent)
|
|
{
|
|
int which, current;
|
|
edict_t *e;
|
|
|
|
which = ceil(random() * dball_ball_startpt_count);
|
|
e = NULL;
|
|
current = 0;
|
|
|
|
while(e = G_Find (e, FOFS(classname), "dm_dball_ball_start"))
|
|
{
|
|
current++;
|
|
if(current == which)
|
|
return e;
|
|
}
|
|
|
|
if(current == 0)
|
|
gi.dprintf("No ball start points found!\n");
|
|
|
|
return G_Find(NULL, FOFS(classname), "dm_dball_ball_start");
|
|
}
|
|
|
|
//==================
|
|
// DBall_BallTouch - if the ball hit another player, hurt them
|
|
//==================
|
|
void DBall_BallTouch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
vec3_t dir;
|
|
float dot;
|
|
float speed;
|
|
|
|
if(other->takedamage == DAMAGE_NO)
|
|
return;
|
|
|
|
// hit a player
|
|
if(other->client)
|
|
{
|
|
if(ent->velocity[0] || ent->velocity[1] || ent->velocity[2])
|
|
{
|
|
speed = VectorLength(ent->velocity);
|
|
|
|
VectorSubtract(ent->s.origin, other->s.origin, dir);
|
|
dot = DotProduct(dir, ent->velocity);
|
|
|
|
if(dot > 0.7)
|
|
{
|
|
T_Damage (other, ent, ent, vec3_origin, ent->s.origin, vec3_origin,
|
|
speed/10, speed/10, 0, MOD_DBALL_CRUSH);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==================
|
|
// DBall_BallPain
|
|
//==================
|
|
void DBall_BallPain (edict_t *self, edict_t *other, float kick, int damage)
|
|
{
|
|
self->enemy = other;
|
|
self->health = self->max_health;
|
|
// if(other->classname)
|
|
// gi.dprintf("hurt by %s -- %d\n", other->classname, self->health);
|
|
}
|
|
|
|
void DBall_BallDie (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
// do the splash effect
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_DBALL_GOAL);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
VectorClear(self->s.angles);
|
|
VectorClear(self->velocity);
|
|
VectorClear(self->avelocity);
|
|
|
|
// make it invisible and desolid until respawn time
|
|
self->solid = SOLID_NOT;
|
|
// self->s.modelindex = 0;
|
|
self->think = DBall_BallRespawn;
|
|
self->nextthink = level.time + 2;
|
|
gi.linkentity(self);
|
|
|
|
}
|
|
|
|
void DBall_BallRespawn (edict_t *self)
|
|
{
|
|
edict_t *start;
|
|
|
|
// do the splash effect
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_DBALL_GOAL);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
// move the ball and stop it
|
|
start = PickBallStart(self);
|
|
if(start)
|
|
{
|
|
VectorCopy(start->s.origin, self->s.origin);
|
|
VectorCopy(start->s.origin, self->s.old_origin);
|
|
}
|
|
|
|
VectorClear(self->s.angles);
|
|
VectorClear(self->velocity);
|
|
VectorClear(self->avelocity);
|
|
|
|
self->solid = SOLID_BBOX;
|
|
self->s.modelindex = gi.modelindex ("models/objects/dball/tris.md2");
|
|
self->s.event = EV_PLAYER_TELEPORT;
|
|
self->groundentity = NULL;
|
|
|
|
// kill anything at the destination
|
|
KillBox (self);
|
|
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
// ************************
|
|
// SPEED CHANGES
|
|
// ************************
|
|
|
|
#define DBALL_SPEED_ONEWAY 1
|
|
|
|
void DBall_SpeedTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
float dot;
|
|
vec3_t vel;
|
|
|
|
if(other != dball_ball_entity)
|
|
return;
|
|
|
|
if(self->timestamp >= level.time)
|
|
return;
|
|
|
|
if(VectorLength(other->velocity) < 1)
|
|
return;
|
|
|
|
if(self->spawnflags & DBALL_SPEED_ONEWAY)
|
|
{
|
|
VectorCopy (other->velocity, vel);
|
|
VectorNormalize (vel);
|
|
dot = DotProduct (vel, self->movedir);
|
|
if(dot < 0.8)
|
|
return;
|
|
}
|
|
|
|
self->timestamp = level.time + self->delay;
|
|
VectorScale (other->velocity, self->speed, other->velocity);
|
|
}
|
|
|
|
// ************************
|
|
// SPAWN FUNCTIONS
|
|
// ************************
|
|
|
|
/*QUAKED dm_dball_ball (1 .5 .5) (-48 -48 -48) (48 48 48)
|
|
Deathball Ball
|
|
*/
|
|
void SP_dm_dball_ball (edict_t *self)
|
|
{
|
|
if(!(deathmatch->value))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if(gamerules && (gamerules->value != RDM_DEATHBALL))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
dball_ball_entity = self;
|
|
// VectorCopy (self->s.origin, dball_ball_startpt);
|
|
|
|
self->s.modelindex = gi.modelindex ("models/objects/dball/tris.md2");
|
|
VectorSet (self->mins, -32, -32, -32);
|
|
VectorSet (self->maxs, 32, 32, 32);
|
|
self->solid = SOLID_BBOX;
|
|
self->movetype = MOVETYPE_NEWTOSS;
|
|
self->clipmask = MASK_MONSTERSOLID;
|
|
|
|
self->takedamage = DAMAGE_YES;
|
|
self->mass = 50;
|
|
self->health = 50000;
|
|
self->max_health = 50000;
|
|
self->pain = DBall_BallPain;
|
|
self->die = DBall_BallDie;
|
|
self->touch = DBall_BallTouch;
|
|
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
/*QUAKED dm_dball_team1_start (1 .5 .5) (-16 -16 -24) (16 16 32)
|
|
Deathball team 1 start point
|
|
*/
|
|
void SP_dm_dball_team1_start (edict_t *self)
|
|
{
|
|
if (!deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
if(gamerules && (gamerules->value != RDM_DEATHBALL))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*QUAKED dm_dball_team2_start (1 .5 .5) (-16 -16 -24) (16 16 32)
|
|
Deathball team 2 start point
|
|
*/
|
|
void SP_dm_dball_team2_start (edict_t *self)
|
|
{
|
|
if (!deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
if(gamerules && (gamerules->value != RDM_DEATHBALL))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*QUAKED dm_dball_ball_start (1 .5 .5) (-48 -48 -48) (48 48 48)
|
|
Deathball ball start point
|
|
*/
|
|
void SP_dm_dball_ball_start (edict_t *self)
|
|
{
|
|
if (!deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
if(gamerules && (gamerules->value != RDM_DEATHBALL))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*QUAKED dm_dball_speed_change (1 .5 .5) ? ONEWAY
|
|
Deathball ball speed changing field.
|
|
|
|
speed: multiplier for speed (.5 = half, 2 = double, etc) (default = double)
|
|
angle: used with ONEWAY so speed change is only one way.
|
|
delay: time between speed changes (default: 0.2 sec)
|
|
*/
|
|
void SP_dm_dball_speed_change (edict_t *self)
|
|
{
|
|
if (!deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
if(gamerules && (gamerules->value != RDM_DEATHBALL))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if(!self->speed)
|
|
self->speed = 2;
|
|
|
|
if(!self->delay)
|
|
self->delay = 0.2;
|
|
|
|
self->touch = DBall_SpeedTouch;
|
|
self->solid = SOLID_TRIGGER;
|
|
self->movetype = MOVETYPE_NONE;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
|
|
if (!VectorCompare(self->s.angles, vec3_origin))
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
else
|
|
VectorSet (self->movedir, 1, 0, 0);
|
|
|
|
gi.setmodel (self, self->model);
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
/*QUAKED dm_dball_goal (1 .5 .5) ? TEAM1 TEAM2
|
|
Deathball goal
|
|
|
|
Team1/Team2 - beneficiary of this goal. when the ball enters this goal, the beneficiary team will score.
|
|
|
|
"wait": score to be given for this goal (default 10) player gets score+5.
|
|
*/
|
|
void SP_dm_dball_goal (edict_t *self)
|
|
{
|
|
if(!(deathmatch->value))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if(gamerules && (gamerules->value != RDM_DEATHBALL))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if(!self->wait)
|
|
self->wait = 10;
|
|
|
|
self->touch = DBall_GoalTouch;
|
|
self->solid = SOLID_TRIGGER;
|
|
self->movetype = MOVETYPE_NONE;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
|
|
if (!VectorCompare(self->s.angles, vec3_origin))
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
|
|
gi.setmodel (self, self->model);
|
|
gi.linkentity (self);
|
|
|
|
}
|