// g_teamplay.c - teamplay oriented code #include "g_local.h" // current teamplay mode (set by "level.style") teamplay_mode_t teamplay_mode; #define CASH_ROLL 10 #define CASH_BAG 25 #define MAX_CASH_ITEMS 10 // never spawn more than this many cash items at once int num_cash_items; char *team_names[] = { "(spectator)", "Dragons", "Nikki's Boyz", NULL }; int team_cash[3]; // cash per team, 0 is neutral so just ignore float last_safe_withdrawal[3]; float last_safe_deposit[3]; //===================================================================== // Entity spawn functions // .................................................................... // Cash Spawning during GRAB DA LOOT void cash_touch( edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { float speed; if (surf && plane && plane->normal[2] > 0.5) { // let it rest here if ((speed = VectorLength( self->velocity )) > 10) { self->s.angles[ROLL] = 0; self->s.angles[PITCH] = 0; self->avelocity[PITCH] = 0; self->avelocity[ROLL] = 0; self->avelocity[YAW] *= 0.5; // randomize bounce VectorAdd( self->velocity, tv( crandom()*speed*0.3, crandom()*speed*0.3, random()*speed*0.15 ), self->velocity ); } else { VectorClear( self->velocity ); VectorClear( self->avelocity ); self->s.angles[PITCH] = 0; self->s.angles[ROLL] = 0; self->movetype = MOVETYPE_NONE; } return; } if (other->client) { if (other->client->pers.currentcash < MAX_CASH_PLAYER) { // they can hold the cash if ((self->currentcash == CASH_BAG) || (self->movetype != MOVETYPE_NONE) || (other->client->ps.pmove.pm_flags & PMF_DUCKED)) { // they can pick it up Touch_Item( self, other, plane, surf ); num_cash_items--; G_FreeEdict( self ); return; } } } } void cash_kill( edict_t *self ) { num_cash_items--; G_FreeEdict( self ); } void cashroll_animate( edict_t *self ) { // reduce XY velocity (air friction) self->velocity[0] *= 0.9; self->velocity[1] *= 0.9; if (level.time > (self->timestamp)) { cash_kill( self ); return; } if (self->movetype != MOVETYPE_NONE) { if (VectorDistance( self->s.origin, self->pos1 ) < 1) self->count++; else self->count = 0; VectorCopy( self->s.origin, self->pos1 ); if (self->count > 2) // rested for 2 frames { VectorClear( self->velocity ); VectorClear( self->avelocity ); self->s.angles[PITCH] = 0; self->s.angles[ROLL] = 0; self->movetype = MOVETYPE_NONE; } } self->nextthink = level.time + 0.1; } void cashspawn_think( edict_t *self ) { edict_t *cash; if (num_cash_items > MAX_CASH_ITEMS) { self->nextthink = level.time + self->delay; return; } // spawn some money cash = G_Spawn(); VectorCopy( self->s.origin, cash->s.origin ); cash->movetype = MOVETYPE_BOUNCE; cash->solid = SOLID_TRIGGER; AngleVectors( self->s.angles, cash->velocity, NULL, NULL ); VectorScale( cash->velocity, self->speed, cash->velocity ); // randomize the velocity a bit VectorAdd( cash->velocity, tv( crandom()*self->speed*0.3, crandom()*self->speed*0.3, crandom()*self->speed*0.15 ), cash->velocity ); cash->s.renderfx2 |= RF2_NOSHADOW; // FIXME: doh this doesn't work, need to spawn actual item's, so the HUD is updated automatically when picking up if (!strcmp(self->type, "cashroll")) { // small dollar notes cash->s.modelindex = gi.modelindex( "models/pu_icon/cash/tris.md2" ); cash->gravity = 0.1 + random()*0.5; cash->think = cashroll_animate; cash->nextthink = level.time + 0.1; cash->s.angles[PITCH] = 10; VectorSet( cash->avelocity, 0, 10000 * cash->gravity, 0 ); VectorSet( cash->mins, -4, -4, -15 ); VectorSet( cash->maxs, 4, 4, -13 ); cash->item = FindItem("Cash"); cash->currentcash = CASH_ROLL; cash->touch = cash_touch; cash->timestamp = level.time + 60; cash->think = cashroll_animate; cash->nextthink = level.time + 0.1; } else { cash->s.modelindex = gi.modelindex( "models/pu_icon/money/money_sm.md2" ); cash->gravity = 1.0; VectorSet( cash->mins, -12, -12, -15 ); VectorSet( cash->maxs, 12, 12, 10 ); cash->item = FindItem("Small Cash Bag"); cash->currentcash = CASH_BAG; cash->touch = cash_touch; cash->think = cash_kill; cash->nextthink = level.time + 60; } num_cash_items++; self->nextthink = level.time + self->delay; } /*QUAKED dm_cashspawn (0.5 0 1) (-16 -16 -16) (16 16 16) Spawn location for cash during "Grab da Loot" games angle - direction to project cash upon spawning speed - speed of projection type - "cashroll" or "cashbag" (more money, longer delay) */ void SP_dm_cashspawn( edict_t *self ) { if (!teamplay->value || ((int)teamplay->value != TM_AUTO && (int)teamplay->value != TM_GRABDALOOT)) return; // set the game to "Grab da Loot" teamplay_mode = TM_GRABDALOOT; num_cash_items = 0; if (!strcmp(self->type, "cashroll")) { self->delay = (float)g_cashspawndelay->value; } else // bag, so longer delay { self->delay = (float)g_cashspawndelay->value * (CASH_BAG / CASH_ROLL); } if (!self->speed) self->speed = 10; self->think = cashspawn_think; self->nextthink = level.time + self->delay; } // .................................................................... // Safe Bag, used for Grab Da Loot and teamplay Cash-Match void safebag_touch( edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { static float last_touch_time; static edict_t *last_touch_ent; static int last_touch_count = 0; if (!other->client) return; if ((level.time < last_touch_time) || (last_touch_time && (last_touch_time < (level.time - 2.0))) || (last_touch_ent && (last_touch_ent != other))) { // reset last_touch_time = 0; last_touch_ent = NULL; last_touch_count = 0; } else if (last_touch_time > (level.time - 0.1)) { return; } else { last_touch_count++; last_touch_time = level.time; if (last_touch_count > (int)(50.0 * (1.0 + (0.5*(other->client->pers.team == self->style))))) { // let them go away on their own terms T_Damage( other, other, other, vec3_origin, other->s.origin, vec3_origin, 9999, 0, 0, MOD_SAFECAMPER ); last_touch_count = 0; } } last_touch_ent = other; if (self->timestamp > (level.time - 1.0)) return; self->timestamp = level.time; // depositing, or withdrawing? if (other->client->pers.team == self->style) { // deposit if (other->client->pers.currentcash > 0 || other->client->pers.bagcash > 0) { int precash, amount; precash = team_cash[self->style]; team_cash[self->style] += other->client->pers.currentcash; team_cash[self->style] += other->client->pers.bagcash; other->client->resp.deposited += other->client->pers.currentcash; other->client->resp.deposited += other->client->pers.bagcash; other->client->pers.currentcash = 0; other->client->pers.bagcash = 0; gi.sound(other, CHAN_ITEM, gi.soundindex("world/pickups/cash.wav"), 1, 3, 0); // make a droping sound gi.positioned_sound(self->s.origin, self, CHAN_ITEM, gi.soundindex("world/doors/dr1_end.wav"), 1, 1, 0); // let everyone know how much was deposited amount = team_cash[self->style] - precash; gi.bprintf( PRINT_MEDIUM, "%s deposited $%i\n", other->client->pers.netname, amount ); last_safe_deposit[self->style] = level.time; } } else if (team_cash[self->style] > 0) { // withdrawal if (other->client->pers.bagcash < MAX_BAGCASH_PLAYER) { int precash, amount; precash = team_cash[self->style]; team_cash[self->style] -= (MAX_BAGCASH_PLAYER - other->client->pers.bagcash); other->client->pers.bagcash += (MAX_BAGCASH_PLAYER - other->client->pers.bagcash); if (team_cash[self->style] < 0) { // don't take more than they have other->client->pers.bagcash += team_cash[self->style]; team_cash[self->style] = 0; } gi.sound(other, CHAN_ITEM, gi.soundindex("world/pickups/cash.wav"), 1, 3, 0); // alarm! gi.positioned_sound(self->s.origin, self, CHAN_ITEM, gi.soundindex("misc/cashmatch_alarm.wav"), 1, 1, 0); // let everyone know how much was stolen amount = precash - team_cash[self->style]; gi.bprintf( PRINT_MEDIUM, "%s stole $%i from %s's safe!\n", other->client->pers.netname, amount, team_names[self->style] ); last_safe_withdrawal[self->style] = level.time; } } } // Safebag think, if a friendly guy has been standing near us for more than a few seconds, make them vulnerable to friendly fire void safebag_think(edict_t *self) { int i; edict_t *trav; qboolean noenemies = true; #define SAFE_CLOSE_DIST 128 #define MAX_TIMEATSAFE 8.0 // first, check if we have any unwanted enemies around, if so, don't count for (i=0; ivalue; i++) { trav = &g_edicts[i+1]; if (!trav->inuse || !trav->client) continue; if (trav->health <= 0) continue; if (!trav->client->pers.team || (trav->client->pers.team == self->style)) continue; if (VectorDistance( self->s.origin, trav->s.origin ) > 512) continue; if (!gi.inPVS( self->s.origin, trav->s.origin )) continue; noenemies = false; } for (i=0; ivalue; i++) { trav = &g_edicts[i+1]; if (!trav->inuse || !trav->client) continue; if (trav->health <= 0) continue; if (!trav->client->pers.team || (trav->client->pers.team != self->style)) continue; if (noenemies) { if (VectorDistance( self->s.origin, trav->s.origin ) > SAFE_CLOSE_DIST) { trav->client->pers.timeatsafe -= 0.2; if (trav->client->pers.timeatsafe < 0) trav->client->pers.timeatsafe = 0; } else { trav->client->pers.timeatsafe += 0.2; if (trav->client->pers.timeatsafe > MAX_TIMEATSAFE) trav->client->pers.timeatsafe = MAX_TIMEATSAFE; } if (trav->client->pers.timeatsafe >= MAX_TIMEATSAFE) trav->client->pers.friendly_vulnerable = true; else trav->client->pers.friendly_vulnerable = false; } else // turn off vulnerability, there is an enemy in range { trav->client->pers.friendly_vulnerable = false; } } self->nextthink = level.time + 0.2; } /*QUAKED dm_safebag (0.5 0 1) (-12 -12 -16) (12 12 12) Bag that holds the money in the safe. style - team that this bag belongs to (1 or 2) */ void SP_dm_safebag( edict_t *self ) { if (!teamplay->value) { G_FreeEdict( self ); return; } if (self->style < 1 || self->style > 2) { gi.dprintf( "dm_safebag has invalid \"style\" at %s, should be 1 or 2.\n", vtos(self->s.origin)); G_FreeEdict( self ); return; } self->s.modelindex = gi.modelindex("models/pu_icon/money/money_lg.md2"); VectorSet( self->mins, -12, -12, -16 ); VectorSet( self->maxs, 12, 12, 12 ); self->movetype = MOVETYPE_NONE; self->solid = SOLID_TRIGGER; gi.linkentity( self ); self->touch = safebag_touch; self->currentcash = 0; // start with no cash self->think = safebag_think; self->nextthink = level.time + 2; } /*QUAKED dm_props_banner (.5 0 1) (-4 -4 -4) (4 4 4) Temp banner for teamplay style = team (1 / 2) scale = scale the size up/down (2 = double size) model="models\props\temp\triangle\small.md2" */ void SP_dm_props_banner (edict_t *self) { // vec3_t end, bestnorm, bestend; // float bestdist; // int x,y; // trace_t tr; if (!deathmatch->value || !teamplay->value) { // remove G_FreeEdict (self); return; } if (!self->style) { gi.dprintf( "%s has invalid style (should be 1 or 2) at %s\n", self->classname, vtos(self->s.origin) ); G_FreeEdict (self); return; } /* // trace a line back, to get the wall, then go out { bestdist = 9999; for (x=-256; x<300; x+= 256) { VectorCopy( self->s.origin, end ); end[0] = self->s.origin[0] + x; tr = gi.trace( self->s.origin, NULL, NULL, end, NULL, MASK_SOLID ); if (tr.fraction < bestdist) { VectorCopy( tr.plane.normal, bestnorm ); VectorCopy( tr.endpos, bestend ); bestdist = tr.fraction; } } for (y=-256; y<300; y+= 256) { VectorCopy( self->s.origin, end ); end[1] = self->s.origin[1] + y; tr = gi.trace( self->s.origin, NULL, NULL, end, NULL, MASK_SOLID ); if (tr.fraction < bestdist) { VectorCopy( tr.plane.normal, bestnorm ); VectorCopy( tr.endpos, bestend ); bestdist = tr.fraction; } } vectoangles( bestnorm, self->s.angles ); VectorMA( bestend, 40 * self->cast_info.scale, bestnorm, self->s.origin ); } */ // Ridah, 1-jun-99, use flag models for now #if 1 { void think_flag (edict_t *self); // self->solid = SOLID_BBOX; self->movetype = MOVETYPE_NONE; if (self->style == 2) { self->model = "models/props/flag/flag1.md2"; } else { self->model = "models/props/flag/flag3.md2"; } self->s.modelindex = gi.modelindex (self->model); self->s.renderfx2 |= RF2_NOSHADOW; self->s.renderfx |= RF_MINLIGHT; if (!self->cast_info.scale) self->cast_info.scale = 1; self->s.scale = (self->cast_info.scale - 1); // VectorMA( bestend, 40 * self->cast_info.scale, bestnorm, self->s.origin ); self->cast_info.scale *= 0.3; gi.linkentity (self); self->s.effects |= EF_ANIM_ALLFAST_NEW; self->s.renderfx2 |= RF2_MODULUS_FRAME; self->s.renderfx2 |= RDF_NOLERP; // Disabled, doesn't animate much, and uses bandwidth // self->nextthink = level.time + FRAMETIME *2; // self->think = think_flag; } #else // TRIANGULAR ROTATING ICONS self->solid = SOLID_NOT; self->movetype = MOVETYPE_NONE; self->s.skinnum = self->style - 1; self->s.renderfx2 |= RF2_NOSHADOW; self->s.renderfx |= RF_MINLIGHT; if (!self->cast_info.scale) self->cast_info.scale = 1; self->s.scale = self->cast_info.scale - 1; self->s.modelindex = gi.modelindex ("models/props/temp/triangle/small.md2"); gi.linkentity (self); { edict_t *arm; arm = G_Spawn(); arm->solid = self->solid; arm->movetype = self->movetype; arm->s.renderfx2 |= RF2_NOSHADOW; arm->s.scale = self->s.scale; VectorCopy( self->s.origin, arm->s.origin ); VectorCopy( self->s.angles, arm->s.angles ); arm->s.modelindex = gi.modelindex ("models/props/temp/triangle/arm.md2"); gi.linkentity (arm); } VectorCopy( self->s.angles, self->last_step_pos ); VectorClear( self->move_angles ); #endif } // .................................................................... void Teamplay_ValidateSkin( edict_t *self ) { // TODO: we need color coded skins, for now, just use any skin } extern void ClientUserinfoChanged (edict_t *ent, char *userinfo); qboolean Teamplay_ValidateJoinTeam( edict_t *self, int teamindex ) { // NOTE: this is called by each player on level change, as well as when a player issues a "join XXX" command // TODO: player limit per team? cvar? // setup client stuff self->movetype = MOVETYPE_WALK; self->solid = SOLID_BBOX; self->svflags &= ~SVF_NOCLIENT; Teamplay_ValidateSkin( self ); // InitClientPersistant (self->client); self->client->pers.team = teamindex; /* // Validate skins { char *str[256]; strcpy( str, self->client->pers.userinfo ); ClientUserinfoChanged ( self, str ); } */ self->health = 0; // so we're not counted in spawn point checking self->client->resp.enterframe = level.framenum; InitClientResp( self->client ); PutClientInServer( self ); // find a new spawn point gi.bprintf( PRINT_HIGH, "%s joined %s\n", self->client->pers.netname, team_names[teamindex] ); return true; } void Teamplay_AutoJoinTeam( edict_t *self ) { int team_count[2]; int i; // count number of players on each team, assign the team with least players team_count[0] = 0; team_count[1] = 0; for (i=1; ivalue; i++) { if (g_edicts[i].client && g_edicts[i].client->pers.team) team_count[g_edicts[i].client->pers.team - 1]++; } if (team_count[0] > team_count[1]) self->client->pers.team = 2; else self->client->pers.team = 1; Teamplay_ValidateJoinTeam( self, self->client->pers.team ); } void Teamplay_InitTeamplay (void) { num_cash_items = 0; memset( team_cash, 0, sizeof(int) * 3 ); memset( last_safe_withdrawal, 0, sizeof(float) * 3 ); memset( last_safe_deposit, 0, sizeof(float) * 3 ); last_safe_deposit[0] = last_safe_deposit[1] = 0; last_safe_withdrawal[0] = last_safe_withdrawal[1] = 0; }