/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "g_local.h" #include "g_gametypes.h" //======================================================================= void player_pain( edict_t *self, edict_t *other, float kick, int damage ) { // player pain is handled at the end of the frame in P_DamageFeedback } void ClientObituary( edict_t *self, edict_t *inflictor, edict_t *attacker ) { int mod; char message[64]; char message2[64]; mod = meansOfDeath; GS_Obituary( self, G_PlayerGender( self ), attacker, mod, message, message2 ); // duplicate message at server console for logging if( attacker && attacker->r.client ) { if ( attacker != self ) { // regular death message self->enemy = attacker; if( dedicated->integer ) G_Printf( "%s %s %s%s\n", self->r.client->pers.netname, message, attacker->r.client->pers.netname, message2 ); } else { // suicide self->enemy = NULL; if( dedicated->integer ) G_Printf( "%s %s%s\n", self->r.client->pers.netname, S_COLOR_WHITE, message ); } G_Obituary( self, attacker, mod ); } else { // wrong place, suicide, etc. self->enemy = NULL; if( dedicated->integer ) G_Printf( "%s %s%s\n", self->r.client->pers.netname, S_COLOR_WHITE, message ); G_Obituary( self, (attacker == self) ? self : world, mod ); } } //================== //LookAtKiller //================== void LookAtKiller( edict_t *self, edict_t *inflictor, edict_t *attacker ) { vec3_t dir; if( attacker && attacker != world && attacker != self ) { VectorSubtract( attacker->s.origin, self->s.origin, dir ); } else if( inflictor && inflictor != world && inflictor != self ) { VectorSubtract( inflictor->s.origin, self->s.origin, dir ); } else { self->r.client->killer_yaw = self->s.angles[YAW]; return; } if( dir[0] ) { self->r.client->killer_yaw = RAD2DEG( atan2(dir[1], dir[0]) ); } else { self->r.client->killer_yaw = 0; if( dir[1] > 0 ) self->r.client->killer_yaw = 90; else if( dir[1] < 0 ) self->r.client->killer_yaw = -90; } if( self->r.client->killer_yaw < 0 ) self->r.client->killer_yaw += 360; } //======================================================= // DEAD BODIES //======================================================= //================== //G_DeadBody_ThirdPersonView //================== void G_DeadBody_ThirdPersonView( vec3_t vieworg, vec3_t viewangles, edict_t *passent ) { float thirdPersonRange = 60; float thirdPersonAngle = 0; float dist, f, r; vec3_t dest, stop; vec3_t chase_dest; trace_t trace; vec3_t mins = { -4, -4, -4 }; vec3_t maxs = { 4, 4, 4 }; vec3_t v_forward, v_right, v_up; AngleVectors( viewangles, v_forward, v_right, v_up ); // calc exact destination VectorCopy( vieworg, chase_dest ); r = DEG2RAD( thirdPersonAngle ); f = -cos( r ); r = -sin( r ); VectorMA( chase_dest, thirdPersonRange * f, v_forward, chase_dest ); VectorMA( chase_dest, thirdPersonRange * r, v_right, chase_dest ); chase_dest[2] += 8; // find the spot the player is looking at VectorMA( vieworg, 512, v_forward, dest ); trap_Trace( &trace, vieworg, mins, maxs, dest, passent, MASK_SOLID ); // calculate pitch to look at the same spot from camera VectorSubtract( trace.endpos, vieworg, stop ); dist = sqrt( stop[0] * stop[0] + stop[1] * stop[1] ); if( dist < 1 ) dist = 1; viewangles[PITCH] = RAD2DEG( -atan2(stop[2], dist) ); viewangles[YAW] -= thirdPersonAngle; AngleVectors( viewangles, v_forward, v_right, v_up ); // move towards destination trap_Trace( &trace, vieworg, mins, maxs, chase_dest, passent, MASK_SOLID ); if( trace.fraction != 1.0 ) { VectorCopy( trace.endpos, stop ); stop[2] += ( 1.0 - trace.fraction ) * 32; trap_Trace( &trace, vieworg, mins, maxs, stop, passent, MASK_SOLID ); VectorCopy( trace.endpos, chase_dest ); } VectorCopy( chase_dest, vieworg ); } //============= //G_Client_DeadView //============= void G_Client_DeadView( edict_t *ent ) { edict_t *body; trace_t trace; if( !ent->deadflag ) return; // find the body for( body = game.edicts + game.maxclients; ENTNUM(body) < game.maxclients + BODY_QUEUE_SIZE + 1; body++ ) { if( !body->r.inuse || body->r.svflags & SVF_NOCLIENT ) continue; if( body->activator == ent ) // this is our body break; } if( body->activator != ent ) { // ran all the list and didn't find our body return; } // move us to body position VectorCopy( body->s.origin, ent->s.origin ); // see if our killer is still in view if( body->enemy && (body->enemy != ent) ) { trap_Trace( &trace, ent->s.origin, vec3_origin, vec3_origin, body->enemy->s.origin, body, MASK_OPAQUE ); if( trace.fraction != 1.0f ) { body->enemy = NULL; } else { LookAtKiller( ent, NULL, body->enemy ); } } else { // nobody killed us, so just circle around the body ? } ent->r.client->ps.viewangles[ROLL] = 0; ent->r.client->ps.viewangles[PITCH] = 0; ent->r.client->ps.viewangles[YAW] = ent->r.client->killer_yaw; G_DeadBody_ThirdPersonView( ent->s.origin, ent->r.client->ps.viewangles, body ); } //============= //G_Client_UnlinkBodies //============= void G_Client_UnlinkBodies( edict_t *ent ) { edict_t *body; // find bodies linked to us for( body = game.edicts + game.maxclients; ENTNUM(body) < game.maxclients + BODY_QUEUE_SIZE + 1; body++ ) { if( !body->r.inuse || body->r.svflags & SVF_NOCLIENT ) continue; if( body->activator == ent ) { // this is our body body->activator = NULL; } } } //================== //InitBodyQue //================== void InitBodyQue( void ) { int i; edict_t *ent; level.body_que = 0; for( i = 0; i < BODY_QUEUE_SIZE; i++ ) { ent = G_Spawn(); ent->classname = "bodyque"; } } //================== //body_die //================== void body_die( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point ) { if( self->health >= GIB_HEALTH ) return; ThrowSmallPileOfGibs( self, 3, damage ); self->s.origin[2] -= 48; ThrowClientHead( self, damage ); } //================== //body_ready //autodestruct the body //================== void body_think( edict_t *self ) { self->health = GIB_HEALTH - 1; //effect: small gibs, and only when it is still a body, not a gibbed head. if( self->s.type == ET_PLAYER ) { ThrowSmallPileOfGibs( self, 2, 25 ); } //disallow interaction with the world. self->takedamage = DAMAGE_NO; self->r.solid = SOLID_NOT; self->s.sound = 0; self->flags |= FL_NO_KNOCKBACK; self->s.type = ET_GENERIC; self->r.svflags &= ~SVF_CORPSE; self->s.modelindex = 0; trap_LinkEntity( self ); } void body_ready( edict_t *body ) { body->takedamage = DAMAGE_YES; body->think = body_think; // body self destruction countdown if( g_deadbody_filter->integer ) { body->nextthink = level.timemsec + 2000; // explode it after 1 secs ( + 2 of being ready ) } else { body->nextthink = level.timemsec + 8000 + random()*10000; } } //================== //CopyToBodyQue //create a indexed player model for the corpse based on client's //================== edict_t *CopyToBodyQue( edict_t *ent, edict_t *attacker, int damage ) { edict_t *body; int contents; trap_UnlinkEntity(ent); contents = trap_PointContents ( ent->s.origin ); if( contents & CONTENTS_NODROP ) return NULL; G_Client_UnlinkBodies( ent ); // grab a body que and cycle to the next one body = &game.edicts[game.maxclients + level.body_que + 1]; level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; // FIXME: send an effect on the removed body if( body->s.modelindex && body->s.type == ET_PLAYER ) ThrowSmallPileOfGibs( body, 3, 10 ); trap_UnlinkEntity( body ); memset( body, 0, sizeof(edict_t) ); //clean up garbage //init body edict G_InitEdict( body ); body->classname = "body"; body->health = ent->health; body->mass = ent->mass; body->deadflag = DEAD_DEAD; body->r.owner = ent->r.owner; body->s.type = ent->s.type; body->s.team = ent->s.team; body->s.effects = 0; body->s.renderfx = 0; body->r.svflags = SVF_CORPSE; body->activator = ent; if( g_deadbody_followkiller->integer ) body->enemy = attacker; //use flat yaw body->s.angles[PITCH] = 0; body->s.angles[ROLL] = 0; body->s.angles[YAW] = ent->s.angles[YAW]; body->s.modelindex2 = 0; // <- is bodyOwner when in ET_PLAYER and bodies, but not in ET_GENERIC body->s.weapon = 0; //copy player position and box size VectorCopy( ent->s.old_origin, body->s.old_origin ); VectorCopy( ent->s.origin, body->s.origin ); VectorCopy( ent->s.origin2, body->s.origin2 ); VectorCopy( ent->r.mins, body->r.mins ); VectorCopy( ent->r.maxs, body->r.maxs ); VectorCopy( ent->r.absmin, body->r.absmin ); VectorCopy( ent->r.absmax, body->r.absmax ); VectorCopy( ent->r.size, body->r.size ); body->r.maxs[2] = body->r.mins[2] + 8; //body->r.solid = ent->r.solid; body->r.solid = SOLID_BBOX; body->takedamage = DAMAGE_YES; body->r.clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; body->movetype = MOVETYPE_TOSS; body->die = body_die; body->think = body_think; // body self destruction countdown if( ent->health < GIB_HEALTH ) { ThrowSmallPileOfGibs( body, 3, damage ); ThrowClientHead( body, damage ); // sets ET_GIB memset( &body->pmAnim, 0, sizeof(body->pmAnim) ); body->pmAnim.anim_priority[LOWER] = ANIM_DEATH; body->pmAnim.anim_priority[UPPER] = ANIM_DEATH; body->pmAnim.anim_priority[HEAD] = ANIM_DEATH; body->s.frame = 0; body->nextthink = level.timemsec + 5000 + random()*10000; } else if( ent->s.type == ET_PLAYER ) { // copy the model body->s.modelindex = ent->s.modelindex; body->s.bodyOwner = ent->s.number; // bodyOwner is the same as modelindex2 body->s.skinnum = ent->s.skinnum; // launch the death animation on the body { static int i; i = (i+1)%3; switch (i) { case 0: body->pmAnim.anim[LOWER] = BOTH_DEAD1; body->pmAnim.anim[UPPER] = BOTH_DEAD1; body->pmAnim.anim[HEAD] = 0; break; case 1: body->pmAnim.anim[LOWER] = BOTH_DEAD2; body->pmAnim.anim[UPPER] = BOTH_DEAD2; body->pmAnim.anim[HEAD] = 0; break; case 2: body->pmAnim.anim[LOWER] = BOTH_DEAD3; body->pmAnim.anim[UPPER] = BOTH_DEAD3; body->pmAnim.anim[HEAD] = 0; break; } //send the death style (1, 2 or 3) inside parameters G_AddEvent( body, EV_DIE, i, qtrue ); body->pmAnim.anim_priority[LOWER] = ANIM_DEATH; body->pmAnim.anim_priority[UPPER] = ANIM_DEATH; body->pmAnim.anim_priority[HEAD] = ANIM_DEATH; body->s.frame = ((body->pmAnim.anim[LOWER] &0x3F)|(body->pmAnim.anim[UPPER] &0x3F)<<6|(body->pmAnim.anim[HEAD] &0xF)<<12); } body->think = body_ready; body->takedamage = DAMAGE_NO; body->nextthink = level.timemsec + 500; // make damageable in 0.5 seconds } else // wasn't a player, just copy it's model { body->s.modelindex = ent->s.modelindex; body->s.frame = ent->s.frame; body->nextthink = level.timemsec + 5000 + random()*10000; } trap_LinkEntity( body ); return body; } //====================================================== // DEATH DROPS //====================================================== //================== //TossClientWeapon //================== void TossClientWeapon( edict_t *self ) { gitem_t *item; edict_t *drop; qboolean quad; float spread; item = NULL; if( self->s.weapon > WEAP_GUNBLADE ) item = game.items[self->s.weapon]; if( !self->r.client->inventory[self->r.client->ammo_weak_index] ) item = NULL; if( !(dmflags->integer & DF_QUAD_DROP) ) quad = qfalse; else quad = ( self->r.client->quad_timeout > (level.timemsec + 1000) ); if (item && quad) spread = 22.5; else spread = 0.0; if( item ) { self->r.client->v_angle[YAW] -= spread; drop = Drop_Item( self, item ); self->r.client->v_angle[YAW] += spread; if( drop ) { drop->spawnflags |= DROPPED_PLAYER_ITEM; //wsw: count of weak ammos to give when picking it up drop->count = self->r.client->inventory[self->r.client->ammo_weak_index]; } } if( quad ) { self->r.client->v_angle[YAW] += spread; drop = Drop_Item( self, game.items[POWERUP_QUAD] ); self->r.client->v_angle[YAW] -= spread; if( drop ) { drop->spawnflags |= DROPPED_PLAYER_ITEM; drop->touch = Touch_Item; drop->nextthink = level.timemsec + (self->r.client->quad_timeout - level.timemsec); drop->think = G_FreeEdict; } } } //================== //G_DropClientBackPack //================== void G_DropClientBackPack( edict_t *self ) { gitem_t *item; int active_ammo_tag; edict_t *pack; float yawoffset; item = GS_FindItemByClassname( "item_ammopack" ); if( !item ) return; if( !G_Gametype_CanDropItem(item) ) return; // find the tags of active weapon ammo if( self->s.weapon ) active_ammo_tag = game.items[self->s.weapon]->ammo_tag; else active_ammo_tag = 0; if( active_ammo_tag == AMMO_CELLS ) active_ammo_tag = 0; // nothing to drop? if( !self->r.client->inventory[AMMO_CELLS] && (!active_ammo_tag || self->r.client->inventory[active_ammo_tag]) ) return; //create the entity yawoffset = random() * self->r.client->v_angle[YAW] * 0.5; self->r.client->v_angle[YAW] -= yawoffset; pack = Drop_Item( self, item ); self->r.client->v_angle[YAW] += yawoffset; if( pack ) { pack->spawnflags |= DROPPED_PLAYER_ITEM; pack->invpak[AMMO_CELLS] = self->r.client->inventory[AMMO_CELLS]; if( active_ammo_tag ) pack->invpak[active_ammo_tag] = self->r.client->inventory[active_ammo_tag]; } } //================== //player_die //================== void player_die( edict_t *ent, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point ) { int contents; edict_t *body; VectorClear( ent->avelocity ); ent->s.angles[0] = 0; ent->s.angles[2] = 0; ent->s.sound = 0; //trap_UnlinkEntity( ent ); ent->r.solid = SOLID_NOT; // player death if( !ent->deadflag ) { contents = trap_PointContents( ent->s.origin ); LookAtKiller( ent, inflictor, attacker ); ent->r.client->ps.pmove.pm_type = PM_DEAD; ClientObituary( ent, inflictor, attacker ); // check if player is in a nodrop area and // reset flags and techs, otherwise drop items if( !(contents & CONTENTS_NODROP) ) { G_Gametype_CTF_DeadDropFlag( ent ); } else { G_Gametype_CTF_ResetClientFlag( ent ); } // create a body body = CopyToBodyQue( ent, attacker, damage ); ent->enemy = NULL; } // clear inventory memset( ent->r.client->inventory, 0, sizeof(ent->r.client->inventory) ); ent->r.client->ps.pmove.pm_type = PM_FREEZE; ent->r.client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; ent->r.client->ps.POVnum = ENTNUM(ent); // clean up powerup info ent->r.client->quad_timeout = 0; ent->r.client->grenade_blew_up = qfalse; ent->r.client->grenade_time = 0; ent->r.client->weapon_sound = 0; ent->r.client->weapon_powered = 0; ent->viewheight = 0; ent->s.modelindex = 0; ent->s.modelindex2 = 0; ent->s.effects = 0; ent->s.weapon = 0; ent->s.sound = 0; ent->s.light = 0; ent->r.solid = SOLID_NOT; ent->takedamage = DAMAGE_NO; ent->movetype = MOVETYPE_NOCLIP; ent->deathtimestamp = level.timemsec; ent->r.client->buttons = ent->r.client->latched_buttons = 0; ent->deadflag = DEAD_DEAD; trap_LinkEntity( ent ); } //======================================================================= /* ============== InitClientPersistant This is only called when the game first initializes in single player, but is called after each death and level change in deathmatch ============== */ void InitClientPersistant (gclient_t *client) { memset (&client->pers, 0, sizeof(client->pers)); client->pers.connected = qtrue; } void InitClientResp (gclient_t *client) { memset (&client->resp, 0, sizeof(client->resp)); } void InitClientTeamChange(gclient_t *client) { memset (&client->teamchange, 0, sizeof(client->teamchange)); } /* ======================================================================= SelectSpawnPoint ======================================================================= */ //jal: moved spawnpoints to their own file, so this one is a little smaller //====================================================================== //newgametypes[start] int ClientRespawn( edict_t *self ) { weapon_info_t *weapon; self->r.svflags &= ~SVF_NOCLIENT; PutClientInServer( self ); G_AddEvent( self, EV_TELEPORT, 0, qtrue ); // add a teleportation effect G_SpawnTeleportEffect( self ); // hold in place briefly self->r.client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; self->r.client->ps.pmove.pm_time = 14; G_FakeClientRespawn( self ); //MbotGame // wsw: pb set default max health self->max_health = 100; self->health = self->max_health; //give default items memset( &self->r.client->inventory, 0, sizeof(self->r.client->inventory) ); if( g_instagib->integer ) { self->r.client->inventory[WEAP_ELECTROBOLT] = 1; self->r.client->inventory[AMMO_BOLTS] = 1; self->r.client->inventory[AMMO_WEAK_BOLTS] = 1; self->r.client->latched_weapon = WEAP_ELECTROBOLT; } else { if( match.state == MATCH_STATE_WARMUP ) { int i; for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ ) { if( i == WEAP_SHOCKWAVE ) // FIXME!!! continue; weapon = G_FiredefForWeapon( i ); if( weapon ) { self->r.client->inventory[i] = 1; if( weapon->firedef_weak->ammo_id ) self->r.client->inventory[weapon->firedef_weak->ammo_id] = weapon->firedef_weak->ammo_max; if( weapon->firedef->ammo_id ) self->r.client->inventory[weapon->firedef->ammo_id] = weapon->firedef->ammo_max; } } self->r.client->armortag = ARMOR_YA; self->r.client->armor = 100; } else { self->r.client->inventory[WEAP_GUNBLADE] = 1; if( G_FiredefForAmmo(AMMO_CELLS) ) self->r.client->inventory[AMMO_CELLS] = G_FiredefForAmmo( AMMO_CELLS )->ammo_max; else self->r.client->inventory[AMMO_CELLS] = 0; self->r.client->inventory[AMMO_WEAK_GUNBLADE] = 0; } self->r.client->latched_weapon = WEAP_GUNBLADE; } ChangeWeapon( self ); self->r.client->weaponstate = WEAPON_ACTIVATING; self->r.client->weapon_nexttime = level.timemsec + 200; return qtrue; } void respawn( edict_t *self ) { self->r.client->respawn_timestamp = level.timemsec; if( G_Gametype_ClientRespawn(self) ) { self->r.client->resp.respawnCount++; //G_Printf( "%s respawn count %i\n", self->r.client->pers.netname, self->r.client->resp.respawnCount ); return; } //MbotGame[start] if (self->r.svflags & SVF_FAKECLIENT)//should never happen BOT_RemoveBot( self->r.client->pers.netname ); //[end] // restart the entire server trap_AddCommandString( "menu_loadgame\n" ); } //newgametypes[end] //============================================================== /* =========== PutClientInServer Called when a player connects to a server or respawns in a deathmatch. ============ */ void PutClientInServer( edict_t *ent ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; client_respawn_t resp; client_teamchange_t teamchange; client_persistant_t pers; char userinfo[MAX_INFO_STRING]; trap_UnlinkEntity( ent ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client // wsw: Medar: Uh? SelectSpawnPoint( ent, spawn_origin, spawn_angles ); index = ent-game.edicts-1; client = ent->r.client; // deathmatch wipes most client data every spawn resp = client->resp; teamchange = client->teamchange; pers = client->pers; memcpy( userinfo, client->pers.userinfo, sizeof(userinfo) ); memset( client, 0, sizeof(*client) ); client->resp = resp; client->teamchange = teamchange; client->pers = pers; ClientUserinfoChanged( ent, userinfo ); // clear entity values ent->groundentity = NULL; ent->r.client = &game.clients[index]; ent->takedamage = DAMAGE_AIM; ent->movetype = MOVETYPE_WALK; ent->viewheight = playerbox_stand_viewheight; ent->r.inuse = qtrue; //MbotGame[start] if( ent->ai.type == AI_ISBOT ) ent->classname = "bot"; else if( ent->r.svflags & SVF_FAKECLIENT ) ent->classname = "fakeclient"; else//[end] ent->classname = "player"; ent->mass = 200; ent->r.solid = SOLID_BBOX; ent->deadflag = DEAD_NO; ent->air_finished = level.time + 12; ent->r.clipmask = MASK_PLAYERSOLID; ent->pain = player_pain; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags &= ~FL_NO_KNOCKBACK; ent->r.svflags &= ~SVF_CORPSE; VectorCopy( playerbox_stand_mins, ent->r.mins ); VectorCopy( playerbox_stand_maxs, ent->r.maxs ); VectorClear( ent->velocity ); VectorClear( ent->avelocity ); // clear playerstate values memset( &ent->r.client->ps, 0, sizeof(client->ps) ); client->ps.pmove.origin[0] = spawn_origin[0]*16; client->ps.pmove.origin[1] = spawn_origin[1]*16; client->ps.pmove.origin[2] = spawn_origin[2]*16; client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; client->ps.POVnum = ENTNUM(ent); // clear entity state values ent->s.type = ET_PLAYER; ent->s.effects = 0; ent->s.light = 0; // modelindex and skinnum are set at calling to update userinfo ent->s.modelindex2 = 0; //splitmodels (PutClientInServer): clean up animations ent->pmAnim.anim_priority[LOWER] = ANIM_BASIC; ent->pmAnim.anim_priority[UPPER] = ANIM_BASIC; ent->pmAnim.anim_priority[HEAD] = ANIM_BASIC; ent->pmAnim.anim[LOWER] = LEGS_STAND; ent->pmAnim.anim[UPPER] = TORSO_STAND; ent->pmAnim.anim[HEAD] = ANIM_NONE; ent->s.frame = 0; VectorCopy (spawn_origin, ent->s.origin); VectorCopy (ent->s.origin, ent->s.old_origin); // set angles ent->s.angles[PITCH] = 0; ent->s.angles[YAW] = spawn_angles[YAW]; ent->s.angles[ROLL] = 0; VectorCopy( ent->s.angles, client->ps.viewangles ); VectorCopy( ent->s.angles, client->v_angle ); // set the delta angle for( i = 0; i < 3; i++ ) client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->s.angles[i]) - client->pers.cmd_angles[i]; //if invalid join spectator if( ent->s.team < 0 || ent->s.team >= GS_MAX_TEAMS ) ent->s.team = TEAM_SPECTATOR; //don't put spectators in the game if( ent->s.team == TEAM_SPECTATOR ) { ent->deadflag = DEAD_NO; ent->movetype = MOVETYPE_NOCLIP; ent->r.solid = SOLID_NOT; ent->r.svflags |= SVF_NOCLIENT; trap_LinkEntity( ent ); return; } if( !KillBox(ent) ) { // could't spawn in? } trap_LinkEntity(ent); // weapon is set properly in respawn client->latched_weapon = WEAP_NONE; ChangeWeapon(ent); } /* ===================== ClientBeginMultiplayerGame A client has just connected to the server in multiplayer mode, so clear everything out before starting them. ===================== */ void ClientBeginMultiplayerGame( edict_t *ent ) { G_InitEdict(ent); InitClientResp( ent->r.client ); InitClientTeamChange( ent->r.client ); // locate ent at a spawn point PutClientInServer(ent); if( match.state >= MATCH_STATE_POSTMATCH ) { G_MoveClientToPostMatchScoreBoards( ent, G_SelectIntermissionSpawnPoint() ); } else if( match.state >= MATCH_STATE_WARMUP && ent->s.team != TEAM_SPECTATOR ) { respawn( ent ); } G_UpdatePlayerMatchMsg( ent ); G_PrintMsg( NULL, "%s %sentered the game\n", ent->r.client->pers.netname, S_COLOR_WHITE ); } /* =========== ClientBeginSinglePlayerGame called when a client has finished connecting in singleplayer mode, and is ready to be placed into the game. This will happen every level load. ============ */ void ClientBeginSinglePlayerGame( edict_t *ent ) { int i; // if there is already a body waiting for us (a loadgame), just // take it, otherwise spawn one from scratch if (ent->r.inuse == qtrue) { // the client has cleared the client side viewangles upon // connecting to the server, which is different than the // state when the game is saved, so we need to compensate // with deltaangles for (i=0 ; i<3 ; i++) ent->r.client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->r.client->ps.viewangles[i]); } else { // a spawn point will completely reinitialize the entity // except for the persistant data that was initialized at // ClientConnect() time G_InitEdict (ent); ent->classname = "player"; InitClientResp (ent->r.client); InitClientTeamChange(ent->r.client); //newgametypes: in singleplayer join neutral team G_Teams_JoinTeam( ent, TEAM_PLAYERS ); //already calls putclientinserver } if( match.state >= MATCH_STATE_POSTMATCH ) { G_MoveClientToPostMatchScoreBoards( ent, G_SelectIntermissionSpawnPoint() ); } else { // send effect if in a multiplayer game if (game.maxclients > 1) { G_SpawnTeleportEffect(ent);//newgametypes G_PrintMsg( NULL, "%s %sentered the game\n", ent->r.client->pers.netname, S_COLOR_WHITE ); } } } /* =========== ClientBegin called when a client has finished connecting, and is ready to be placed into the game. This will happen every level load. ============ */ void ClientBegin( edict_t *ent ) { //ent->r.client = game.clients + PLAYERNUM(ent); G_Gametypes_ClienBegin(ent); ent->r.client->resp.respawnCount = 0; //clear respawncount // remove reconnecting state ent->r.client->pers.connecting = qfalse; //MbotGame[start] AI_EnemyAdded(ent); //[end] // make sure all view stuff is valid ClientEndServerFrame(ent); } /* =========== G_SetName Validate and change client's name. ============ */ void G_SetName( edict_t *ent, char *orginal_name ) { static char *invalid_names[] = { "console", NULL }; edict_t *other; char name[MAX_INFO_VALUE]; char colorless[MAX_INFO_VALUE]; int i, try; if( !ent->r.client ) return; if( !orginal_name || !strlen(orginal_name) ) Q_strncpyz( name, "Player", sizeof(name) ); else Q_strncpyz( name, orginal_name, sizeof(name) ); Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) ); if( !strlen(colorless) ) { Q_strncpyz( name, "Player", sizeof(name) ); Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) ); } if( strlen(colorless) > MAX_NAME_CHARS ) { char *in = name; int len = 0; while( *in && len < MAX_NAME_CHARS ) { if( Q_IsColorString( in ) ) { in += 2; } else { len++; in++; } } *(in+1) = 0; Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) ); } for( i = 0; invalid_names[i] != NULL; i++ ) { if( !Q_stricmp(colorless, invalid_names[i]) ) { Q_strncpyz( name, "Player", sizeof(name) ); Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) ); } } try = 1; do { for( i = 0; i < game.maxclients; i++ ) { other = game.edicts + 1 + i; if( !other->r.inuse || !other->r.client || other == ent ) continue; // if nick is already in use, try with (number) appended if( !Q_stricmp(colorless, COM_RemoveColorTokens(other->r.client->pers.netname)) ) { // remove enough characters, so that us adding ^7(1) won't matter if( try == 1 ) { name[sizeof(name)-6] = 0; Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) ); if( strlen(colorless) > MAX_NAME_CHARS - 3 ) { int remove = strlen(colorless) - (MAX_NAME_CHARS - 3); while( remove > 0 && strlen(name) >= 2 ) { if( Q_IsColorString(name + (strlen(name)-2)) ) { name[strlen(name)-2] = 0; } else { name[strlen(name)-1] = 0; remove--; } } } } else { name[strlen(name)-5] = 0; } Q_strncatz( name, va("%s(%i)", S_COLOR_WHITE, try), sizeof(name) ); Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) ); try++; break; } } } while( i != game.maxclients && try < 10 ); Q_strncpyz( ent->r.client->pers.netname, name, sizeof(ent->r.client->pers.netname) ); } /* =========== ClientUserInfoChanged called whenever the player updates a userinfo variable. The game can override any of the settings in place (forcing skins or names, etc) before copying it off. ============ */ void ClientUserinfoChanged( edict_t *ent, char *userinfo ) { char *s; char oldname[MAX_INFO_VALUE]; gclient_t *cl; char playerString[MAX_CONFIGSTRING_CHARS]; cl = ent->r.client; // check for malformed or illegal info strings if( !Info_Validate(userinfo) ) { //use default_playermodel Q_snprintfz( userinfo, sizeof(userinfo), "\\name\\badinfo\\hand\\0\\model\\%s\\skin\\%s", DEFAULT_PLAYERMODEL, DEFAULT_PLAYERSKIN ); } // color { int rgbcolor; rgbcolor = COM_ReadColorRGBString( Info_ValueForKey( userinfo, "color" ) ); if( rgbcolor != -1 ) { Vector4Set( cl->pers.color, COLOR_R(rgbcolor), COLOR_G(rgbcolor), COLOR_B(rgbcolor), 255 ); } else { G_PrintMsg( ent, "Warning: Bad 'color' cvar values. Using white\n" ); Vector4Set( cl->pers.color, 255, 255, 255, 255 ); } } // set name, it's validated and possibly changed first Q_strncpyz( oldname, cl->pers.netname, sizeof(oldname) ); G_SetName( ent, Info_ValueForKey( userinfo, "name" ) ); if( Q_stricmp( oldname, cl->pers.netname ) && cl->pers.connected ) { G_PrintMsg( NULL, "%s%s is now known as %s%s\n", oldname, S_COLOR_WHITE, cl->pers.netname, S_COLOR_WHITE ); } // handedness s = Info_ValueForKey( userinfo, "hand" ); if( strlen(s) ) { cl->pers.hand = atoi(s); } else cl->pers.hand = 2; // update client information in cgame playerString[0] = 0; Info_SetValueForKey( playerString, "name", cl->pers.netname ); Info_SetValueForKey( playerString, "hand", va("%i",cl->pers.hand) ); Info_SetValueForKey( playerString, "color", va("%i %i %i",cl->pers.color[0], cl->pers.color[1], cl->pers.color[2]) ); trap_ConfigString( CS_PLAYERINFOS + PLAYERNUM(ent), playerString ); // set skin if( ent->r.client->pers.connected ) G_Teams_AssignTeamSkin( ent, userinfo ); // fov cl->pers.fov = atoi( Info_ValueForKey(userinfo, "fov") ); if( cl->pers.fov < 1 ) cl->pers.fov = 90; else if( cl->pers.fov > 160 ) cl->pers.fov = 160; // save off the userinfo in case we want to check something later Q_strncpyz( cl->pers.userinfo, userinfo, sizeof(cl->pers.userinfo) ); } /* =========== ClientConnect Called when a player begins connecting to the server. The game can refuse entrance to a client by returning false. If the client is allowed, the connection process will continue and eventually get to ClientBegin() Changing levels will NOT cause this to be called again, but loadgames will. ============ */ qboolean ClientConnect( edict_t *ent, char *userinfo, qboolean fakeClient ) { char *value; char message[MAX_STRING_CHARS]; // check to see if they are on the banned IP list value = Info_ValueForKey( userinfo, "ip" ); if( SV_FilterPacket(value) ) { Info_SetValueForKey( userinfo, "rejtype", va("%i", DROP_TYPE_GENERAL) ); Info_SetValueForKey( userinfo, "rejflag", va("%i", 0) ); Info_SetValueForKey( userinfo, "rejmsg", "You're banned from this server" ); return qfalse; } //MbotGame[start] if( fakeClient && !G_FakeClientBeginConnection(ent) ) return qfalse; //[end] // check for a password value = Info_ValueForKey( userinfo, "password" ); if( !fakeClient && (*password->string && strcmp(password->string, value)) ) { Info_SetValueForKey( userinfo, "rejtype", va("%i", DROP_TYPE_PASSWORD) ); Info_SetValueForKey( userinfo, "rejflag", va("%i", 0) ); if( value && strlen(value) > 0 ) Info_SetValueForKey( userinfo, "rejmsg", "Password incorrect" ); else Info_SetValueForKey( userinfo, "rejmsg", "Password required" ); return qfalse; } // they can connect // make sure we start with known default if( fakeClient ) ent->r.svflags = SVF_FAKECLIENT; else ent->r.svflags = SVF_NOCLIENT; ent->s.team = TEAM_SPECTATOR; //ent->r.client = game.clients + (ent - game.edicts - 1); ent->r.client = game.clients + PLAYERNUM(ent); memset( ent->r.client, 0, sizeof( gclient_t ) ); InitClientPersistant( ent->r.client ); InitClientResp( ent->r.client ); ClientUserinfoChanged( ent, userinfo ); ent->r.client->pers.connected = qtrue; ent->r.client->pers.connecting = qtrue; #ifdef BATTLEYE if( sv_battleye->integer ) ent->r.client->pers.battleye = (atoi(Info_ValueForKey( userinfo, "cl_battleye" )) != 0); else ent->r.client->pers.battleye = 0; #endif Q_snprintfz( message, sizeof(message), "%s%s connected", ent->r.client->pers.netname, S_COLOR_WHITE ); #ifdef BATTLEYE if( sv_battleye->integer == 1 ) { if( ent->r.client->pers.battleye ) Q_strncatz( message, " (BE enabled)", sizeof(message) ); else Q_strncatz( message, " (BE disabled)", sizeof(message) ); } #endif G_PrintMsg( NULL, "%s\n", message ); G_Printf( "%s%s connected from %s\n", ent->r.client->pers.netname, S_COLOR_WHITE, Info_ValueForKey (userinfo, "ip") ); return qtrue; } /* =========== ClientDisconnect Called when a player drops from the server. Will not be called between levels. ============ */ void ClientDisconnect( edict_t *ent ) { int team; if( !ent->r.client ) return; for( team = TEAM_PLAYERS; team < GS_MAX_TEAMS; team++ ) G_Teams_UnInvitePlayer( team, ent ); G_PrintMsg( NULL, "%s %sdisconnected\n", ent->r.client->pers.netname, S_COLOR_WHITE ); G_Gametype_CTF_DeadDropFlag(ent); // send effect if( ent->s.team > TEAM_SPECTATOR ) G_SpawnTeleportEffect(ent); //MbotGame[start] G_FreeAI(ent); AI_EnemyRemoved(ent); G_FakeClientDisconnect(ent); // remove the svflag (and more) //[end] ent->s.modelindex = ent->s.modelindex2 = 0; ent->r.solid = SOLID_NOT; ent->r.inuse = qfalse; ent->r.svflags = SVF_NOCLIENT; ent->classname = "disconnected"; ent->s.team = TEAM_SPECTATOR; ent->s.weapon = WEAP_NONE; memset( ent->r.client, 0, sizeof(*ent->r.client) ); trap_ConfigString( CS_PLAYERINFOS+PLAYERNUM(ent), "" ); trap_UnlinkEntity(ent); G_Teams_UpdateMembersList(); G_Match_CheckReadys(); } //============================================================== edict_t *pm_passent; // pmove doesn't need to know about passent and contentmask void PM_trace( trace_t *tr, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end ) { if( !G_IsDead(pm_passent) ) { // wsw: pb disable player collision in race mode (thanks Medar) if(game.gametype==GAMETYPE_RACE) trap_Trace( tr, start, mins, maxs, end, pm_passent, MASK_DEADSOLID ); else trap_Trace( tr, start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID ); } else trap_Trace( tr, start, mins, maxs, end, pm_passent, MASK_DEADSOLID ); } /* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame. ============== */ void ClientThink( edict_t *ent, usercmd_t *ucmd ) { gclient_t *client; edict_t *other; int i, j, xyspeedcheck; pmove_t pm; level.current_entity = ent; client = ent->r.client; // clear events here, because ClientThink can be called // several times during one server frame (G_RunFrame hasn't advanced yet) if( !ent->s.events[0] ) { ent->numEvents = 0; ent->eventPriority[0] = ent->eventPriority[1] = qfalse; } VectorCopy( ucmd->angles, client->pers.cmd_angles ); if( match.state >= MATCH_STATE_POSTMATCH ) { // set the delta angle for( i = 0 ; i < 3 ; i++ ) client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->s.angles[i]) - ucmd->angles[i]; client->ps.pmove.pm_type = PM_FREEZE; // can exit intermission after five seconds if( match.state == MATCH_STATE_WAITEXIT && (ucmd->buttons & BUTTON_ATTACK) ) level.exitnow = qtrue; return; } pm_passent = ent; //ZOID if( ent->r.client->chase.active ) { // set the delta angle for( i = 0 ; i < 3 ; i++ ) client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->s.angles[i]) - ucmd->angles[i]; client->ps.pmove.pm_type = PM_CHASECAM; //CHASEHACKv2 & splitmodels [start] if( ucmd->upmove && game.realtime > client->chase.keytime ){ client->chase.keytime = game.realtime + 1000; client->chase.keyNext = qtrue; }//[end] return; } //ZOID // set up for pmove memset( &pm, 0, sizeof(pm) ); if( ent->r.svflags & SVF_NOCLIENT ) client->ps.pmove.pm_type = PM_SPECTATOR; else if( ent->s.type == ET_GIB ) client->ps.pmove.pm_type = PM_GIB; else if( ent->deadflag ) client->ps.pmove.pm_type = PM_FREEZE; else client->ps.pmove.pm_type = PM_NORMAL; if( gtimeout.active && client->ps.pmove.pm_type != PM_SPECTATOR ) { // only set angles the last few seconds of the timeout, to save bandwidth if( (gtimeout.endtime - gtimeout.time) < TIMEIN_TIME ) { // set the delta angle for( i = 0 ; i < 3 ; i++ ) client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->s.angles[i]) - ucmd->angles[i]; } client->ps.pmove.pm_type = PM_FREEZE; return; } client->ps.pmove.gravity = g_gravity->value; pm.s = client->ps.pmove; for( i = 0; i < 3; i++ ) { pm.s.origin[i] = ent->s.origin[i]*16; pm.s.velocity[i] = ent->velocity[i]*16; } if( memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)) ) { pm.snapinitial = qtrue; // G_Printf ("pmove changed!\n"); } pm.cmd = *ucmd; pm.trace = PM_trace; // adds default parms pm.pointcontents = trap_PointContents; // wsw : jal : set max_walljumps for gametype pm.max_walljumps = GS_GameType_MaxWallJumps( game.gametype ); // perform a pmove Pmove( &pm ); // save results of pmove client->ps.pmove = pm.s; client->old_pmove = pm.s; for( i=0; i<3; i++ ) { ent->s.origin[i] = pm.s.origin[i]*(1.0/16.0); ent->velocity[i] = pm.s.velocity[i]*(1.0/16.0); } VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); //splitmodels jal[start] xyspeedcheck = sqrt( ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1] ); // send jumping events if( client->walljumped ) { if( !(pm.s.pm_flags & PMF_WALLJUMPING) ) client->walljumped = qfalse; } else { if( pm.s.pm_flags & PMF_WALLJUMPING ) { G_AddEvent( ent, EV_JUMP, 1, qtrue ); client->walljumped = qtrue; } } if( pm.s.pm_flags & PMF_DASHING && ent->velocity[2] > 0.0f && ent->groundentity && (pm.groundentity == -1) ) G_AddEvent( ent, EV_JUMP, 2, qtrue ); if( ent->groundentity && (pm.groundentity == -1) && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0) ) { if(ent->velocity[2] > 40.0f) { // typhontaur fix G_AddEvent( ent, EV_JUMP, 0, qtrue ); //LAUNCH JUMP animation //vertical velocity: //a simple strafe jump gives a velocity of 4179 //double jumps & ramp jumps are over 5500 (and over 5600...) // //note: this checks may not work with altered gravity //Com_Printf ("%i\n",pm.s.velocity[2]); if( (pm.s.velocity[2] < 5500) || !ent->pmAnim.anim_jump )//is not a double jump { if( ent->pmAnim.anim_jump == qtrue && ent->pmAnim.anim_jump_thunk == qtrue ) { //REBOUNCE if( xyspeedcheck > 50 ){ if( ent->pmAnim.anim_jump_style < 2 ) { ent->pmAnim.anim_jump_style = 2; } else ent->pmAnim.anim_jump_style = 1; } else ent->pmAnim.anim_jump_style = 0; } else {// is a simple jump if( pm.cmd.forwardmove >= 20 && xyspeedcheck > 50 ) { if( ent->pmAnim.anim_jump_style < 2 ) ent->pmAnim.anim_jump_style = 2; else ent->pmAnim.anim_jump_style = 1; } else ent->pmAnim.anim_jump_style = 0; } //set the jump as active and launch the animation change process. ent->pmAnim.anim_jump_thunk = qfalse; ent->pmAnim.anim_jump = qtrue; } } } //jal [end] ent->viewheight = pm.viewheight; ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; if( pm.groundentity != -1 ) { ent->groundentity = &game.edicts[pm.groundentity]; ent->groundentity_linkcount = ent->groundentity->r.linkcount; } else { ent->groundentity = NULL; } if( ent->deadflag ) { G_Client_DeadView(ent); } else { VectorCopy( pm.viewangles, client->v_angle ); VectorCopy( pm.viewangles, client->ps.viewangles ); } trap_LinkEntity(ent); if( ent->movetype != MOVETYPE_NOCLIP ) G_TouchTriggers(ent); // touch other objects for( i = 0; i < pm.numtouch; i++ ) { other = &game.edicts[pm.touchents[i]]; for( j = 0; j < i; j++ ) { if( &game.edicts[pm.touchents[j]] == other ) break; } if( j != i ) continue; // duplicated if( !other->touch ) continue; other->touch( other, ent, NULL, 0 ); } // during the min respawn time, clear all buttons if( ent->deathtimestamp + g_respawn_delay_min->integer > level.timemsec ) { // during the first 100 msecs after a death, clear up buttons. //if( ent->deathtimestamp + 100 > level.timemsec ) { client->latched_buttons = client->buttons = 0; } else { client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; } //splitmodels: jal[begin]: set movement flags for animations ent->pmAnim.anim_moveflags = 0;//start from 0 if( ucmd->forwardmove < -1 ) ent->pmAnim.anim_moveflags |= ANIMMOVE_BACK; else if( ucmd->forwardmove > 1 ) ent->pmAnim.anim_moveflags |= ANIMMOVE_FRONT; if( ucmd->sidemove < -1 ) ent->pmAnim.anim_moveflags |= ANIMMOVE_LEFT; else if( ucmd->sidemove > 1 ) ent->pmAnim.anim_moveflags |= ANIMMOVE_RIGHT; if( !(client->buttons & BUTTON_WALK) && xyspeedcheck ) ent->pmAnim.anim_moveflags |= ANIMMOVE_RUN; else if( xyspeedcheck ) ent->pmAnim.anim_moveflags |= ANIMMOVE_WALK; if( client->ps.pmove.pm_flags & PMF_DUCKED ) ent->pmAnim.anim_moveflags |= ANIMMOVE_DUCK; //jal[end] // fire weapon from final position if needed if( client->latched_buttons & BUTTON_ATTACK && ent->movetype != MOVETYPE_NOCLIP ) { if( !client->weapon_thunk ) { client->weapon_thunk = qtrue; Think_Weapon(ent); } } //MBotGame[start] AITools_DropNodes(ent); //MBotGame[end] } /* ============== ClientBeginServerFrame This will be called once for each server frame, before running any other entities in the world. ============== */ void ClientBeginServerFrame( edict_t *ent ) { gclient_t *client; int buttonMask; if( match.state >= MATCH_STATE_POSTMATCH ) return; client = ent->r.client; // run weapon animations if it hasn't been done by a ucmd_t if( !gtimeout.active && !client->weapon_thunk && ent->movetype != MOVETYPE_NOCLIP ) Think_Weapon (ent); else client->weapon_thunk = qfalse; if( ent->deadflag ) { // in deathmatch, only wait for attack button buttonMask = BUTTON_ATTACK; // wait for any button just going down if( level.timemsec > ent->deathtimestamp + g_respawn_delay_min->integer ) { if( client->latched_buttons & buttonMask ) { respawn(ent); client->latched_buttons = client->buttons = 0; } } // too much time passed if( g_respawn_delay_max->integer && (level.timemsec > ent->deathtimestamp + g_respawn_delay_max->integer) ) { respawn(ent); } client->latched_buttons = client->buttons = 0; return; } client->latched_buttons = 0; }