/* =========================================================================== Copyright (C) 1997-2001 Id Software, Inc. Copyright (C) 2000-2002 Mr. Hyde and Mad Dog This file is part of Lazarus Quake 2 Mod source code. Lazarus Quake 2 Mod source code 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. Lazarus Quake 2 Mod source code 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 Lazarus Quake 2 Mod source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "g_local.h" #include "m_player.h" #define MUD1BASE 0.20 #define MUD1AMP 0.08 #define MUD3 0.08 void ClientUserinfoChanged (edict_t *ent, char *userinfo); void SP_misc_teleporter_dest (edict_t *ent); // // Gross, ugly, disgustuing hack section // // this function is an ugly as hell hack to fix some map flaws // // the coop spawn spots on some maps are SNAFU. There are coop spots // with the wrong targetname as well as spots with no name at all // // we use carnal knowledge of the maps to fix the coop spot targetnames to match // that of the nearest named single player spot /*static*/ void SP_FixCoopSpots (edict_t *self) { edict_t *spot; vec3_t d; spot = NULL; while (1) { spot = G_Find(spot, FOFS(classname), "info_player_start"); if (!spot) return; if (!spot->targetname) continue; VectorSubtract(self->s.origin, spot->s.origin, d); if (VectorLength(d) < 384) { if ((!self->targetname) || Q_stricmp(self->targetname, spot->targetname) != 0) { // gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname); self->targetname = spot->targetname; } return; } } } // now if that one wasn't ugly enough for you then try this one on for size // some maps don't have any coop spots at all, so we need to create them // where they should have been /*static*/ void SP_CreateCoopSpots (edict_t *self) { edict_t *spot; if (Q_stricmp(level.mapname, "security") == 0) { spot = G_Spawn(); spot->classname = "info_player_coop"; spot->s.origin[0] = 188 - 64; spot->s.origin[1] = -164; spot->s.origin[2] = 80; spot->targetname = "jail3"; spot->s.angles[1] = 90; spot = G_Spawn(); spot->classname = "info_player_coop"; spot->s.origin[0] = 188 + 64; spot->s.origin[1] = -164; spot->s.origin[2] = 80; spot->targetname = "jail3"; spot->s.angles[1] = 90; spot = G_Spawn(); spot->classname = "info_player_coop"; spot->s.origin[0] = 188 + 128; spot->s.origin[1] = -164; spot->s.origin[2] = 80; spot->targetname = "jail3"; spot->s.angles[1] = 90; return; } } /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) The normal starting point for a level. */ void SP_info_player_start(edict_t *self) { self->class_id = ENTITY_INFO_PLAYER_START; if (!coop->value) return; if (Q_stricmp(level.mapname, "security") == 0) { // invoke one of our gross, ugly, disgusting hacks self->think = SP_CreateCoopSpots; self->nextthink = level.time + FRAMETIME; } } /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) potential spawning position for deathmatch games */ void SP_info_player_deathmatch(edict_t *self) { if (!deathmatch->value) { G_FreeEdict (self); return; } SP_misc_teleporter_dest (self); self->class_id = ENTITY_INFO_PLAYER_DEATHMATCH; } /*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) potential spawning position for coop games */ void SP_info_player_coop(edict_t *self) { if (!coop->value) { G_FreeEdict (self); return; } self->class_id = ENTITY_INFO_PLAYER_COOP; if ((Q_stricmp(level.mapname, "jail2") == 0) || (Q_stricmp(level.mapname, "jail4") == 0) || (Q_stricmp(level.mapname, "mine1") == 0) || (Q_stricmp(level.mapname, "mine2") == 0) || (Q_stricmp(level.mapname, "mine3") == 0) || (Q_stricmp(level.mapname, "mine4") == 0) || (Q_stricmp(level.mapname, "lab") == 0) || (Q_stricmp(level.mapname, "boss1") == 0) || (Q_stricmp(level.mapname, "fact3") == 0) || (Q_stricmp(level.mapname, "biggun") == 0) || (Q_stricmp(level.mapname, "space") == 0) || (Q_stricmp(level.mapname, "command") == 0) || (Q_stricmp(level.mapname, "power2") == 0) || (Q_stricmp(level.mapname, "strike") == 0)) { // invoke one of our gross, ugly, disgusting hacks self->think = SP_FixCoopSpots; self->nextthink = level.time + FRAMETIME; } } /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) LETTERBOX The deathmatch intermission point will be at one of these Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' */ void SP_info_player_intermission (edict_t *self) //was void { self->class_id = ENTITY_INFO_PLAYER_INTERMISSION; } //======================================================================= 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 } qboolean IsFemale (edict_t *ent) { char *info; if (!ent->client) return false; info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); if (info[0] == 'f' || info[0] == 'F') return true; if (strstr(info, "crakhor")) // Knightmare added return true; return false; } qboolean IsNeutral (edict_t *ent) { char *info; if (!ent->client) return false; info = Info_ValueForKey (ent->client->pers.userinfo, "gender"); if (strstr(info, "crakhor")) // Knightmare added return false; if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M') return true; return false; } void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker) { int mod; char *message; char *message2; qboolean ff; if (coop->value && attacker->client) meansOfDeath |= MOD_FRIENDLY_FIRE; if (deathmatch->value || coop->value || !deathmatch->value) { ff = meansOfDeath & MOD_FRIENDLY_FIRE; mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; message = NULL; message2 = ""; switch (mod) { case MOD_SUICIDE: message = "suicides"; break; case MOD_FALLING: message = "cratered"; break; case MOD_CRUSH: message = "was squished"; break; case MOD_WATER: message = "sank like a rock"; break; case MOD_SLIME: message = "melted"; break; case MOD_LAVA: message = "does a back flip into the lava"; break; case MOD_EXPLOSIVE: case MOD_BARREL: message = "blew up"; break; case MOD_EXIT: message = "found a way out"; break; case MOD_TARGET_LASER: message = "saw the light"; break; case MOD_TARGET_BLASTER: message = "got blasted"; break; case MOD_BOMB: case MOD_SPLASH: case MOD_TRIGGER_HURT: case MOD_VEHICLE: message = "was in the wrong place"; break; } if (attacker == self) { switch (mod) { case MOD_HELD_GRENADE: message = "tried to put the pin back in"; break; case MOD_HG_SPLASH: case MOD_G_SPLASH: if (IsNeutral(self)) message = "tripped on its own grenade"; else if (IsFemale(self)) message = "tripped on her own grenade"; else message = "tripped on his own grenade"; break; case MOD_R_SPLASH: if (IsNeutral(self)) message = "blew itself up"; else if (IsFemale(self)) message = "blew herself up"; else message = "blew himself up"; break; case MOD_BFG_BLAST: message = "should have used a smaller gun"; break; default: if (IsNeutral(self)) message = "killed itself"; else if (IsFemale(self)) message = "killed herself"; else message = "killed himself"; break; } } if (message) { safe_bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message); if (deathmatch->value) self->client->resp.score--; self->enemy = NULL; return; } self->enemy = attacker; if (attacker && attacker->client) { switch (mod) { case MOD_BLASTER: message = "was blasted by"; break; case MOD_SHOTGUN: message = "was gunned down by"; break; case MOD_SSHOTGUN: message = "was blown away by"; message2 = "'s super shotgun"; break; case MOD_MACHINEGUN: message = "was machinegunned by"; break; case MOD_CHAINGUN: message = "was cut in half by"; message2 = "'s chaingun"; break; case MOD_GRENADE: message = "was popped by"; message2 = "'s grenade"; break; case MOD_G_SPLASH: message = "was shredded by"; message2 = "'s shrapnel"; break; case MOD_ROCKET: message = "ate"; message2 = "'s rocket"; break; case MOD_R_SPLASH: message = "almost dodged"; message2 = "'s rocket"; break; case MOD_HYPERBLASTER: message = "was melted by"; message2 = "'s hyperblaster"; break; case MOD_RAILGUN: message = "was railed by"; break; case MOD_BFG_LASER: message = "saw the pretty lights from"; message2 = "'s BFG"; break; case MOD_BFG_BLAST: message = "was disintegrated by"; message2 = "'s BFG blast"; break; case MOD_BFG_EFFECT: message = "couldn't hide from"; message2 = "'s BFG"; break; case MOD_HANDGRENADE: message = "caught"; message2 = "'s handgrenade"; break; case MOD_HG_SPLASH: message = "didn't see"; message2 = "'s handgrenade"; break; case MOD_HELD_GRENADE: message = "feels"; message2 = "'s pain"; break; case MOD_TELEFRAG: message = "tried to invade"; message2 = "'s personal space"; break; //ZOID case MOD_GRAPPLE: message = "was caught by"; message2 = "'s grapple"; break; //ZOID case MOD_VEHICLE: message = "was splattered by"; message2 = "'s vehicle"; break; case MOD_KICK: message = "was booted by"; message2 = "'s foot"; break; } if (message) { safe_bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2); if (deathmatch->value) { if (ff) attacker->client->resp.score--; else attacker->client->resp.score++; } return; } } // Knightmare- Single-player obits if (attacker->svflags & SVF_MONSTER) { // Light Guard if (!strcmp(attacker->classname, "monster_soldier_light")) message = "was blasted by a"; // Shotgun Guard else if (!strcmp(attacker->classname, "monster_soldier")) message = "was gunned down by a"; // Machinegun Guard else if (!strcmp(attacker->classname, "monster_soldier_ss")) message = "was machinegunned by a"; // Enforcer else if (!strcmp(attacker->classname, "monster_infantry")) { if (mod == MOD_HIT) message = "was bludgened by an"; else message = "was pumped full of lead by an"; } // Gunner else if (!strcmp(attacker->classname, "monster_gunner")) { if (mod == MOD_GRENADE) { message = "was popped by a"; message2 = "'s grenade"; } else if (mod == MOD_G_SPLASH) { message = "was shredded by a"; message2 = "'s shrapnel"; } else message = "was machinegunned by a"; } // Berserker else if (!strcmp(attacker->classname, "monster_berserk")) message = "was smashed by a"; // Gladiator else if (!strcmp(attacker->classname, "monster_gladiator")) { if (mod == MOD_RAILGUN) message = "was railed by a"; else { message = "was mangled by a"; message2 = "'s claw"; } } // Medic else if (!strcmp(attacker->classname, "monster_medic")) message = "was blasted by a"; // Icarus else if (!strcmp(attacker->classname, "monster_hover")) message = "was blasted by an"; // Iron Maiden else if (!strcmp(attacker->classname, "monster_chick")) { if (mod == MOD_ROCKET) { message = "ate an"; message2 = "'s rocket"; } else if (mod == MOD_R_SPLASH) { message = "almost dodged an"; message2 = "'s rocket"; } else if (mod == MOD_HIT) message = "was bitch-slapped by an"; } // Parasite else if (!strcmp(attacker->classname, "monster_parasite")) message = "was exsanguiated by a"; // Brain else if (!strcmp(attacker->classname, "monster_brain")) { message = "was torn up by a"; message2 = "'s tentacles"; } // Flyer else if (!strcmp(attacker->classname, "monster_flyer")) { if (mod == MOD_BLASTER) message = "was blasted by a"; else if (mod == MOD_HIT) { message = "was cut up by a"; message2 = "'s sharp wings"; } } // Technician else if (!strcmp(attacker->classname, "monster_floater")) { if (mod == MOD_BLASTER) message = "was blasted by a"; else if (mod == MOD_HIT) { message = "was gouged to death by a"; message2 = "'s utility claw"; } } // Tank/Tank Commander else if (!strcmp(attacker->classname, "monster_tank") || !strcmp(attacker->classname, "monster_tank_commander")) { if (mod == MOD_BLASTER) message = "was blasted by a"; else if (mod == MOD_ROCKET) { message = "ate a"; message2 = "'s rocket"; } else if (mod == MOD_R_SPLASH) { message = "almost dodged a"; message2 = "'s rocket"; } else message = "was pumped full of lead by a"; } // Supertank else if (!strcmp(attacker->classname, "monster_supertank")) { if (mod == MOD_ROCKET) { message = "ate a"; message2 = "'s rocket"; } else if (mod == MOD_R_SPLASH) { message = "almost dodged a"; message2 = "'s rocket"; } else message = "was chaingunned by a"; } // Hornet else if (!strcmp(attacker->classname, "monster_boss2")) { if (mod == MOD_ROCKET) { message = "ate a"; message2 = "'s rocket"; } else if (mod == MOD_R_SPLASH) { message = "almost dodged a"; message2 = "'s rockets"; } else message = "was chaingunned by a"; } // Jorg else if (!strcmp(attacker->classname, "monster_jorg")) { if (mod == MOD_BFG_LASER) { message = "saw the pretty lights from the"; message2 = "'s BFG"; } else if (mod == MOD_BFG_BLAST) { message = "was disintegrated by the"; message2 = "'s BFG blast"; } else if (mod == MOD_BFG_EFFECT) { message = "couldn't hide from the"; message2 = "'s BFG"; } else { message = "was shredded by the"; message2 = "'s chain-cannons"; } } // Makron else if (!strcmp(attacker->classname, "monster_makron")) { if (mod == MOD_BLASTER) { message = "was melted by the"; message2 = "'s hyperblaster"; } else if (mod == MOD_RAILGUN) message = "was railed by the"; else if (mod == MOD_BFG_LASER) { message = "saw the pretty lights from the"; message2 = "'s BFG"; } else if (mod == MOD_BFG_BLAST) { message = "was disintegrated by the"; message2 = "'s BFG blast"; } else if (mod == MOD_BFG_EFFECT) { message = "couldn't hide from the"; message2 = "'s BFG"; } } // Barracuda Shark else if (!strcmp(attacker->classname, "monster_flipper")) message = "was chewed up by a"; // Mutant else if (!strcmp(attacker->classname, "monster_mutant")) message = "was clawed by a"; // Turret Driver /* else if ( !strcmp(attacker->classname, "turret_driver") || !strcmp(attacker->classname, "turret_breach") ) { if (mod == MOD_ROCKET) { message = "ate a"; message2 = "'s rocket"; } else if (mod == MOD_R_SPLASH) { message = "almost dodged a"; message2 = "'s rocket"; } else message = "was killed by a"; } */ } if (message) { safe_bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->common_name, message2); if (coop->value) self->client->resp.score--; self->enemy = NULL; return; } // end Knightmare } safe_bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname); if (deathmatch->value) self->client->resp.score--; } void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); void TossClientWeapon (edict_t *self) { gitem_t *item; edict_t *drop; qboolean quad; float spread; if (!deathmatch->value) return; item = self->client->pers.weapon; if (! self->client->pers.inventory[self->client->ammo_index] ) item = NULL; if (item && (strcmp (item->pickup_name, "Blaster") == 0)) item = NULL; if (item && (strcmp (item->pickup_name, "Grapple") == 0)) item = NULL; if (item && (strcmp (item->pickup_name, "No Weapon") == 0)) item = NULL; // Knightmare- don't drop homing rocket launcher (null model error), drop rocket launcher instead if (item && (strcmp (item->pickup_name, "Homing Rocket Launcher") == 0)) item = FindItem("Rocket Launcher"); if (!((int)(dmflags->value) & DF_QUAD_DROP)) quad = false; else quad = (self->client->quad_framenum > (level.framenum + 10)); if (item && quad) spread = 22.5; else spread = 0.0; if (item) { self->client->v_angle[YAW] -= spread; drop = Drop_Item (self, item); self->client->v_angle[YAW] += spread; drop->spawnflags = DROPPED_PLAYER_ITEM; } if (quad) { self->client->v_angle[YAW] += spread; drop = Drop_Item (self, FindItemByClassname ("item_quad")); self->client->v_angle[YAW] -= spread; drop->spawnflags |= DROPPED_PLAYER_ITEM; drop->touch = Touch_Item; drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME; drop->think = G_FreeEdict; } } /* ================== 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->client->killer_yaw = self->s.angles[YAW]; return; } if (dir[0]) self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]); else { self->client->killer_yaw = 0; if (dir[1] > 0) self->client->killer_yaw = 90; else if (dir[1] < 0) self->client->killer_yaw = -90; } if (self->client->killer_yaw < 0) self->client->killer_yaw += 360; } /* ================== player_die ================== */ void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { int n; // tpp if (self->client->chasetoggle) { ChasecamRemove (self, OPTION_OFF); self->client->pers.chasetoggle = 1; } else self->client->pers.chasetoggle = 0; // end tpp self->client->pers.spawn_landmark = false; // paranoia check self->client->pers.spawn_levelchange = false; SetLazarusCrosshair (self); // backup crosshair self->client->zooming = 0; self->client->zoomed = false; SetSensitivities (self, true); if (self->client->spycam) camera_off(self); if (self->turret) turret_disengage(self->turret); if (self->client->textdisplay) Text_Close(self); VectorClear (self->avelocity); self->takedamage = DAMAGE_YES; self->movetype = MOVETYPE_TOSS; self->s.modelindex2 = 0; // remove linked weapon model //ZOID self->s.modelindex3 = 0; // remove linked ctf flag //ZOID self->s.angles[0] = 0; self->s.angles[2] = 0; self->s.sound = 0; self->client->weapon_sound = 0; self->maxs[2] = -8; // self->solid = SOLID_NOT; self->svflags |= SVF_DEADMONSTER; if (!self->deadflag) { self->client->respawn_time = level.time + 1.0; LookAtKiller (self, inflictor, attacker); self->client->ps.pmove.pm_type = PM_DEAD; ClientObituary (self, inflictor, attacker); //ZOID if (ctf->value) { // if at start and same team, clear if (meansOfDeath == MOD_TELEFRAG && self->client->resp.ctf_state < 2 && self->client->resp.ctf_team == attacker->client->resp.ctf_team) { attacker->client->resp.score--; self->client->resp.ctf_state = 0; } CTFFragBonuses(self, inflictor, attacker); } //ZOID TossClientWeapon (self); //ZOID if (ctf->value) { CTFPlayerResetGrapple(self); CTFDeadDropFlag(self); } CTFDeadDropTech(self); //ZOID // Knightmare added- drop ammogen backpack if (!OnSameTeam(self, attacker)) CTFApplyAmmogen (attacker, self); if (deathmatch->value) Cmd_Help_f (self); // show scores // clear inventory // this is kind of ugly, but it's how we want to handle keys in coop for (n = 0; n < game.num_items; n++) { if (coop->value && itemlist[n].flags & IT_KEY) self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n]; self->client->pers.inventory[n] = 0; } } // remove powerups self->client->quad_framenum = 0; self->client->invincible_framenum = 0; self->client->breather_framenum = 0; self->client->enviro_framenum = 0; self->flags &= ~(FL_POWER_SHIELD|FL_POWER_SCREEN); self->client->flashlight = false; // turn off alt-fire mode if on self->client->pers.fire_mode=0; //self->client->nNewLatch &= ~BUTTON_ATTACK2; //if (self->health < self->gib_health) if (self->health < player_gib_health->value) { // gib int num_giblets = 4; //if (deathmatch->value && (self->health < (self->gib_health*2))) if (deathmatch->value && (self->health < (player_gib_health->value*2))) num_giblets = 8; gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); for (n= 0; n < num_giblets; n++) ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 0, 0, damage, GIB_ORGANIC); if (mega_gibs->value) { ThrowGib (self, "models/objects/gibs/arm/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/arm/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/leg/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/leg/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/bone/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/bone2/tris.md2", 0, 0, damage, GIB_ORGANIC); } ThrowClientHead (self, damage); //ZOID self->client->anim_priority = ANIM_DEATH; self->client->anim_end = 0; //ZOID self->takedamage = DAMAGE_NO; } else { // normal death if (!self->deadflag) { static int i; i = (i+1)%3; // start a death animation self->client->anim_priority = ANIM_DEATH; if (self->client->ps.pmove.pm_flags & PMF_DUCKED) { self->s.frame = FRAME_crdeath1-1; self->client->anim_end = FRAME_crdeath5; } else switch (i) { case 0: self->s.frame = FRAME_death101-1; self->client->anim_end = FRAME_death106; break; case 1: self->s.frame = FRAME_death201-1; self->client->anim_end = FRAME_death206; break; case 2: self->s.frame = FRAME_death301-1; self->client->anim_end = FRAME_death308; break; } gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); } } #ifdef JETPACK_MOD if ( self->client->jetpack ) { Jet_BecomeExplosion( self, damage ); /*stop jetting when dead*/ self->client->jetpack_framenum = 0; self->client->jetpack = false; // DWH: force player to gib //self->health = self->gib_health-1; self->health = player_gib_health->value-1; } #endif self->deadflag = DEAD_DEAD; gi.linkentity (self); } //======================================================================= void SwitchToBestStartWeapon (gclient_t *client) { if (!client) return; if ( client->pers.inventory[slugs_index] && client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] ) { client->pers.weapon = FindItem ("railgun"); return; } if ( client->pers.inventory[cells_index] && client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] ) { client->pers.weapon = FindItem ("hyperblaster"); return; } if ( client->pers.inventory[bullets_index] && client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] ) { client->pers.weapon = FindItem ("chaingun"); return; } if ( client->pers.inventory[bullets_index] && client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] ) { client->pers.weapon = FindItem ("machinegun"); return; } if ( client->pers.inventory[shells_index] > 1 && client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] ) { client->pers.weapon = FindItem ("super shotgun"); return; } if ( client->pers.inventory[shells_index] && client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] ) { client->pers.weapon = FindItem ("shotgun"); return; } // DWH: Dude may not HAVE a blaster //ent->client->newweapon = FindItem ("blaster"); if ( client->pers.inventory[ITEM_INDEX(FindItem("blaster"))] ) client->pers.weapon = FindItem ("blaster"); else client->pers.weapon = FindItem ("No Weapon"); } void SelectStartWeapon (gclient_t *client, int style) { gitem_t *item; int n; // Lazarus: We allow choice of weapons (or no weapon) at startup // If style is non-zero, first clear player inventory of all // weapons and ammo that might have been passed over through // target_changelevel or acquired when previously called by // InitClientPersistant if (style) { for (n = 0; n < MAX_ITEMS; n++) { if (itemlist[n].flags & IT_WEAPON) client->pers.inventory[n] = 0; } client->pers.inventory[shells_index] = 0; client->pers.inventory[bullets_index] = 0; client->pers.inventory[grenades_index] = 0; client->pers.inventory[rockets_index] = 0; client->pers.inventory[cells_index] = 0; client->pers.inventory[slugs_index] = 0; client->pers.inventory[homing_index] = 0; } switch (style) { case -1: item = FindItem("No Weapon"); break; case -2: case 2: item = FindItem("Shotgun"); break; case -3: case 3: item = FindItem("Super Shotgun"); break; case -4: case 4: item = FindItem("Machinegun"); break; case -5: case 5: item = FindItem("Chaingun"); break; case -6: case 6: item = FindItem("Grenade Launcher"); break; case -7: case 7: item = FindItem("Rocket Launcher"); break; case -8: case 8: item = FindItem("HyperBlaster"); break; case -9: case 9: item = FindItem("Railgun"); break; case -10: case 10: item = FindItem("BFG10K"); break; default: item = FindItem("Blaster"); break; } client->pers.selected_item = ITEM_INDEX(item); client->pers.inventory[client->pers.selected_item] = 1; client->pers.weapon = item; //ZOID if (ctf->value) { client->pers.lastweapon = item; item = FindItem("Grapple"); client->pers.inventory[ITEM_INDEX(item)] = 1; } //ZOID // Lazarus: If default weapon is NOT "No Weapon", then give player // a blaster if (style > 1) client->pers.inventory[ITEM_INDEX(FindItem("Blaster"))] = 1; // Knightmare- player always has null weapon to allow holstering client->pers.inventory[ITEM_INDEX(FindItem("No Weapon"))] = 1; // and give him standard ammo if (item->ammo) { gitem_t *ammo; ammo = FindItem (item->ammo); if ( deathmatch->value && ((int)dmflags->value & DF_INFINITE_AMMO) ) client->pers.inventory[ITEM_INDEX(ammo)] += 1000; else client->pers.inventory[ITEM_INDEX(ammo)] += ammo->quantity; } // Knightmare- DM start values if (deathmatch->value) { client->pers.inventory[ITEM_INDEX(FindItem("Shells"))] = sk_dm_start_shells->value; client->pers.inventory[ITEM_INDEX(FindItem("Bullets"))] = sk_dm_start_bullets->value; client->pers.inventory[ITEM_INDEX(FindItem("Rockets"))] = sk_dm_start_rockets->value; client->pers.inventory[ITEM_INDEX(FindItem("Homing Rockets"))] = sk_dm_start_homing->value; client->pers.inventory[ITEM_INDEX(FindItem("Grenades"))] = sk_dm_start_grenades->value; client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] = sk_dm_start_cells->value; client->pers.inventory[ITEM_INDEX(FindItem("Slugs"))] = sk_dm_start_slugs->value; client->pers.inventory[ITEM_INDEX(FindItem("Shotgun"))] = sk_dm_start_shotgun->value; client->pers.inventory[ITEM_INDEX(FindItem("Super Shotgun"))] = sk_dm_start_sshotgun->value; client->pers.inventory[ITEM_INDEX(FindItem("Machinegun"))] = sk_dm_start_machinegun->value; client->pers.inventory[ITEM_INDEX(FindItem("Chaingun"))] = sk_dm_start_chaingun->value; client->pers.inventory[ITEM_INDEX(FindItem("Grenade Launcher"))] = sk_dm_start_grenadelauncher->value; client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))] = sk_dm_start_rocketlauncher->value; client->pers.inventory[ITEM_INDEX(FindItem("Homing Rocket Launcher"))] = sk_dm_start_rocketlauncher->value; client->pers.inventory[ITEM_INDEX(FindItem("HyperBlaster"))] = sk_dm_start_hyperblaster->value; client->pers.inventory[ITEM_INDEX(FindItem("Railgun"))] = sk_dm_start_railgun->value; client->pers.inventory[ITEM_INDEX(FindItem("BFG10K"))] = sk_dm_start_bfg->value; SwitchToBestStartWeapon (client); } } /* ============== 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, int style) { memset (&client->pers, 0, sizeof(client->pers)); client->homing_rocket = NULL; SelectStartWeapon (client, style); client->pers.health = 100; if (deathmatch->value) client->pers.max_health = sk_max_health_dm->value; else client->pers.max_health = sk_max_health->value; client->pers.max_bullets = sk_max_bullets->value; client->pers.max_shells = sk_max_shells->value; client->pers.max_rockets = sk_max_rockets->value; client->pers.max_grenades = sk_max_grenades->value; client->pers.max_cells = sk_max_cells->value; client->pers.max_slugs = sk_max_slugs->value; client->pers.max_fuel = sk_max_fuel->value; client->pers.max_homing_rockets = sk_max_rockets->value; client->pers.fire_mode = 0; // Lazarus alternate fire mode client->pers.connected = true; // tpp //Default chasecam to tpp setting client->pers.chasetoggle = tpp->value; // end tpp // Lazarus client->zooming = 0; client->zoomed = false; client->spycam = NULL; client->pers.spawn_landmark = false; client->pers.spawn_levelchange = false; } void InitClientResp (gclient_t *client) { //ZOID int ctf_team = client->resp.ctf_team; qboolean id_state = client->resp.id_state; //ZOID memset (&client->resp, 0, sizeof(client->resp)); //ZOID client->resp.ctf_team = ctf_team; client->resp.id_state = id_state; //ZOID client->resp.enterframe = level.framenum; client->resp.coop_respawn = client->pers; //ZOID if (ctf->value && client->resp.ctf_team < CTF_TEAM1) CTFAssignTeam(client); //ZOID } /* ================== SaveClientData Some information that should be persistant, like health, is still stored in the edict structure, so it needs to be mirrored out to the client structure before all the edicts are wiped. ================== */ void SaveClientData (void) { int i; edict_t *ent; for (i=0 ; iinuse) continue; // tpp game.clients[i].pers.chasetoggle = ent->client->pers.chasetoggle; // end tpp game.clients[i].pers.newweapon = ent->client->newweapon; game.clients[i].pers.health = ent->health; game.clients[i].pers.max_health = ent->max_health; game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_SHIELD|FL_POWER_SCREEN)); if (coop->value) game.clients[i].pers.score = ent->client->resp.score; } } void FetchClientEntData (edict_t *ent) { ent->health = ent->client->pers.health; ent->gib_health = player_gib_health->value; // was -40 ent->max_health = ent->client->pers.max_health; ent->flags |= ent->client->pers.savedFlags; if (coop->value) ent->client->resp.score = ent->client->pers.score; } /* ======================================================================= SelectSpawnPoint ======================================================================= */ /* ================ PlayersRangeFromSpot Returns the distance to the nearest player from the given spot ================ */ float PlayersRangeFromSpot (edict_t *spot) { edict_t *player; float bestplayerdistance; vec3_t v; int n; float playerdistance; bestplayerdistance = 9999999; for (n = 1; n <= maxclients->value; n++) { player = &g_edicts[n]; if (!player->inuse) continue; if (player->health <= 0) continue; VectorSubtract (spot->s.origin, player->s.origin, v); playerdistance = VectorLength (v); if (playerdistance < bestplayerdistance) bestplayerdistance = playerdistance; } return bestplayerdistance; } /* ================ SelectRandomDeathmatchSpawnPoint go to a random point, but NOT the two points closest to other players ================ */ edict_t *SelectRandomDeathmatchSpawnPoint (void) { edict_t *spot, *spot1, *spot2; int count = 0; int selection; float range, range1, range2; spot = NULL; range1 = range2 = 99999; spot1 = spot2 = NULL; while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { count++; range = PlayersRangeFromSpot(spot); if (range < range1) { range1 = range; spot1 = spot; } else if (range < range2) { range2 = range; spot2 = spot; } } if (!count) return NULL; if (count <= 2) { spot1 = spot2 = NULL; } // Lazarus: This is wrong. If there is no spot1 or spot2, all spots should // be valid. // else // count -= 2; else { if (spot1) count--; if (spot2) count--; } selection = rand() % count; spot = NULL; do { spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); if (spot == spot1 || spot == spot2) selection++; } while (selection--); return spot; } /* ================ SelectFarthestDeathmatchSpawnPoint ================ */ edict_t *SelectFarthestDeathmatchSpawnPoint (void) { edict_t *bestspot; float bestdistance, bestplayerdistance; edict_t *spot; spot = NULL; bestspot = NULL; bestdistance = 0; while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { bestplayerdistance = PlayersRangeFromSpot (spot); if (bestplayerdistance > bestdistance) { bestspot = spot; bestdistance = bestplayerdistance; } } if (bestspot) { return bestspot; } // if there is a player just spawned on each and every start spot // we have no choice to turn one into a telefrag meltdown spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); return spot; } edict_t *SelectDeathmatchSpawnPoint (void) { if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) return SelectFarthestDeathmatchSpawnPoint (); else return SelectRandomDeathmatchSpawnPoint (); } edict_t *SelectCoopSpawnPoint (edict_t *ent) { int index; edict_t *spot = NULL; char *target; index = ent->client - game.clients; // player 0 starts in normal player spawn point if (!index) return NULL; spot = NULL; // assume there are four coop spots at each spawnpoint while (1) { spot = G_Find (spot, FOFS(classname), "info_player_coop"); if (!spot) return NULL; // we didn't have enough... target = spot->targetname; if (!target) target = ""; if ( Q_stricmp(game.spawnpoint, target) == 0 ) { // this is a coop spawn point for one of the clients here index--; if (!index) return spot; // this is it } } return spot; } /* =========== SelectSpawnPoint Chooses a player start, deathmatch start, coop start, etc ============ */ void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles, int *style, int *health) { edict_t *spot = NULL; if (deathmatch->value) { //ZOID if (ctf->value) spot = SelectCTFSpawnPoint(ent); else //ZOID spot = SelectDeathmatchSpawnPoint (); } else if (coop->value) spot = SelectCoopSpawnPoint (ent); // find a single player start spot if (!spot) { while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) { if (!game.spawnpoint[0] && !spot->targetname) break; if (!game.spawnpoint[0] || !spot->targetname) continue; if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) break; } if (!spot) { if (!game.spawnpoint[0]) { // there wasn't a spawnpoint without a target, so use any spot = G_Find (spot, FOFS(classname), "info_player_start"); } if (!spot) gi.error ("Couldn't find spawn point %s\n", game.spawnpoint); } } if (style) *style = spot->style; if (health) *health = spot->health; VectorCopy (spot->s.origin, origin); origin[2] += 9; VectorCopy (spot->s.angles, angles); if (!deathmatch->value && !coop->value) { spot->count--; if (!spot->count) { spot->think = G_FreeEdict; spot->nextthink = level.time + 1; } } } //====================================================================== void InitBodyQue (void) { // DWH: bodyque isn't used in SP, so why reserve space for it? if (deathmatch->value || coop->value) { int i; edict_t *ent; level.body_que = 0; for (i=0; iclassname = "bodyque"; } } } void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { int n; //if (self->health < self->gib_health) if (self->health < player_gib_health->value) { int num_giblets = 4; //if (deathmatch->value && (self->health < (self->gib_health*2))) if (deathmatch->value && (self->health < (player_gib_health->value*2))) num_giblets = 8; gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); for (n=0; n < num_giblets; n++) ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 0, 0, damage, GIB_ORGANIC); if (mega_gibs->value) { ThrowGib (self, "models/objects/gibs/arm/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/arm/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/leg/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/leg/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/bone/tris.md2", 0, 0, damage, GIB_ORGANIC); ThrowGib (self, "models/objects/gibs/bone2/tris.md2", 0, 0, damage, GIB_ORGANIC); } self->s.origin[2] -= 48; ThrowClientHead (self, damage); self->takedamage = DAMAGE_NO; } } void CopyToBodyQue (edict_t *ent) { edict_t *body; // grab a body que and cycle to the next one body = &g_edicts[(int)maxclients->value + level.body_que + 1]; level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; // FIXME: send an effect on the removed body gi.unlinkentity (ent); gi.unlinkentity (body); body->s = ent->s; body->s.number = body - g_edicts; body->svflags = ent->svflags; VectorCopy (ent->mins, body->mins); VectorCopy (ent->maxs, body->maxs); VectorCopy (ent->absmin, body->absmin); VectorCopy (ent->absmax, body->absmax); VectorCopy (ent->size, body->size); body->solid = ent->solid; body->clipmask = ent->clipmask; body->owner = ent->owner; body->movetype = ent->movetype; body->die = body_die; body->takedamage = DAMAGE_YES; gi.linkentity (body); } void respawn (edict_t *self) { // tpp if (self->crosshair) G_FreeEdict(self->crosshair); self->crosshair = NULL; if (self->client->oldplayer) G_FreeEdict (self->client->oldplayer); self->client->oldplayer = NULL; if (self->client->chasecam) G_FreeEdict (self->client->chasecam); self->client->chasecam = NULL; // end tpp if (deathmatch->value || coop->value) { // ACEBOT_ADD special respawning code if (self->is_bot) { ACESP_Respawn (self); return; } // ACEBOT_END // spectator's don't leave bodies if (self->movetype != MOVETYPE_NOCLIP) CopyToBodyQue (self); self->svflags &= ~SVF_NOCLIENT; PutClientInServer (self); // add a teleportation effect self->s.event = EV_PLAYER_TELEPORT; // hold in place briefly self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; self->client->ps.pmove.pm_time = 14; self->client->respawn_time = level.time; return; } // restart the entire server gi.AddCommandString ("menu_loadgame\n"); } /* * only called when pers.spectator changes * note that resp.spectator should be the opposite of pers.spectator here */ void spectator_respawn (edict_t *ent) { int i, numspec; // if the user wants to become a spectator, make sure he doesn't // exceed max_spectators if (ent->client->pers.spectator) { char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator"); if (*spectator_password->string && strcmp(spectator_password->string, "none") && strcmp(spectator_password->string, value)) { safe_cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n"); ent->client->pers.spectator = false; gi.WriteByte (svc_stufftext); gi.WriteString ("spectator 0\n"); gi.unicast(ent, true); return; } // count spectators for (i = 1, numspec = 0; i <= maxclients->value; i++) if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator) numspec++; if (numspec >= maxspectators->value) { safe_cprintf(ent, PRINT_HIGH, "Server spectator limit is full."); ent->client->pers.spectator = false; // reset his spectator var gi.WriteByte (svc_stufftext); gi.WriteString ("spectator 0\n"); gi.unicast(ent, true); return; } } else { // he was a spectator and wants to join the game // he must have the right password char *value = Info_ValueForKey (ent->client->pers.userinfo, "password"); if (*password->string && strcmp(password->string, "none") && strcmp(password->string, value)) { safe_cprintf(ent, PRINT_HIGH, "Password incorrect.\n"); ent->client->pers.spectator = true; gi.WriteByte (svc_stufftext); gi.WriteString ("spectator 1\n"); gi.unicast(ent, true); return; } } // clear client on respawn ent->client->resp.score = ent->client->pers.score = 0; ent->svflags &= ~SVF_NOCLIENT; PutClientInServer (ent); // add a teleportation effect if (!ent->client->pers.spectator) { // send effect gi.WriteByte (svc_muzzleflash); gi.WriteShort (ent-g_edicts); gi.WriteByte (MZ_LOGIN); gi.multicast (ent->s.origin, MULTICAST_PVS); // hold in place briefly ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; ent->client->ps.pmove.pm_time = 14; } ent->client->respawn_time = level.time; if (ent->client->pers.spectator) safe_bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname); else safe_bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname); } //============================================================== /* =========== PutClientInServer Called when a player connects to a server or respawns in a deathmatch. ============ */ void PutClientInServer (edict_t *ent) { extern int nostatus; vec3_t mins = {-16, -16, -24}; vec3_t maxs = {16, 16, 32}; int index; vec3_t spawn_origin, spawn_angles, spawn_viewangles; gclient_t *client; int i; // tpp int chasetoggle; gitem_t *newweapon; char userinfo[MAX_INFO_STRING]; // end tpp qboolean spawn_landmark; qboolean spawn_levelchange; int spawn_gunframe; int spawn_modelframe; int spawn_anim_end; int spawn_pm_flags; int spawn_style; int spawn_health; client_persistant_t saved; client_respawn_t resp; // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client SelectSpawnPoint (ent, spawn_origin, spawn_angles, &spawn_style, &spawn_health); index = ent-g_edicts-1; client = ent->client; // tpp chasetoggle = client->pers.chasetoggle; // end tpp newweapon = client->pers.newweapon; spawn_landmark = client->pers.spawn_landmark; spawn_levelchange= client->pers.spawn_levelchange; spawn_gunframe = client->pers.spawn_gunframe; spawn_modelframe = client->pers.spawn_modelframe; spawn_anim_end = client->pers.spawn_anim_end; client->pers.spawn_landmark = false; client->pers.spawn_levelchange = false; if (spawn_landmark) { spawn_origin[2] -= 9; VectorAdd(spawn_origin,client->pers.spawn_offset,spawn_origin); VectorCopy(client->pers.spawn_angles,spawn_angles); VectorCopy(client->pers.spawn_viewangles,spawn_viewangles); VectorCopy(client->pers.spawn_velocity,ent->velocity); spawn_pm_flags = client->pers.spawn_pm_flags; } // deathmatch wipes most client data every spawn if (deathmatch->value) { char userinfo[MAX_INFO_STRING]; resp = client->resp; memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); InitClientPersistant (client,spawn_style); ClientUserinfoChanged (ent, userinfo); } else if (coop->value) { // int n; char userinfo[MAX_INFO_STRING]; resp = client->resp; memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged; resp.coop_respawn.helpchanged = client->pers.helpchanged; client->pers = resp.coop_respawn; ClientUserinfoChanged (ent, userinfo); if (resp.score > client->pers.score) client->pers.score = resp.score; } else { memset (&resp, 0, sizeof(resp)); } // tpp // A bug in Q2 that you couldn't see without thirdpp memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); ClientUserinfoChanged (ent, userinfo); // end tpp // clear everything but the persistant data saved = client->pers; memset (client, 0, sizeof(*client)); client->pers = saved; if (client->pers.health <= 0) InitClientPersistant(client, spawn_style); else if (spawn_style) SelectStartWeapon(client, spawn_style); client->resp = resp; // tpp client->pers.chasetoggle = chasetoggle; // end tpp client->pers.newweapon = newweapon; // copy some data from the client to the entity FetchClientEntData (ent); // Lazarus: Starting health < max. Presumably player was hurt in a crash if ( (spawn_health > 0) && !deathmatch->value && !coop->value) ent->health = min(ent->health, spawn_health); // clear entity values ent->groundentity = NULL; ent->client = &game.clients[index]; ent->takedamage = DAMAGE_AIM; ent->movetype = MOVETYPE_WALK; ent->viewheight = 22; ent->inuse = true; ent->classname = "player"; ent->mass = 200; ent->solid = SOLID_BBOX; ent->deadflag = DEAD_NO; ent->air_finished = level.time + 12; ent->clipmask = MASK_PLAYERSOLID; ent->model = "players/male/tris.md2"; ent->pain = player_pain; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags &= ~FL_NO_KNOCKBACK; ent->svflags &= ~SVF_DEADMONSTER; // tpp ent->svflags &= ~SVF_NOCLIENT; // turn on prediction ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; // end tpp ent->client->spycam = NULL; ent->client->camplayer = NULL; // ACEBOT_ADD ent->is_bot = false; ent->last_node = -1; ent->is_jumping = false; // ACEBOT_END VectorCopy (mins, ent->mins); VectorCopy (maxs, ent->maxs); if (!spawn_landmark) VectorClear (ent->velocity); // clear playerstate values memset (&ent->client->ps, 0, sizeof(client->ps)); if (spawn_landmark) client->ps.pmove.pm_flags = spawn_pm_flags; client->ps.pmove.origin[0] = spawn_origin[0]*8; client->ps.pmove.origin[1] = spawn_origin[1]*8; client->ps.pmove.origin[2] = spawn_origin[2]*8; if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) { client->ps.fov = 90; } else { client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); if (client->ps.fov < 1) client->ps.fov = 90; else if (client->ps.fov > 160) client->ps.fov = 160; } // DWH client->original_fov = client->ps.fov; // end DWH client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); // Server-side speed control stuff #ifdef KMQUAKE2_ENGINE_MOD client->ps.maxspeed = player_max_speed->value; client->ps.duckspeed = player_crouch_speed->value; client->ps.accel = player_accel->value; client->ps.stopspeed = player_stopspeed->value; #endif // clear entity state values ent->s.effects = 0; ent->s.modelindex = MAX_MODELS-1; // will use the skin specified model if (ITEM_INDEX(client->pers.weapon) == noweapon_index) ent->s.modelindex2 = 0; else ent->s.modelindex2 = MAX_MODELS-1; // custom gun model // sknum is player num and weapon number // weapon number will be added in changeweapon ent->s.skinnum = ent - g_edicts - 1; ent->s.frame = 0; VectorCopy (spawn_origin, ent->s.origin); ent->s.origin[2] += 1; // make sure off ground VectorCopy (ent->s.origin, ent->s.old_origin); // set the delta angle for (i=0 ; i<3 ; i++) { client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); } ent->s.angles[PITCH] = ent->s.angles[ROLL] = 0; ent->s.angles[YAW] = spawn_angles[YAW]; if (spawn_landmark) { VectorCopy(spawn_viewangles, client->ps.viewangles); // client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; } else VectorCopy(ent->s.angles, client->ps.viewangles); VectorCopy (client->ps.viewangles, client->v_angle); // spawn a spectator if (client->pers.spectator) { client->chase_target = NULL; client->resp.spectator = true; ent->movetype = MOVETYPE_NOCLIP; ent->solid = SOLID_NOT; ent->svflags |= SVF_NOCLIENT; ent->client->ps.gunindex = 0; gi.linkentity (ent); return; } else client->resp.spectator = false; // DWH: client->flashlight = false; client->secs_per_frame = 0.025; // assumed 40 fps until we know better client->fps_time_start = level.time; if (!KillBox (ent)) { // could't spawn in? } //ZOID if (ctf->value && CTFStartClient(ent)) return; //ZOID gi.linkentity (ent); // tpp client->chasetoggle = 0; // If chasetoggle set then turn on (delayed start of 5 frames - 0.5s) if (client->pers.chasetoggle) client->delayedstart = 5; // end tpp if (spawn_levelchange && !client->pers.chasetoggle && !client->pers.newweapon) { // we already had a weapon when the level changed... no need to bring it up int i; client->pers.lastweapon = client->pers.weapon; client->newweapon = NULL; client->machinegun_shots = 0; i = ((client->pers.weapon->weapmodel & 0xff) << 8); ent->s.skinnum = (ent - g_edicts - 1) | i; if (client->pers.weapon->ammo) client->ammo_index = ITEM_INDEX(FindItem(client->pers.weapon->ammo)); else client->ammo_index = 0; client->weaponstate = WEAPON_READY; client->ps.gunframe = 0; client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); client->ps.gunframe = spawn_gunframe; ent->s.frame = spawn_modelframe; client->anim_end = spawn_anim_end; } else { // force the current weapon up client->newweapon = client->pers.weapon; ChangeWeapon (ent); } // Paril's fix for this getting reset after map changes if (!ent->client->pers.connected) ent->client->pers.connected = true; } /* ===================== ClientBeginDeathmatch A client has just connected to the server in deathmatch mode, so clear everything out before starting them. ===================== */ void ClientBeginDeathmatch (edict_t *ent) { // ACEBOT_ADD //static char current_map[55]; // ACEBOT_END G_InitEdict (ent); InitClientResp (ent->client); // ACEBOT_ADD ACEIT_PlayerAdded(ent); // ACEBOT_END // locate ent at a spawn point PutClientInServer (ent); if (level.intermissiontime) { MoveClientToIntermission (ent); } else { // send effect gi.WriteByte (svc_muzzleflash); gi.WriteShort (ent-g_edicts); gi.WriteByte (MZ_LOGIN); gi.multicast (ent->s.origin, MULTICAST_PVS); } safe_bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); // ACEBOT_ADD safe_centerprintf(ent,"\n======================================\nACE Bot II Mod\n\n'sv addbot' to add a new bot.\n'sv removebot ' to remove bot.\n'sv dmpause' to pause the game.\n'sv savenodes' to save level path data.\n======================================\n\n"); // Knightmare- moved this to g_spawn.c for bot support in dedicated servers // If the map changes on us, init and reload the nodes /*if (strcmp(level.mapname,current_map)) { ACEND_InitNodes(); ACEND_LoadNodes(); //ACESP_LoadBots(); // Knightmare- removed this ACESP_LoadBotInfo(); // Knightmare- load bot info file // strncpy(current_map, level.mapname); Q_strncpyz(current_map, sizeof(current_map), level.mapname); }*/ // ACEBOT_END // make sure all view stuff is valid ClientEndServerFrame (ent); } /* =========== 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) { int i; ent->client = game.clients + (ent - g_edicts - 1); // Lazarus: Set the alias for our alternate attack // stuffcmd(ent, "alias +attack2 attack2_on; alias -attack2 attack2_off\n"); if (deathmatch->value) { ClientBeginDeathmatch (ent); return; } Fog_Off (ent); // Fog (ent); stuffcmd(ent,"alias +zoomin zoomin;alias -zoomin zoominstop\n"); stuffcmd(ent,"alias +zoomout zoomout;alias -zoomout zoomoutstop\n"); stuffcmd(ent,"alias +zoom zoomon;alias -zoom zoomoff\n"); // if there is already a body waiting for us (a loadgame), just // take it, otherwise spawn one from scratch if (ent->inuse == true) { // 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->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->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->client); PutClientInServer (ent); } if (level.intermissiontime) { MoveClientToIntermission (ent); } else { // send effect if in a multiplayer game if (game.maxclients > 1) { gi.WriteByte (svc_muzzleflash); gi.WriteShort (ent-g_edicts); gi.WriteByte (MZ_LOGIN); gi.multicast (ent->s.origin, MULTICAST_PVS); safe_bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); } } // DWH SetLazarusCrosshair (ent); // backup crosshair SetSensitivities (ent, true); if (game.maxclients == 1) { // For SP games, check for monsters who were mad at player // in previous level and have changed levels with the player edict_t *monster; for (i=2; iinuse) continue; if (!(monster->svflags & SVF_MONSTER)) continue; if (monster->health <= 0) continue; if (monster->monsterinfo.aiflags & AI_RESPAWN_FINDPLAYER) { monster->monsterinfo.aiflags &= ~AI_RESPAWN_FINDPLAYER; if (!monster->enemy) { monster->enemy = ent; FoundTarget(monster); } } } } // make sure all view stuff is valid ClientEndServerFrame (ent); } /* =========== 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; int playernum; // check for malformed or illegal info strings if (!Info_Validate(userinfo)) { // strncpy (userinfo, "\\name\\badinfo\\skin\\male/grunt"); Q_strncpyz (userinfo, MAX_INFO_STRING, "\\name\\badinfo\\skin\\male/grunt"); // userinfo is always length of MAX_INFO_STRING } // set name s = Info_ValueForKey (userinfo, "name"); strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); // set spectator s = Info_ValueForKey (userinfo, "spectator"); // spectators are only supported in deathmatch if (deathmatch->value && *s && strcmp(s, "0")) ent->client->pers.spectator = true; else ent->client->pers.spectator = false; // set skin s = Info_ValueForKey (userinfo, "skin"); playernum = ent-g_edicts-1; // combine name and skin into a configstring //ZOID if (ctf->value) CTFAssignSkin(ent, s); else //ZOID gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); //ZOID // set player name field (used in id_state view) gi.configstring (CS_GENERAL+playernum, ent->client->pers.netname); //ZOID // fov if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) { ent->client->ps.fov = 90; ent->client->original_fov = ent->client->ps.fov; } else { float new_fov; new_fov = atoi(Info_ValueForKey(userinfo, "fov")); if (new_fov < 1) new_fov = 90; else if (new_fov > 160) new_fov = 160; if (new_fov != ent->client->original_fov) { ent->client->ps.fov = new_fov; ent->client->original_fov = new_fov; } } // handedness s = Info_ValueForKey (userinfo, "hand"); if (strlen(s)) ent->client->pers.hand = atoi(s); // save off the userinfo in case we want to check something later strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); } /* =========== 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) { char *value; // check to see if they are on the banned IP list value = Info_ValueForKey (userinfo, "ip"); if (SV_FilterPacket(value)) { Info_SetValueForKey(userinfo, "rejmsg", "Banned."); return false; } // check for a spectator value = Info_ValueForKey (userinfo, "spectator"); if (deathmatch->value && *value && strcmp(value, "0")) { int i, numspec; if (*spectator_password->string && strcmp(spectator_password->string, "none") && strcmp(spectator_password->string, value)) { Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect."); return false; } // count spectators for (i = numspec = 0; i < maxclients->value; i++) if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator) numspec++; if (numspec >= maxspectators->value) { Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full."); return false; } } else { // check for a password value = Info_ValueForKey (userinfo, "password"); if (*password->string && strcmp(password->string, "none") && strcmp(password->string, value)) { Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); return false; } } // they can connect ent->client = game.clients + (ent - g_edicts - 1); // if there is already a body waiting for us (a loadgame), just // take it, otherwise spawn one from scratch if (ent->inuse == false) { // clear the respawning variables //ZOID -- force team join if (ctf->value) { ent->client->resp.ctf_team = -1; ent->client->resp.id_state = true; } //ZOID InitClientResp (ent->client); if (!game.autosaved || !ent->client->pers.weapon) InitClientPersistant (ent->client, world->style); } ClientUserinfoChanged (ent, userinfo); if (game.maxclients > 1) gi.dprintf ("%s connected\n", ent->client->pers.netname); ent->svflags = 0; // make sure we start with known default ent->client->pers.connected = true; return true; } /* =========== ClientDisconnect Called when a player drops from the server. Will not be called between levels. ============ */ void ClientDisconnect (edict_t *ent) { int playernum; if (!ent->client) return; // tpp if (ent->client->chasetoggle) ChasecamRemove(ent,OPTION_OFF); // end tpp // DWH SetLazarusCrosshair (ent); // backup crosshair ent->client->zooming = 0; ent->client->zoomed = false; SetSensitivities (ent, true); // end DWH if (ent->client->textdisplay) Text_Close(ent); safe_bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); // ACEBOT_ADD ACEIT_PlayerRemoved(ent); // ACEBOT_END //ZOID CTFDeadDropFlag(ent); CTFDeadDropTech(ent); //ZOID // send effect gi.WriteByte (svc_muzzleflash); gi.WriteShort (ent-g_edicts); gi.WriteByte (MZ_LOGOUT); gi.multicast (ent->s.origin, MULTICAST_PVS); gi.unlinkentity (ent); ent->s.modelindex = 0; ent->solid = SOLID_NOT; ent->inuse = false; ent->classname = "disconnected"; ent->client->pers.connected = false; if (ent->client->spycam) camera_off(ent); playernum = ent-g_edicts-1; gi.configstring (CS_PLAYERSKINS+playernum, ""); } //============================================================== edict_t *pm_passent; // pmove doesn't need to know about passent and contentmask trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) { if (pm_passent->health > 0) return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); else return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID); } unsigned CheckBlock (void *b, int c) { int v,i; v = 0; for (i=0 ; is, sizeof(pm->s)); c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd)); Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); } // DWH //========================================================================== // DWH: PM_CmdScale was ripped from Q3 source //========================================================================== float PM_CmdScale( usercmd_t *cmd ) { int max; float total; float scale; max = abs( cmd->forwardmove ); if ( abs( cmd->sidemove ) > max ) { max = abs( cmd->sidemove ); } if ( abs( cmd->upmove ) > max ) { max = abs( cmd->upmove ); } if ( !max ) { return 0; } total = sqrt( cmd->forwardmove * cmd->forwardmove + cmd->sidemove * cmd->sidemove + cmd->upmove * cmd->upmove ); scale = max / total; return scale; } void RemovePush(edict_t *ent) { ent->client->push->s.sound = 0; ent->client->push->activator = NULL; ent->client->push = NULL; ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; // If tpp is NOT always on, and auto-switch for func_pushables IS on, // and we're currently in third-person view, switch it off // Knightmare- don't autoswitch if client-side chasecam is on #ifdef KMQUAKE2_ENGINE_MOD if (!tpp->value && tpp_auto->value && (!cl_thirdperson->value || deathmatch->value || coop->value) && ent->client->chasetoggle) #else if (!tpp->value && tpp_auto->value && ent->client->chasetoggle) #endif Cmd_Chasecam_Toggle(ent); } void ClientPushPushable(edict_t *ent) { edict_t *box = ent->client->push; vec_t dist; vec3_t new_origin, v, vbox; VectorAdd (box->absmax,box->absmin,vbox); VectorScale(vbox,0.5,vbox); if (point_infront(ent,vbox)) { VectorSubtract(ent->s.origin,box->offset,new_origin); VectorSubtract(new_origin,box->s.origin,v); v[2] = 0; dist = VectorLength(v); if (dist > 8) { // func_pushable got hung up somehow. Break off contact RemovePush(ent); } else if (dist > 0) { if (!box->speaker) box->s.sound = box->noise_index; box_walkmove( box, vectoyaw(v), dist ); } else box->s.sound = 0; } else RemovePush(ent); } void ClientSpycam (edict_t *ent) { gclient_t *client = ent->client; edict_t *camera = ent->client->spycam; pmove_t pm; qboolean is_actor; trace_t tr; vec3_t forward, left, up; vec3_t dir, start; float dist; int i; memset (&pm, 0, sizeof(pm)); if ( client->ucmd.sidemove && (level.time > ent->last_move_time + 1) ) { camera->flags &= ~FL_ROBOT; if (camera->viewer == ent) camera->viewer = NULL; if (client->ucmd.sidemove > 0) camera = G_FindNextCamera(camera, client->monitor); else camera = G_FindPrevCamera(camera, client->monitor); if (camera) { if (!camera->viewer) camera->viewer = ent; client->spycam = camera; VectorAdd (camera->s.origin, camera->move_origin, ent->s.origin); if (camera->viewmessage) safe_centerprintf(ent,camera->viewmessage); ent->last_move_time = level.time; } else camera = client->spycam; if (camera->monsterinfo.aiflags & AI_ACTOR) { camera->flags |= FL_ROBOT; if (camera->monsterinfo.aiflags & AI_FOLLOW_LEADER) { camera->monsterinfo.aiflags &= ~AI_FOLLOW_LEADER; camera->monsterinfo.old_leader = NULL; camera->monsterinfo.leader = NULL; camera->movetarget = camera->goalentity = NULL; camera->monsterinfo.stand(camera); } } } if ( (camera->svflags & SVF_MONSTER) && (camera->monsterinfo.aiflags & AI_ACTOR) ) is_actor = true; else is_actor = false; if (camera->enemy && (camera->enemy->deadflag || !camera->enemy->inuse)) camera->enemy = NULL; AngleVectors (camera->s.angles, forward, left, up); if (is_actor && !camera->enemy) { if ( (abs(client->ucmd.forwardmove) > 199) && (camera->groundentity) ) { // walk/run edict_t *thing; vec3_t end; float dist; thing = camera->vehicle; VectorMA(camera->s.origin,WORLD_SIZE,forward,end); // was 8192 tr = gi.trace(camera->s.origin, camera->mins, camera->maxs, end, camera, MASK_SOLID); if (client->ucmd.forwardmove < 0) { trace_t back; VectorMA(camera->s.origin,-WORLD_SIZE,forward,end); // was -8192 back = gi.trace(camera->s.origin, camera->mins, camera->maxs, end, camera, MASK_SOLID); VectorSubtract (back.endpos, camera->s.origin, end); dist = VectorLength(end); VectorCopy (tr.endpos, end); } else { VectorSubtract (tr.endpos, camera->s.origin, end); dist = VectorLength(end) - 8; VectorMA (camera->s.origin, dist, forward, end); } if (dist > 8) { if (!thing || !thing->inuse || Q_stricmp(thing->classname,"thing")) thing = camera->vehicle = SpawnThing(); thing->touch_debounce_time = level.time + 5.0; thing->target_ent = camera; VectorCopy(end,thing->s.origin); ED_CallSpawn (thing); camera->monsterinfo.aiflags |= AI_CHASE_THING; camera->monsterinfo.aiflags &= ~(AI_CHICKEN | AI_STAND_GROUND); camera->monsterinfo.pausetime = 0; camera->movetarget = camera->goalentity = thing; camera->monsterinfo.old_leader = NULL; camera->monsterinfo.leader = thing; VectorSubtract (thing->s.origin, camera->s.origin, dir); camera->ideal_yaw = vectoyaw(dir); if (client->ucmd.forwardmove > 300) actor_run(camera); else if (client->ucmd.forwardmove > 199) actor_walk(camera); else if (client->ucmd.forwardmove < -300) actor_run_back(camera); else actor_walk_back(camera); } else if (thing) { camera->monsterinfo.aiflags &= ~AI_CHASE_THING; camera->movetarget = camera->goalentity = NULL; G_FreeEdict (thing); camera->vehicle = NULL; actor_stand(camera); } } if ((client->ucmd.forwardmove == 0) && (camera->groundentity)) { // stop edict_t *thing = camera->vehicle; if (thing) { camera->monsterinfo.aiflags &= ~AI_CHASE_THING; camera->movetarget = camera->goalentity = NULL; G_FreeEdict (thing); camera->vehicle = NULL; actor_stand(camera); } } if (client->ucmd.upmove) { if ((client->ucmd.upmove > 0) && camera->groundentity && !camera->waterlevel) { // jump if (client->ucmd.forwardmove > 300) VectorScale (forward, 400, camera->velocity); else if (client->ucmd.forwardmove > 199) VectorScale (forward, 200, camera->velocity); else if (client->ucmd.forwardmove < -300) VectorScale (forward, -400, camera->velocity); else if (client->ucmd.forwardmove < -199) VectorScale (forward, -200, camera->velocity); camera->velocity[2] = 250; camera->monsterinfo.savemove = camera->monsterinfo.currentmove; actor_jump(camera); camera->groundentity = NULL; } else if ((client->ucmd.upmove < 0) && (camera->groundentity) && !(camera->monsterinfo.aiflags & AI_CROUCH)) { // crouch if ( (camera->monsterinfo.currentmove == &actor_move_walk) || (camera->monsterinfo.currentmove == &actor_move_run) || (camera->monsterinfo.currentmove == &actor_move_run_bad) ) { camera->monsterinfo.currentmove = &actor_move_crouchwalk; camera->maxs[2] -= 28; camera->viewheight -= 28; camera->move_origin[2] -= 28; camera->monsterinfo.aiflags |= AI_CROUCH; } else if ( (camera->monsterinfo.currentmove == &actor_move_walk_back) || (camera->monsterinfo.currentmove == &actor_move_run_back) ) { camera->monsterinfo.currentmove = &actor_move_crouchwalk_back; camera->maxs[2] -= 28; camera->viewheight -= 28; camera->move_origin[2] -= 28; camera->monsterinfo.aiflags |= AI_CROUCH; } else if (camera->monsterinfo.currentmove == &actor_move_stand) { camera->monsterinfo.currentmove = &actor_move_crouch; camera->maxs[2] -= 28; camera->viewheight -= 28; camera->move_origin[2] -= 28; camera->monsterinfo.aiflags |= AI_CROUCH; } } } if ( (client->ucmd.upmove >= 0) && (camera->monsterinfo.aiflags & AI_CROUCH)) { // come out of crouch camera->maxs[2] += 28; camera->viewheight += 28; camera->move_origin[2] += 28; camera->monsterinfo.aiflags &= ~AI_CROUCH; if (camera->monsterinfo.currentmove == &actor_move_crouchwalk) actor_walk(camera); else if (camera->monsterinfo.currentmove == &actor_move_crouchwalk_back) actor_walk_back(camera); else if (camera->monsterinfo.currentmove == &actor_move_crouch) actor_stand(camera); } } client->ps.pmove.pm_type = PM_FREEZE; if (camera->viewer == ent) { if ( (client->old_owner_angles[0] != client->ucmd.angles[0]) || (client->old_owner_angles[1] != client->ucmd.angles[1]) ) { // Give game a bit of time to catch up after player // causes ucmd pitch angle to roll over... otherwise // we'll hit on the above test even though player // hasn't hit +lookup/+lookdown float delta; delta = level.time - camera->touch_debounce_time; if ( delta < 0 || delta > 1.0) { if (is_actor) { float diff; diff = SHORT2ANGLE(client->ucmd.angles[1] - client->old_owner_angles[1]); if (diff < -180) diff += 360; if (diff > 180) diff -= 360; camera->ideal_yaw += diff; if ((abs(diff) > 100) && camera->vehicle) { vec3_t angles; vec3_t end, f; VectorSet (angles, 0, camera->ideal_yaw, 0); AngleVectors (angles, f, NULL, NULL); VectorMA (camera->s.origin, WORLD_SIZE, f, end); // was 8192 tr = gi.trace(camera->s.origin, camera->mins, camera->maxs, end, camera, MASK_SOLID); VectorCopy (tr.endpos, camera->vehicle->s.origin); camera->vehicle->touch_debounce_time = level.time + 5.0f; gi.linkentity(camera->vehicle); } ai_turn (camera, 0.0f); diff = SHORT2ANGLE(client->ucmd.angles[0] - client->old_owner_angles[0]); if (diff < -180) diff += 360; if (diff > 180) diff -= 360; camera->move_angles[0] += diff; client->old_owner_angles[0] = client->ucmd.angles[0]; client->old_owner_angles[1] = client->ucmd.angles[1]; } } } if ( (client->ucmd.buttons & BUTTON_ATTACK) && (camera->sounds >= 0) ) { // Knightmare- stop firing weapon when switching to turret client->latched_buttons &= ~BUTTONS_ATTACK; client->buttons &= ~BUTTONS_ATTACK; if (level.time >= camera->monsterinfo.attack_finished) { // client->latched_buttons &= ~BUTTONS_ATTACK; if ( !Q_stricmp(camera->classname, "turret_breach") || !Q_stricmp(camera->classname, "model_turret") ) { if ( (camera->sounds == 5) || (camera->sounds == 6) ) camera->monsterinfo.attack_finished = level.time; else camera->monsterinfo.attack_finished = level.time + 1.0; turret_breach_fire (camera); } else if (is_actor) { int weapon = camera->actor_weapon[camera->actor_current_weapon]; if (!camera->enemy) { edict_t *target; target = LookingAt(ent, 0, NULL, NULL); if ( target && target->takedamage && (target != client->camplayer) ) { if (camera->vehicle) { // Currently following "thing" - turn that off camera->monsterinfo.aiflags &= ~AI_CHASE_THING; camera->movetarget = camera->goalentity = NULL; G_FreeEdict (camera->vehicle); camera->vehicle = NULL; } camera->enemy = target; actor_fire(camera); camera->enemy = NULL; if (camera->monsterinfo.aiflags & AI_HOLD_FRAME) camera->monsterinfo.attack_finished = level.time + FRAMETIME; else camera->monsterinfo.attack_finished = level.time + 1.0; } } } } } // Knightmare- stop firing weapon when switching to spycam else if ( (client->ucmd.buttons & BUTTON_ATTACK) && (camera->sounds < 0) ) { client->latched_buttons &= ~BUTTONS_ATTACK; client->buttons &= ~BUTTONS_ATTACK; } if (client->zoomed) { camera->touch_debounce_time = max(camera->touch_debounce_time, level.time + 1.0); } } VectorMA (camera->s.origin, camera->move_origin[0], forward, start); VectorMA (start, -camera->move_origin[1], left, start); VectorMA (start, camera->move_origin[2], up, start); tr = gi.trace(camera->s.origin, NULL, NULL, start, camera, MASK_SOLID); if (tr.fraction < 1.0) { VectorSubtract(tr.endpos,camera->s.origin,dir); dist = VectorNormalize(dir) - 2; if (dist < 0) dist = 0.; VectorMA(camera->s.origin,dist,dir,start); } VectorCopy (start, ent->s.origin); VectorCopy (camera->velocity, ent->velocity); client->resp.cmd_angles[0] = SHORT2ANGLE(client->ucmd.angles[0]); client->resp.cmd_angles[1] = SHORT2ANGLE(client->ucmd.angles[1]); client->resp.cmd_angles[2] = SHORT2ANGLE(client->ucmd.angles[2]); memset (&pm, 0, sizeof(pm)); pm.s = client->ps.pmove; for (i=0 ; i<3 ; i++) { pm.s.origin[i] = ent->s.origin[i]*8; client->ps.pmove.delta_angles[i] = ANGLE2SHORT(client->ps.viewangles[i] - client->resp.cmd_angles[i]); } if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) pm.snapinitial = true; pm.cmd = client->ucmd; pm.trace = PM_trace; // adds default parms pm.pointcontents = gi.pointcontents; gi.Pmove (&pm); gi.linkentity (ent); // client->old_owner_angles[0] = client->ucmd.angles[0]; // client->old_owner_angles[1] = client->ucmd.angles[1]; G_TouchTriggers (ent); // we'll only allow touching trigger_look with "Cam Owner" SF } /* ============== 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; edict_t *ground; pmove_t pm; vec_t t; //vec3_t view; vec3_t oldorigin, oldvelocity; int i, j; float ground_speed; // short save_forwardmove; // Knightmare- dm pause if (paused && deathmatch->value) { safe_centerprintf(ent, "PAUSED\n\n(type \"sv dmpause\" to resume)"); ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; return; } level.current_entity = ent; client = ent->client; // Lazarus: Copy latest usercmd stuff for use in other routines client->ucmd = *ucmd; VectorCopy(ent->s.origin,oldorigin); VectorCopy(ent->velocity,oldvelocity); ground = ent->groundentity; if (ground && (ground->movetype == MOVETYPE_PUSH) && (ground != world) && ground->turn_rider) ground_speed = VectorLength(ground->velocity); else ground_speed = 0; if ( (ent->in_mud) || (ent->client->push) || (ent->vehicle) || (ent->client->chasetoggle) || (ent->turret) || (ent->client->spycam) || (ground_speed > 0) ) ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; else ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; // Server-side speed control stuff #ifdef KMQUAKE2_ENGINE_MOD client->ps.maxspeed = player_max_speed->value; client->ps.duckspeed = player_crouch_speed->value; client->ps.accel = player_accel->value; client->ps.stopspeed = player_stopspeed->value; #endif if (client->startframe == 0) client->startframe = level.framenum; client->fps_frames++; if (client->fps_frames >= 100) { client->secs_per_frame = (level.time-client->fps_time_start)/100; client->fps_frames = 0; client->fps_time_start = level.time; client->frame_zoomrate = zoomrate->value * client->secs_per_frame; } // VectorCopy(ent->s.origin,view); // view[2] += ent->viewheight; Fog(ent); //view); // MUD - get mud level if (level.mud_puddles) { edict_t *mud; ent->in_mud = 0; for (i=game.maxclients+1; iin_mud; i++) { mud = &g_edicts[i]; if (!mud->inuse) continue; if (!(mud->svflags & SVF_MUD)) continue; if (ent->absmin[0] > mud->absmax[0]) continue; if (ent->absmin[1] > mud->absmax[1]) continue; if (ent->absmin[2] > mud->absmax[2]) continue; if (ent->absmax[0] < mud->absmin[0]) continue; if (ent->absmax[1] < mud->absmin[1]) continue; if (ent->absmax[2] < mud->absmin[2]) continue; ent->in_mud = 1; if (ent->s.origin[2] < mud->absmax[2]) ent->in_mud = 2; if (ent->s.origin[2] + ent->viewheight < mud->absmax[2]) ent->in_mud = 3; } } // USE - special actions taken when +use is pressed if (!client->use && (ucmd->buttons & BUTTON_USE)) { // use key was NOT pressed, but now is client->use = 1; if (client->spycam) camera_off(ent); else { edict_t *viewing; vec3_t intersect; float range; viewing = LookingAt(ent,0,intersect,&range); if (viewing && viewing->classname) { if (!Q_stricmp(viewing->classname, "crane_control") && range <= 100) crane_control_action (viewing, ent, intersect); if (!Q_stricmp(viewing->classname, "target_lock_digit") && range <= 100) lock_digit_increment (viewing, ent); if (!Q_stricmp(viewing->classname, "func_trainbutton") && (viewing->spawnflags & 1) && range <= 64) trainbutton_use (viewing, ent, ent); // Knightmare- different range for chasecam if (!Q_stricmp(viewing->classname, "func_monitor") && ((range <= 100) || (client->chasetoggle && range <= client->zoom + 160.00)) ) { use_camera(viewing,ent,ent); if (client->spycam && client->spycam->viewer == ent) { client->old_owner_angles[0] = ucmd->angles[0]; client->old_owner_angles[1] = ucmd->angles[1]; } } if (viewing->monsterinfo.aiflags & AI_ACTOR) { if (viewing->monsterinfo.aiflags & AI_FOLLOW_LEADER) { viewing->monsterinfo.aiflags &= ~AI_FOLLOW_LEADER; viewing->monsterinfo.old_leader = NULL; viewing->monsterinfo.leader = NULL; viewing->movetarget = viewing->goalentity = NULL; viewing->monsterinfo.stand(viewing); } else { vec3_t dir; viewing->monsterinfo.aiflags |= AI_FOLLOW_LEADER; viewing->monsterinfo.leader = ent; VectorSubtract(ent->s.origin,viewing->s.origin,dir); viewing->ideal_yaw = vectoyaw(dir); if (fabs(viewing->s.angles[YAW] - viewing->ideal_yaw) < 90) actor_salute(viewing); } } } } } if (ucmd->buttons & BUTTON_USE) client->use = 1; else client->use = 0; if ( client->push ) { // currently pushing or pulling a func_pushable if ( !client->use ) { // whoops - released USE key RemovePush(ent); } else if ( (!ent->groundentity) && (ent->waterlevel==0 || client->push->waterlevel == 0 ) ) { // oops, we fall down RemovePush(ent); } else { // Scale client velocity by mass of func_pushable t = VectorLength(ent->velocity); if ( t > client->maxvelocity ) VectorScale(ent->velocity, client->maxvelocity/t, ent->velocity); client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; t = 200./client->push->mass; ucmd->forwardmove *= t; ucmd->sidemove *= t; } } if (ent->turret && ucmd->upmove > 10) turret_disengage(ent->turret); // INTERMISSION if (level.intermissiontime) { // tpp if (client->chasetoggle) ChasecamRemove (ent, OPTION_OFF); // end tpp // Lazarus spycam if (client->spycam) camera_off(ent); client->ps.pmove.pm_type = PM_FREEZE; // can exit intermission after five seconds if (level.time > level.intermissiontime + 5.0 && (ucmd->buttons & BUTTON_ANY) ) level.exitintermission = true; return; } if (ent->target_ent && !Q_stricmp(ent->target_ent->classname, "target_monitor")) { edict_t *monitor = ent->target_ent; if (monitor->target_ent && monitor->target_ent->inuse) { if (monitor->spawnflags & 2) VectorCopy (monitor->target_ent->s.angles, client->ps.viewangles); else { vec3_t dir; VectorSubtract (monitor->target_ent->s.origin, monitor->s.origin, dir); vectoangles (dir, client->ps.viewangles); } } else VectorCopy (monitor->s.angles, client->ps.viewangles); VectorCopy (monitor->s.origin, ent->s.origin); client->ps.pmove.pm_type = PM_FREEZE; return; } // THIRDPERSON VIEW in/out // if NOT pushing something AND in third person AND use key is pressed, // move viewpoint in/out if (client->chasetoggle && !client->push) { // Knigthtmare- and if not about to push something or infront of func_monitor edict_t *viewing; vec3_t intersect; float range; viewing = LookingAt(ent,0,intersect,&range); if ( !(viewing && viewing->classname && (Q_stricmp(viewing->classname,"func_monitor") || Q_stricmp(viewing->classname,"func_pushable")) && range <= 100) ) { if ((ucmd->buttons & BUTTON_USE) && (!deathmatch->value)) { client->use = 1; if ((ucmd->forwardmove < 0) && (client->zoom < 100)) client->zoom++; else if ((ucmd->forwardmove > 0) && (client->zoom > -40)) client->zoom--; ucmd->forwardmove = 0; ucmd->sidemove = 0; } else if (client->use) { //client->zoom = 0; if (client->oldplayer) { // set angles for (i=0 ; i<3 ; i++) { ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->oldplayer->s.angles[i] - ent->client->resp.cmd_angles[i]); } } client->use = 0; } } } // ZOOM if (client->zooming) { client->pers.hand = 2; if (client->zooming > 0) { if (client->ps.fov > 5) { client->ps.fov -= client->frame_zoomrate; if (client->ps.fov < 5) client->ps.fov = 5; } else { client->ps.fov = 5; } client->zoomed = true; } else { if (client->ps.fov < client->original_fov) { client->ps.fov += client->frame_zoomrate; if (client->ps.fov > client->original_fov) { client->ps.fov = client->original_fov; client->zoomed = false; } else client->zoomed = true; } else { client->ps.fov = client->original_fov; client->zoomed = false; } } } // SPYCAM if (client->spycam) { ClientSpycam(ent); return; // no movement while in cam } // END SPYCAM pm_passent = ent; // Lazarus: developer item movement if (client->use && client->shift_dir) ShiftItem(ent, client->shift_dir); if (client->chase_target) { client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); } else { // set up for pmove memset (&pm, 0, sizeof(pm)); if (ent->movetype == MOVETYPE_NOCLIP) client->ps.pmove.pm_type = PM_SPECTATOR; else if (ent->s.modelindex != MAX_MODELS-1) client->ps.pmove.pm_type = PM_GIB; else if (ent->deadflag) client->ps.pmove.pm_type = PM_DEAD; else client->ps.pmove.pm_type = PM_NORMAL; if (level.time > ent->gravity_debounce_time) client->ps.pmove.gravity = sv_gravity->value; else client->ps.pmove.gravity = 0; #ifdef JETPACK_MOD if ( client->jetpack ) { if ( (ucmd->upmove != 0) || (ucmd->forwardmove != 0) || (ucmd->sidemove != 0) ) { if (ucmd->upmove > 0 || !ent->groundentity) { if (!client->jetpack_thrusting) { gi.sound (ent, CHAN_AUTO, gi.soundindex("jetpack/rev.wav"), 1, ATTN_NORM, 0); client->jetpack_start_thrust = level.framenum; } client->jetpack_thrusting = true; } else client->jetpack_thrusting = false; } else client->jetpack_thrusting = false; if (client->jetpack_framenum + client->pers.inventory[fuel_index] > level.framenum) { if (jetpack_weenie->value) { Jet_ApplyJet( ent, ucmd ); if (client->jetpack_framenum < level.framenum) { if (!client->jetpack_infinite) client->pers.inventory[fuel_index] -= 10; client->jetpack_framenum = level.framenum + 10; } } else { if (client->jetpack_thrusting) Jet_ApplyJet( ent, ucmd ); if (client->jetpack_framenum <= level.framenum) { if (client->jetpack_thrusting) { if (!client->jetpack_infinite) client->pers.inventory[fuel_index] -= 11; client->jetpack_framenum = level.framenum + 10; } else { if (!client->jetpack_infinite) client->pers.inventory[fuel_index]--; client->jetpack_framenum = level.framenum + 10; } } if (ucmd->upmove == 0) { // accelerate to 75% gravity in 2 seconds float gravity; float g_max = 0.75 * sv_gravity->value; gravity = g_max * (level.framenum - client->jetpack_last_thrust)/20; if (gravity > g_max) gravity = g_max; client->ps.pmove.gravity = (short)gravity; } else client->jetpack_last_thrust = level.framenum; } } else { client->jetpack = false; ent->s.frame = FRAME_jump2; // reset from stand to avoid goofiness } } #endif // #ifdef JETPACK_MOD pm.s = client->ps.pmove; for (i=0 ; i<3 ; i++) { pm.s.origin[i] = ent->s.origin[i]*8; // FIXME: make sure this short doesn't overflow pm.s.velocity[i] = ent->velocity[i]*8; } if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) { pm.snapinitial = true; // gi.dprintf ("pmove changed!\n"); } pm.cmd = *ucmd; pm.trace = PM_trace; // adds default parms pm.pointcontents = gi.pointcontents; if (ent->vehicle) pm.s.pm_flags |= PMF_ON_GROUND; // perform a pmove gi.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]*0.125; ent->velocity[i] = pm.s.velocity[i]*0.125; } VectorCopy (pm.mins, ent->mins); VectorCopy (pm.maxs, ent->maxs); client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); #ifdef JETPACK_MOD if ( client->jetpack && jetpack_weenie->value ) { if ( pm.groundentity ) // are we on ground if ( Jet_AvoidGround(ent) ) // then lift us if possible pm.groundentity = NULL; // now we are no longer on ground } #endif // MUD - "correct" Pmove physics if (pm.waterlevel && ent->in_mud) { vec3_t point; vec3_t end; vec3_t deltapos, deltavel; float frac; pm.watertype |= CONTENTS_MUD; ent->in_mud = pm.waterlevel; VectorSubtract(ent->s.origin,oldorigin,deltapos); VectorSubtract(ent->velocity,oldvelocity,deltavel); if (pm.waterlevel == 1) { frac = MUD1BASE + MUD1AMP*sin( (float)(level.framenum%10)/10.*2*M_PI); ent->s.origin[0] = oldorigin[0] + frac*deltapos[0]; ent->s.origin[1] = oldorigin[1] + frac*deltapos[1]; ent->s.origin[2] = oldorigin[2] + 0.75*deltapos[2]; ent->velocity[0] = oldvelocity[0] + frac*deltavel[0]; ent->velocity[1] = oldvelocity[1] + frac*deltavel[1]; ent->velocity[2] = oldvelocity[2] + 0.75*deltavel[2]; } else if (pm.waterlevel == 2) { trace_t tr; float dist; VectorCopy(oldorigin,point); point[2] += ent->maxs[2]; end[0] = point[0]; end[1] = point[1]; end[2] = oldorigin[2] + ent->mins[2]; tr = gi.trace(point,NULL,NULL,end,ent,CONTENTS_WATER); dist = point[2] - tr.endpos[2]; // frac = waterlevel 1 frac at dist=32 or more, // = waterlevel 3 frac at dist=10 or less if (dist <= 10) frac = MUD3; else frac = MUD3 + (dist-10)/22.*(MUD1BASE-MUD3); ent->s.origin[0] = oldorigin[0] + frac*deltapos[0]; ent->s.origin[1] = oldorigin[1] + frac*deltapos[1]; ent->s.origin[2] = oldorigin[2] + frac*deltapos[2]; ent->velocity[0] = oldvelocity[0] + frac*deltavel[0]; ent->velocity[1] = oldvelocity[1] + frac*deltavel[1]; ent->velocity[2] = oldvelocity[2] + frac*deltavel[2]; if (!ent->groundentity) { // Player can't possibly move up ent->s.origin[2] = min(oldorigin[2], ent->s.origin[2]); ent->velocity[2] = min(oldvelocity[2],ent->velocity[2]); ent->velocity[2] = min(-10,ent->velocity[2]); } } else { ent->s.origin[0] = oldorigin[0] + MUD3*deltapos[0]; ent->s.origin[1] = oldorigin[1] + MUD3*deltapos[1]; ent->velocity[0] = oldvelocity[0] + MUD3*deltavel[0]; ent->velocity[1] = oldvelocity[1] + MUD3*deltavel[1]; if (ent->groundentity) { ent->s.origin[2] = oldorigin[2] + MUD3*deltapos[2]; ent->velocity[2] = oldvelocity[2] + MUD3*deltavel[2]; } else { ent->s.origin[2] = min(oldorigin[2],ent->s.origin[2]); ent->velocity[2] = min(oldvelocity[2], 0); } } } else ent->in_mud = 0; // end MUD if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0) && !client->jetpack) { // Knightmare- allow disabling of STUPID grunting when jumping if ((deathmatch->value || player_jump_sounds->value) && !ent->vehicle) { gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); PlayerNoise(ent, ent->s.origin, PNOISE_SELF); } // Paril's vehicle targeting if (ent->vehicle) G_UseTargets (ent->vehicle, ent); // Lazarus: temporarily match velocities with entity we just // jumped from VectorAdd (ent->groundentity->velocity, ent->velocity, ent->velocity); } if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) ent->client->jumping = 1; if (ent->deadflag != DEAD_FROZEN) ent->viewheight = pm.viewheight; ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; ent->groundentity = pm.groundentity; if (pm.groundentity) ent->groundentity_linkcount = pm.groundentity->linkcount; // Lazarus - lie about ground when driving a vehicle. // Pmove apparently doesn't think the ground // can be "owned" if (ent->vehicle && !ent->groundentity) { ent->groundentity = ent->vehicle; ent->groundentity_linkcount = ent->vehicle->linkcount; } if (ent->deadflag) { if (ent->deadflag != DEAD_FROZEN) { client->ps.viewangles[ROLL] = 40; client->ps.viewangles[PITCH] = -15; client->ps.viewangles[YAW] = client->killer_yaw; } } else { VectorCopy (pm.viewangles, client->v_angle); VectorCopy (pm.viewangles, client->ps.viewangles); } #ifdef JETPACK_MOD if ( client->jetpack && !(ucmd->buttons & BUTTONS_ATTACK)) ent->s.frame = FRAME_stand20; #endif //ZOID if (client->ctf_grapple) CTFGrapplePull(client->ctf_grapple); //ZOID gi.linkentity (ent); if (ent->movetype != MOVETYPE_NOCLIP) G_TouchTriggers (ent); if ( (world->effects & FX_WORLDSPAWN_JUMPKICK) && (ent->client->jumping) && (ent->solid != SOLID_NOT)) kick_attack(ent); // touch other objects // Lazarus: but NOT if game is frozen if (!level.freeze) { for (i=0 ; itouch) continue; other->touch (other, ent, NULL, NULL); } } } client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // save light level the player is standing on for // monster sighting AI ent->light_level = ucmd->lightlevel; // CDawg - add here! if (ucmd->forwardmove < -1) ent->client->backpedaling = true; else ent->client->backpedaling = false; // CDawg end here! // fire weapon from final position if needed if (client->latched_buttons & BUTTONS_ATTACK //ZOID && !(ctf->value && ent->movetype == MOVETYPE_NOCLIP) ) //ZOID { if (client->resp.spectator) { client->latched_buttons = 0; if (client->chase_target) { client->chase_target = NULL; client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; } else GetChaseTarget(ent); } else if (!client->weapon_thunk) { client->weapon_thunk = true; Think_Weapon (ent); } } // ACEBOT_ADD if (!ent->is_bot && !ent->deadflag && !ent->client->resp.spectator) ACEND_PathMap(ent); // ACEBOT_END if (client->resp.spectator) { if (ucmd->upmove >= 10) { if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) { client->ps.pmove.pm_flags |= PMF_JUMP_HELD; if (client->chase_target) ChaseNext(ent); else GetChaseTarget(ent); } } else client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD; } //ZOID //regen tech CTFApplyRegeneration(ent); //ZOID // update chase cam if being followed for (i = 1; i <= maxclients->value; i++) { other = g_edicts + i; if (other->inuse && other->client->chase_target == ent) UpdateChaseCam(other); } if (client->push != NULL) { if (client->use && ( (ucmd->forwardmove != 0) || (ucmd->sidemove != 0) ) ) ClientPushPushable(ent); else client->push->s.sound = 0; } } /* ============== 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 (level.intermissiontime) return; client = ent->client; // DWH if (client->spycam) client = client->camplayer->client; // tpp if (client->delayedstart > 0) client->delayedstart--; if (client->delayedstart == 1) ChasecamStart (ent); // end tpp if (deathmatch->value && client->pers.spectator != client->resp.spectator && (level.time - client->respawn_time) >= 5) { spectator_respawn(ent); return; } // run weapon animations if it hasn't been done by a ucmd_t if (!client->weapon_thunk && !client->resp.spectator //ZOID && !(ctf->value && ent->movetype == MOVETYPE_NOCLIP) ) //ZOID Think_Weapon (ent); else client->weapon_thunk = false; if (ent->deadflag) { // wait for any button just going down if ( level.time > client->respawn_time) { // tpp if (ent->crosshair) G_FreeEdict(ent->crosshair); ent->crosshair = NULL; if (ent->client->oldplayer) G_FreeEdict (ent->client->oldplayer); ent->client->oldplayer = NULL; if (ent->client->chasecam) G_FreeEdict (ent->client->chasecam); ent->client->chasecam = NULL; // end tpp // in deathmatch, only wait for attack button if (deathmatch->value) buttonMask = BUTTONS_ATTACK; else buttonMask = -1; if ( ( client->latched_buttons & buttonMask ) || (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) ) { respawn(ent); client->latched_buttons = 0; } } return; } // add player trail so monsters can follow // DWH: Don't add player trail for players in camera if (!deathmatch->value && !client->spycam) if (!visible (ent, PlayerTrail_LastSpot() ) ) PlayerTrail_Add (ent->s.old_origin); client->latched_buttons = 0; }