mirror of
https://github.com/ReactionQuake3/reaction.git
synced 2024-11-26 14:11:22 +00:00
2067 lines
60 KiB
C
2067 lines
60 KiB
C
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id$
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Log$
|
|
// Revision 1.135 2004/01/26 21:26:08 makro
|
|
// no message
|
|
//
|
|
// Revision 1.134 2003/04/26 22:33:06 jbravo
|
|
// Wratted all calls to G_FreeEnt() to avoid crashing and provide debugging
|
|
//
|
|
// Revision 1.133 2003/04/23 17:49:11 slicer
|
|
// Fixed Crash Bug caused by merging 1.32 source code
|
|
//
|
|
// Revision 1.132 2003/04/19 17:41:26 jbravo
|
|
// Applied changes that where in 1.29h -> 1.32b gamecode.
|
|
//
|
|
// Revision 1.131 2003/04/19 15:27:30 jbravo
|
|
// Backing out of most of unlagged. Only optimized prediction and smooth clients
|
|
// remains.
|
|
//
|
|
// Revision 1.130 2003/04/09 02:00:43 jbravo
|
|
// Fixed team none in DM and some final cleanups for the 3.0 release
|
|
//
|
|
// Revision 1.129 2003/04/02 22:23:51 jbravo
|
|
// More replacements tweaks. Added zcam_stfu
|
|
//
|
|
// Revision 1.128 2003/03/28 10:36:02 jbravo
|
|
// Tweaking the replacement system a bit. Reactionmale now the default model
|
|
//
|
|
// Revision 1.127 2003/03/22 20:29:26 jbravo
|
|
// wrapping linkent and unlinkent calls
|
|
//
|
|
// Revision 1.126 2003/03/10 07:07:58 jbravo
|
|
// Small unlagged fixes and voting delay added.
|
|
//
|
|
// Revision 1.125 2003/03/09 21:30:38 jbravo
|
|
// Adding unlagged. Still needs work.
|
|
//
|
|
// Revision 1.124 2003/02/27 03:58:35 jbravo
|
|
// Fixed the FF system after adding TDM broke it. Added color to error messages
|
|
//
|
|
// Revision 1.123 2002/11/17 20:14:15 jbravo
|
|
// Itembanning added
|
|
//
|
|
// Revision 1.122 2002/11/13 00:50:38 jbravo
|
|
// Fixed item dropping, specmode selection on death and helmet probs.
|
|
//
|
|
// Revision 1.121 2002/10/29 01:34:52 jbravo
|
|
// Added g_RQ3_tdmMode (0 = TP style, 1 = DM style) including UI support.
|
|
//
|
|
// Revision 1.120 2002/10/26 22:03:43 jbravo
|
|
// Made TeamDM work RQ3 style.
|
|
//
|
|
// Revision 1.119 2002/10/26 18:29:17 jbravo
|
|
// Added allweap and allitem funtionality.
|
|
//
|
|
// Revision 1.118 2002/10/21 21:00:39 slicer
|
|
// New MM features and bug fixes
|
|
//
|
|
// Revision 1.117 2002/09/29 16:06:44 jbravo
|
|
// Work done at the HPWorld expo
|
|
//
|
|
// Revision 1.116 2002/09/24 05:06:16 blaze
|
|
// fixed spectating so ref\'s can now use all the chasecam modes.
|
|
//
|
|
// Revision 1.115 2002/09/02 03:30:53 jbravo
|
|
// Hopefully fixed the skinhacking
|
|
//
|
|
// Revision 1.114 2002/08/30 01:09:06 jbravo
|
|
// Semi fixed the bodies thing in CTB
|
|
//
|
|
// Revision 1.113 2002/08/28 23:10:06 jbravo
|
|
// Added cg_RQ3_SuicideLikeARealMan, timestamping to server logs and
|
|
// fixed stats for non-TP modes.
|
|
//
|
|
// Revision 1.112 2002/08/23 14:25:05 slicer
|
|
// Added a new Referee System with multiple ref support
|
|
//
|
|
// Revision 1.111 2002/08/21 07:00:07 jbravo
|
|
// Added CTB respawn queue and fixed game <-> cgame synch problem in CTB
|
|
//
|
|
// Revision 1.110 2002/08/07 03:35:57 jbravo
|
|
// Added dynamic radio and stopped all radio usage during lca
|
|
//
|
|
// Revision 1.109 2002/07/26 22:28:38 jbravo
|
|
// Fixed the server about menu, made the UI handle illegal models and skins
|
|
// better.
|
|
//
|
|
// Revision 1.108 2002/07/26 06:21:43 jbravo
|
|
// Fixed the MM settings stuff so it works on remote servers also.
|
|
// Removed the MM_NAMES_COLOR since it broke on nicks with color in them.
|
|
//
|
|
// Revision 1.107 2002/07/16 04:09:14 niceass
|
|
// stupid team scoreboard fix
|
|
//
|
|
// Revision 1.106 2002/07/11 04:32:24 niceass
|
|
// misc CTB changes for joining a team or becoming a spectator on map load. Also team check before weapon equip
|
|
//
|
|
// Revision 1.105 2002/07/09 05:42:18 niceass
|
|
// small change to userinfo
|
|
//
|
|
// Revision 1.104 2002/07/02 03:41:59 jbravo
|
|
// Fixed a 2 frags pr kill bug, the use cmd now cancels weaponchanges in progress
|
|
// and fixed the captain status lingering on people after switching from MM
|
|
//
|
|
// Revision 1.103 2002/06/30 17:33:01 jbravo
|
|
// New radio sounds and the 0wned sound was added.
|
|
//
|
|
// Revision 1.102 2002/06/26 15:58:13 makro
|
|
// Fixed a crash bug in the spawning code
|
|
// (happenned on maps with one spawn point)
|
|
//
|
|
// Revision 1.101 2002/06/24 05:51:51 jbravo
|
|
// CTF mode is now semi working
|
|
//
|
|
// Revision 1.100 2002/06/23 23:32:29 jbravo
|
|
// Fixed logging of clients IP addresses.
|
|
//
|
|
// Revision 1.99 2002/06/21 11:55:32 freud
|
|
// Changed spawning system to move spawns up 9 pixels (q3 style)
|
|
//
|
|
// Revision 1.98 2002/06/20 22:32:43 jbravo
|
|
// Added last damaged player and fixed a test2 model problem (atrimum my ass :)
|
|
// Changed g_RQ3_printOwnObits to g_RQ3_showOwnKills and it also controls $K
|
|
//
|
|
// Revision 1.97 2002/06/19 18:18:09 jbravo
|
|
// Small cleanups for compiler warnings
|
|
//
|
|
// Revision 1.96 2002/06/19 18:13:57 jbravo
|
|
// New TNG spawning system :)
|
|
//
|
|
// Revision 1.95 2002/06/17 00:23:59 slicer
|
|
// Lasersight problem fixed
|
|
//
|
|
// Revision 1.94 2002/06/16 20:06:14 jbravo
|
|
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
|
|
//
|
|
// Revision 1.93 2002/06/16 17:38:00 jbravo
|
|
// Removed the MISSIONPACK ifdefs and missionpack only code.
|
|
//
|
|
// Revision 1.92 2002/06/13 20:59:35 slicer
|
|
// Setting ( for real ) gender on DM
|
|
//
|
|
// Revision 1.91 2002/06/12 22:32:24 slicer
|
|
// Even better way to improve the Cvar Anti-Cheat System
|
|
//
|
|
// Revision 1.90 2002/06/12 15:29:53 slicer
|
|
// Improved and fixed the Anti-Cheat System
|
|
//
|
|
// Revision 1.89 2002/06/12 03:37:38 blaze
|
|
// some fixes for the add bot code
|
|
//
|
|
// Revision 1.88 2002/06/11 23:37:27 blaze
|
|
// moved the cheat cvars to be sent at a different time, should work better
|
|
//
|
|
// Revision 1.87 2002/06/11 01:43:08 blaze
|
|
// g_rq3_cvarfile allows you to change which file holds the restricted cvars
|
|
//
|
|
// Revision 1.86 2002/06/07 19:07:08 slicer
|
|
// removed cvars for teamXready, replaced by level.teamXready
|
|
//
|
|
// Revision 1.85 2002/06/05 22:09:21 niceass
|
|
// bot fix. NOT MY FAULT
|
|
//
|
|
// Revision 1.84 2002/06/04 08:25:44 niceass
|
|
// cgame team fix. (when you change your name, etc)
|
|
//
|
|
// Revision 1.83 2002/06/04 07:12:32 niceass
|
|
// spectators spawn where you are rather than at a spawnpoint
|
|
//
|
|
// Revision 1.82 2002/05/31 17:32:11 jbravo
|
|
// HC gibs almost working :)
|
|
//
|
|
// Revision 1.81 2002/05/28 04:46:12 niceass
|
|
// headless fix
|
|
//
|
|
// Revision 1.80 2002/05/28 01:17:01 jbravo
|
|
// More gib fixes. g_RQ3_gib added
|
|
//
|
|
// Revision 1.79 2002/05/27 06:50:01 niceass
|
|
// further spawning and removed kamakazi
|
|
//
|
|
// Revision 1.78 2002/05/25 16:31:18 blaze
|
|
// moved breakable stuff over to config strings
|
|
//
|
|
// Revision 1.77 2002/05/21 23:16:30 blaze
|
|
// Only send cheat vars on client connect instead of every spawn
|
|
//
|
|
// Revision 1.76 2002/05/20 04:59:33 jbravo
|
|
// Lots of small fixes.
|
|
//
|
|
// Revision 1.75 2002/05/19 21:27:28 blaze
|
|
// added force and buoyancy to breakables
|
|
//
|
|
// Revision 1.74 2002/05/13 07:29:14 jbravo
|
|
// Fixed server chrasing on incorrect models in TP and also added default skins
|
|
//
|
|
// Revision 1.73 2002/05/10 13:21:53 makro
|
|
// Mainly bot stuff. Also fixed a couple of crash bugs
|
|
//
|
|
// Revision 1.72 2002/05/05 15:51:16 slicer
|
|
// Captain and subs get saved on map_restarts ( moved to "sess" )
|
|
//
|
|
// Revision 1.71 2002/05/03 18:09:20 makro
|
|
// Bot stuff. Jump kicks
|
|
//
|
|
// Revision 1.70 2002/04/30 11:54:37 makro
|
|
// Bots rule ! Also, added clips to give all. Maybe some other things
|
|
//
|
|
// Revision 1.69 2002/04/30 01:23:05 jbravo
|
|
// Changed the server logging to be more like a normal AQ server. Cleaned
|
|
// all colors from the logs.
|
|
//
|
|
// Revision 1.68 2002/04/23 00:21:44 jbravo
|
|
// Cleanups of the new model code. Removed the spectator bar for zcam modes.
|
|
//
|
|
// Revision 1.67 2002/04/22 02:27:57 jbravo
|
|
// Dynamic model recognition
|
|
//
|
|
// Revision 1.66 2002/04/18 16:13:23 jbravo
|
|
// Scoreboard now shows green for live players and white for dead.
|
|
// Time should not get reset on deaths any more.
|
|
//
|
|
// Revision 1.65 2002/04/09 14:30:10 jbravo
|
|
// Made cg_thirdPerson a CVAR_ROM, Made bots understand team aliases (1 and 2) and
|
|
// made TP spawns more random.
|
|
//
|
|
// Revision 1.64 2002/04/07 17:50:54 makro
|
|
// Abbey
|
|
//
|
|
// Revision 1.63 2002/04/07 12:49:10 slicer
|
|
// Added 'teamname' command for MM, and tweaked the cvar system.
|
|
//
|
|
// Revision 1.62 2002/04/07 03:22:48 jbravo
|
|
// Tweaks and crashbug fixes
|
|
//
|
|
// Revision 1.61 2002/04/05 18:53:26 jbravo
|
|
// Warning fixes
|
|
//
|
|
// Revision 1.60 2002/04/03 03:13:16 blaze
|
|
// NEW BREAKABLE CODE - will break all old breakables(wont appear in maps)
|
|
//
|
|
// Revision 1.59 2002/04/02 20:23:12 jbravo
|
|
// Bots dont get to use any specmode other than FREE and the recive radio cmds
|
|
// as text and not sounds.
|
|
//
|
|
// Revision 1.58 2002/04/01 22:23:14 slicer
|
|
// Added "weapon" command buffering | Solved Gren Mode Bug
|
|
//
|
|
// Revision 1.57 2002/03/31 03:31:24 jbravo
|
|
// Compiler warning cleanups
|
|
//
|
|
// Revision 1.56 2002/03/30 21:51:42 jbravo
|
|
// Removed all those ifdefs for zcam.
|
|
//
|
|
// Revision 1.55 2002/03/30 02:29:43 jbravo
|
|
// Lots of spectator code updates. Removed debugshit, added some color.
|
|
//
|
|
// Revision 1.54 2002/03/26 11:32:04 jbravo
|
|
// Remember specstate between rounds.
|
|
//
|
|
// Revision 1.53 2002/03/26 10:32:52 jbravo
|
|
// Bye bye LCA lag
|
|
//
|
|
// Revision 1.52 2002/03/17 23:43:43 slicer
|
|
// Made Bots visible again at DM
|
|
//
|
|
// Revision 1.51 2002/03/17 02:03:48 jbravo
|
|
// Fixed a bug where a players laser would stay in the map after he disconnects
|
|
//
|
|
// Revision 1.50 2002/03/17 01:44:39 jbravo
|
|
// Fixed the "xxx died" fraglines, did some code cleanups andalmost fixed
|
|
// DM. Only DM problem I can see is that bots are invisible.
|
|
//
|
|
// Revision 1.49 2002/03/14 23:54:12 jbravo
|
|
// Added a variable system from AQ. Works the same except it uses $ for %
|
|
//
|
|
// Revision 1.48 2002/03/14 02:24:39 jbravo
|
|
// Adding radio :)
|
|
//
|
|
// Revision 1.47 2002/03/11 18:02:33 slicer
|
|
// Fixed team changes and scoreboard bugs
|
|
//
|
|
// Revision 1.46 2002/03/07 01:38:36 assimon
|
|
// Changed Ref System. New cvar added - g_RQ3_RefID. Now referee is peserved even on map changes or map_restarts.
|
|
//
|
|
// Revision 1.45 2002/03/03 13:49:28 jbravo
|
|
// Initializing weapon modes on connect.
|
|
//
|
|
// Revision 1.44 2002/03/03 03:11:37 jbravo
|
|
// Use propper weapon anims on TP spawns
|
|
//
|
|
// Revision 1.43 2002/03/02 15:39:34 jbravo
|
|
// Fixed team auto (PickTeam) up for TP
|
|
//
|
|
// Revision 1.42 2002/03/02 14:54:24 jbravo
|
|
// Added the skin and model names to the name of the player thats being
|
|
// followed, as in AQ
|
|
//
|
|
// Revision 1.41 2002/03/01 18:21:26 jbravo
|
|
// Cleanups and removal of g_RQ3_sniperup
|
|
//
|
|
// Revision 1.40 2002/02/27 01:54:29 jbravo
|
|
// More spectatorfixes and finally stopped all fallingdamage anims and
|
|
// sounds during LCA.
|
|
//
|
|
// Revision 1.39 2002/02/26 21:59:10 jbravo
|
|
// Fixed death on switching teams while dead
|
|
//
|
|
// Revision 1.38 2002/02/26 02:58:47 jbravo
|
|
// Fixing the spectator_free mode not being predicted in the client.
|
|
//
|
|
// Revision 1.37 2002/02/10 21:21:22 slicer
|
|
// Saving persistant and other data on some events..
|
|
//
|
|
// Revision 1.36 2002/02/10 16:26:55 jbravo
|
|
// Attempting to intergrate zcam better into rq3 and a fix for lights.wav
|
|
//
|
|
// Revision 1.35 2002/02/07 23:01:07 slicer
|
|
// Small fix..
|
|
//
|
|
// Revision 1.34 2002/02/06 12:06:48 slicer
|
|
// TP Scores bug fix
|
|
//
|
|
// Revision 1.33 2002/02/06 03:10:43 jbravo
|
|
// Fix the instant spectate on death and an attempt to fix the scores
|
|
//
|
|
// Revision 1.32 2002/02/05 23:41:27 slicer
|
|
// More on matchmode..
|
|
//
|
|
// Revision 1.31 2002/02/04 00:10:49 slicer
|
|
// Matchmode: Teams Ready/Not Ready goes by cvar MM_team1/2
|
|
//
|
|
// Revision 1.28 2002/02/01 01:00:36 slicer
|
|
// Adding Matchmode: just a few basics and files...
|
|
//
|
|
// Revision 1.27 2002/01/27 13:33:28 jbravo
|
|
// Teamplay antistick system.
|
|
//
|
|
// Revision 1.26 2002/01/23 15:26:31 niceass
|
|
// body sinkage removed
|
|
// weapon reset fixed
|
|
//
|
|
// Revision 1.25 2002/01/11 20:20:58 jbravo
|
|
// Adding TP to main branch
|
|
//
|
|
// Revision 1.24 2002/01/11 19:48:30 jbravo
|
|
// Formatted the source in non DOS format.
|
|
//
|
|
// Revision 1.23 2001/12/31 16:28:42 jbravo
|
|
// I made a Booboo with the Log tag.
|
|
//
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
#include "g_local.h"
|
|
#include "zcam.h"
|
|
|
|
// JBravo: fixme. Hack to use SelectInitialSpawnPoint() in ClientSpawn.
|
|
gentity_t *SelectInitialSpawnPoint(vec3_t origin, vec3_t angles);
|
|
|
|
//Blaze: for the breakables
|
|
breakable_t rq3_breakables[RQ3_MAX_BREAKABLES];
|
|
|
|
#define RQ3_NONAMEPLAYER "Nameless"
|
|
|
|
// JBravo: for models
|
|
extern legitmodel_t legitmodels[MAXMODELS];
|
|
int RQ3_Validatemodel(char *model);
|
|
|
|
extern char *settings[];
|
|
extern char *settings2[];
|
|
|
|
// g_client.c -- client functions that don't happen every frame
|
|
|
|
static vec3_t playerMins = { -15, -15, -24 };
|
|
static vec3_t playerMaxs = { 15, 15, 32 };
|
|
|
|
/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
|
|
potential spawning position for deathmatch games.
|
|
The first time a player enters the game, they will be at an 'initial' spot.
|
|
Targets will be fired when someone spawns in on them.
|
|
"nobots" will prevent bots from using this spot.
|
|
"nohumans" will prevent non-bots from using this spot.
|
|
*/
|
|
void SP_info_player_deathmatch(gentity_t * ent)
|
|
{
|
|
int i;
|
|
|
|
G_SpawnInt("nobots", "0", &i);
|
|
if (i) {
|
|
ent->flags |= FL_NO_BOTS;
|
|
}
|
|
G_SpawnInt("nohumans", "0", &i);
|
|
if (i) {
|
|
ent->flags |= FL_NO_HUMANS;
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
|
|
equivelant to info_player_deathmatch
|
|
*/
|
|
void SP_info_player_start(gentity_t * ent)
|
|
{
|
|
ent->classname = "info_player_deathmatch";
|
|
SP_info_player_deathmatch(ent);
|
|
}
|
|
|
|
/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
|
|
The intermission will be viewed from this point. Target an info_notnull for the view direction.
|
|
*/
|
|
void SP_info_player_intermission(gentity_t * ent)
|
|
{
|
|
|
|
}
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
SelectSpawnPoint
|
|
|
|
=======================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
SpotWouldTelefrag
|
|
|
|
================
|
|
*/
|
|
qboolean SpotWouldTelefrag(gentity_t * spot)
|
|
{
|
|
int i, num;
|
|
int touch[MAX_GENTITIES];
|
|
gentity_t *hit;
|
|
vec3_t mins, maxs;
|
|
|
|
VectorAdd(spot->s.origin, playerMins, mins);
|
|
VectorAdd(spot->s.origin, playerMaxs, maxs);
|
|
num = trap_EntitiesInBox(mins, maxs, touch, MAX_GENTITIES);
|
|
|
|
for (i = 0; i < num; i++) {
|
|
//Blaze: Print out some Debug info
|
|
if (&g_entities[touch[i]] == NULL) G_Printf("Ln 0376\n");
|
|
hit = &g_entities[touch[i]];
|
|
if (hit->client) {
|
|
return qtrue;
|
|
}
|
|
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SelectNearestDeathmatchSpawnPoint
|
|
|
|
Find the spot that we DON'T want to use
|
|
================
|
|
*/
|
|
// Moved to g_local.h
|
|
//#define MAX_SPAWN_POINTS 128
|
|
gentity_t *SelectNearestDeathmatchSpawnPoint(vec3_t from)
|
|
{
|
|
gentity_t *spot;
|
|
vec3_t delta;
|
|
float dist, nearestDist;
|
|
gentity_t *nearestSpot;
|
|
|
|
nearestDist = 999999;
|
|
nearestSpot = NULL;
|
|
spot = NULL;
|
|
|
|
while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
|
|
|
|
VectorSubtract(spot->s.origin, from, delta);
|
|
dist = VectorLength(delta);
|
|
if (dist < nearestDist) {
|
|
nearestDist = dist;
|
|
nearestSpot = spot;
|
|
}
|
|
}
|
|
|
|
return nearestSpot;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SelectRandomDeathmatchSpawnPoint
|
|
|
|
go to a random point that doesn't telefrag
|
|
================
|
|
*/
|
|
// Moved to g_local.h
|
|
//#define MAX_SPAWN_POINTS 128
|
|
gentity_t *SelectRandomDeathmatchSpawnPoint(void)
|
|
{
|
|
gentity_t *spot;
|
|
int count;
|
|
int selection;
|
|
gentity_t *spots[MAX_SPAWN_POINTS];
|
|
|
|
count = 0;
|
|
spot = NULL;
|
|
|
|
while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
|
|
if (SpotWouldTelefrag(spot)) {
|
|
continue;
|
|
}
|
|
spots[count] = spot;
|
|
count++;
|
|
}
|
|
|
|
if (!count) { // no spots that won't telefrag
|
|
return G_Find(NULL, FOFS(classname), "info_player_deathmatch");
|
|
}
|
|
|
|
selection = rand() % count;
|
|
return spots[selection];
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectRandomFurthestSpawnPoint
|
|
|
|
Chooses a player start, deathmatch start, etc
|
|
============
|
|
*/
|
|
gentity_t *SelectRandomFurthestSpawnPoint(vec3_t avoidPoint, vec3_t origin, vec3_t angles)
|
|
{
|
|
gentity_t *spot;
|
|
vec3_t delta;
|
|
float dist;
|
|
float list_dist[64];
|
|
gentity_t *list_spot[64];
|
|
int numSpots, rnd, i, j;
|
|
|
|
numSpots = 0;
|
|
spot = NULL;
|
|
|
|
while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
|
|
if (SpotWouldTelefrag(spot)) {
|
|
continue;
|
|
}
|
|
VectorSubtract(spot->s.origin, avoidPoint, delta);
|
|
dist = VectorLength(delta);
|
|
for (i = 0; i < numSpots; i++) {
|
|
if (dist > list_dist[i]) {
|
|
if (numSpots >= 64)
|
|
numSpots = 64 - 1;
|
|
for (j = numSpots; j > i; j--) {
|
|
list_dist[j] = list_dist[j - 1];
|
|
list_spot[j] = list_spot[j - 1];
|
|
}
|
|
list_dist[i] = dist;
|
|
list_spot[i] = spot;
|
|
numSpots++;
|
|
if (numSpots > 64)
|
|
numSpots = 64;
|
|
break;
|
|
}
|
|
}
|
|
if (i >= numSpots && numSpots < 64) {
|
|
list_dist[numSpots] = dist;
|
|
list_spot[numSpots] = spot;
|
|
numSpots++;
|
|
}
|
|
}
|
|
|
|
//Makro - on a map with one spawn point, if one player has to respawn and someone is
|
|
//already near the spawnpoint, Q3 crashes; added check
|
|
if (numSpots != 0) {
|
|
// select a random spot from the spawn points furthest away
|
|
rnd = random() * (numSpots / 3); // NiceAss: divided by 2 changed to 3 to cut down on close spawns
|
|
|
|
VectorCopy(list_spot[rnd]->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy(list_spot[rnd]->s.angles, angles);
|
|
|
|
return list_spot[rnd];
|
|
} else {
|
|
//Makro - added; note - plenty of room for improvement here, I just wanted to get rid of the crash bug
|
|
spot = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
|
|
if (!spot) {
|
|
G_Error("Couldn't find a spawn point");
|
|
}
|
|
VectorCopy(spot->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy(spot->s.angles, angles);
|
|
|
|
return spot;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectSpawnPoint
|
|
|
|
Chooses a player start, deathmatch start, etc
|
|
============
|
|
*/
|
|
gentity_t *SelectSpawnPoint(vec3_t avoidPoint, vec3_t origin, vec3_t angles)
|
|
{
|
|
return SelectRandomFurthestSpawnPoint(avoidPoint, origin, angles);
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectInitialSpawnPoint
|
|
|
|
Try to find a spawn point marked 'initial', otherwise
|
|
use normal spawn selection.
|
|
============
|
|
*/
|
|
gentity_t *SelectInitialSpawnPoint(vec3_t origin, vec3_t angles)
|
|
{
|
|
gentity_t *spot;
|
|
|
|
spot = NULL;
|
|
while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
|
|
if (spot->spawnflags & 1) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!spot || SpotWouldTelefrag(spot)) {
|
|
return SelectSpawnPoint(vec3_origin, origin, angles);
|
|
}
|
|
|
|
VectorCopy(spot->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy(spot->s.angles, angles);
|
|
|
|
return spot;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectSpectatorSpawnPoint
|
|
|
|
============
|
|
*/
|
|
gentity_t *SelectSpectatorSpawnPoint(vec3_t origin, vec3_t angles)
|
|
{
|
|
FindIntermissionPoint();
|
|
|
|
VectorCopy(level.intermission_origin, origin);
|
|
VectorCopy(level.intermission_angle, angles);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
BODYQUE
|
|
|
|
=======================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
InitBodyQue
|
|
===============
|
|
*/
|
|
void InitBodyQue(void)
|
|
{
|
|
int i;
|
|
gentity_t *ent;
|
|
|
|
level.bodyQueIndex = 0;
|
|
for (i = 0; i < BODY_QUEUE_SIZE; i++) {
|
|
ent = G_Spawn();
|
|
ent->classname = "bodyque";
|
|
ent->neverFree = qtrue;
|
|
level.bodyQue[i] = ent;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
ClearBodyQue - By NiceAss
|
|
===============
|
|
*/
|
|
void ClearBodyQue(void)
|
|
{
|
|
int i;
|
|
gentity_t *ent;
|
|
|
|
level.bodyQueIndex = 0;
|
|
for (i = 0; i < BODY_QUEUE_SIZE; i++) {
|
|
ent = level.bodyQue[i];
|
|
trap_UnlinkEntity(ent);
|
|
ent->physicsObject = qfalse;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
BodySink
|
|
|
|
After sitting around for five seconds, fall into the ground and dissapear
|
|
=============
|
|
*/
|
|
void BodySink(gentity_t * ent)
|
|
{
|
|
// NiceAss: Prevent body sink in TP. Bodies will be removed at before LCA
|
|
if (g_gametype.integer == GT_TEAMPLAY)
|
|
return;
|
|
|
|
if (level.time - ent->timestamp > 6500) {
|
|
// the body ques are never actually freed, they are just unlinked
|
|
trap_UnlinkEntity(ent);
|
|
ent->physicsObject = qfalse;
|
|
return;
|
|
}
|
|
ent->nextthink = level.time + 100;
|
|
ent->s.pos.trBase[2] -= 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CopyToBodyQue
|
|
|
|
A player is respawning, so make an entity that looks
|
|
just like the existing corpse to leave behind.
|
|
=============
|
|
*/
|
|
void CopyToBodyQue(gentity_t * ent)
|
|
{
|
|
gentity_t *body;
|
|
int contents;
|
|
|
|
trap_UnlinkEntity(ent);
|
|
|
|
// if client is in a nodrop area, don't leave the body
|
|
contents = trap_PointContents(ent->s.origin, -1);
|
|
if (contents & CONTENTS_NODROP) {
|
|
return;
|
|
}
|
|
// grab a body que and cycle to the next one
|
|
body = level.bodyQue[level.bodyQueIndex];
|
|
level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE;
|
|
|
|
trap_UnlinkEntity(body);
|
|
|
|
body->s = ent->s;
|
|
|
|
// clear EF_TALK, etc
|
|
if (body->s.eFlags & EF_HEADLESS)
|
|
body->s.eFlags = EF_DEAD | EF_HEADLESS;
|
|
else
|
|
body->s.eFlags = EF_DEAD;
|
|
|
|
body->s.powerups = 0; // clear powerups
|
|
body->s.loopSound = 0; // clear lava burning
|
|
body->s.number = body - g_entities;
|
|
body->timestamp = level.time;
|
|
body->physicsObject = qtrue;
|
|
body->physicsBounce = 0; // don't bounce
|
|
if (body->s.groundEntityNum == ENTITYNUM_NONE) {
|
|
body->s.pos.trType = TR_GRAVITY;
|
|
body->s.pos.trTime = level.time;
|
|
VectorCopy(ent->client->ps.velocity, body->s.pos.trDelta);
|
|
} else {
|
|
body->s.pos.trType = TR_STATIONARY;
|
|
}
|
|
body->s.event = 0;
|
|
|
|
// change the animation to the last-frame only, so the sequence
|
|
// doesn't repeat anew for the body
|
|
switch (body->s.legsAnim & ~ANIM_TOGGLEBIT) {
|
|
case BOTH_DEATH1:
|
|
case BOTH_DEAD1:
|
|
body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1;
|
|
break;
|
|
case BOTH_DEATH2:
|
|
case BOTH_DEAD2:
|
|
body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2;
|
|
break;
|
|
case BOTH_DEATH3:
|
|
case BOTH_DEAD3:
|
|
default:
|
|
body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3;
|
|
break;
|
|
}
|
|
|
|
body->r.svFlags = ent->r.svFlags;
|
|
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);
|
|
|
|
body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
|
|
body->r.contents = CONTENTS_CORPSE;
|
|
body->r.ownerNum = ent->s.number;
|
|
|
|
body->nextthink = level.time + 5000;
|
|
|
|
// NiceAss: Prevent sinkage of the body in TP
|
|
body->think = BodySink;
|
|
|
|
body->die = body_die;
|
|
|
|
// don't take more damage if already gibbed
|
|
if (ent->health <= GIB_HEALTH) {
|
|
body->takedamage = qfalse;
|
|
} else {
|
|
body->takedamage = qtrue;
|
|
}
|
|
|
|
VectorCopy(body->s.pos.trBase, body->r.currentOrigin);
|
|
trap_LinkEntity(body);
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
/*
|
|
==================
|
|
SetClientViewAngle
|
|
|
|
==================
|
|
*/
|
|
void SetClientViewAngle(gentity_t * ent, vec3_t angle)
|
|
{
|
|
int i;
|
|
|
|
// set the delta angle
|
|
for (i = 0; i < 3; i++) {
|
|
int cmdAngle;
|
|
|
|
cmdAngle = ANGLE2SHORT(angle[i]);
|
|
ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i];
|
|
}
|
|
VectorCopy(angle, ent->s.angles);
|
|
VectorCopy(ent->s.angles, ent->client->ps.viewangles);
|
|
}
|
|
|
|
/*
|
|
================
|
|
respawn
|
|
================
|
|
*/
|
|
void respawn(gentity_t * ent)
|
|
{
|
|
CopyToBodyQue(ent);
|
|
ClientSpawn(ent);
|
|
}
|
|
|
|
/*
|
|
================
|
|
TeamCount
|
|
|
|
Returns number of players on a team
|
|
================
|
|
*/
|
|
int TeamCount(int ignoreClientNum, int team)
|
|
{
|
|
int i;
|
|
int count = 0;
|
|
|
|
for (i = 0; i < level.maxclients; i++) {
|
|
if (i == ignoreClientNum) {
|
|
continue;
|
|
}
|
|
if (level.clients[i].pers.connected == CON_DISCONNECTED) {
|
|
continue;
|
|
}
|
|
if (level.clients[i].sess.sessionTeam == team) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
================
|
|
TeamLeader
|
|
|
|
Returns the client number of the team leader
|
|
================
|
|
*/
|
|
int TeamLeader(int team)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < level.maxclients; i++) {
|
|
if (level.clients[i].pers.connected == CON_DISCONNECTED) {
|
|
continue;
|
|
}
|
|
if (level.clients[i].sess.sessionTeam == team) {
|
|
if (level.clients[i].sess.teamLeader)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
PickTeam
|
|
|
|
================
|
|
*/
|
|
team_t PickTeam(int ignoreClientNum)
|
|
{
|
|
int counts[TEAM_NUM_TEAMS];
|
|
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
counts[TEAM_BLUE] = RQ3TeamCount(ignoreClientNum, TEAM_BLUE);
|
|
counts[TEAM_RED] = RQ3TeamCount(ignoreClientNum, TEAM_RED);
|
|
} else {
|
|
counts[TEAM_BLUE] = TeamCount(ignoreClientNum, TEAM_BLUE);
|
|
counts[TEAM_RED] = TeamCount(ignoreClientNum, TEAM_RED);
|
|
}
|
|
|
|
if (counts[TEAM_BLUE] > counts[TEAM_RED]) {
|
|
return TEAM_RED;
|
|
}
|
|
if (counts[TEAM_RED] > counts[TEAM_BLUE]) {
|
|
return TEAM_BLUE;
|
|
}
|
|
// equal team count, so join the team with the lowest score
|
|
if (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]) {
|
|
return TEAM_RED;
|
|
}
|
|
return TEAM_BLUE;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientCheckName
|
|
============
|
|
*/
|
|
static void ClientCleanName(const char *in, char *out, int outSize)
|
|
{
|
|
int outpos = 0, colorlessLen = 0, spaces = 0;
|
|
|
|
// discard leading spaces
|
|
for (; *in == ' '; in++);
|
|
for (; *in && outpos < outSize - 1; in++) {
|
|
out[outpos] = *in;
|
|
|
|
if (*in == ' ') {
|
|
// don't allow too many consecutive spaces
|
|
if (spaces > 2)
|
|
continue;
|
|
spaces++;
|
|
} else if (outpos > 0 && out[outpos - 1] == Q_COLOR_ESCAPE) {
|
|
if (Q_IsColorString(&out[outpos - 1])) {
|
|
colorlessLen--;
|
|
if (ColorIndex(*in) == 0) {
|
|
// Disallow color black in names to prevent players
|
|
// from getting advantage playing in front of black backgrounds
|
|
outpos--;
|
|
continue;
|
|
}
|
|
} else {
|
|
spaces = 0;
|
|
colorlessLen++;
|
|
}
|
|
} else {
|
|
spaces = 0;
|
|
colorlessLen++;
|
|
}
|
|
outpos++;
|
|
}
|
|
|
|
out[outpos] = '\0';
|
|
|
|
// don't allow empty names
|
|
if( *out == '\0' || colorlessLen == 0)
|
|
Q_strncpyz(out, RQ3_NONAMEPLAYER, outSize);
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientUserInfoChanged
|
|
|
|
Called from ClientConnect when the player first connects and
|
|
directly by the server system when the player updates a userinfo variable.
|
|
|
|
The game can override any of the settings and call trap_SetUserinfo
|
|
if desired.
|
|
============
|
|
*/
|
|
void ClientUserinfoChanged(int clientNum)
|
|
{
|
|
gentity_t *ent;
|
|
gclient_t *client;
|
|
int teamTask, teamLeader, team, health, gender;
|
|
char *s, model[MAX_QPATH], headModel[MAX_QPATH], oldname[MAX_STRING_CHARS];
|
|
char c1[MAX_INFO_STRING], c2[MAX_INFO_STRING], redTeam[MAX_INFO_STRING];
|
|
char blueTeam[MAX_INFO_STRING], userinfo[MAX_INFO_STRING];
|
|
char *skin2, model2[MAX_STRING_CHARS];
|
|
|
|
ent = g_entities + clientNum;
|
|
client = ent->client;
|
|
|
|
trap_GetUserinfo(clientNum, userinfo, sizeof(userinfo));
|
|
|
|
// check for malformed or illegal info strings
|
|
if (!Info_Validate(userinfo)) {
|
|
strcpy(userinfo, "\\name\\badinfo");
|
|
// don't keep those clients and userinfo
|
|
trap_DropClient(clientNum, "Invalid userinfo");
|
|
}
|
|
// check for local client
|
|
s = Info_ValueForKey(userinfo, "ip");
|
|
if (!strcmp(s, "localhost")) {
|
|
client->pers.localClient = qtrue;
|
|
}
|
|
// check the item prediction
|
|
s = Info_ValueForKey(userinfo, "cg_predictItems");
|
|
if (!atoi(s)) {
|
|
client->pers.predictItemPickup = qfalse;
|
|
} else {
|
|
client->pers.predictItemPickup = qtrue;
|
|
}
|
|
|
|
// JBravo: unlagged
|
|
s = Info_ValueForKey(userinfo, "cg_delag");
|
|
if (!atoi(s)) {
|
|
client->pers.delag = 0;
|
|
} else {
|
|
client->pers.delag = atoi(s);
|
|
}
|
|
|
|
// set name
|
|
Q_strncpyz(oldname, client->pers.netname, sizeof(oldname));
|
|
s = Info_ValueForKey(userinfo, "name");
|
|
ClientCleanName(s, client->pers.netname, sizeof(client->pers.netname));
|
|
|
|
if (client->sess.sessionTeam == TEAM_SPECTATOR) {
|
|
if (client->sess.spectatorState == SPECTATOR_SCOREBOARD) {
|
|
Q_strncpyz(client->pers.netname, "scoreboard", sizeof(client->pers.netname));
|
|
}
|
|
}
|
|
|
|
if (client->pers.connected == CON_CONNECTED) {
|
|
if (strcmp(oldname, client->pers.netname)) {
|
|
trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname,
|
|
client->pers.netname));
|
|
}
|
|
}
|
|
// set max health
|
|
health = atoi(Info_ValueForKey(userinfo, "handicap"));
|
|
client->pers.maxHealth = health;
|
|
if (client->pers.maxHealth < 1 || client->pers.maxHealth > 100) {
|
|
client->pers.maxHealth = 100;
|
|
}
|
|
// set model
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
Q_strncpyz(model, Info_ValueForKey(userinfo, "team_model"), sizeof(model));
|
|
Q_strncpyz(headModel, Info_ValueForKey(userinfo, "team_headmodel"), sizeof(headModel));
|
|
} else {
|
|
Q_strncpyz(model, Info_ValueForKey(userinfo, "model"), sizeof(model));
|
|
Q_strncpyz(headModel, Info_ValueForKey(userinfo, "headmodel"), sizeof(headModel));
|
|
}
|
|
// JBravo: set the radiosoundset
|
|
s = Info_ValueForKey(userinfo, "cg_RQ3_radiovoice_male");
|
|
if (!atoi(s)) {
|
|
client->radioSetMale = 0;
|
|
} else {
|
|
client->radioSetMale = atoi(s);
|
|
}
|
|
s = Info_ValueForKey(userinfo, "cg_RQ3_radiovoice_female");
|
|
if (!atoi(s)) {
|
|
client->radioSetFemale = 0;
|
|
} else {
|
|
client->radioSetFemale = atoi(s);
|
|
}
|
|
// JBravo: Does the client get a frag reduced on suicides or not.
|
|
s = Info_ValueForKey(userinfo, "cg_RQ3_SuicideLikeARealMan");
|
|
if (!atoi(s)) {
|
|
client->SuicideLikeARealMan = 0;
|
|
} else {
|
|
client->SuicideLikeARealMan = atoi(s);
|
|
}
|
|
// JBravo: to silence zcam messages
|
|
s = Info_ValueForKey(userinfo, "cg_RQ3_zcam_stfu");
|
|
if (!atoi(s)) {
|
|
client->zcam_stfu = 0;
|
|
} else {
|
|
client->zcam_stfu = atoi(s);
|
|
}
|
|
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
if (client->sess.savedTeam == TEAM_RED) {
|
|
Q_strncpyz(model2, g_RQ3_team1model.string, sizeof(model));
|
|
skin2 = strrchr(model2, '/');
|
|
if (skin2) {
|
|
*skin2++ = '\0';
|
|
} else {
|
|
skin2 = "robbers";
|
|
}
|
|
if (RQ3_Validatemodel(model2) != -1) {
|
|
Com_sprintf(model, sizeof(model), "%s/%s", model2, skin2);
|
|
Com_sprintf(headModel, sizeof(headModel), "%s/%s", model2, skin2);
|
|
} else {
|
|
Com_sprintf(model, sizeof(model), "reactionmale/robbers");
|
|
Com_sprintf(headModel, sizeof(headModel), "reactionmale/robbers");
|
|
}
|
|
} else {
|
|
Q_strncpyz(model2, g_RQ3_team2model.string, sizeof(model));
|
|
skin2 = strrchr(model2, '/');
|
|
if (skin2) {
|
|
*skin2++ = '\0';
|
|
} else {
|
|
skin2 = "cops";
|
|
}
|
|
if (RQ3_Validatemodel(model2) != -1) {
|
|
Com_sprintf(model, sizeof(model), "%s/%s", model2, skin2);
|
|
Com_sprintf(headModel, sizeof(headModel), "%s/%s", model2, skin2);
|
|
} else {
|
|
Com_sprintf(model, sizeof(model), "reactionmale/cops");
|
|
Com_sprintf(headModel, sizeof(headModel), "reactionmale/cops");
|
|
}
|
|
}
|
|
} else {
|
|
Q_strncpyz(model2, model, sizeof(model));
|
|
skin2 = strrchr(model2, '/');
|
|
if (skin2) {
|
|
*skin2++ = '\0';
|
|
} else {
|
|
skin2 = "default";
|
|
}
|
|
|
|
// JBravo: Validating the model
|
|
gender = RQ3_Validatemodel(model2);
|
|
if (gender == -1) {
|
|
trap_SendServerCommand(ent - g_entities,
|
|
va("print \"^1Illegal player model (%s). Forcing change on server.\n\"",
|
|
model2));
|
|
Q_strncpyz(model, "reactionmale/default", sizeof("reactionmale/default"));
|
|
Q_strncpyz(headModel, "reactionmale/default", sizeof("reactionmale/default"));
|
|
client->radioGender = 0; // Male
|
|
} else if (gender != GENDER_NEUTER)
|
|
client->radioGender = gender;
|
|
}
|
|
// bots set their team a few frames later
|
|
if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) {
|
|
s = Info_ValueForKey(userinfo, "team");
|
|
if (!Q_stricmp(s, "red") || !Q_stricmp(s, "r") || !Q_stricmp(s, "1")) {
|
|
team = TEAM_RED;
|
|
} else if (!Q_stricmp(s, "blue") || !Q_stricmp(s, "b") || !Q_stricmp(s, "2")) {
|
|
team = TEAM_BLUE;
|
|
} else {
|
|
// pick the team with the least number of players
|
|
team = PickTeam(clientNum);
|
|
}
|
|
} else {
|
|
team = client->sess.sessionTeam;
|
|
}
|
|
|
|
// teamInfo
|
|
s = Info_ValueForKey(userinfo, "teamoverlay");
|
|
if (!*s || atoi(s) != 0) {
|
|
client->pers.teamInfo = qtrue;
|
|
} else {
|
|
client->pers.teamInfo = qfalse;
|
|
}
|
|
|
|
// team task (0 = none, 1 = offence, 2 = defence)
|
|
teamTask = atoi(Info_ValueForKey(userinfo, "teamtask"));
|
|
// team Leader (1 = leader, 0 is normal player)
|
|
teamLeader = client->sess.teamLeader;
|
|
|
|
// colors
|
|
strcpy(c1, Info_ValueForKey(userinfo, "color1"));
|
|
strcpy(c2, Info_ValueForKey(userinfo, "color2"));
|
|
|
|
strcpy(redTeam, Info_ValueForKey(userinfo, "g_redteam"));
|
|
strcpy(blueTeam, Info_ValueForKey(userinfo, "g_blueteam"));
|
|
|
|
// send over a subset of the userinfo keys so other clients can
|
|
// print scoreboards, display models, and play custom sounds
|
|
if (ent->r.svFlags & SVF_BOT) {
|
|
//Makro - adding teamplay weapon/item info for bots
|
|
s = va
|
|
("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d\\tpw\\%s\\tpi\\%s",
|
|
client->pers.netname, team, model, headModel, c1, c2, client->pers.maxHealth, client->sess.wins,
|
|
client->sess.losses,
|
|
//Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader );
|
|
Info_ValueForKey(userinfo, "skill"), teamTask, teamLeader, Info_ValueForKey(userinfo, "tpw"),
|
|
Info_ValueForKey(userinfo, "tpi"));
|
|
} else {
|
|
s = va
|
|
("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d",
|
|
client->pers.netname, client->sess.savedTeam, model, headModel, redTeam, blueTeam, c1, c2,
|
|
client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader);
|
|
}
|
|
|
|
trap_SetConfigstring(CS_PLAYERS + clientNum, s);
|
|
|
|
// this is not the userinfo, more like the configstring actually
|
|
// JBravo: ugly in the logs. Enable to debug if necessary
|
|
// G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s );
|
|
}
|
|
|
|
/*
|
|
===========
|
|
G_SendCheatVars(int)
|
|
|
|
Send which cvars are cheats, and the acceptable values
|
|
===========
|
|
*/
|
|
int G_SendCheatVars(int clientNum)
|
|
{
|
|
char *text_p;
|
|
int len;
|
|
int i;
|
|
char *token;
|
|
char text[20000];
|
|
fileHandle_t f;
|
|
|
|
char cheatVar[40], cl_cheatvar[128];
|
|
float lowval, highval;
|
|
|
|
//NiceAss: Added so /devmap will not have the client check cvars. Lie to the server that it loaded fine =)
|
|
if (g_cheats.integer)
|
|
return qtrue;
|
|
|
|
// load the file
|
|
len = trap_FS_FOpenFile(g_RQ3_cvarfile.string, &f, FS_READ);
|
|
if (len <= 0) {
|
|
return qfalse;
|
|
}
|
|
if (len >= sizeof(text) - 1) {
|
|
G_Printf("File %s too long\n", g_RQ3_cvarfile.string);
|
|
return qfalse;
|
|
}
|
|
|
|
trap_FS_Read(text, len, f);
|
|
text[len] = 0;
|
|
trap_FS_FCloseFile(f);
|
|
|
|
// parse the text
|
|
text_p = text;
|
|
|
|
for (i = 0; i < 30; i++) {
|
|
token = COM_Parse(&text_p);
|
|
if (!token)
|
|
break;
|
|
if (strlen(token) >= 40) {
|
|
Com_Printf("Cheatvar %s is too long\n", token);
|
|
return qfalse;
|
|
}
|
|
Q_strncpyz(cheatVar, token, sizeof(cheatVar));
|
|
// Fix from hal9000. Was NULL in that strcmp
|
|
if (!strcmp(token, ""))
|
|
return qtrue;
|
|
token = COM_Parse(&text_p);
|
|
if (!token)
|
|
break;
|
|
lowval = atof(token);
|
|
token = COM_Parse(&text_p);
|
|
if (!token)
|
|
break;
|
|
highval = atof(token);
|
|
|
|
Com_sprintf(cl_cheatvar, sizeof(cl_cheatvar), "addCheatVar %s %f %f\n", cheatVar, lowval, highval);
|
|
//Com_Printf("%s", cl_cheatvar);
|
|
trap_SendServerCommand(clientNum, va("%s", cl_cheatvar));
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientConnect
|
|
|
|
Called when a player begins connecting to the server.
|
|
Called again for every map change or tournement restart.
|
|
|
|
The session information will be valid after exit.
|
|
|
|
Return NULL if the client should be allowed, otherwise return
|
|
a string with the reason for denial.
|
|
|
|
Otherwise, the client will be sent the current gamestate
|
|
and will eventually get to ClientBegin.
|
|
|
|
firstTime will be qtrue the very first time a client connects
|
|
to the server machine, but qfalse on map changes and tournement
|
|
restarts.
|
|
============
|
|
*/
|
|
char *ClientConnect(int clientNum, qboolean firstTime, qboolean isBot)
|
|
{
|
|
char *value;
|
|
gclient_t *client;
|
|
char userinfo[MAX_INFO_STRING], ipaddr[64];
|
|
gentity_t *ent;
|
|
|
|
ent = &g_entities[clientNum];
|
|
trap_GetUserinfo(clientNum, userinfo, sizeof(userinfo));
|
|
|
|
// IP filtering
|
|
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500
|
|
// recommanding PB based IP / GUID banning, the builtin system is pretty limited
|
|
// check to see if they are on the banned IP list
|
|
value = Info_ValueForKey(userinfo, "ip");
|
|
strncpy(ipaddr, value, sizeof(ipaddr));
|
|
if (G_FilterPacket(value)) {
|
|
return "You are banned from this server..";
|
|
}
|
|
// we don't check password for bots and local client
|
|
// NOTE: local client <-> "ip" "localhost"
|
|
// this means this client is not running in our current process
|
|
if ( !isBot && (strcmp(value, "localhost") != 0)) {
|
|
// check for a password
|
|
value = Info_ValueForKey(userinfo, "password");
|
|
if (g_password.string[0] && Q_stricmp(g_password.string, "none") &&
|
|
strcmp(g_password.string, value) != 0) {
|
|
return "Invalid password";
|
|
}
|
|
}
|
|
// if a player reconnects quickly after a disconnect, the client disconnect may never be called, thus flag can get lost in the ether
|
|
if (ent->inuse) {
|
|
G_LogPrintf("Forcing disconnect on active client: %i\n", clientNum);
|
|
// so lets just fix up anything that should happen on a disconnect
|
|
ClientDisconnect(clientNum);
|
|
}
|
|
// they can connect
|
|
ent->client = level.clients + clientNum;
|
|
client = ent->client;
|
|
memset(client, 0, sizeof(*client));
|
|
|
|
client->pers.connected = CON_CONNECTING;
|
|
// JBravo: Antistick
|
|
client->ps.stats[STAT_RQ3] &= ~RQ3_PLAYERSOLID;
|
|
// JBravo: Clear zcam flag for cgame
|
|
client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM;
|
|
|
|
// read or initialize the session data
|
|
if (firstTime || level.newSession) {
|
|
G_InitSessionData(client, userinfo);
|
|
}
|
|
G_ReadSessionData(client);
|
|
|
|
if (isBot) {
|
|
ent->r.svFlags |= SVF_BOT;
|
|
ent->inuse = qtrue;
|
|
if (!G_BotConnect(clientNum, !firstTime)) {
|
|
return "BotConnectfailed";
|
|
}
|
|
}
|
|
// slicer : make sessionTeam = to savedTeam for scoreboard on cgame
|
|
// JBravo: only for teambased games. Could break DM
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
client->sess.sessionTeam = client->sess.savedTeam;
|
|
ClientUserinfoChanged(clientNum);
|
|
} else
|
|
ClientUserinfoChanged(clientNum);
|
|
|
|
// don't do the "xxx connected" messages if they were caried over from previous level
|
|
if (firstTime) {
|
|
trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname));
|
|
G_LogPrintf("%s@%s connected (clientNum %i)\n", client->pers.netname, ipaddr, clientNum);
|
|
}
|
|
|
|
if (g_gametype.integer >= GT_TEAM && g_gametype.integer != GT_TEAMPLAY && g_gametype.integer != GT_CTF &&
|
|
client->sess.sessionTeam != TEAM_SPECTATOR) {
|
|
BroadcastTeamChange(client, -1);
|
|
}
|
|
// count current clients and rank for scoreboard
|
|
CalculateRanks();
|
|
|
|
// JBravo: clients in TP begin as spectators
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
client->sess.sessionTeam = TEAM_SPECTATOR;
|
|
client->ps.persistant[PERS_TEAM] = TEAM_SPECTATOR;
|
|
client->sess.spectatorState = SPECTATOR_FREE;
|
|
client->specMode = SPECTATOR_FREE;
|
|
client->ps.pm_flags &= ~PMF_FOLLOW;
|
|
client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM;
|
|
camera_begin(ent);
|
|
client->camera->mode = CAMERA_MODE_SWING;
|
|
} else {
|
|
client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM;
|
|
camera_begin(ent);
|
|
client->camera->mode = CAMERA_MODE_SWING;
|
|
}
|
|
// JBravo: moved from ClientBegin
|
|
client->pers.enterTime = level.time;
|
|
// JBravo: ditto
|
|
if (g_gametype.integer == GT_FFA) {
|
|
client->sess.sessionTeam = TEAM_FREE;
|
|
client->sess.savedTeam = TEAM_FREE;
|
|
client->ps.persistant[PERS_TEAM] = TEAM_FREE;
|
|
client->sess.spectatorState = SPECTATOR_NOT;
|
|
client->specMode = SPECTATOR_NOT;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientBegin
|
|
|
|
called when a client has finished connecting, and is ready
|
|
to be placed into the level. This will happen every level load,
|
|
and on transition between teams, but doesn't happen on respawns
|
|
============
|
|
*/
|
|
void ClientBegin(int clientNum)
|
|
{
|
|
gentity_t *ent;
|
|
gclient_t *client;
|
|
int flags, savedPing = 0, i;
|
|
int savedPers[MAX_PERSISTANT];
|
|
char str[256];
|
|
|
|
ent = g_entities + clientNum;
|
|
client = level.clients + clientNum;
|
|
|
|
if (ent->r.linked) {
|
|
trap_UnlinkEntity(ent);
|
|
}
|
|
|
|
G_InitGentity(ent);
|
|
ent->touch = 0;
|
|
ent->pain = 0;
|
|
ent->client = client;
|
|
|
|
//Slicer: Saving persistant and ping
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
savedPing = client->ps.ping;
|
|
for (i = 0; i < MAX_PERSISTANT; i++)
|
|
savedPers[i] = client->ps.persistant[i];
|
|
}
|
|
|
|
client->pers.connected = CON_CONNECTED;
|
|
client->pers.teamState.state = TEAM_BEGIN;
|
|
|
|
// save eflags around this, because changing teams will
|
|
// cause this to happen with a valid entity, and we
|
|
// want to make sure the teleport bit is set right
|
|
// so the viewpoint doesn't interpolate through the
|
|
// world to the new position
|
|
flags = client->ps.eFlags;
|
|
memset(&client->ps, 0, sizeof(client->ps));
|
|
client->ps.eFlags = flags;
|
|
|
|
//Slicer: Repost score and ping
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
client->ps.ping = savedPing;
|
|
for (i = 0; i < MAX_PERSISTANT; i++)
|
|
client->ps.persistant[i] = savedPers[i];
|
|
}
|
|
// locate ent at a spawn point
|
|
ClientSpawn(ent);
|
|
|
|
// JBravo: if teamplay and the client has not been on teams, make them a spectator.
|
|
if ((g_gametype.integer >= GT_TEAM) &&
|
|
client->sess.sessionTeam != TEAM_RED && client->sess.sessionTeam != TEAM_BLUE) {
|
|
client->sess.sessionTeam = TEAM_SPECTATOR;
|
|
client->ps.persistant[PERS_SAVEDTEAM] = TEAM_SPECTATOR;
|
|
client->ps.persistant[PERS_TEAM] = TEAM_SPECTATOR;
|
|
client->sess.spectatorState = SPECTATOR_FREE;
|
|
client->specMode = SPECTATOR_FREE;
|
|
client->ps.pm_flags &= ~PMF_FOLLOW;
|
|
client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM;
|
|
RQ3_SpectatorMode(ent);
|
|
// JBravo: Set grenades to short, and the other weapons to full automatic on connect.
|
|
client->ps.persistant[PERS_WEAPONMODES] |= RQ3_GRENSHORT;
|
|
client->ps.persistant[PERS_WEAPONMODES] &= ~RQ3_GRENMED;
|
|
client->ps.persistant[PERS_WEAPONMODES] |= RQ3_KNIFEMODE;
|
|
client->ps.persistant[PERS_WEAPONMODES] &= ~RQ3_MP5MODE;
|
|
client->ps.persistant[PERS_WEAPONMODES] &= ~RQ3_M4MODE;
|
|
client->ps.persistant[PERS_WEAPONMODES] &= ~RQ3_MK23MODE;
|
|
client->camera->mode = CAMERA_MODE_SWING;
|
|
}
|
|
|
|
// JBravo: moved this up to clientconnect so DM players can join the spectator team.
|
|
/* if (g_gametype.integer == GT_FFA) {
|
|
client->sess.sessionTeam = TEAM_FREE;
|
|
client->ps.persistant[PERS_TEAM] = TEAM_FREE;
|
|
client->sess.spectatorState = SPECTATOR_NOT;
|
|
client->specMode = SPECTATOR_NOT;
|
|
} */
|
|
|
|
if (client->sess.sessionTeam != TEAM_SPECTATOR && g_gametype.integer != GT_TEAMPLAY) {
|
|
client->ps.persistant[PERS_WEAPONMODES] |= RQ3_GRENSHORT; //set to short range
|
|
client->ps.persistant[PERS_WEAPONMODES] |= RQ3_KNIFEMODE; //set to slash attack
|
|
|
|
if (g_gametype.integer != GT_TOURNAMENT) {
|
|
trap_SendServerCommand(-1,
|
|
va("print \"%s" S_COLOR_WHITE " entered the game\n\"",
|
|
client->pers.netname));
|
|
}
|
|
}
|
|
G_LogPrintf("ClientBegin: %i\n", clientNum);
|
|
|
|
// JBravo: synching the cvars over to clients for the MM ingame menu.
|
|
if ((g_gametype.integer >= GT_TEAM) && g_RQ3_matchmode.integer) {
|
|
for (i = 0; i < 9; ++i) {
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i %s %i", CVARSET, settings2[i],
|
|
trap_Cvar_VariableIntegerValue(settings[i])));
|
|
}
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i cg_RQ3_capturelimit %i", CVARSET,
|
|
trap_Cvar_VariableIntegerValue("capturelimit")));
|
|
}
|
|
// JBravo: setting cvars for the about menu
|
|
trap_Cvar_VariableStringBuffer("sv_hostname", str, sizeof(str));
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i cg_RQ3_hostname %s", CVARSET, str));
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i cg_RQ3_needpass %i", CVARSET, g_needpass.integer));
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i cg_RQ3_dmflags %i", CVARSET, g_dmflags.integer));
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i cg_RQ3_showOwnKills %i", CVARSET, g_RQ3_showOwnKills.integer));
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i cg_RQ3_timelimit %i", CVARSET, g_timelimit.integer));
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i cg_RQ3_fraglimit %i", CVARSET, g_fraglimit.integer));
|
|
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i cg_RQ3_bot_minplayers %i", CVARSET,
|
|
trap_Cvar_VariableIntegerValue("bot_minplayers")));
|
|
|
|
// count current clients and rank for scoreboard
|
|
CalculateRanks();
|
|
|
|
// JBravo: default weapons
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
// NiceAss: Only set it if there is no value. Fix for going into spectator resetting values.
|
|
if (ent->r.svFlags & SVF_BOT) {
|
|
//Makro - changed to m4/laser from pistol/kevlar
|
|
if (!client->teamplayWeapon) {
|
|
if ((int) g_RQ3_weaponban.integer & WPF_M4) {
|
|
client->teamplayWeapon = WP_M4;
|
|
} else if ((int) g_RQ3_weaponban.integer & WPF_MK23) {
|
|
client->teamplayWeapon = WP_PISTOL;
|
|
} else if ((int) g_RQ3_weaponban.integer & WPF_KNIFE) {
|
|
client->teamplayWeapon = WP_KNIFE;
|
|
} else {
|
|
client->teamplayWeapon = WP_PISTOL;
|
|
}
|
|
}
|
|
if (!client->teamplayItem && ((int) g_RQ3_weaponban.integer & ITF_LASER))
|
|
client->teamplayItem = HI_LASER;
|
|
} else {
|
|
if (!client->teamplayWeapon) {
|
|
if ((int) g_RQ3_weaponban.integer & WPF_MP5) {
|
|
client->teamplayWeapon = WP_MP5;
|
|
} else if ((int) g_RQ3_weaponban.integer & WPF_MK23) {
|
|
client->teamplayWeapon = WP_PISTOL;
|
|
} else if ((int) g_RQ3_weaponban.integer & WPF_KNIFE) {
|
|
client->teamplayWeapon = WP_KNIFE;
|
|
} else {
|
|
client->teamplayWeapon = WP_PISTOL;
|
|
}
|
|
}
|
|
if (!client->teamplayItem && ((int) g_RQ3_weaponban.integer & ITF_KEVLAR))
|
|
client->teamplayItem = HI_KEVLAR;
|
|
}
|
|
i = RQ3TeamCount(-1, client->sess.sessionTeam);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientSpawn
|
|
|
|
Called every time a client is placed fresh in the world:
|
|
after the first ClientBegin, and after each respawn
|
|
Initializes all non-persistant parts of playerState
|
|
============
|
|
*/
|
|
void ClientSpawn(gentity_t * ent)
|
|
{
|
|
int index, i, flags, savedPing;
|
|
int persistant[MAX_PERSISTANT];
|
|
vec3_t spawn_origin, spawn_angles;
|
|
gclient_t *client;
|
|
clientPersistant_t saved;
|
|
clientSession_t savedSess;
|
|
gentity_t *spawnPoint;
|
|
int accuracy_hits, accuracy_shots, eventSequence;
|
|
int savedWeapon, savedItem, savedSpec; // JBravo: to save weapon/item info
|
|
int savedRadiopower, savedRadiogender; // JBravo: for radio.
|
|
int savedMaleSet, savedFemaleSet; // JBravo: for soundset saves.
|
|
camera_t savedCamera; // JBravo: to save camera stuff
|
|
char userinfo[MAX_INFO_STRING];
|
|
char *classname;
|
|
|
|
//Slicer : Laser FIX
|
|
|
|
//Try to turn the laser off if it's on
|
|
if (ent->client->lasersight)
|
|
Laser_Gen(ent, qfalse);
|
|
|
|
index = ent - g_entities;
|
|
client = ent->client;
|
|
spawnPoint = NULL;
|
|
|
|
// find a spawn point
|
|
// do it before setting health back up, so farthest
|
|
// ranging doesn't count this client
|
|
|
|
if (client->sess.sessionTeam == TEAM_SPECTATOR) {
|
|
if (g_gametype.integer == GT_CTF &&
|
|
(client->sess.savedTeam == TEAM_RED || client->sess.savedTeam == TEAM_BLUE)) {
|
|
if (client->sess.savedTeam == TEAM_RED)
|
|
classname = "team_CTF_redflag";
|
|
else if (client->sess.savedTeam == TEAM_BLUE)
|
|
classname = "team_CTF_blueflag";
|
|
|
|
while ((spawnPoint = G_Find(spawnPoint, FOFS(classname), classname)) != NULL) {
|
|
if (!(spawnPoint->flags & FL_DROPPED_ITEM))
|
|
break;
|
|
}
|
|
if (spawnPoint) {
|
|
VectorCopy(spawnPoint->r.currentOrigin, spawn_origin);
|
|
VectorCopy(spawnPoint->s.angles, spawn_angles);
|
|
spawn_origin[2] += 30;
|
|
}
|
|
} else if (VectorLength(ent->client->ps.origin) == 0.0f) {
|
|
// Origin is not set yet? Use a spawn point
|
|
spawnPoint = SelectSpectatorSpawnPoint(spawn_origin, spawn_angles);
|
|
// Have a set origin already? Use it. (where you died or changed to spectator)
|
|
} else {
|
|
spawnPoint = NULL;
|
|
VectorCopy(ent->client->ps.origin, spawn_origin);
|
|
VectorCopy(ent->client->ps.viewangles, spawn_angles);
|
|
}
|
|
} else if (g_gametype.integer >= GT_CTF) {
|
|
// all base oriented team games use the CTF spawn points
|
|
spawnPoint = SelectCTFSpawnPoint(client->sess.sessionTeam,
|
|
client->pers.teamState.state, spawn_origin, spawn_angles);
|
|
// JBravo: If we are in Teamplay mode, use the teamspawnpoints.
|
|
} else if (g_gametype.integer == GT_TEAMPLAY || (g_gametype.integer == GT_TEAM && client->sess.teamSpawn)) {
|
|
// Freud: Assign the spawns from the spawning system (g_teamplay.c)
|
|
level.team1spawnpoint = level.teamplay_spawns[0];
|
|
level.team2spawnpoint = level.teamplay_spawns[1];
|
|
|
|
if (client->sess.sessionTeam == TEAM_RED) {
|
|
client->sess.spawnPoint = level.team1spawnpoint;
|
|
spawnPoint = level.team1spawnpoint;
|
|
VectorCopy(level.team1spawnpoint->s.angles, spawn_angles);
|
|
VectorCopy(level.team1spawnpoint->s.origin, spawn_origin);
|
|
} else {
|
|
client->sess.spawnPoint = level.team2spawnpoint;
|
|
spawnPoint = level.team2spawnpoint;
|
|
VectorCopy(level.team2spawnpoint->s.angles, spawn_angles);
|
|
VectorCopy(level.team2spawnpoint->s.origin, spawn_origin);
|
|
}
|
|
// Freud: Quake3 moves spawns up 9 pixs?
|
|
spawn_origin[2] += 9;
|
|
// End JBravo.
|
|
} else {
|
|
do {
|
|
// the first spawn should be at a good looking spot
|
|
if (!client->pers.initialSpawn && client->pers.localClient) {
|
|
client->pers.initialSpawn = qtrue;
|
|
spawnPoint = SelectInitialSpawnPoint(spawn_origin, spawn_angles);
|
|
} else {
|
|
// don't spawn near existing origin if possible
|
|
spawnPoint = SelectSpawnPoint(client->ps.origin, spawn_origin, spawn_angles);
|
|
}
|
|
|
|
// Tim needs to prevent bots from spawning at the initial point
|
|
// on q3dm0...
|
|
if ((spawnPoint->flags & FL_NO_BOTS) && (ent->r.svFlags & SVF_BOT)) {
|
|
continue; // try again
|
|
}
|
|
// just to be symetric, we have a nohumans option...
|
|
if ((spawnPoint->flags & FL_NO_HUMANS) && !(ent->r.svFlags & SVF_BOT)) {
|
|
continue; // try again
|
|
}
|
|
|
|
break;
|
|
|
|
} while (1);
|
|
}
|
|
client->pers.teamState.state = TEAM_ACTIVE;
|
|
|
|
// toggle the teleport bit so the client knows to not lerp
|
|
// and never clear the voted flag
|
|
flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED);
|
|
flags ^= EF_TELEPORT_BIT;
|
|
|
|
// JBravo: unlagged
|
|
//ent->client->saved.leveltime = 0;
|
|
|
|
// clear everything but the persistant data
|
|
|
|
saved = client->pers;
|
|
savedSess = client->sess;
|
|
savedPing = client->ps.ping;
|
|
accuracy_hits = client->accuracy_hits;
|
|
accuracy_shots = client->accuracy_shots;
|
|
|
|
for (i = 0; i < MAX_PERSISTANT; i++) {
|
|
persistant[i] = client->ps.persistant[i];
|
|
}
|
|
eventSequence = client->ps.eventSequence;
|
|
|
|
// JBravo: save weapon/item info
|
|
savedWeapon = client->teamplayWeapon;
|
|
savedItem = client->teamplayItem;
|
|
savedSpec = client->specMode;
|
|
// JBravo: save radiosettings
|
|
savedRadiopower = client->radioOff;
|
|
savedRadiogender = client->radioGender;
|
|
savedMaleSet = client->radioSetMale;
|
|
savedFemaleSet = client->radioSetFemale;
|
|
memcpy(&savedCamera, &client->camera, sizeof(camera_t));
|
|
|
|
Com_Memset (client, 0, sizeof(*client));
|
|
|
|
// JBravo: restore weapon/item info
|
|
client->teamplayWeapon = savedWeapon;
|
|
client->teamplayItem = savedItem;
|
|
client->specMode = savedSpec;
|
|
// JBravo: restore radiosettings
|
|
client->radioOff = savedRadiopower;
|
|
client->radioGender = savedRadiogender;
|
|
client->radioSetMale = savedMaleSet;
|
|
client->radioSetFemale = savedFemaleSet;
|
|
memcpy(&client->camera, &savedCamera, sizeof(camera_t));
|
|
|
|
client->pers = saved;
|
|
client->sess = savedSess;
|
|
client->ps.ping = savedPing;
|
|
client->accuracy_hits = accuracy_hits;
|
|
client->accuracy_shots = accuracy_shots;
|
|
client->lastkilled_client[0] = NULL;
|
|
//Slicer
|
|
client->weapon_attempts = 0;
|
|
client->weapon_after_bandage_warned = qfalse;
|
|
client->gibbed = qfalse;
|
|
|
|
for (i = 0; i < MAX_PERSISTANT; i++) {
|
|
client->ps.persistant[i] = persistant[i];
|
|
}
|
|
client->ps.eventSequence = eventSequence;
|
|
// increment the spawncount so the client will detect the respawn
|
|
client->ps.persistant[PERS_SPAWN_COUNT]++;
|
|
client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
|
|
|
|
client->airOutTime = level.time + 12000;
|
|
|
|
trap_GetUserinfo(index, userinfo, sizeof(userinfo));
|
|
// set max health
|
|
client->pers.maxHealth = atoi(Info_ValueForKey(userinfo, "handicap"));
|
|
if (client->pers.maxHealth < 1 || client->pers.maxHealth > 100) {
|
|
client->pers.maxHealth = 100;
|
|
}
|
|
// clear entity values
|
|
client->ps.eFlags = flags;
|
|
|
|
ent->s.groundEntityNum = ENTITYNUM_NONE;
|
|
ent->client = &level.clients[index];
|
|
ent->takedamage = qtrue;
|
|
ent->inuse = qtrue;
|
|
ent->classname = "player";
|
|
ent->r.contents = CONTENTS_BODY;
|
|
ent->clipmask = MASK_PLAYERSOLID;
|
|
ent->die = player_die;
|
|
ent->waterlevel = 0;
|
|
ent->watertype = 0;
|
|
ent->flags = 0;
|
|
|
|
VectorCopy(playerMins, ent->r.mins);
|
|
VectorCopy(playerMaxs, ent->r.maxs);
|
|
|
|
client->ps.clientNum = index;
|
|
|
|
camera_begin(ent);
|
|
|
|
//Blaze: changed WP_MACHINEGUN to WP_PISTOL, makes the base weapon you start with the pistol
|
|
// JBravo: Not in TP
|
|
if (g_gametype.integer == GT_FFA || (g_gametype.integer == GT_TEAM && g_RQ3_tdmMode.integer)) {
|
|
if ((int) g_RQ3_weaponban.integer & WPF_MK23) {
|
|
client->ps.stats[STAT_WEAPONS] = (1 << WP_PISTOL);
|
|
client->numClips[WP_PISTOL] = 0;
|
|
client->ps.ammo[WP_PISTOL] = ClipAmountForAmmo(WP_PISTOL);
|
|
}
|
|
if ((int) g_RQ3_weaponban.integer & WPF_KNIFE) {
|
|
client->ps.stats[STAT_WEAPONS] |= (1 << WP_KNIFE);
|
|
client->ps.ammo[WP_KNIFE] = 1;
|
|
}
|
|
}
|
|
//Blaze: Set the bandage variable to 0
|
|
client->bleedtick = 0;
|
|
// health will count down towards max_health
|
|
ent->health = client->ps.stats[STAT_HEALTH] = 100; //Blaze: removed * 1.25 becase we wanna start at 100 health
|
|
// reset streak count
|
|
client->killStreak = 0;
|
|
|
|
G_SetOrigin(ent, spawn_origin);
|
|
VectorCopy(spawn_origin, client->ps.origin);
|
|
|
|
// the respawned flag will be cleared after the attack and jump keys come up
|
|
client->ps.pm_flags |= PMF_RESPAWNED;
|
|
|
|
trap_GetUsercmd(client - level.clients, &ent->client->pers.cmd);
|
|
SetClientViewAngle(ent, spawn_angles);
|
|
|
|
if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) {
|
|
|
|
} else {
|
|
G_KillBox(ent);
|
|
trap_LinkEntity(ent);
|
|
|
|
// force the base weapon up
|
|
//Blaze: Changed WP_MACHINEGUN to WP_PISTOL
|
|
// JBravo: we dont want the endless pistol in TP
|
|
if (g_gametype.integer < GT_TEAM || (g_gametype.integer == GT_TEAM && g_RQ3_tdmMode.integer)) {
|
|
client->ps.weapon = WP_PISTOL;
|
|
client->ps.weaponstate = WEAPON_READY;
|
|
}
|
|
}
|
|
|
|
//Blaze: Set the opendoor flag to 0
|
|
client->openDoor = qfalse;
|
|
client->openDoorTime = 0;
|
|
|
|
// don't allow full run speed for a bit
|
|
client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
|
|
client->ps.pm_time = 100;
|
|
|
|
client->respawnTime = level.time;
|
|
client->inactivityTime = level.time + g_inactivity.integer * 1000;
|
|
client->latched_buttons = 0;
|
|
|
|
//Elder: reset all RQ3 non-persistent stats
|
|
ent->client->ps.stats[STAT_RQ3] = 0;
|
|
|
|
// JBravo: remember saved specmodes.
|
|
if ((g_gametype.integer >= GT_TEAM) &&
|
|
client->sess.sessionTeam == TEAM_SPECTATOR) {
|
|
if (client->specMode == SPECTATOR_FOLLOW || client->specMode == SPECTATOR_FREE) {
|
|
client->sess.spectatorState = client->specMode;
|
|
client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM;
|
|
}
|
|
if (client->specMode == SPECTATOR_ZCAM && client->sess.sessionTeam == TEAM_SPECTATOR) {
|
|
client->sess.spectatorState = client->specMode;
|
|
client->ps.stats[STAT_RQ3] |= RQ3_ZCAM;
|
|
client->ps.pm_flags &= ~PMF_FOLLOW;
|
|
}
|
|
}
|
|
//Elder: set weaponfireNextTime amount
|
|
client->weaponfireNextTime = 0;
|
|
|
|
//Elder: Initialize fast reloads stuff
|
|
client->fastReloads = 0;
|
|
client->lastReloadTime = 0;
|
|
|
|
//Elder: initialize consecutive shots for M4 ride-up
|
|
client->consecutiveShots = 0;
|
|
|
|
// set default animations
|
|
client->ps.torsoAnim = TORSO_STAND;
|
|
client->ps.legsAnim = LEGS_IDLE;
|
|
// weapon animations
|
|
if (g_gametype.integer == GT_FFA || (g_gametype.integer == GT_TEAM && g_RQ3_tdmMode.integer))
|
|
client->ps.generic1 = ((client->ps.generic1 & ANIM_TOGGLEBIT)
|
|
^ ANIM_TOGGLEBIT) | WP_ANIM_IDLE;
|
|
|
|
if (level.intermissiontime) {
|
|
MoveClientToIntermission(ent);
|
|
} else {
|
|
// fire the targets of the spawn point
|
|
if (spawnPoint)
|
|
G_UseTargets(spawnPoint, ent);
|
|
|
|
// select the highest weapon number available, after any
|
|
// spawn given items have fired
|
|
// JBravo: Lets make sure we have the right weapons
|
|
if (g_gametype.integer >= GT_TEAM && !(g_gametype.integer == GT_TEAM && g_RQ3_tdmMode.integer) &&
|
|
(client->sess.sessionTeam == TEAM_RED || client->sess.sessionTeam == TEAM_BLUE)) {
|
|
EquipPlayer(ent);
|
|
} else {
|
|
client->ps.weapon = 1;
|
|
for (i = WP_NUM_WEAPONS - 1; i > 0; i--) {
|
|
if (i == WP_KNIFE)
|
|
continue;
|
|
if (client->ps.stats[STAT_WEAPONS] & (1 << i)) {
|
|
client->ps.weapon = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// run a client frame to drop exactly to the floor,
|
|
// initialize animations and other things
|
|
client->ps.commandTime = level.time - 100;
|
|
ent->client->pers.cmd.serverTime = level.time;
|
|
// JBravo: We should not have to call this during TP spawns
|
|
if (g_gametype.integer == GT_FFA || (g_gametype.integer == GT_TEAM && g_RQ3_tdmMode.integer))
|
|
ClientThink(ent - g_entities);
|
|
|
|
// positively link the client, even if the command times are weird
|
|
if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) {
|
|
BG_PlayerStateToEntityState(&client->ps, &ent->s, qtrue);
|
|
VectorCopy(ent->client->ps.origin, ent->r.currentOrigin);
|
|
trap_LinkEntity(ent);
|
|
}
|
|
// run the presend to set anything else
|
|
// JBravo: We should not have to call this during TP spawns
|
|
if (g_gametype.integer == GT_FFA || (g_gametype.integer == GT_TEAM && g_RQ3_tdmMode.integer))
|
|
ClientEndFrame(ent);
|
|
ent->client->noHead = qfalse;
|
|
|
|
// JBravo: adding allitems.
|
|
if (g_RQ3_allItems.integer) {
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] = (1 << HI_KEVLAR);
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] |= (1 << HI_LASER);
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] |= (1 << HI_SILENCER);
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] |= (1 << HI_BANDOLIER);
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] |= (1 << HI_SLIPPERS);
|
|
if (g_RQ3_haveHelmet.integer) {
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] |= (1 << HI_HELMET);
|
|
ent->client->uniqueItems = 6;
|
|
} else {
|
|
ent->client->uniqueItems = 5;
|
|
}
|
|
}
|
|
|
|
// JBravo: adding allweapons.
|
|
if (g_RQ3_allWeapons.integer) {
|
|
ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - (1 << WP_NONE);
|
|
ent->client->weaponCount[WP_SSG3000] = 1;
|
|
ent->client->weaponCount[WP_MP5] = 1;
|
|
ent->client->weaponCount[WP_M3] = 1;
|
|
ent->client->weaponCount[WP_M4] = 1;
|
|
ent->client->weaponCount[WP_AKIMBO] = 1;
|
|
ent->client->weaponCount[WP_HANDCANNON] = 1;
|
|
ent->client->uniqueWeapons = 5;
|
|
//Makro - added knives
|
|
ent->client->weaponCount[WP_KNIFE] = 1;
|
|
for (i = 0; i < MAX_WEAPONS; i++) {
|
|
ent->client->ps.ammo[i] = ClipAmountForAmmo(i);
|
|
Add_Ammo(ent, i, 100, 1);
|
|
}
|
|
if (ent->client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_BANDOLIER))
|
|
ent->client->ps.ammo[WP_KNIFE] = 20;
|
|
else
|
|
ent->client->ps.ammo[WP_KNIFE] = 10;
|
|
|
|
ent->client->ps.stats[STAT_WEAPONS] |= (1 << WP_GRENADE) | (1 << WP_KNIFE);
|
|
}
|
|
|
|
// JBravo: lock the player down
|
|
if (g_gametype.integer == GT_CTF && ent->client->sess.sessionTeam == TEAM_SPECTATOR &&
|
|
(ent->client->sess.savedTeam == TEAM_RED || ent->client->sess.savedTeam == TEAM_BLUE) &&
|
|
!level.lights_camera_action)
|
|
ent->client->ps.pm_type = PM_FREEZE;
|
|
|
|
// clear entity state values
|
|
BG_PlayerStateToEntityState(&client->ps, &ent->s, qtrue);
|
|
}
|
|
|
|
/*
|
|
===========
|
|
ClientDisconnect
|
|
|
|
Called when a player drops from the server.
|
|
Will not be called between levels.
|
|
|
|
This should NOT be called directly by any game logic,
|
|
call trap_DropClient(), which will call this and do
|
|
server system housekeeping.
|
|
============
|
|
*/
|
|
void ClientDisconnect(int clientNum)
|
|
{
|
|
gentity_t *ent;
|
|
gentity_t *tent;
|
|
int oldTeam = 0, i;
|
|
|
|
// cleanup if we are kicking a bot that
|
|
// hasn't spawned yet
|
|
G_RemoveQueuedBotBegin(clientNum);
|
|
|
|
ent = g_entities + clientNum;
|
|
if (!ent->client || ent->client->pers.connected == CON_DISCONNECTED) {
|
|
return;
|
|
}
|
|
// JBravo: to keep the ui teamcount cvars right.
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
oldTeam = ent->client->sess.sessionTeam;
|
|
}
|
|
//Slicer: matchmode
|
|
if (g_RQ3_matchmode.integer) {
|
|
if(ent->client->sess.referee)
|
|
--level.refAmmount;
|
|
switch (ent->client->sess.captain) {
|
|
case TEAM_RED:
|
|
level.team1ready = qfalse;
|
|
break;
|
|
case TEAM_BLUE:
|
|
level.team2ready = qfalse;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
// aasimon: Referee. If player is referee, clean ref
|
|
/*if (clientNum == g_RQ3_RefID.integer)
|
|
trap_Cvar_Set("g_RQ3_RefID", "-1"); */
|
|
|
|
// JBravo: if the client had a laser, turn it off so it doesnt stay there forever.
|
|
if (ent->client->lasersight) {
|
|
G_FreeEntity(ent->client->lasersight);
|
|
ent->client->lasersight = NULL;
|
|
}
|
|
// stop any following clients
|
|
for (i = 0; i < level.maxclients; i++) {
|
|
if (level.clients[i].sess.sessionTeam == TEAM_SPECTATOR
|
|
&& level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW
|
|
&& level.clients[i].sess.spectatorClient == clientNum) {
|
|
//Blaze: Prit out some Debug info
|
|
if (&g_entities[i] == NULL) G_Printf("Ln 2049\n");
|
|
|
|
StopFollowing(&g_entities[i]);
|
|
}
|
|
}
|
|
|
|
camera_disconnect(ent);
|
|
|
|
// send effect if they were completely connected
|
|
if (ent->client->pers.connected == CON_CONNECTED && ent->client->sess.sessionTeam != TEAM_SPECTATOR) {
|
|
tent = G_TempEntity(ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT);
|
|
tent->s.clientNum = ent->s.clientNum;
|
|
|
|
// They don't get to take powerups with them!
|
|
// Especially important for stuff like CTF flags
|
|
TossClientItems(ent);
|
|
}
|
|
// JBravo: Make this more like AQ
|
|
// G_LogPrintf( "ClientDisconnect: %i\n", clientNum );
|
|
G_LogPrintf("%s disconnected (clientNum %i)\n", ent->client->pers.netname, clientNum);
|
|
|
|
// if we are playing in tourney mode and losing, give a win to the other player
|
|
if ((g_gametype.integer == GT_TOURNAMENT)
|
|
&& !level.intermissiontime && !level.warmupTime && level.sortedClients[1] == clientNum) {
|
|
level.clients[level.sortedClients[0]].sess.wins++;
|
|
ClientUserinfoChanged(level.sortedClients[0]);
|
|
}
|
|
|
|
trap_UnlinkEntity(ent);
|
|
ent->s.modelindex = 0;
|
|
ent->inuse = qfalse;
|
|
ent->classname = "disconnected";
|
|
ent->client->pers.connected = CON_DISCONNECTED;
|
|
ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE;
|
|
ent->client->sess.sessionTeam = TEAM_FREE;
|
|
|
|
trap_SetConfigstring(CS_PLAYERS + clientNum, "");
|
|
|
|
CalculateRanks();
|
|
|
|
// JBravo: to keep the ui teamcount cvars right.
|
|
if (g_gametype.integer >= GT_TEAM) {
|
|
i = RQ3TeamCount(-1, oldTeam);
|
|
}
|
|
|
|
if (ent->r.svFlags & SVF_BOT) {
|
|
BotAIShutdownClient(clientNum, qfalse);
|
|
}
|
|
}
|