Initial commit.

This commit is contained in:
Jim Gray
2013-04-04 14:32:05 -07:00
parent ba5c81da32
commit d71d53e8ec
2180 changed files with 1393544 additions and 1 deletions

View File

@@ -0,0 +1,49 @@
//rww - callbacks the navigation system needs to make to the game code.
#include "../../game/q_shared.h"
#include "../../game/g_public.h"
#include "../server.h"
qboolean GNavCallback_NAV_ClearPathToPoint( sharedEntity_t *self, vec3_t pmins, vec3_t pmaxs, vec3_t point, int clipmask, int okToHitEntNum )
{
return (qboolean)VM_Call(gvm, GAME_NAV_CLEARPATHTOPOINT, self->s.number, pmins, pmaxs, point, clipmask, okToHitEntNum);
}
qboolean GNavCallback_NPC_ClearLOS( sharedEntity_t *ent, const vec3_t end )
{
return (qboolean)VM_Call(gvm, GAME_NAV_CLEARLOS, ent->s.number, end);
}
int GNavCallback_NAVNEW_ClearPathBetweenPoints(vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int ignore, int clipmask)
{
return VM_Call(gvm, GAME_NAV_CLEARPATHBETWEENPOINTS, start, end, mins, maxs, ignore, clipmask);
}
qboolean GNavCallback_NAV_CheckNodeFailedForEnt( sharedEntity_t *ent, int nodeNum )
{
return (qboolean)VM_Call(gvm, GAME_NAV_CHECKNODEFAILEDFORENT, ent->s.number, nodeNum);
}
qboolean GNavCallback_G_EntIsUnlockedDoor( int entityNum )
{
return (qboolean)VM_Call(gvm, GAME_NAV_ENTISUNLOCKEDDOOR, entityNum);
}
qboolean GNavCallback_G_EntIsDoor( int entityNum )
{
return (qboolean)VM_Call(gvm, GAME_NAV_ENTISDOOR, entityNum);
}
qboolean GNavCallback_G_EntIsBreakable( int entityNum )
{
return (qboolean)VM_Call(gvm, GAME_NAV_ENTISBREAKABLE, entityNum);
}
qboolean GNavCallback_G_EntIsRemovableUsable( int entNum )
{
return (qboolean)VM_Call(gvm, GAME_NAV_ENTISREMOVABLEUSABLE, entNum);
}
void GNavCallback_CP_FindCombatPointWaypoints( void )
{
VM_Call(gvm, GAME_NAV_FINDCOMBATPOINTWAYPOINTS);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,280 @@
#ifndef __G_NAVIGATOR__
#define __G_NAVIGATOR__
#define __NEWCOLLECT 1
#define _HARD_CONNECT 1
//Node flags
#define NF_ANY 0
//#define NF_CLEAR_LOS 0x00000001
#define NF_CLEAR_PATH 0x00000002
#define NF_RECALC 0x00000004
//Edge flags
#define EFLAG_NONE 0
#define EFLAG_BLOCKED 0x00000001
#define EFLAG_FAILED 0x00000002
//Miscellaneous defines
#define NODE_NONE -1
#define NAV_HEADER_ID 'JNV5'
#define NODE_HEADER_ID 'NODE'
#pragma warning( disable : 4786)
#if defined(_WIN32) && !defined(_XBOX)
#define COM_NO_WINDOWS_H
#include <objbase.h>
#endif
#include <map>
#include <vector>
#include <list>
using namespace std;
#include "../server.h"
#include "../../game/q_shared.h"
typedef multimap<int, int> EdgeMultimap;
typedef multimap<int, int>::iterator EdgeMultimapIt;
/*
-------------------------
CEdge
-------------------------
*/
class CEdge
{
public:
CEdge( void );
CEdge( int first, int second, int cost );
~CEdge( void );
int m_first;
int m_second;
int m_cost;
};
/*
-------------------------
CNode
-------------------------
*/
class CNode
{
typedef struct edge_s
{
int ID;
int cost;
BYTE flags;
} edge_t;
typedef vector< edge_t > edge_v;
public:
CNode( void );
~CNode( void );
static CNode *Create( vec3_t position, int flags, int radius, int ID );
static CNode *Create( void );
void AddEdge( int ID, int cost, int flags = EFLAG_NONE );
void AddRank( int ID, int rank );
void Draw( qboolean radius );
int GetID( void ) const { return m_ID; }
void GetPosition( vec3_t position ) const { if ( position ) VectorCopy( m_position, position ); }
int GetNumEdges( void ) const { return m_numEdges; }
int GetEdgeNumToNode( int ID );
int GetEdge( int edgeNum );
int GetEdgeCost( int edgeNum );
BYTE GetEdgeFlags( int edgeNum );
void SetEdgeFlags( int edgeNum, int newFlags );
int GetRadius( void ) const { return m_radius; }
void InitRanks( int size );
int GetRank( int ID );
int GetFlags( void ) const { return m_flags; }
void AddFlag( int newFlag ) { m_flags |= newFlag; }
void RemoveFlag( int oldFlag ) { m_flags &= ~oldFlag; }
int Save( int numNodes, fileHandle_t file );
int Load( int numNodes, fileHandle_t file );
protected:
vec3_t m_position;
int m_flags;
int m_radius;
int m_ID;
edge_v m_edges;
int *m_ranks;
int m_numEdges;
};
/*
-------------------------
CNavigator
-------------------------
*/
#define MAX_FAILED_EDGES 32
class CNavigator
{
typedef vector < CNode * > node_v;
typedef list < CEdge > edge_l;
#if __NEWCOLLECT
typedef struct nodeList_t
{
int nodeID;
unsigned int distance;
};
typedef list < nodeList_t > nodeChain_l;
#endif //__NEWCOLLECT
public:
CNavigator( void );
~CNavigator( void );
void Init( void );
void Free( void );
bool Load( const char *filename, int checksum );
bool Save( const char *filename, int checksum );
int AddRawPoint( vec3_t point, int flags, int radius );
void CalculatePaths( qboolean recalc=qfalse );
#if _HARD_CONNECT
void HardConnect( int first, int second );
#endif
void ShowNodes( void );
void ShowEdges( void );
void ShowPath( int start, int end );
int GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int targetID );
int GetBestNode( int startID, int endID, int rejectID = NODE_NONE );
int GetNodePosition( int nodeID, vec3_t out );
int GetNodeNumEdges( int nodeID );
int GetNodeEdge( int nodeID, int edge );
float GetNodeLeadDistance( int nodeID );
int GetNumNodes( void ) const { return m_nodes.size(); }
bool Connected( int startID, int endID );
unsigned int GetPathCost( int startID, int endID );
unsigned int GetEdgeCost( int startID, int endID );
int GetProjectedNode( vec3_t origin, int nodeID );
//MCG Added BEGIN
void CheckFailedNodes( sharedEntity_t *ent );
void AddFailedNode( sharedEntity_t *ent, int nodeID );
qboolean NodeFailed( sharedEntity_t *ent, int nodeID );
qboolean NodesAreNeighbors( int startID, int endID );
void ClearFailedEdge( failedEdge_t *failedEdge );
void ClearAllFailedEdges( void );
int EdgeFailed( int startID, int endID );
void AddFailedEdge( int entID, int startID, int endID );
qboolean CheckFailedEdge( failedEdge_t *failedEdge );
void CheckAllFailedEdges( void );
qboolean RouteBlocked( int startID, int testEdgeID, int endID, int rejectRank );
int GetBestNodeAltRoute( int startID, int endID, int *pathCost, int rejectID = NODE_NONE );
int GetBestNodeAltRoute( int startID, int endID, int rejectID = NODE_NONE );
int GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goal, int flags );
int GetNodeRadius( int nodeID );
void CheckBlockedEdges( void );
void ClearCheckedNodes( void );
byte CheckedNode(int wayPoint,int ent);
void SetCheckedNode(int wayPoint,int ent,byte value);
void FlagAllNodes( int newFlag );
qboolean pathsCalculated;
//MCG Added END
protected:
int TestNodePath( sharedEntity_t *ent, int okToHitEntNum, vec3_t position, qboolean includeEnts );
int TestNodeLOS( sharedEntity_t *ent, vec3_t position );
int TestBestFirst( sharedEntity_t *ent, int lastID, int flags );
#if __NEWCOLLECT
int CollectNearestNodes( vec3_t origin, int radius, int maxCollect, nodeChain_l &nodeChain );
#else
int CollectNearestNodes( vec3_t origin, int radius, int maxCollect, int *nodeChain );
#endif //__NEWCOLLECT
char GetChar( fileHandle_t file );
int GetInt( fileHandle_t file );
float GetFloat( fileHandle_t file );
long GetLong( fileHandle_t file );
//void ConnectNodes( void );
void SetEdgeCost( int ID1, int ID2, int cost );
int GetEdgeCost( CNode *first, CNode *second );
void AddNodeEdges( CNode *node, int addDist, edge_l &edgeList, bool *checkedNodes );
void CalculatePath( CNode *node );
//rww - made failedEdges private as it doesn't seem to need to be public.
//And I'd rather shoot myself than have to devise a way of setting/accessing this
//array via trap calls.
failedEdge_t failedEdges[MAX_FAILED_EDGES];
node_v m_nodes;
EdgeMultimap m_edgeLookupMap;
};
//////////////////////////////////////////////////////////////////////
// class Priority Queue
//////////////////////////////////////////////////////////////////////
class CPriorityQueue
{
// CONSTRUCTION /DESTRUCTION
//--------------------------------------------------------------
public:
CPriorityQueue() {};
~CPriorityQueue();
// Functionality
//--------------------------------------------------------------
public:
CEdge* Pop();
CEdge* Find(int npNum);
void Push( CEdge* theEdge );
void Update(CEdge* edge );
bool Empty();
// DATA
//--------------------------------------------------------------
private:
vector<CEdge*> mHeap;
};
extern CNavigator navigator;
#endif //__G_NAVIGATOR__

View File

@@ -0,0 +1,5 @@
// The file that generates the PCH for the whole executable...
//
#include "../server/exe_headers.h"

View File

@@ -0,0 +1,13 @@
// stuff added for PCH files. I want to have a lot of stuff included here so the PCH is pretty rich,
// but without exposing too many extra protos, so for now (while I experiment)...
//
#include "../game/q_shared.h"
#include "../qcommon/qcommon.h"
#include "../client/client.h"
#include "../server/server.h"
#include "../renderer/tr_local.h"
#pragma hdrstop

443
codemp/server/server.h Normal file
View File

@@ -0,0 +1,443 @@
#if defined (_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#if !defined(SERVER_H_INC)
#define SERVER_H_INC
#include "../game/q_shared.h"
#include "../qcommon/qcommon.h"
#include "../game/g_public.h"
#include "../game/bg_public.h"
#ifdef _XBOX
#include "../xbox/XBLive.h"
#include "../xbox/XBoxCommon.h"
#include "../xbox/XBVoice.h"
#endif
//=============================================================================
#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND
// GAME BOTH REFERENCE !!!
#define MAX_ENT_CLUSTERS 16
typedef struct svEntity_s {
struct worldSector_s *worldSector;
struct svEntity_s *nextEntityInWorldSector;
entityState_t baseline; // for delta compression of initial sighting
#ifdef _XBOX
signed char numClusters; // if -1, use headnode instead
short clusternums[MAX_ENT_CLUSTERS];
short lastCluster; // if all the clusters don't fit in clusternums
short areanum, areanum2;
char snapshotCounter; // used to prevent double adding from portal views
#else
int numClusters; // if -1, use headnode instead
int clusternums[MAX_ENT_CLUSTERS];
int lastCluster; // if all the clusters don't fit in clusternums
int areanum, areanum2;
int snapshotCounter; // used to prevent double adding from portal views
#endif
} svEntity_t;
typedef enum {
SS_DEAD, // no map loaded
SS_LOADING, // spawning level entities
SS_GAME // actively running
} serverState_t;
typedef struct {
serverState_t state;
qboolean restarting; // if true, send configstring changes during SS_LOADING
int serverId; // changes each server start
int restartedServerId; // serverId before a map_restart
int checksumFeed; //
#ifdef _XBOX
char snapshotCounter; // incremented for each snapshot built
#else
int snapshotCounter; // incremented for each snapshot built
#endif
int timeResidual; // <= 1000 / sv_frame->value
int nextFrameTime; // when time > nextFrameTime, process world
struct cmodel_s *models[MAX_MODELS];
char *configstrings[MAX_CONFIGSTRINGS];
svEntity_t svEntities[MAX_GENTITIES];
char *entityParsePoint; // used during game VM init
// the game virtual machine will update these on init and changes
sharedEntity_t *gentities;
int gentitySize;
int num_entities; // current number, <= MAX_GENTITIES
playerState_t *gameClients;
int gameClientSize; // will be > sizeof(playerState_t) due to game private data
int restartTime;
//rwwRMG - added:
/*
int mLocalSubBSPIndex;
int mLocalSubBSPModelOffset;
char *mLocalSubBSPEntityParsePoint;
*/
char *mSharedMemory;
} server_t;
typedef struct {
int areabytes;
byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits
playerState_t ps;
playerState_t vps; //vehicle I'm riding's playerstate (if applicable) -rww
#ifdef _ONEBIT_COMBO
int *pDeltaOneBit;
int *pDeltaOneBitVeh;
int *pDeltaNumBit;
int *pDeltaNumBitVeh;
#endif
int num_entities;
int first_entity; // into the circular sv_packet_entities[]
// the entities MUST be in increasing state number
// order, otherwise the delta compression will fail
int messageSent; // time the message was transmitted
int messageAcked; // time the message was acked
int messageSize; // used to rate drop packets
} clientSnapshot_t;
typedef enum {
CS_FREE, // can be reused for a new connection
CS_ZOMBIE, // client has been disconnected, but don't reuse
// connection for a couple seconds
CS_CONNECTED, // has been assigned to a client_t, but no gamestate yet
CS_PRIMED, // gamestate has been sent, but client hasn't sent a usercmd
CS_ACTIVE // client is fully in game
} clientState_t;
typedef struct client_s {
clientState_t state;
char userinfo[MAX_INFO_STRING]; // name, etc
qboolean sentGamedir; //see if he has been sent an svc_setgame
char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS];
int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet
int reliableAcknowledge; // last acknowledged reliable message
int reliableSent; // last sent reliable message, not necesarily acknowledged yet
int messageAcknowledge;
int gamestateMessageNum; // netchan->outgoingSequence of gamestate
int challenge;
usercmd_t lastUsercmd;
int lastMessageNum; // for delta compression
int lastClientCommand; // reliable client message sequence
char lastClientCommandString[MAX_STRING_CHARS];
sharedEntity_t *gentity; // SV_GentityNum(clientnum)
char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked
// downloading
#ifndef _XBOX // No downloads on Xbox
char downloadName[MAX_QPATH]; // if not empty string, we are downloading
fileHandle_t download; // file being downloaded
int downloadSize; // total bytes (can't use EOF because of paks)
int downloadCount; // bytes sent
int downloadClientBlock; // last block we sent to the client, awaiting ack
int downloadCurrentBlock; // current block number
int downloadXmitBlock; // last block we xmited
unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks
int downloadBlockSize[MAX_DOWNLOAD_WINDOW];
qboolean downloadEOF; // We have sent the EOF block
int downloadSendTime; // time we last got an ack from the client
#endif
int deltaMessage; // frame last client usercmd message
int nextReliableTime; // svs.time when another reliable command will be allowed
int lastPacketTime; // svs.time when packet was last received
int lastConnectTime; // svs.time when connection started
int nextSnapshotTime; // send another snapshot when svs.time >= nextSnapshotTime
qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec
int timeoutCount; // must timeout a few frames in a row so debugging doesn't break
clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here
int ping;
int rate; // bytes / second
int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked
int pureAuthentic;
netchan_t netchan;
int lastUserInfoChange; //if > svs.time && count > x, deny change -rww
int lastUserInfoCount; //allow a certain number of changes within a certain time period -rww
#ifdef _XBOX
int refIndex; // Copy of refIndex in xbOnlineInfo.xbPlayerList[]
qboolean usePrivateSlot; // Was this person eligble for a private slot when they joined?
#endif
} client_t;
//=============================================================================
// MAX_CHALLENGES is made large to prevent a denial
// of service attack that could cycle all of them
// out before legitimate users connected
#define MAX_CHALLENGES 1024
#define AUTHORIZE_TIMEOUT 5000
typedef struct {
netadr_t adr;
int challenge;
int time; // time the last packet was sent to the autherize server
int pingTime; // time the challenge response was sent to client
int firstTime; // time the adr was first used, for authorize timeout checks
qboolean connected;
} challenge_t;
#define MAX_MASTERS 8 // max recipients for heartbeat packets
// this structure will be cleared only when the game dll changes
typedef struct {
qboolean initialized; // sv_init has completed
int time; // will be strictly increasing across level changes
int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer()
client_t *clients; // [sv_maxclients->integer];
int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES
int nextSnapshotEntities; // next snapshotEntities to use
entityState_t *snapshotEntities; // [numSnapshotEntities]
int nextHeartbeatTime;
challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting
netadr_t redirectAddress; // for rcon return messages
netadr_t authorizeAddress; // for rcon return messages
#ifdef _XBOX
int clientRefNum; // Index into xbonlineinfo array
int syslinkAdvertTime; // Time of next system link SVC_Info packet
#endif
} serverStatic_t;
//=============================================================================
extern serverStatic_t svs; // persistant server info across maps
extern server_t sv; // cleared each map
extern vm_t *gvm; // game virtual machine
#define MAX_MASTER_SERVERS 5
extern cvar_t *sv_fps;
extern cvar_t *sv_timeout;
extern cvar_t *sv_zombietime;
extern cvar_t *sv_rconPassword;
extern cvar_t *sv_privatePassword;
extern cvar_t *sv_allowDownload;
extern cvar_t *sv_maxclients;
extern cvar_t *sv_privateClients;
extern cvar_t *sv_hostname;
extern cvar_t *sv_master[MAX_MASTER_SERVERS];
extern cvar_t *sv_reconnectlimit;
extern cvar_t *sv_showghoultraces;
extern cvar_t *sv_showloss;
extern cvar_t *sv_padPackets;
extern cvar_t *sv_killserver;
extern cvar_t *sv_mapname;
extern cvar_t *sv_mapChecksum;
extern cvar_t *sv_serverid;
extern cvar_t *sv_maxRate;
extern cvar_t *sv_minPing;
extern cvar_t *sv_maxPing;
extern cvar_t *sv_gametype;
extern cvar_t *sv_pure;
extern cvar_t *sv_floodProtect;
extern cvar_t *sv_allowAnonymous;
extern cvar_t *sv_needpass;
extern cvar_t *xb_gameType;
//===========================================================
//
// sv_main.c
//
void SV_FinalMessage (char *message);
void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...);
void SV_AddOperatorCommands (void);
void SV_RemoveOperatorCommands (void);
void SV_MasterHeartbeat (void);
void SV_MasterShutdown (void);
//
// sv_init.c
//
void SV_SetConfigstring( int index, const char *val );
void SV_GetConfigstring( int index, char *buffer, int bufferSize );
int SV_AddConfigstring (const char *name, int start, int max);
void SV_SetUserinfo( int index, const char *val );
void SV_GetUserinfo( int index, char *buffer, int bufferSize );
void SV_ChangeMaxClients( void );
void SV_SpawnServer( char *server, qboolean killBots, ForceReload_e eForceReload );
//
// sv_client.c
//
void SV_GetChallenge( netadr_t from );
void SV_DirectConnect( netadr_t from );
void SV_AuthorizeIpPacket( netadr_t from );
void SV_SendClientMapChange( client_t *client );
void SV_ExecuteClientMessage( client_t *cl, msg_t *msg );
void SV_UserinfoChanged( client_t *cl );
void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd );
void SV_DropClient( client_t *drop, const char *reason );
void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK );
void SV_ClientThink (client_t *cl, usercmd_t *cmd);
void SV_WriteDownloadToClient( client_t *cl , msg_t *msg );
// Need to broadcast info about clients on join/leave
#ifdef _XBOX
struct XBPlayerInfo;
void SV_SendClientNewPeer(client_t* client, XBPlayerInfo* info);
void SV_SendClientRemovePeer(client_t* client, int index);
void SV_SendClientXbInfo(client_t *client);
#endif
//
// sv_ccmds.c
//
void SV_Heartbeat_f( void );
//
// sv_snapshot.c
//
void SV_AddServerCommand( client_t *client, const char *cmd );
void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg );
void SV_WriteFrameToClient (client_t *client, msg_t *msg);
void SV_SendMessageToClient( msg_t *msg, client_t *client );
void SV_SendClientMessages( void );
void SV_SendClientSnapshot( client_t *client );
//
// sv_game.c
//
int SV_NumForGentity( sharedEntity_t *ent );
sharedEntity_t *SV_GentityNum( int num );
playerState_t *SV_GameClientNum( int num );
svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt );
sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt );
void SV_InitGameProgs ( void );
void SV_ShutdownGameProgs ( void );
void SV_RestartGameProgs( void );
qboolean SV_inPVS (const vec3_t p1, const vec3_t p2);
//
// sv_bot.c
//
void SV_BotFrame( int time );
int SV_BotAllocateClient(void);
void SV_BotFreeClient( int clientNum );
void SV_BotInitCvars(void);
int SV_BotLibSetup( void );
int SV_BotLibShutdown( void );
int SV_BotGetSnapshotEntity( int client, int ent );
int SV_BotGetConsoleMessage( int client, char *buf, int size );
void *Bot_GetMemoryGame(int size);
void Bot_FreeMemoryGame(void *ptr);
int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points);
void BotImport_DebugPolygonDelete(int id);
//============================================================
//
// high level object sorting to reduce interaction tests
//
void SV_ClearWorld (void);
// called after the world model has been loaded, before linking any entities
void SV_UnlinkEntity( sharedEntity_t *ent );
// call before removing an entity, and before trying to move one,
// so it doesn't clip against itself
void SV_LinkEntity( sharedEntity_t *ent );
// Needs to be called any time an entity changes origin, mins, maxs,
// or solid. Automatically unlinks if needed.
// sets ent->v.absmin and ent->v.absmax
// sets ent->leafnums[] for pvs determination even if the entity
// is not solid
clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent );
void SV_SectorList_f( void );
int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount );
// fills in a table of entity numbers with entities that have bounding boxes
// that intersect the given area. It is possible for a non-axial bmodel
// to be returned that doesn't actually intersect the area on an exact
// test.
// returns the number of pointers filled in
// The world entity is never returned in this list.
int SV_PointContents( const vec3_t p, int passEntityNum );
// returns the CONTENTS_* value from the world and all entities at the given point.
void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule, int traceFlags, int useLod );
// mins and maxs are relative
// if the entire move stays in a solid volume, trace.allsolid will be set,
// trace.startsolid will be set, and trace.fraction will be 0
// if the starting point is in a solid, it will be allowed to move out
// to an open area
// passEntityNum is explicitly excluded from clipping checks (normally ENTITYNUM_NONE)
void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule );
// clip to a specific entity
//
// sv_net_chan.c
//
void SV_Netchan_Transmit( client_t *client, msg_t *msg); //int length, const byte *data );
void SV_Netchan_TransmitNextFragment( netchan_t *chan );
qboolean SV_Netchan_Process( client_t *client, msg_t *msg );
#endif // SERVER_H_INC

804
codemp/server/sv_bot.cpp Normal file
View File

@@ -0,0 +1,804 @@
// sv_bot.c
//Anything above this #include will be ignored by the compiler
#include "../qcommon/exe_headers.h"
#include "server.h"
#include "../game/botlib.h"
typedef struct bot_debugpoly_s
{
int inuse;
int color;
int numPoints;
vec3_t points[128];
} bot_debugpoly_t;
static bot_debugpoly_t *debugpolygons;
int bot_maxdebugpolys;
extern botlib_export_t *botlib_export;
int bot_enable;
static int gWPNum = 0;
static wpobject_t *gWPArray[MAX_WPARRAY_SIZE];
static int NotWithinRange(int base, int extent)
{
if (extent > base && base+5 >= extent)
{
return 0;
}
if (extent < base && base-5 <= extent)
{
return 0;
}
return 1;
}
int SV_OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore, int rmg)
{
trace_t tr;
if (rmg)
{
SV_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID, 0, 0, 10);
}
else
{
SV_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID, 0, 0, 10);
}
if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
{
return 1;
}
return 0;
}
void *BotVMShift( int ptr );
void SV_BotWaypointReception(int wpnum, wpobject_t **wps)
{
int i = 0;
gWPNum = wpnum;
while (i < gWPNum)
{
gWPArray[i] = (wpobject_t *)BotVMShift((int)wps[i]);
i++;
}
}
/*
==================
SV_BotCalculatePaths
==================
*/
void SV_BotCalculatePaths(int rmg)
{
int i;
int c;
int forceJumpable;
int maxNeighborDist = MAX_NEIGHBOR_LINK_DISTANCE;
float nLDist;
vec3_t a;
vec3_t mins, maxs;
if (!gWPNum)
{
return;
}
if (rmg)
{
maxNeighborDist = DEFAULT_GRID_SPACING + (DEFAULT_GRID_SPACING*0.5);
}
mins[0] = -15;
mins[1] = -15;
mins[2] = -15; //-1
maxs[0] = 15;
maxs[1] = 15;
maxs[2] = 15; //1
//now clear out all the neighbor data before we recalculate
i = 0;
while (i < gWPNum)
{
if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->neighbornum)
{
while (gWPArray[i]->neighbornum >= 0)
{
gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = 0;
gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0;
gWPArray[i]->neighbornum--;
}
gWPArray[i]->neighbornum = 0;
}
i++;
}
i = 0;
while (i < gWPNum)
{
if (gWPArray[i] && gWPArray[i]->inuse)
{
c = 0;
while (c < gWPNum)
{
if (gWPArray[c] && gWPArray[c]->inuse && i != c &&
NotWithinRange(i, c))
{
VectorSubtract(gWPArray[i]->origin, gWPArray[c]->origin, a);
nLDist = VectorLength(a);
forceJumpable = qfalse;//CanForceJumpTo(i, c, nLDist);
if ((nLDist < maxNeighborDist || forceJumpable) &&
((int)gWPArray[i]->origin[2] == (int)gWPArray[c]->origin[2] || forceJumpable) &&
(SV_OrgVisibleBox(gWPArray[i]->origin, mins, maxs, gWPArray[c]->origin, ENTITYNUM_NONE, rmg) || forceJumpable))
{
gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = c;
if (forceJumpable && ((int)gWPArray[i]->origin[2] != (int)gWPArray[c]->origin[2] || nLDist < maxNeighborDist))
{
gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 999;//forceJumpable; //FJSR
}
else
{
gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0;
}
gWPArray[i]->neighbornum++;
}
if (gWPArray[i]->neighbornum >= MAX_NEIGHBOR_SIZE)
{
break;
}
}
c++;
}
}
i++;
}
}
/*
==================
SV_BotAllocateClient
==================
*/
int SV_BotAllocateClient(void) {
int i = 0;
client_t *cl;
// Find a client slot - skip the first two for split screen, humans
// MUST be the first two clients!
extern bool SplitScreenModeActive( void );
if( SplitScreenModeActive() )
i = 2;
// Likewise, for a listen server, we want the local client to be #0:
else if( !com_dedicated->integer )
i = 1;
for ( cl = svs.clients + i; i < sv_maxclients->integer; i++, cl++ ) {
if ( cl->state == CS_FREE ) {
break;
}
}
if ( i == sv_maxclients->integer ) {
return -1;
}
cl->gentity = SV_GentityNum( i );
cl->gentity->s.number = i;
cl->state = CS_ACTIVE;
cl->lastPacketTime = svs.time;
cl->netchan.remoteAddress.type = NA_BOT;
cl->rate = 16384;
return i;
}
/*
==================
SV_BotFreeClient
==================
*/
void SV_BotFreeClient( int clientNum ) {
client_t *cl;
if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
Com_Error( ERR_DROP, "SV_BotFreeClient: bad clientNum: %i", clientNum );
}
cl = &svs.clients[clientNum];
cl->state = CS_FREE;
cl->name[0] = 0;
if ( cl->gentity ) {
cl->gentity->r.svFlags &= ~SVF_BOT;
}
}
/*
==================
BotDrawDebugPolygons
==================
*/
void BotDrawDebugPolygons(void (*drawPoly)(int color, int numPoints, float *points), int value) {
static cvar_t *bot_debug, *bot_groundonly, *bot_reachability, *bot_highlightarea;
bot_debugpoly_t *poly;
int i, parm0;
if (!debugpolygons)
return;
//bot debugging
if (!bot_debug) bot_debug = Cvar_Get("bot_debug", "0", 0);
//
if (bot_enable && bot_debug->integer) {
//show reachabilities
if (!bot_reachability) bot_reachability = Cvar_Get("bot_reachability", "0", 0);
//show ground faces only
if (!bot_groundonly) bot_groundonly = Cvar_Get("bot_groundonly", "1", 0);
//get the hightlight area
if (!bot_highlightarea) bot_highlightarea = Cvar_Get("bot_highlightarea", "0", 0);
//
parm0 = 0;
if (svs.clients[0].lastUsercmd.buttons & BUTTON_ATTACK) parm0 |= 1;
if (bot_reachability->integer) parm0 |= 2;
if (bot_groundonly->integer) parm0 |= 4;
botlib_export->BotLibVarSet("bot_highlightarea", bot_highlightarea->string);
botlib_export->Test(parm0, NULL, svs.clients[0].gentity->r.currentOrigin,
svs.clients[0].gentity->r.currentAngles);
} //end if
//draw all debug polys
for (i = 0; i < bot_maxdebugpolys; i++) {
poly = &debugpolygons[i];
if (!poly->inuse) continue;
drawPoly(poly->color, poly->numPoints, (float *) poly->points);
//Com_Printf("poly %i, numpoints = %d\n", i, poly->numPoints);
}
}
/*
==================
BotImport_Print
==================
*/
void QDECL BotImport_Print(int type, char *fmt, ...)
{
char str[2048];
va_list ap;
va_start(ap, fmt);
vsprintf(str, fmt, ap);
va_end(ap);
switch(type) {
case PRT_MESSAGE: {
Com_Printf("%s", str);
break;
}
case PRT_WARNING: {
Com_Printf(S_COLOR_YELLOW "Warning: %s", str);
break;
}
case PRT_ERROR: {
Com_Printf(S_COLOR_RED "Error: %s", str);
break;
}
case PRT_FATAL: {
Com_Printf(S_COLOR_RED "Fatal: %s", str);
break;
}
case PRT_EXIT: {
Com_Error(ERR_DROP, S_COLOR_RED "Exit: %s", str);
break;
}
default: {
Com_Printf("unknown print type\n");
break;
}
}
}
/*
==================
BotImport_Trace
==================
*/
void BotImport_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) {
trace_t trace;
SV_Trace(&trace, start, mins, maxs, end, passent, contentmask, qfalse, 0, 10);
//copy the trace information
bsptrace->allsolid = (qboolean)trace.allsolid;
bsptrace->startsolid = (qboolean)trace.startsolid;
bsptrace->fraction = trace.fraction;
VectorCopy(trace.endpos, bsptrace->endpos);
bsptrace->plane.dist = trace.plane.dist;
VectorCopy(trace.plane.normal, bsptrace->plane.normal);
bsptrace->plane.signbits = trace.plane.signbits;
bsptrace->plane.type = trace.plane.type;
bsptrace->surface.value = trace.surfaceFlags;
bsptrace->ent = trace.entityNum;
bsptrace->exp_dist = 0;
bsptrace->sidenum = 0;
bsptrace->contents = 0;
}
/*
==================
BotImport_EntityTrace
==================
*/
void BotImport_EntityTrace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask) {
trace_t trace;
SV_ClipToEntity(&trace, start, mins, maxs, end, entnum, contentmask, qfalse);
//copy the trace information
bsptrace->allsolid = (qboolean)trace.allsolid;
bsptrace->startsolid = (qboolean)trace.startsolid;
bsptrace->fraction = trace.fraction;
VectorCopy(trace.endpos, bsptrace->endpos);
bsptrace->plane.dist = trace.plane.dist;
VectorCopy(trace.plane.normal, bsptrace->plane.normal);
bsptrace->plane.signbits = trace.plane.signbits;
bsptrace->plane.type = trace.plane.type;
bsptrace->surface.value = trace.surfaceFlags;
bsptrace->ent = trace.entityNum;
bsptrace->exp_dist = 0;
bsptrace->sidenum = 0;
bsptrace->contents = 0;
}
/*
==================
BotImport_PointContents
==================
*/
int BotImport_PointContents(vec3_t point) {
return SV_PointContents(point, -1);
}
/*
==================
BotImport_inPVS
==================
*/
int BotImport_inPVS(vec3_t p1, vec3_t p2) {
return SV_inPVS (p1, p2);
}
/*
==================
BotImport_BSPEntityData
==================
*/
char *BotImport_BSPEntityData(void) {
return CM_EntityString();
}
/*
==================
BotImport_BSPModelMinsMaxsOrigin
==================
*/
void BotImport_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t outmins, vec3_t outmaxs, vec3_t origin) {
clipHandle_t h;
vec3_t mins, maxs;
float max;
int i;
h = CM_InlineModel(modelnum);
CM_ModelBounds(h, mins, maxs);
//if the model is rotated
if ((angles[0] || angles[1] || angles[2])) {
// expand for rotation
max = RadiusFromBounds(mins, maxs);
for (i = 0; i < 3; i++) {
mins[i] = -max;
maxs[i] = max;
}
}
if (outmins) VectorCopy(mins, outmins);
if (outmaxs) VectorCopy(maxs, outmaxs);
if (origin) VectorClear(origin);
}
/*
==================
BotImport_GetMemoryGame
==================
*/
#ifndef _XBOX // These are unused, I want the tag back
void *Bot_GetMemoryGame(int size) {
void *ptr;
ptr = Z_Malloc( size, TAG_BOTGAME, qtrue );
return ptr;
}
/*
==================
BotImport_FreeMemoryGame
==================
*/
void Bot_FreeMemoryGame(void *ptr) {
Z_Free(ptr);
}
#endif
/*
==================
BotImport_GetMemory
==================
*/
void *BotImport_GetMemory(int size) {
void *ptr;
ptr = Z_Malloc( size, TAG_BOTLIB, qtrue );
return ptr;
}
/*
==================
BotImport_FreeMemory
==================
*/
void BotImport_FreeMemory(void *ptr) {
Z_Free(ptr);
}
/*
=================
BotImport_HunkAlloc
=================
*/
void *BotImport_HunkAlloc( int size ) {
if( Hunk_CheckMark() ) {
Com_Error( ERR_DROP, "SV_Bot_HunkAlloc: Alloc with marks already set\n" );
}
return Hunk_Alloc( size, h_high );
}
/*
==================
BotImport_DebugPolygonCreate
==================
*/
int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points) {
bot_debugpoly_t *poly;
int i;
if (!debugpolygons)
return 0;
for (i = 1; i < bot_maxdebugpolys; i++) {
if (!debugpolygons[i].inuse)
break;
}
if (i >= bot_maxdebugpolys)
return 0;
poly = &debugpolygons[i];
poly->inuse = qtrue;
poly->color = color;
poly->numPoints = numPoints;
Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t));
//
return i;
}
/*
==================
BotImport_DebugPolygonShow
==================
*/
void BotImport_DebugPolygonShow(int id, int color, int numPoints, vec3_t *points) {
bot_debugpoly_t *poly;
if (!debugpolygons) return;
poly = &debugpolygons[id];
poly->inuse = qtrue;
poly->color = color;
poly->numPoints = numPoints;
Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t));
}
/*
==================
BotImport_DebugPolygonDelete
==================
*/
void BotImport_DebugPolygonDelete(int id)
{
if (!debugpolygons) return;
debugpolygons[id].inuse = qfalse;
}
/*
==================
BotImport_DebugLineCreate
==================
*/
int BotImport_DebugLineCreate(void) {
vec3_t points[1];
return BotImport_DebugPolygonCreate(0, 0, points);
}
/*
==================
BotImport_DebugLineDelete
==================
*/
void BotImport_DebugLineDelete(int line) {
BotImport_DebugPolygonDelete(line);
}
/*
==================
BotImport_DebugLineShow
==================
*/
void BotImport_DebugLineShow(int line, vec3_t start, vec3_t end, int color) {
vec3_t points[4], dir, cross, up = {0, 0, 1};
float dot;
VectorCopy(start, points[0]);
VectorCopy(start, points[1]);
//points[1][2] -= 2;
VectorCopy(end, points[2]);
//points[2][2] -= 2;
VectorCopy(end, points[3]);
VectorSubtract(end, start, dir);
VectorNormalize(dir);
dot = DotProduct(dir, up);
if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0);
else CrossProduct(dir, up, cross);
VectorNormalize(cross);
VectorMA(points[0], 2, cross, points[0]);
VectorMA(points[1], -2, cross, points[1]);
VectorMA(points[2], -2, cross, points[2]);
VectorMA(points[3], 2, cross, points[3]);
BotImport_DebugPolygonShow(line, color, 4, points);
}
/*
==================
SV_BotClientCommand
==================
*/
void BotClientCommand( int client, char *command ) {
SV_ExecuteClientCommand( &svs.clients[client], command, qtrue );
}
/*
==================
SV_BotFrame
==================
*/
void SV_BotFrame( int time ) {
if (!bot_enable) return;
//NOTE: maybe the game is already shutdown
if (!gvm) return;
VM_Call( gvm, BOTAI_START_FRAME, time );
}
/*
===============
SV_BotLibSetup
===============
*/
int SV_BotLibSetup( void ) {
if (!bot_enable) {
return 0;
}
if ( !botlib_export ) {
Com_Printf( S_COLOR_RED "Error: SV_BotLibSetup without SV_BotInitBotLib\n" );
return -1;
}
return botlib_export->BotLibSetup();
}
/*
===============
SV_ShutdownBotLib
Called when either the entire server is being killed, or
it is changing to a different game directory.
===============
*/
int SV_BotLibShutdown( void ) {
if ( !botlib_export ) {
return -1;
}
return botlib_export->BotLibShutdown();
}
/*
==================
SV_BotInitCvars
==================
*/
void SV_BotInitCvars(void) {
Cvar_Get("bot_enable", "1", 0); //enable the bot
Cvar_Get("bot_developer", "0", CVAR_CHEAT); //bot developer mode
Cvar_Get("bot_debug", "0", CVAR_CHEAT); //enable bot debugging
Cvar_Get("bot_maxdebugpolys", "2", 0); //maximum number of debug polys
Cvar_Get("bot_groundonly", "1", 0); //only show ground faces of areas
Cvar_Get("bot_reachability", "0", 0); //show all reachabilities to other areas
Cvar_Get("bot_visualizejumppads", "0", CVAR_CHEAT); //show jumppads
Cvar_Get("bot_forceclustering", "0", 0); //force cluster calculations
Cvar_Get("bot_forcereachability", "0", 0); //force reachability calculations
Cvar_Get("bot_forcewrite", "0", 0); //force writing aas file
Cvar_Get("bot_aasoptimize", "0", 0); //no aas file optimisation
Cvar_Get("bot_saveroutingcache", "0", 0); //save routing cache
Cvar_Get("bot_thinktime", "100", CVAR_CHEAT); //msec the bots thinks
Cvar_Get("bot_reloadcharacters", "0", 0); //reload the bot characters each time
Cvar_Get("bot_testichat", "0", 0); //test ichats
Cvar_Get("bot_testrchat", "0", 0); //test rchats
Cvar_Get("bot_testsolid", "0", CVAR_CHEAT); //test for solid areas
Cvar_Get("bot_testclusters", "0", CVAR_CHEAT); //test the AAS clusters
Cvar_Get("bot_fastchat", "0", 0); //fast chatting bots
Cvar_Get("bot_nochat", "0", 0); //disable chats
Cvar_Get("bot_pause", "0", CVAR_CHEAT); //pause the bots thinking
Cvar_Get("bot_report", "0", CVAR_CHEAT); //get a full report in ctf
Cvar_Get("bot_grapple", "0", 0); //enable grapple
Cvar_Get("bot_rocketjump", "1", 0); //enable rocket jumping
Cvar_Get("bot_challenge", "0", 0); //challenging bot
Cvar_Get("bot_minplayers", "0", 0); //minimum players in a team or the game
Cvar_Get("bot_interbreedchar", "", CVAR_CHEAT); //bot character used for interbreeding
Cvar_Get("bot_interbreedbots", "10", CVAR_CHEAT); //number of bots used for interbreeding
Cvar_Get("bot_interbreedcycle", "20", CVAR_CHEAT); //bot interbreeding cycle
Cvar_Get("bot_interbreedwrite", "", CVAR_CHEAT); //write interbreeded bots to this file
}
extern botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import );
// there's no such thing as this now, since the zone is unlimited, but I have to provide something
// so it doesn't run out of control alloc-wise (since the bot code calls this in a while() loop to free
// up bot mem until zone has > 1MB available again. So, simulate a reasonable limit...
//
static int bot_Z_AvailableMemory(void)
{
const int iMaxBOTLIBMem = 8 * 1024 * 1024; // adjust accordingly.
return iMaxBOTLIBMem - Z_MemSize( TAG_BOTLIB );
}
/*
==================
SV_BotInitBotLib
==================
*/
void SV_BotInitBotLib(void) {
botlib_import_t botlib_import;
if (debugpolygons) Z_Free(debugpolygons);
bot_maxdebugpolys = Cvar_VariableIntegerValue("bot_maxdebugpolys");
debugpolygons = (struct bot_debugpoly_s *)Z_Malloc(sizeof(bot_debugpoly_t) * bot_maxdebugpolys, TAG_BOTLIB, qtrue);
botlib_import.Print = BotImport_Print;
botlib_import.Trace = BotImport_Trace;
botlib_import.EntityTrace = BotImport_EntityTrace;
botlib_import.PointContents = BotImport_PointContents;
botlib_import.inPVS = BotImport_inPVS;
botlib_import.BSPEntityData = BotImport_BSPEntityData;
botlib_import.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin;
botlib_import.BotClientCommand = BotClientCommand;
//memory management
botlib_import.GetMemory = BotImport_GetMemory;
botlib_import.FreeMemory = BotImport_FreeMemory;
botlib_import.AvailableMemory = bot_Z_AvailableMemory; //Z_AvailableMemory;
botlib_import.HunkAlloc = BotImport_HunkAlloc;
// file system access
botlib_import.FS_FOpenFile = FS_FOpenFileByMode;
botlib_import.FS_Read = FS_Read2;
botlib_import.FS_Write = FS_Write;
botlib_import.FS_FCloseFile = FS_FCloseFile;
botlib_import.FS_Seek = FS_Seek;
//debug lines
botlib_import.DebugLineCreate = BotImport_DebugLineCreate;
botlib_import.DebugLineDelete = BotImport_DebugLineDelete;
botlib_import.DebugLineShow = BotImport_DebugLineShow;
//debug polygons
botlib_import.DebugPolygonCreate = BotImport_DebugPolygonCreate;
botlib_import.DebugPolygonDelete = BotImport_DebugPolygonDelete;
botlib_export = (botlib_export_t *)GetBotLibAPI( BOTLIB_API_VERSION, &botlib_import );
assert(botlib_export); // bk001129 - somehow we end up with a zero import.
}
//
// * * * BOT AI CODE IS BELOW THIS POINT * * *
//
/*
==================
SV_BotGetConsoleMessage
==================
*/
int SV_BotGetConsoleMessage( int client, char *buf, int size )
{
client_t *cl;
int index;
cl = &svs.clients[client];
cl->lastPacketTime = svs.time;
if ( cl->reliableAcknowledge == cl->reliableSequence ) {
return qfalse;
}
cl->reliableAcknowledge++;
index = cl->reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 );
if ( !cl->reliableCommands[index][0] ) {
return qfalse;
}
Q_strncpyz( buf, cl->reliableCommands[index], size );
return qtrue;
}
#if 0
/*
==================
EntityInPVS
==================
*/
int EntityInPVS( int client, int entityNum ) {
client_t *cl;
clientSnapshot_t *frame;
int i;
cl = &svs.clients[client];
frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK];
for ( i = 0; i < frame->num_entities; i++ ) {
if ( svs.snapshotEntities[(frame->first_entity + i) % svs.numSnapshotEntities].number == entityNum ) {
return qtrue;
}
}
return qfalse;
}
#endif
/*
==================
SV_BotGetSnapshotEntity
==================
*/
int SV_BotGetSnapshotEntity( int client, int sequence ) {
client_t *cl;
clientSnapshot_t *frame;
cl = &svs.clients[client];
frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK];
if (sequence < 0 || sequence >= frame->num_entities) {
return -1;
}
return svs.snapshotEntities[(frame->first_entity + sequence) % svs.numSnapshotEntities].number;
}

1040
codemp/server/sv_ccmds.cpp Normal file

File diff suppressed because it is too large Load Diff

1897
codemp/server/sv_client.cpp Normal file

File diff suppressed because it is too large Load Diff

1776
codemp/server/sv_game.cpp Normal file

File diff suppressed because it is too large Load Diff

1156
codemp/server/sv_init.cpp Normal file

File diff suppressed because it is too large Load Diff

937
codemp/server/sv_main.cpp Normal file
View File

@@ -0,0 +1,937 @@
//Anything above this #include will be ignored by the compiler
#include "../qcommon/exe_headers.h"
#include "server.h"
//rww - RAGDOLL_BEGIN
#include "../ghoul2/ghoul2_shared.h"
//rww - RAGDOLL_END
#ifdef _XBOX
#include "../cgame/cg_local.h"
#include "../client/cl_data.h"
#endif
serverStatic_t svs; // persistant server info
server_t sv; // local server
vm_t *gvm = NULL; // game virtual machine // bk001212 init
cvar_t *sv_fps; // time rate for running non-clients
cvar_t *sv_timeout; // seconds without any message
cvar_t *sv_zombietime; // seconds to sink messages after disconnect
cvar_t *sv_rconPassword; // password for remote server commands
cvar_t *sv_privatePassword; // password for the privateClient slots
cvar_t *sv_maxclients;
cvar_t *sv_privateClients; // number of clients reserved for password
cvar_t *sv_hostname;
#ifndef _XBOX // No master or downloads on Xbox
cvar_t *sv_allowDownload;
cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address
#endif
cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
cvar_t *sv_showghoultraces; // report ghoul2 traces
cvar_t *sv_showloss; // report when usercmds are lost
cvar_t *sv_padPackets; // add nop bytes to messages
cvar_t *sv_killserver; // menu system can set to 1 to shut server down
cvar_t *sv_mapname;
cvar_t *sv_mapChecksum;
cvar_t *sv_serverid;
cvar_t *sv_maxRate;
cvar_t *sv_minPing;
cvar_t *sv_maxPing;
cvar_t *sv_gametype;
cvar_t *sv_pure;
cvar_t *sv_floodProtect;
cvar_t *sv_allowAnonymous;
cvar_t *sv_needpass;
cvar_t *xb_gameType;
/*
=============================================================================
EVENT MESSAGES
=============================================================================
*/
/*
===============
SV_ExpandNewlines
Converts newlines to "\n" so a line prints nicer
===============
*/
char *SV_ExpandNewlines( char *in ) {
static char string[1024];
int l;
l = 0;
while ( *in && l < sizeof(string) - 3 ) {
if ( *in == '\n' ) {
string[l++] = '\\';
string[l++] = 'n';
} else {
string[l++] = *in;
}
in++;
}
string[l] = 0;
return string;
}
/*
======================
SV_ReplacePendingServerCommands
This is ugly
======================
*/
int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) {
int i, index, csnum1, csnum2;
for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
index = i & ( MAX_RELIABLE_COMMANDS - 1 );
//
if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) {
sscanf(cmd, "cs %i", &csnum1);
sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
if ( csnum1 == csnum2 ) {
Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
/*
if ( client->netchan.remoteAddress.type != NA_BOT ) {
Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd );
}
*/
return qtrue;
}
}
}
return qfalse;
}
/*
======================
SV_AddServerCommand
The given command will be transmitted to the client, and is guaranteed to
not have future snapshot_t executed before it is executed
======================
*/
void SV_AddServerCommand( client_t *client, const char *cmd ) {
int index, i;
// this is very ugly but it's also a waste to for instance send multiple config string updates
// for the same config string index in one snapshot
// if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
// return;
// }
client->reliableSequence++;
// if we would be losing an old command that hasn't been acknowledged,
// we must drop the connection
// we check == instead of >= so a broadcast print added by SV_DropClient()
// doesn't cause a recursive drop client
if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
#ifndef FINAL_BUILD
Com_Printf( "===== pending server commands =====\n" );
for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
}
Com_Printf( "cmd %5d: %s\n", i, cmd );
#endif
// SV_DropClient( client, "Server command overflow" );
SV_DropClient( client, "@MENUS_LOST_CONNECTION" );
return;
}
index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
}
/*
=================
SV_SendServerCommand
Sends a reliable command string to be interpreted by
the client game module: "cp", "print", "chat", etc
A NULL client will broadcast to all clients
=================
*/
void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
va_list argptr;
byte message[MAX_MSGLEN];
client_t *client;
int j;
va_start (argptr,fmt);
vsprintf ((char *)message, fmt,argptr);
va_end (argptr);
if ( cl != NULL ) {
SV_AddServerCommand( cl, (char *)message );
return;
}
// hack to echo broadcast prints to console
if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) {
Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) );
}
// send the data to all relevent clients
for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
if ( client->state < CS_PRIMED ) {
continue;
}
SV_AddServerCommand( client, (char *)message );
}
}
/*
==============================================================================
MASTER SERVER FUNCTIONS
==============================================================================
*/
#ifndef _XBOX // No master on Xbox
#define NEW_RESOLVE_DURATION 86400000 //24 hours
static int g_lastResolveTime[MAX_MASTER_SERVERS];
static inline bool SV_MasterNeedsResolving(int server, int time)
{ //refresh every so often regardless of if the actual address was modified -rww
if (g_lastResolveTime[server] > time)
{ //time flowed backwards?
return true;
}
if ((time-g_lastResolveTime[server]) > NEW_RESOLVE_DURATION)
{ //it's time again
return true;
}
return false;
}
/*
================
SV_MasterHeartbeat
Send a message to the masters every few minutes to
let it know we are alive, and log information.
We will also have a heartbeat sent when a server
changes from empty to non-empty, and full to non-full,
but not on every player enter or exit.
================
*/
#define HEARTBEAT_MSEC 300*1000
#define HEARTBEAT_GAME "QuakeArena-1"
void SV_MasterHeartbeat( void ) {
static netadr_t adr[MAX_MASTER_SERVERS];
int i;
int time;
// "dedicated 1" is for lan play, "dedicated 2" is for inet public play
if ( !com_dedicated || com_dedicated->integer != 2 ) {
return; // only dedicated servers send heartbeats
}
// if not time yet, don't send anything
if ( svs.time < svs.nextHeartbeatTime ) {
return;
}
svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;
//we need to use this instead of svs.time since svs.time resets over map changes (or rather
//every time the game restarts), and we don't really need to resolve every map change
time = Com_Milliseconds();
// send to group masters
for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
if ( !sv_master[i]->string[0] ) {
continue;
}
// see if we haven't already resolved the name
// resolving usually causes hitches on win95, so only
// do it when needed
if ( sv_master[i]->modified || SV_MasterNeedsResolving(i, time) ) {
sv_master[i]->modified = qfalse;
g_lastResolveTime[i] = time;
Com_Printf( "Resolving %s\n", sv_master[i]->string );
if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) {
// if the address failed to resolve, clear it
// so we don't take repeated dns hits
Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string );
Cvar_Set( sv_master[i]->name, "" );
sv_master[i]->modified = qfalse;
continue;
}
if ( !strstr( ":", sv_master[i]->string ) ) {
adr[i].port = BigShort( PORT_MASTER );
}
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string,
adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3],
BigShort( adr[i].port ) );
}
Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string );
// this command should be changed if the server info / status format
// ever incompatably changes
NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", HEARTBEAT_GAME );
}
}
/*
=================
SV_MasterShutdown
Informs all masters that this server is going down
=================
*/
void SV_MasterShutdown( void ) {
// send a hearbeat right now
svs.nextHeartbeatTime = -9999;
SV_MasterHeartbeat();
// send it again to minimize chance of drops
svs.nextHeartbeatTime = -9999;
SV_MasterHeartbeat();
// when the master tries to poll the server, it won't respond, so
// it will be removed from the list
}
#endif // _XBOX - No master on Xbox
/*
==============================================================================
CONNECTIONLESS COMMANDS
==============================================================================
*/
/*
================
SVC_Status
Responds with all the info that qplug or qspy can see about the server
and all connected players. Used for getting detailed information after
the simple info query.
================
*/
void SVC_Status( netadr_t from ) {
char player[1024];
char status[MAX_MSGLEN];
int i;
client_t *cl;
playerState_t *ps;
int statusLength;
int playerLength;
char infostring[MAX_INFO_STRING];
// ignore if we are in single player
/*
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
return;
}
*/
strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
// echo back the parameter to status. so master servers can use it as a challenge
// to prevent timed spoofed reply packets that add ghost servers
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
// add "demo" to the sv_keywords if restricted
if ( Cvar_VariableValue( "fs_restrict" ) ) {
char keywords[MAX_INFO_STRING];
Com_sprintf( keywords, sizeof( keywords ), "demo %s",
Info_ValueForKey( infostring, "sv_keywords" ) );
Info_SetValueForKey( infostring, "sv_keywords", keywords );
}
status[0] = 0;
statusLength = 0;
for (i=0 ; i < sv_maxclients->integer ; i++) {
cl = &svs.clients[i];
if ( cl->state >= CS_CONNECTED ) {
ps = SV_GameClientNum( i );
Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
ps->persistant[PERS_SCORE], cl->ping, cl->name);
playerLength = strlen(player);
if (statusLength + playerLength >= sizeof(status) ) {
break; // can't hold any more
}
strcpy (status + statusLength, player);
statusLength += playerLength;
}
}
NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status );
}
/*
================
SVC_Info
New Xbox version - sends a short broadcast packet about our game,
only done in system link games, every THREE seconds.
================
*/
void SVC_Info( void )
{
int i, count, wDisable;
char infostring[MAX_INFO_STRING];
// We only send if we're in system link
if (xb_gameType->integer != 2)
return;
// Wait three seconds to send another
int now = Sys_Milliseconds();
if (now < svs.syslinkAdvertTime)
return;
svs.syslinkAdvertTime = now + 3000;
// don't count bots!
count = 0;
for ( i = 0 ; i < sv_maxclients->integer ; i++ ) {
if ( svs.clients[i].state >= CS_CONNECTED && svs.clients[i].netchan.remoteAddress.type != NA_BOT ) {
count++;
}
}
infostring[0] = 0;
Info_SetValueForKey( infostring, "hostname", sv_hostname->string );
Info_SetValueForKey( infostring, "mapname", sv_mapname->string );
Info_SetValueForKey( infostring, "clients", va("%i", count) );
Info_SetValueForKey( infostring, "sv_maxclients", va("%i", sv_maxclients->integer) );
Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) );
if ( sv_gametype->integer == GT_DUEL || sv_gametype->integer == GT_POWERDUEL )
{
wDisable = Cvar_VariableIntegerValue( "g_duelWeaponDisable" );
}
else
{
wDisable = Cvar_VariableIntegerValue( "g_weaponDisable" );
}
Info_SetValueForKey( infostring, "wdisable", va("%i", wDisable ) );
Info_SetValueForKey( infostring, "fdisable", va("%i", Cvar_VariableIntegerValue( "g_forcePowerDisable" ) ) );
#ifdef _XBOX
// Include Xbox specific networking info
char sxnkid[XNKID_STRING_LEN];
XNKIDToString(SysLink_GetXNKID(), sxnkid);
Info_SetValueForKey(infostring, "xnkid", sxnkid);
char sxnkey[XNKEY_STRING_LEN];
XNKEYToString(SysLink_GetXNKEY(), sxnkey);
Info_SetValueForKey(infostring, "xnkey", sxnkey);
char sxnaddr[XNADDR_STRING_LEN];
XnAddrToString(Net_GetXNADDR(), sxnaddr);
Info_SetValueForKey(infostring, "xnaddr", sxnaddr);
#endif
// NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
NET_BroadcastPrint( NS_SERVER, "infoResponse\n%s", infostring );
}
/*
================
SVC_FlushRedirect
================
*/
void SV_FlushRedirect( char *outputbuf ) {
NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
}
/*
===============
SVC_RemoteCommand
An rcon packet arrived from the network.
Shift down the remaining args
Redirect all printfs
===============
*/
void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
qboolean valid;
unsigned int i, time;
char remaining[1024];
#define SV_OUTPUTBUF_LENGTH (MAX_MSGLEN - 16)
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
static unsigned int lasttime = 0;
time = Com_Milliseconds();
if (time<(lasttime+500)) {
return;
}
lasttime = time;
if ( !strlen( sv_rconPassword->string ) ||
strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
valid = qfalse;
Com_DPrintf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
} else {
valid = qtrue;
Com_DPrintf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
}
// start redirecting all print outputs to the packet
svs.redirectAddress = from;
Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
if ( !strlen( sv_rconPassword->string ) ) {
Com_Printf ("No rconpassword set.\n");
} else if ( !valid ) {
Com_Printf ("Bad rconpassword.\n");
} else {
remaining[0] = 0;
for (i=2 ; i<Cmd_Argc() ; i++) {
strcat (remaining, Cmd_Argv(i) );
strcat (remaining, " ");
}
Cmd_ExecuteString (remaining);
}
Com_EndRedirect ();
}
/*
=================
SV_ConnectionlessPacket
A connectionless packet has four leading 0xff
characters to distinguish it from a game channel.
Clients that are in the game can still send
connectionless packets.
=================
*/
void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
char *s;
char *c;
MSG_BeginReadingOOB( msg );
MSG_ReadLong( msg ); // skip the -1 marker
if (!Q_strncmp("connect", (const char *)&msg->data[4], 7)) {
Huff_Decompress(msg, 12);
}
s = MSG_ReadStringLine( msg );
Cmd_TokenizeString( s );
c = Cmd_Argv(0);
Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
if (!Q_stricmp(c, "getstatus")) {
SVC_Status( from );
// } else if (!Q_stricmp(c, "getinfo")) {
// SVC_Info( from ); // Pass along the client's nonce so we can echo
} else if (!Q_stricmp(c, "getchallenge")) {
SV_GetChallenge( from );
} else if (!Q_stricmp(c, "connect")) {
SV_DirectConnect( from );
#ifndef _XBOX // No authorization on Xbox
} else if (!Q_stricmp(c, "ipAuthorize")) {
SV_AuthorizeIpPacket( from );
#endif
} else if (!Q_stricmp(c, "rcon")) {
SVC_RemoteCommand( from, msg );
} else if (!Q_stricmp(c, "disconnect")) {
// if a client starts up a local server, we may see some spurious
// server disconnect messages when their new server sees our final
// sequenced messages to the old client
} else {
Com_DPrintf ("bad connectionless packet from %s:\n%s\n"
, NET_AdrToString (from), s);
}
}
//============================================================================
/*
=================
SV_ReadPackets
=================
*/
void SV_PacketEvent( netadr_t from, msg_t *msg ) {
int i;
client_t *cl;
int qport;
// check for connectionless packet (0xffffffff) first
if ( msg->cursize >= 4 && *(int *)msg->data == -1) {
SV_ConnectionlessPacket( from, msg );
return;
}
// Broadcast packets should never be treated as sequenced packets!
// Don't even bother telling them to go away. It's not our game!
// if ( broadcast )
// return;
// read the qport out of the message so we can fix up
// stupid address translating routers
MSG_BeginReadingOOB( msg );
MSG_ReadLong( msg ); // sequence number
qport = MSG_ReadShort( msg ) & 0xffff;
// find which client the message is from
for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
#ifdef _XBOX
ClientManager::ActivateClient(i);
#endif
if (cl->state == CS_FREE) {
continue;
}
if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) {
continue;
}
// it is possible to have multiple clients from a single IP
// address, so they are differentiated by the qport variable
if (cl->netchan.qport != qport) {
continue;
}
// the IP port can't be used to differentiate them, because
// some address translating routers periodically change UDP
// port assignments
if (cl->netchan.remoteAddress.port != from.port) {
Com_Printf( "SV_ReadPackets: fixing up a translated port\n" );
cl->netchan.remoteAddress.port = from.port;
}
// make sure it is a valid, in sequence packet
if (SV_Netchan_Process(cl, msg)) {
// zombie clients still need to do the Netchan_Process
// to make sure they don't need to retransmit the final
// reliable message, but they don't do any other processing
if (cl->state != CS_ZOMBIE) {
cl->lastPacketTime = svs.time; // don't timeout
SV_ExecuteClientMessage( cl, msg );
}
}
return;
}
// if we received a sequenced packet from an address we don't reckognize,
// send an out of band disconnect packet to it
NET_OutOfBandPrint( NS_SERVER, from, "disconnect" );
}
/*
===================
SV_CalcPings
Updates the cl->ping variables
===================
*/
void SV_CalcPings( void ) {
int i, j;
client_t *cl;
int total, count;
int delta;
playerState_t *ps;
for (i=0 ; i < sv_maxclients->integer ; i++) {
cl = &svs.clients[i];
if ( cl->state != CS_ACTIVE ) {
cl->ping = 999;
continue;
}
if ( !cl->gentity ) {
cl->ping = 999;
continue;
}
if ( cl->gentity->r.svFlags & SVF_BOT ) {
cl->ping = 0;
continue;
}
total = 0;
count = 0;
for ( j = 0 ; j < PACKET_BACKUP ; j++ ) {
if ( cl->frames[j].messageAcked <= 0 ) {
continue;
}
delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
count++;
total += delta;
}
if (!count) {
cl->ping = 999;
} else {
cl->ping = total/count;
if ( cl->ping > 999 ) {
cl->ping = 999;
}
}
// let the game dll know about the ping
ps = SV_GameClientNum( i );
ps->ping = cl->ping;
}
}
/*
==================
SV_CheckTimeouts
If a packet has not been received from a client for timeout->integer
seconds, drop the conneciton. Server time is used instead of
realtime to avoid dropping the local client while debugging.
When a client is normally dropped, the client_t goes into a zombie state
for a few seconds to make sure any final reliable message gets resent
if necessary
==================
*/
void SV_CheckTimeouts( void ) {
int i;
client_t *cl;
int droppoint;
int zombiepoint;
droppoint = svs.time - 1000 * sv_timeout->integer;
zombiepoint = svs.time - 1000 * sv_zombietime->integer;
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
// message times may be wrong across a changelevel
if (cl->lastPacketTime > svs.time) {
cl->lastPacketTime = svs.time;
}
if (cl->state == CS_ZOMBIE
&& cl->lastPacketTime < zombiepoint) {
Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for %s\n", cl->name );
cl->state = CS_FREE; // can now be reused
continue;
}
if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) {
// wait several frames so a debugger session doesn't
// cause a timeout
if ( ++cl->timeoutCount > 5 ) {
// SV_DropClient (cl, "timed out");
SV_DropClient (cl, "@MENUS_LOST_CONNECTION");
cl->state = CS_FREE; // don't bother with zombie state
}
} else {
cl->timeoutCount = 0;
}
}
}
/*
==================
SV_CheckPaused
==================
*/
qboolean SV_CheckPaused( void ) {
int count;
client_t *cl;
int i;
if ( !cl_paused->integer ) {
return qfalse;
}
// only pause if there is just a single client connected
count = 0;
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
// if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) {
// BTO - make the cutoff be zombie. This means that the server can hit START,
// kick a player, and the game will continue to run long enough to disconnect them.
if ( cl->state >= CS_ZOMBIE && cl->netchan.remoteAddress.type != NA_BOT ) {
count++;
}
}
// if ( count > 1 ) {
if ( count > ClientManager::NumClients() ) { // So we can pause split screen
// don't pause
sv_paused->integer = 0;
return qfalse;
}
sv_paused->integer = 1;
return qtrue;
}
/*
==================
SV_CheckCvars
==================
*/
void SV_CheckCvars( void ) {
static int lastMod = -1;
qboolean changed = qfalse;
if ( sv_hostname->modificationCount != lastMod ) {
char hostname[MAX_INFO_STRING];
char *c = hostname;
lastMod = sv_hostname->modificationCount;
strcpy( hostname, sv_hostname->string );
while( *c )
{
if ( (*c == '\\') || (*c == ';') || (*c == '"'))
{
*c = '.';
changed = qtrue;
}
c++;
}
if( changed )
{
Cvar_Set("sv_hostname", hostname );
}
}
}
/*
==================
SV_Frame
Player movement occurs as a result of packet events, which
happen before SV_Frame is called
==================
*/
void SV_Frame( int msec ) {
int frameMsec;
int startTime;
// the menu kills the server with this cvar
if ( sv_killserver->integer ) {
SV_Shutdown ("Server was killed.\n");
Cvar_Set( "sv_killserver", "0" );
return;
}
if ( !com_sv_running->integer ) {
return;
}
// Send a system link info packet if needed, even if paused!
SVC_Info();
// allow pause if only the local client is connected
if ( SV_CheckPaused() ) {
return;
}
// if it isn't time for the next frame, do nothing
if ( sv_fps->integer < 1 ) {
Cvar_Set( "sv_fps", "10" );
}
frameMsec = 1000 / sv_fps->integer ;
sv.timeResidual += msec;
if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual );
if ( com_dedicated->integer && sv.timeResidual < frameMsec && (!com_timescale || com_timescale->value >= 1) ) {
// NET_Sleep will give the OS time slices until either get a packet
// or time enough for a server frame has gone by
NET_Sleep(frameMsec - sv.timeResidual);
return;
}
// if time is about to hit the 32nd bit, kick all clients
// and clear sv.time, rather
// than checking for negative time wraparound everywhere.
// 2giga-milliseconds = 23 days, so it won't be too often
if ( svs.time > 0x70000000 ) {
SV_Shutdown( "Restarting server due to time wrapping" );
//Cbuf_AddText( "vstr nextmap\n" );
Cbuf_AddText( "map_restart 0\n" );
return;
}
// this can happen considerably earlier when lots of clients play and the map doesn't change
if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) {
SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" );
//Cbuf_AddText( "vstr nextmap\n" );
Cbuf_AddText( "map_restart 0\n" );
return;
}
if( sv.restartTime && svs.time >= sv.restartTime ) {
sv.restartTime = 0;
Cbuf_AddText( "map_restart 0\n" );
return;
}
// update infostrings if anything has been changed
if ( cvar_modifiedFlags & CVAR_SERVERINFO ) {
SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) );
cvar_modifiedFlags &= ~CVAR_SERVERINFO;
}
if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) {
SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
}
if ( com_speeds->integer ) {
startTime = Sys_Milliseconds ();
} else {
startTime = 0; // quite a compiler warning
}
// update ping based on the all received frames
SV_CalcPings();
if (com_dedicated->integer) SV_BotFrame( svs.time );
// run the game simulation in chunks
while ( sv.timeResidual >= frameMsec ) {
sv.timeResidual -= frameMsec;
svs.time += frameMsec;
// let everything in the world think and move
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
}
//rww - RAGDOLL_BEGIN
G2API_SetTime(svs.time,0);
//rww - RAGDOLL_END
if ( com_speeds->integer ) {
time_game = Sys_Milliseconds () - startTime;
}
// check timeouts
SV_CheckTimeouts();
// send messages back to the clients
SV_SendClientMessages();
SV_CheckCvars();
// send a heartbeat to the master if needed
#ifndef _XBOX // No master on Xbox
SV_MasterHeartbeat();
#endif
}
//============================================================================

View File

@@ -0,0 +1,179 @@
//Anything above this #include will be ignored by the compiler
#include "../qcommon/exe_headers.h"
#include "server.h"
// TTimo: unused, commenting out to make gcc happy
#if 1
/*
==============
SV_Netchan_Encode
// first four bytes of the data are always:
long reliableAcknowledge;
==============
*/
static void SV_Netchan_Encode( client_t *client, msg_t *msg ) {
#ifdef _XBOX
return;
#endif
long reliableAcknowledge, i, index;
byte key, *string;
int srdc, sbit, soob;
if ( msg->cursize < SV_ENCODE_START ) {
return;
}
srdc = msg->readcount;
sbit = msg->bit;
soob = msg->oob;
msg->bit = 0;
msg->readcount = 0;
msg->oob = (qboolean)0;
reliableAcknowledge = MSG_ReadLong(msg);
msg->oob = (qboolean)soob;
msg->bit = sbit;
msg->readcount = srdc;
string = (byte *)client->lastClientCommandString;
index = 0;
// xor the client challenge with the netchan sequence number
key = client->challenge ^ client->netchan.outgoingSequence;
for (i = SV_ENCODE_START; i < msg->cursize; i++) {
// modify the key with the last received and with this message acknowledged client command
if (!string[index])
index = 0;
if (/*string[index] > 127 ||*/ // eurofix: remove this so we can chat in european languages... -ste
string[index] == '%')
{
key ^= '.' << (i & 1);
}
else {
key ^= string[index] << (i & 1);
}
index++;
// encode the data with this key
*(msg->data + i) = *(msg->data + i) ^ key;
}
}
/*
==============
SV_Netchan_Decode
// first 12 bytes of the data are always:
long serverId;
long messageAcknowledge;
long reliableAcknowledge;
==============
*/
static void SV_Netchan_Decode( client_t *client, msg_t *msg ) {
#ifdef _XBOX
return;
#endif
int serverId, messageAcknowledge, reliableAcknowledge;
int i, index, srdc, sbit, soob;
byte key, *string;
srdc = msg->readcount;
sbit = msg->bit;
soob = msg->oob;
msg->oob = (qboolean)0;
serverId = MSG_ReadLong(msg);
messageAcknowledge = MSG_ReadLong(msg);
reliableAcknowledge = MSG_ReadLong(msg);
msg->oob = (qboolean)soob;
msg->bit = sbit;
msg->readcount = srdc;
string = (byte *)client->reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ];
index = 0;
//
key = client->challenge ^ serverId ^ messageAcknowledge;
for (i = msg->readcount + SV_DECODE_START; i < msg->cursize; i++) {
// modify the key with the last sent and acknowledged server command
if (!string[index])
index = 0;
if (/*string[index] > 127 || */ // eurofix: remove this so we can chat in european languages... -ste
string[index] == '%')
{
key ^= '.' << (i & 1);
}
else {
key ^= string[index] << (i & 1);
}
index++;
// decode the data with this key
*(msg->data + i) = *(msg->data + i) ^ key;
}
}
#endif
/*
=================
SV_Netchan_TransmitNextFragment
=================
*/
void SV_Netchan_TransmitNextFragment( netchan_t *chan ) {
Netchan_TransmitNextFragment( chan );
}
/*
===============
SV_Netchan_Transmit
================
*/
//extern byte chksum[65536];
void SV_Netchan_Transmit( client_t *client, msg_t *msg) { //int length, const byte *data ) {
// To avoid endless recursion:
static bool droppingClient = false;
MSG_WriteByte( msg, svc_EOF );
SV_Netchan_Encode( client, msg );
if( !Netchan_Transmit( &client->netchan, msg->cursize, msg->data ) &&
!droppingClient )
{
// Don't fail when we get around to sending the removepeer to this person again!
droppingClient = true;
// Quick detection of dropped clients!
SV_DropClient( client, "@MENUS_LOST_CONNECTION" );
droppingClient = false;
}
}
/*
=================
Netchan_SV_Process
=================
*/
qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) {
int ret;
// int i;
ret = Netchan_Process( &client->netchan, msg );
if (!ret)
return qfalse;
SV_Netchan_Decode( client, msg );
// Huff_Decompress( msg, SV_DECODE_START );
// for(i=SV_DECODE_START+msg->readcount;i<msg->cursize;i++) {
// if (msg->data[i] != chksum[i-(SV_DECODE_START+msg->readcount)]) {
// Com_Error(ERR_DROP,"bad\n");
// }
// }
return qtrue;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,882 @@
//Anything above this #include will be ignored by the compiler
#include "../qcommon/exe_headers.h"
#include "server.h"
#ifdef _XBOX
#include "../cgame/cg_local.h"
#include "../client/cl_data.h"
#endif
/*
=============================================================================
Delta encode a client frame onto the network channel
A normal server packet will look like:
4 sequence number (high bit set if an oversize fragment)
<optional reliable commands>
1 svc_snapshot
4 last client reliable command
4 serverTime
1 lastframe for delta compression
1 snapFlags
1 areaBytes
<areabytes>
<playerstate>
<packetentities>
=============================================================================
*/
/*
=============
SV_EmitPacketEntities
Writes a delta update of an entityState_t list to the message.
=============
*/
static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg ) {
entityState_t *oldent, *newent;
int oldindex, newindex;
int oldnum, newnum;
int from_num_entities;
// generate the delta update
if ( !from ) {
from_num_entities = 0;
} else {
from_num_entities = from->num_entities;
}
newent = NULL;
oldent = NULL;
newindex = 0;
oldindex = 0;
while ( newindex < to->num_entities || oldindex < from_num_entities ) {
if ( newindex >= to->num_entities ) {
newnum = 9999;
} else {
newent = &svs.snapshotEntities[(to->first_entity+newindex) % svs.numSnapshotEntities];
newnum = newent->number;
}
if ( oldindex >= from_num_entities ) {
oldnum = 9999;
} else {
oldent = &svs.snapshotEntities[(from->first_entity+oldindex) % svs.numSnapshotEntities];
oldnum = oldent->number;
}
if ( newnum == oldnum ) {
// delta update from old position
// because the force parm is qfalse, this will not result
// in any bytes being emited if the entity has not changed at all
MSG_WriteDeltaEntity (msg, oldent, newent, qfalse );
oldindex++;
newindex++;
continue;
}
if ( newnum < oldnum ) {
// this is a new entity, send it from the baseline
MSG_WriteDeltaEntity (msg, &sv.svEntities[newnum].baseline, newent, qtrue );
newindex++;
continue;
}
if ( newnum > oldnum ) {
// the old entity isn't present in the new message
MSG_WriteDeltaEntity (msg, oldent, NULL, qtrue );
oldindex++;
continue;
}
}
MSG_WriteBits( msg, (MAX_GENTITIES-1), GENTITYNUM_BITS ); // end of packetentities
}
// Which client are we sending voice info about this frame?
static int curVoiceClient = 0;
static short curVoiceData[3]; // Lo-res coords
static bool bSendCurVoiceClient ;
/*
==================
SV_WriteSnapshotToClient
==================
*/
static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) {
clientSnapshot_t *frame, *oldframe;
int lastframe;
int i;
int snapFlags;
// this is the snapshot we are creating
frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ];
// try to use a previous frame as the source for delta compressing the snapshot
if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) {
// client is asking for a retransmit
oldframe = NULL;
lastframe = 0;
} else if ( client->netchan.outgoingSequence - client->deltaMessage
>= (PACKET_BACKUP - 3) ) {
// client hasn't gotten a good message through in a long time
Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name);
oldframe = NULL;
lastframe = 0;
} else {
// we have a valid snapshot to delta from
oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ];
lastframe = client->netchan.outgoingSequence - client->deltaMessage;
// the snapshot's entities may still have rolled off the buffer, though
if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) {
Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name);
oldframe = NULL;
lastframe = 0;
}
}
MSG_WriteByte (msg, svc_snapshot);
// NOTE, MRE: now sent at the start of every message from server to client
// let the client know which reliable clientCommands we have received
//MSG_WriteLong( msg, client->lastClientCommand );
// send over the current server time so the client can drift
// its view of time to try to match
MSG_WriteLong (msg, svs.time);
// what we are delta'ing from
MSG_WriteByte (msg, lastframe);
snapFlags = svs.snapFlagServerBit;
if ( client->rateDelayed ) {
snapFlags |= SNAPFLAG_RATE_DELAYED;
}
if ( client->state != CS_ACTIVE ) {
snapFlags |= SNAPFLAG_NOT_ACTIVE;
}
MSG_WriteByte (msg, snapFlags);
// send over the areabits
MSG_WriteByte (msg, frame->areabytes);
MSG_WriteData (msg, frame->areabits, frame->areabytes);
// delta encode the playerstate
if ( oldframe ) {
#ifdef _ONEBIT_COMBO
MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps, frame->pDeltaOneBit, frame->pDeltaNumBit );
#else
MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps );
#endif
if (frame->ps.m_iVehicleNum)
{ //then write the vehicle's playerstate too
if (!oldframe->ps.m_iVehicleNum)
{ //if last frame didn't have vehicle, then the old vps isn't gonna delta
//properly (because our vps on the client could be anything)
#ifdef _ONEBIT_COMBO
MSG_WriteDeltaPlayerstate( msg, NULL, &frame->vps, NULL, NULL, qtrue );
#else
MSG_WriteDeltaPlayerstate( msg, NULL, &frame->vps, qtrue );
#endif
}
else
{
#ifdef _ONEBIT_COMBO
MSG_WriteDeltaPlayerstate( msg, &oldframe->vps, &frame->vps, frame->pDeltaOneBitVeh, frame->pDeltaNumBitVeh, qtrue );
#else
MSG_WriteDeltaPlayerstate( msg, &oldframe->vps, &frame->vps, qtrue );
#endif
}
}
} else {
#ifdef _ONEBIT_COMBO
MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps, NULL, NULL );
#else
MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps );
#endif
if (frame->ps.m_iVehicleNum)
{ //then write the vehicle's playerstate too
#ifdef _ONEBIT_COMBO
MSG_WriteDeltaPlayerstate( msg, NULL, &frame->vps, NULL, NULL, qtrue );
#else
MSG_WriteDeltaPlayerstate( msg, NULL, &frame->vps, qtrue );
#endif
}
}
// delta encode the entities
SV_EmitPacketEntities (oldframe, frame, msg);
// All clients need to know every other client's position so they can do voice
// proximity code. But we don't want to use much bandwidth, so we send one client's
// position per snapshot, using only four bytes. This is computed outside of here:
if( bSendCurVoiceClient )
{
MSG_WriteByte( msg, svc_plyrPos0 + curVoiceClient ); // svc_plyrPos0 .. svc_PlyrPos9
MSG_WriteData( msg, curVoiceData, sizeof(curVoiceData) );
}
// padding for rate debugging
if ( sv_padPackets->integer ) {
for ( i = 0 ; i < sv_padPackets->integer ; i++ ) {
MSG_WriteByte (msg, svc_nop);
}
}
}
/*
==================
SV_UpdateServerCommandsToClient
(re)send all server commands the client hasn't acknowledged yet
==================
*/
void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ) {
int i;
// write any unacknowledged serverCommands
for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
MSG_WriteByte( msg, svc_serverCommand );
MSG_WriteLong( msg, i );
MSG_WriteString( msg, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
}
client->reliableSent = client->reliableSequence;
}
/*
=============================================================================
Build a client snapshot structure
=============================================================================
*/
#define MAX_SNAPSHOT_ENTITIES 1024
typedef struct {
int numSnapshotEntities;
int snapshotEntities[MAX_SNAPSHOT_ENTITIES];
} snapshotEntityNumbers_t;
/*
=======================
SV_QsortEntityNumbers
=======================
*/
static int QDECL SV_QsortEntityNumbers( const void *a, const void *b ) {
int *ea, *eb;
ea = (int *)a;
eb = (int *)b;
if ( *ea == *eb ) {
Com_Error( ERR_DROP, "SV_QsortEntityStates: duplicated entity" );
}
if ( *ea < *eb ) {
return -1;
}
return 1;
}
/*
===============
SV_AddEntToSnapshot
===============
*/
static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntity_t *gEnt, snapshotEntityNumbers_t *eNums ) {
// if we have already added this entity to this snapshot, don't add again
if ( svEnt->snapshotCounter == sv.snapshotCounter ) {
return;
}
svEnt->snapshotCounter = sv.snapshotCounter;
// if we are full, silently discard entities
if ( eNums->numSnapshotEntities == MAX_SNAPSHOT_ENTITIES ) {
return;
}
eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number;
eNums->numSnapshotEntities++;
}
/*
===============
SV_AddEntitiesVisibleFromPoint
===============
*/
float g_svCullDist = -1.0f;
static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame,
snapshotEntityNumbers_t *eNums, qboolean portal ) {
int e, i;
sharedEntity_t *ent;
svEntity_t *svEnt;
int l;
int clientarea, clientcluster;
int leafnum;
int c_fullsend;
#ifdef _XBOX
const byte *clientpvs;
const byte *bitvector;
#else
byte *clientpvs;
byte *bitvector;
#endif
vec3_t difference;
float length, radius;
// during an error shutdown message we may need to transmit
// the shutdown message after the server has shutdown, so
// specfically check for it
if ( !sv.state ) {
return;
}
leafnum = CM_PointLeafnum (origin);
clientarea = CM_LeafArea (leafnum);
clientcluster = CM_LeafCluster (leafnum);
// calculate the visible areas
frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea );
clientpvs = CM_ClusterPVS (clientcluster);
c_fullsend = 0;
for ( e = 0 ; e < sv.num_entities ; e++ ) {
ent = SV_GentityNum(e);
// never send entities that aren't linked in
if ( !ent->r.linked ) {
continue;
}
if (ent->s.eFlags & EF_PERMANENT)
{ // he's permanent, so don't send him down!
continue;
}
if (ent->s.number != e) {
Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n");
ent->s.number = e;
}
// entities can be flagged to explicitly not be sent to the client
if ( ent->r.svFlags & SVF_NOCLIENT ) {
continue;
}
// entities can be flagged to be sent to only one client
if ( ent->r.svFlags & SVF_SINGLECLIENT ) {
if ( ent->r.singleClient != frame->ps.clientNum ) {
continue;
}
}
// entities can be flagged to be sent to everyone but one client
if ( ent->r.svFlags & SVF_NOTSINGLECLIENT ) {
if ( ent->r.singleClient == frame->ps.clientNum ) {
continue;
}
}
svEnt = SV_SvEntityForGentity( ent );
// don't double add an entity through portals
if ( svEnt->snapshotCounter == sv.snapshotCounter ) {
continue;
}
// broadcast entities are always sent, and so is the main player so we don't see noclip weirdness
if ( ent->r.svFlags & SVF_BROADCAST || (e == frame->ps.clientNum) || (ent->r.broadcastClients[frame->ps.clientNum/32] & (1<<(frame->ps.clientNum%32))))
{
SV_AddEntToSnapshot( svEnt, ent, eNums );
continue;
}
if (ent->s.isPortalEnt)
{ //rww - portal entities are always sent as well
SV_AddEntToSnapshot( svEnt, ent, eNums );
continue;
}
if (com_RMG && com_RMG->integer)
{
VectorAdd(ent->r.absmax, ent->r.absmin, difference);
VectorScale(difference, 0.5f, difference);
VectorSubtract(origin, difference, difference);
length = VectorLength(difference);
// calculate the diameter
VectorSubtract(ent->r.absmax, ent->r.absmin, difference);
radius = VectorLength(difference);
if (length-radius < /*sv_RMGDistanceCull->integer*/5000.0f)
{ // more of a diameter check
SV_AddEntToSnapshot( svEnt, ent, eNums );
}
}
else
{
// ignore if not touching a PV leaf
// check area
if ( !CM_AreasConnected( clientarea, svEnt->areanum ) ) {
// doors can legally straddle two areas, so
// we may need to check another one
if ( !CM_AreasConnected( clientarea, svEnt->areanum2 ) ) {
continue; // blocked by a door
}
}
bitvector = clientpvs;
// check individual leafs
if ( !svEnt->numClusters ) {
continue;
}
l = 0;
#ifdef _XBOX
if(bitvector) {
#endif
for ( i=0 ; i < svEnt->numClusters ; i++ ) {
l = svEnt->clusternums[i];
if ( bitvector[l >> 3] & (1 << (l&7) ) ) {
break;
}
}
#ifdef _XBOX
}
#endif
// if we haven't found it to be visible,
// check overflow clusters that coudln't be stored
#ifdef _XBOX
if ( bitvector && i == svEnt->numClusters ) {
#else
if ( i == svEnt->numClusters ) {
#endif
if ( svEnt->lastCluster ) {
for ( ; l <= svEnt->lastCluster ; l++ ) {
if ( bitvector[l >> 3] & (1 << (l&7) ) ) {
break;
}
}
if ( l == svEnt->lastCluster ) {
continue; // not visible
}
} else {
continue;
}
}
if (g_svCullDist != -1.0f)
{ //do a distance cull check
VectorAdd(ent->r.absmax, ent->r.absmin, difference);
VectorScale(difference, 0.5f, difference);
VectorSubtract(origin, difference, difference);
length = VectorLength(difference);
// calculate the diameter
VectorSubtract(ent->r.absmax, ent->r.absmin, difference);
radius = VectorLength(difference);
if (length-radius >= g_svCullDist)
{ //then don't add it
continue;
}
}
// add it
SV_AddEntToSnapshot( svEnt, ent, eNums );
// if its a portal entity, add everything visible from its camera position
if ( ent->r.svFlags & SVF_PORTAL ) {
if ( ent->s.generic1 ) {
vec3_t dir;
VectorSubtract(ent->s.origin, origin, dir);
if ( VectorLengthSquared(dir) > (float) ent->s.generic1 * ent->s.generic1 ) {
continue;
}
}
SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue );
#ifdef _XBOX
//Must get clientpvs again since above call destroyed it.
clientpvs = CM_ClusterPVS (clientcluster);
#endif
}
}
}
}
/*
=============
SV_BuildClientSnapshot
Decides which entities are going to be visible to the client, and
copies off the playerstate and areabits.
This properly handles multiple recursive portals, but the render
currently doesn't.
For viewing through other player's eyes, clent can be something other than client->gentity
=============
*/
static void SV_BuildClientSnapshot( client_t *client ) {
vec3_t org;
clientSnapshot_t *frame;
snapshotEntityNumbers_t entityNumbers;
int i;
sharedEntity_t *ent;
entityState_t *state;
svEntity_t *svEnt;
sharedEntity_t *clent;
playerState_t *ps;
// bump the counter used to prevent double adding
sv.snapshotCounter++;
// this is the frame we are creating
frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ];
// clear everything in this snapshot
entityNumbers.numSnapshotEntities = 0;
Com_Memset( frame->areabits, 0, sizeof( frame->areabits ) );
frame->num_entities = 0;
clent = client->gentity;
if ( !clent || client->state == CS_ZOMBIE ) {
return;
}
// grab the current playerState_t
ps = SV_GameClientNum( client - svs.clients );
frame->ps = *ps;
#ifdef _ONEBIT_COMBO
frame->pDeltaOneBit = &ps->deltaOneBits;
frame->pDeltaNumBit = &ps->deltaNumBits;
#endif
if (ps->m_iVehicleNum)
{ //get the vehicle's playerstate too then
sharedEntity_t *veh = SV_GentityNum(ps->m_iVehicleNum);
if (veh && veh->playerState)
{ //Now VMA it and we've got ourselves a playerState
playerState_t *vps = ((playerState_t *)VM_ArgPtr((int)veh->playerState));
frame->vps = *vps;
#ifdef _ONEBIT_COMBO
frame->pDeltaOneBitVeh = &vps->deltaOneBits;
frame->pDeltaNumBitVeh = &vps->deltaNumBits;
#endif
}
}
int clientNum;
// never send client's own entity, because it can
// be regenerated from the playerstate
clientNum = frame->ps.clientNum;
if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) {
Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" );
}
svEnt = &sv.svEntities[ clientNum ];
svEnt->snapshotCounter = sv.snapshotCounter;
// find the client's viewpoint
VectorCopy( ps->origin, org );
org[2] += ps->viewheight;
// add all the entities directly visible to the eye, which
// may include portal entities that merge other viewpoints
SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse );
// if there were portals visible, there may be out of order entities
// in the list which will need to be resorted for the delta compression
// to work correctly. This also catches the error condition
// of an entity being included twice.
qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities,
sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers );
// now that all viewpoint's areabits have been OR'd together, invert
// all of them to make it a mask vector, which is what the renderer wants
for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) {
((int *)frame->areabits)[i] = ((int *)frame->areabits)[i] ^ -1;
}
// copy the entity states out
frame->num_entities = 0;
frame->first_entity = svs.nextSnapshotEntities;
for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) {
ent = SV_GentityNum(entityNumbers.snapshotEntities[i]);
state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities];
*state = ent->s;
svs.nextSnapshotEntities++;
// this should never hit, map should always be restarted first in SV_Frame
if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) {
Com_Error(ERR_FATAL, "svs.nextSnapshotEntities wrapped");
}
frame->num_entities++;
}
}
/*
====================
SV_RateMsec
Return the number of msec a given size message is supposed
to take to clear, based on the current rate
====================
*/
#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead
static int SV_RateMsec( client_t *client, int messageSize ) {
int rate;
int rateMsec;
// individual messages will never be larger than fragment size
if ( messageSize > 1500 ) {
messageSize = 1500;
}
rate = client->rate;
if ( sv_maxRate->integer ) {
if ( sv_maxRate->integer < 1000 ) {
Cvar_Set( "sv_MaxRate", "1000" );
}
if ( sv_maxRate->integer < rate ) {
rate = sv_maxRate->integer;
}
}
rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / rate;
return rateMsec;
}
/*
=======================
SV_SendMessageToClient
Called by SV_SendClientSnapshot and SV_SendClientGameState
=======================
*/
void SV_SendMessageToClient( msg_t *msg, client_t *client ) {
int rateMsec;
// MW - my attempt to fix illegible server message errors caused by
// packet fragmentation of initial snapshot.
while(client->state&&client->netchan.unsentFragments)
{
// send additional message fragments if the last message
// was too large to send at once
#ifndef FINAL_BUILD
Com_Printf ("[ISM]SV_SendClientGameState() [1] for %s, writing out old fragments\n", client->name);
#endif
SV_Netchan_TransmitNextFragment(&client->netchan);
}
// record information about the message
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize;
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time;
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1;
// send the datagram
SV_Netchan_Transmit( client, msg ); //msg->cursize, msg->data );
// set nextSnapshotTime based on rate and requested number of updates
// local clients get snapshots every frame when paused
if ( client->netchan.remoteAddress.type == NA_LOOPBACK || !logged_on ) { //Sys_IsLANAddress (client->netchan.remoteAddress) ) {
client->nextSnapshotTime = svs.time - 1;
return;
}
// normal rate / snapshotMsec calculation
rateMsec = SV_RateMsec( client, msg->cursize );
if ( rateMsec < client->snapshotMsec ) {
// never send more packets than this, no matter what the rate is at
rateMsec = client->snapshotMsec;
client->rateDelayed = qfalse;
} else {
client->rateDelayed = qtrue;
}
client->nextSnapshotTime = svs.time + rateMsec;
// don't pile up empty snapshots while connecting
if ( client->state != CS_ACTIVE ) {
// a gigantic connection message may have already put the nextSnapshotTime
// more than a second away, so don't shorten it
// do shorten if client is downloading
#ifdef _XBOX // No downloads on Xbox
if ( client->nextSnapshotTime < svs.time + 1000 ) {
#else
if ( !*client->downloadName && client->nextSnapshotTime < svs.time + 1000 ) {
#endif
client->nextSnapshotTime = svs.time + 1000;
}
}
}
/*
=======================
SV_SendClientSnapshot
Also called by SV_FinalMessage
=======================
*/
extern cvar_t *fs_gamedirvar;
void SV_SendClientSnapshot( client_t *client ) {
byte msg_buf[MAX_MSGLEN];
msg_t msg;
if (!client->sentGamedir)
{ //rww - if this is the case then make sure there is an svc_setgame sent before this snap
int i = 0;
MSG_Init (&msg, msg_buf, sizeof(msg_buf));
//have to include this for each message.
MSG_WriteLong( &msg, client->lastClientCommand );
MSG_WriteByte (&msg, svc_setgame);
while (fs_gamedirvar->string[i])
{
MSG_WriteByte(&msg, fs_gamedirvar->string[i]);
i++;
}
MSG_WriteByte(&msg, 0);
// MW - my attempt to fix illegible server message errors caused by
// packet fragmentation of initial snapshot.
//rww - reusing this code here
while(client->state&&client->netchan.unsentFragments)
{
// send additional message fragments if the last message
// was too large to send at once
#ifndef FINAL_BUILD
Com_Printf ("[ISM]SV_SendClientGameState() [1] for %s, writing out old fragments\n", client->name);
#endif
SV_Netchan_TransmitNextFragment(&client->netchan);
}
// record information about the message
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg.cursize;
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time;
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1;
// send the datagram
SV_Netchan_Transmit( client, &msg ); //msg->cursize, msg->data );
client->sentGamedir = qtrue;
}
// build the snapshot
SV_BuildClientSnapshot( client );
// bots need to have their snapshots build, but
// the query them directly without needing to be sent
if ( client->gentity && client->gentity->r.svFlags & SVF_BOT ) {
return;
}
MSG_Init (&msg, msg_buf, sizeof(msg_buf));
msg.allowoverflow = qtrue;
// NOTE, MRE: all server->client messages now acknowledge
// let the client know which reliable clientCommands we have received
MSG_WriteLong( &msg, client->lastClientCommand );
// (re)send any reliable server commands
SV_UpdateServerCommandsToClient( client, &msg );
// send over all the relevant entityState_t
// and the playerState_t
SV_WriteSnapshotToClient( client, &msg );
// Add any download data if the client is downloading
#ifndef _XBOX // No downloads on Xbox
SV_WriteDownloadToClient( client, &msg );
#endif
// check for overflow
if ( msg.overflowed ) {
Com_Printf ("WARNING: msg overflowed for %s\n", client->name);
MSG_Clear (&msg);
}
SV_SendMessageToClient( &msg, client );
}
/*
=======================
SV_SendClientMessages
=======================
*/
void SV_SendClientMessages( void ) {
int i;
client_t *c;
// Move our voice client update to the next client:
curVoiceClient = (curVoiceClient + 1) % sv_maxclients->integer;
if( svs.clients[curVoiceClient].state != CS_ACTIVE )
{
// Don't waste bandwidth:
bSendCurVoiceClient = false;
}
else
{
// Make sure we send this client's position:
bSendCurVoiceClient = true;
// Get the entity
sharedEntity_t *ent = SV_GentityNum( curVoiceClient );
// Scale their origin down to -1..1
vec3_t vcOrigin;
VectorScale( ent->playerState->origin, 1.0f / MAX_WORLD_COORD, vcOrigin );
curVoiceData[0] = vcOrigin[0] * 32767;
curVoiceData[1] = vcOrigin[1] * 32767;
curVoiceData[2] = vcOrigin[2] * 32767;
}
// send a message to each connected client
for (i=0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++) {
#ifdef _XBOX
ClientManager::ActivateClient(i);
#endif
if (!c->state) {
continue; // not connected
}
if ( svs.time < c->nextSnapshotTime ) {
continue; // not time yet
}
// send additional message fragments if the last message
// was too large to send at once
if ( c->netchan.unsentFragments ) {
c->nextSnapshotTime = svs.time +
SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart );
SV_Netchan_TransmitNextFragment( &c->netchan );
continue;
}
// generate and send a new message
SV_SendClientSnapshot( c );
}
}

894
codemp/server/sv_world.cpp Normal file
View File

@@ -0,0 +1,894 @@
//Anything above this #include will be ignored by the compiler
#include "../qcommon/exe_headers.h"
// world.c -- world query functions
#include "server.h"
#include "../ghoul2/G2_local.h"
extern CMiniHeap *G2VertSpaceServer;
/*
================
SV_ClipHandleForEntity
Returns a headnode that can be used for testing or clipping to a
given entity. If the entity is a bsp model, the headnode will
be returned, otherwise a custom box tree will be constructed.
================
*/
clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ) {
if ( ent->r.bmodel ) {
// explicit hulls in the BSP model
return CM_InlineModel( ent->s.modelindex );
}
if ( ent->r.svFlags & SVF_CAPSULE ) {
// create a temp capsule from bounding box sizes
return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue );
}
// create a temp tree from bounding box sizes
return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse );
}
/*
===============================================================================
ENTITY CHECKING
To avoid linearly searching through lists of entities during environment testing,
the world is carved up with an evenly spaced, axially aligned bsp tree. Entities
are kept in chains either at the final leafs, or at the first node that splits
them, which prevents having to deal with multiple fragments of a single entity.
===============================================================================
*/
typedef struct worldSector_s {
int axis; // -1 = leaf node
float dist;
struct worldSector_s *children[2];
svEntity_t *entities;
} worldSector_t;
#define AREA_DEPTH 4
#define AREA_NODES 64
worldSector_t sv_worldSectors[AREA_NODES];
int sv_numworldSectors;
/*
===============
SV_SectorList_f
===============
*/
void SV_SectorList_f( void ) {
int i, c;
worldSector_t *sec;
svEntity_t *ent;
for ( i = 0 ; i < AREA_NODES ; i++ ) {
sec = &sv_worldSectors[i];
c = 0;
for ( ent = sec->entities ; ent ; ent = ent->nextEntityInWorldSector ) {
c++;
}
Com_Printf( "sector %i: %i entities\n", i, c );
}
}
/*
===============
SV_CreateworldSector
Builds a uniformly subdivided tree for the given world size
===============
*/
worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) {
worldSector_t *anode;
vec3_t size;
vec3_t mins1, maxs1, mins2, maxs2;
anode = &sv_worldSectors[sv_numworldSectors];
sv_numworldSectors++;
if (depth == AREA_DEPTH) {
anode->axis = -1;
anode->children[0] = anode->children[1] = NULL;
return anode;
}
VectorSubtract (maxs, mins, size);
if (size[0] > size[1]) {
anode->axis = 0;
} else {
anode->axis = 1;
}
anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]);
VectorCopy (mins, mins1);
VectorCopy (mins, mins2);
VectorCopy (maxs, maxs1);
VectorCopy (maxs, maxs2);
maxs1[anode->axis] = mins2[anode->axis] = anode->dist;
anode->children[0] = SV_CreateworldSector (depth+1, mins2, maxs2);
anode->children[1] = SV_CreateworldSector (depth+1, mins1, maxs1);
return anode;
}
/*
===============
SV_ClearWorld
===============
*/
void SV_ClearWorld( void ) {
clipHandle_t h;
vec3_t mins, maxs;
Com_Memset( sv_worldSectors, 0, sizeof(sv_worldSectors) );
sv_numworldSectors = 0;
// get world map bounds
h = CM_InlineModel( 0 );
CM_ModelBounds( h, mins, maxs );
SV_CreateworldSector( 0, mins, maxs );
}
/*
===============
SV_UnlinkEntity
===============
*/
void SV_UnlinkEntity( sharedEntity_t *gEnt ) {
svEntity_t *ent;
svEntity_t *scan;
worldSector_t *ws;
ent = SV_SvEntityForGentity( gEnt );
gEnt->r.linked = qfalse;
ws = ent->worldSector;
if ( !ws ) {
return; // not linked in anywhere
}
ent->worldSector = NULL;
if ( ws->entities == ent ) {
ws->entities = ent->nextEntityInWorldSector;
return;
}
for ( scan = ws->entities ; scan ; scan = scan->nextEntityInWorldSector ) {
if ( scan->nextEntityInWorldSector == ent ) {
scan->nextEntityInWorldSector = ent->nextEntityInWorldSector;
return;
}
}
Com_Printf( "WARNING: SV_UnlinkEntity: not found in worldSector\n" );
}
/*
===============
SV_LinkEntity
===============
*/
#define MAX_TOTAL_ENT_LEAFS 128
void SV_LinkEntity( sharedEntity_t *gEnt ) {
worldSector_t *node;
int leafs[MAX_TOTAL_ENT_LEAFS];
int cluster;
int num_leafs;
int i, j, k;
int area;
int lastLeaf;
float *origin, *angles;
svEntity_t *ent;
ent = SV_SvEntityForGentity( gEnt );
if ( ent->worldSector ) {
SV_UnlinkEntity( gEnt ); // unlink from old position
}
// encode the size into the entityState_t for client prediction
if ( gEnt->r.bmodel ) {
gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value
} else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) {
// assume that x/y are equal and symetric
i = gEnt->r.maxs[0];
if (i<1)
i = 1;
if (i>255)
i = 255;
// z is not symetric
j = (-gEnt->r.mins[2]);
if (j<1)
j = 1;
if (j>255)
j = 255;
// and z maxs can be negative...
k = (gEnt->r.maxs[2]+32);
if (k<1)
k = 1;
if (k>255)
k = 255;
gEnt->s.solid = (k<<16) | (j<<8) | i;
if (gEnt->s.solid == SOLID_BMODEL)
{ //yikes, this would make everything explode violently.
gEnt->s.solid = (k<<16) | (j<<8) | i-1;
}
}
else
{
gEnt->s.solid = 0;
}
// get the position
origin = gEnt->r.currentOrigin;
angles = gEnt->r.currentAngles;
// set the abs box
if ( gEnt->r.bmodel && (angles[0] || angles[1] || angles[2]) ) {
// expand for rotation
float max;
int i;
max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs );
for (i=0 ; i<3 ; i++) {
gEnt->r.absmin[i] = origin[i] - max;
gEnt->r.absmax[i] = origin[i] + max;
}
} else {
// normal
VectorAdd (origin, gEnt->r.mins, gEnt->r.absmin);
VectorAdd (origin, gEnt->r.maxs, gEnt->r.absmax);
}
// because movement is clipped an epsilon away from an actual edge,
// we must fully check even when bounding boxes don't quite touch
gEnt->r.absmin[0] -= 1;
gEnt->r.absmin[1] -= 1;
gEnt->r.absmin[2] -= 1;
gEnt->r.absmax[0] += 1;
gEnt->r.absmax[1] += 1;
gEnt->r.absmax[2] += 1;
// link to PVS leafs
ent->numClusters = 0;
ent->lastCluster = 0;
ent->areanum = -1;
ent->areanum2 = -1;
//get all leafs, including solids
num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax,
leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf );
// if none of the leafs were inside the map, the
// entity is outside the world and can be considered unlinked
if ( !num_leafs ) {
return;
}
// set areas, even from clusters that don't fit in the entity array
for (i=0 ; i<num_leafs ; i++) {
area = CM_LeafArea (leafs[i]);
if (area != -1) {
// doors may legally straggle two areas,
// but nothing should evern need more than that
if (ent->areanum != -1 && ent->areanum != area) {
if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) {
Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n",
gEnt->s.number,
gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2]);
}
ent->areanum2 = area;
} else {
ent->areanum = area;
}
}
}
// store as many explicit clusters as we can
ent->numClusters = 0;
for (i=0 ; i < num_leafs ; i++) {
cluster = CM_LeafCluster( leafs[i] );
if ( cluster != -1 ) {
ent->clusternums[ent->numClusters++] = cluster;
if ( ent->numClusters == MAX_ENT_CLUSTERS ) {
break;
}
}
}
// store off a last cluster if we need to
if ( i != num_leafs ) {
ent->lastCluster = CM_LeafCluster( lastLeaf );
}
gEnt->r.linkcount++;
// find the first world sector node that the ent's box crosses
node = sv_worldSectors;
while (1)
{
if (node->axis == -1)
break;
if ( gEnt->r.absmin[node->axis] > node->dist)
node = node->children[0];
else if ( gEnt->r.absmax[node->axis] < node->dist)
node = node->children[1];
else
break; // crosses the node
}
// link it in
ent->worldSector = node;
ent->nextEntityInWorldSector = node->entities;
node->entities = ent;
gEnt->r.linked = qtrue;
}
/*
============================================================================
AREA QUERY
Fills in a list of all entities who's absmin / absmax intersects the given
bounds. This does NOT mean that they actually touch in the case of bmodels.
============================================================================
*/
typedef struct {
const float *mins;
const float *maxs;
int *list;
int count, maxcount;
} areaParms_t;
/*
====================
SV_AreaEntities_r
====================
*/
void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) {
svEntity_t *check, *next;
sharedEntity_t *gcheck;
int count;
count = 0;
for ( check = node->entities ; check ; check = next ) {
next = check->nextEntityInWorldSector;
gcheck = SV_GEntityForSvEntity( check );
if ( gcheck->r.absmin[0] > ap->maxs[0]
|| gcheck->r.absmin[1] > ap->maxs[1]
|| gcheck->r.absmin[2] > ap->maxs[2]
|| gcheck->r.absmax[0] < ap->mins[0]
|| gcheck->r.absmax[1] < ap->mins[1]
|| gcheck->r.absmax[2] < ap->mins[2]) {
continue;
}
if ( ap->count == ap->maxcount ) {
Com_DPrintf ("SV_AreaEntities: MAXCOUNT\n");
return;
}
ap->list[ap->count] = check - sv.svEntities;
ap->count++;
}
if (node->axis == -1) {
return; // terminal node
}
// recurse down both sides
if ( ap->maxs[node->axis] > node->dist ) {
SV_AreaEntities_r ( node->children[0], ap );
}
if ( ap->mins[node->axis] < node->dist ) {
SV_AreaEntities_r ( node->children[1], ap );
}
}
/*
================
SV_AreaEntities
================
*/
int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ) {
areaParms_t ap;
ap.mins = mins;
ap.maxs = maxs;
ap.list = entityList;
ap.count = 0;
ap.maxcount = maxcount;
SV_AreaEntities_r( sv_worldSectors, &ap );
return ap.count;
}
//===========================================================================
typedef struct {
vec3_t boxmins, boxmaxs;// enclose the test object along entire move
const float *mins;
const float *maxs; // size of the moving object
/*
Ghoul2 Insert Start
*/
vec3_t start;
vec3_t end;
int passEntityNum;
int contentmask;
int capsule;
int traceFlags;
int useLod;
trace_t trace; // make sure nothing goes under here for Ghoul2 collision purposes
/*
Ghoul2 Insert End
*/
} moveclip_t;
/*
====================
SV_ClipToEntity
====================
*/
void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ) {
sharedEntity_t *touch;
clipHandle_t clipHandle;
float *origin, *angles;
touch = SV_GentityNum( entityNum );
Com_Memset(trace, 0, sizeof(trace_t));
// if it doesn't have any brushes of a type we
// are looking for, ignore it
if ( ! ( contentmask & touch->r.contents ) ) {
trace->fraction = 1.0;
return;
}
// might intersect, so do an exact clip
clipHandle = SV_ClipHandleForEntity (touch);
origin = touch->r.currentOrigin;
angles = touch->r.currentAngles;
if ( !touch->r.bmodel ) {
angles = vec3_origin; // boxes don't rotate
}
CM_TransformedBoxTrace ( trace, (float *)start, (float *)end,
(float *)mins, (float *)maxs, clipHandle, contentmask,
origin, angles, capsule);
if ( trace->fraction < 1 ) {
trace->entityNum = touch->s.number;
}
}
/*
====================
SV_ClipMoveToEntities
====================
*/
#ifndef FINAL_BUILD
static float VectorDistance(vec3_t p1, vec3_t p2)
{
vec3_t dir;
VectorSubtract(p2, p1, dir);
return VectorLength(dir);
}
#endif
#pragma warning(disable : 4701) //local variable used without having been init
static void SV_ClipMoveToEntities( moveclip_t *clip ) {
static int touchlist[MAX_GENTITIES];
int i, num;
sharedEntity_t *touch;
int passOwnerNum;
trace_t trace, oldTrace= {0};
clipHandle_t clipHandle;
float *origin, *angles;
int thisOwnerShared = 1;
num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES);
if ( clip->passEntityNum != ENTITYNUM_NONE ) {
passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum;
if ( passOwnerNum == ENTITYNUM_NONE ) {
passOwnerNum = -1;
}
} else {
passOwnerNum = -1;
}
if ( SV_GentityNum(clip->passEntityNum)->r.svFlags & SVF_OWNERNOTSHARED )
{
thisOwnerShared = 0;
}
for ( i=0 ; i<num ; i++ ) {
if ( clip->trace.allsolid ) {
return;
}
touch = SV_GentityNum( touchlist[i] );
// see if we should ignore this entity
if ( clip->passEntityNum != ENTITYNUM_NONE ) {
if ( touchlist[i] == clip->passEntityNum ) {
continue; // don't clip against the pass entity
}
if ( touch->r.ownerNum == clip->passEntityNum) {
if (touch->r.svFlags & SVF_OWNERNOTSHARED)
{
if ( clip->contentmask != (MASK_SHOT | CONTENTS_LIGHTSABER) &&
clip->contentmask != (MASK_SHOT))
{ //it's not a laser hitting the other "missile", don't care then
continue;
}
}
else
{
continue; // don't clip against own missiles
}
}
if ( touch->r.ownerNum == passOwnerNum &&
!(touch->r.svFlags & SVF_OWNERNOTSHARED) &&
thisOwnerShared ) {
continue; // don't clip against other missiles from our owner
}
if (touch->s.eType == ET_MISSILE &&
!(touch->r.svFlags & SVF_OWNERNOTSHARED) &&
touch->r.ownerNum == passOwnerNum)
{ //blah, hack
continue;
}
}
// if it doesn't have any brushes of a type we
// are looking for, ignore it
if ( ! ( clip->contentmask & touch->r.contents ) ) {
continue;
}
if ((clip->contentmask == (MASK_SHOT|CONTENTS_LIGHTSABER) || clip->contentmask == MASK_SHOT) && (touch->r.contents > 0 && (touch->r.contents & CONTENTS_NOSHOT)))
{
continue;
}
// might intersect, so do an exact clip
clipHandle = SV_ClipHandleForEntity (touch);
origin = touch->r.currentOrigin;
angles = touch->r.currentAngles;
if ( !touch->r.bmodel ) {
angles = vec3_origin; // boxes don't rotate
}
CM_TransformedBoxTrace ( &trace, (float *)clip->start, (float *)clip->end,
(float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask,
origin, angles, clip->capsule);
if (clip->traceFlags & G2TRFLAG_DOGHOULTRACE)
{ // keep these older variables around for a bit, incase we need to replace them in the Ghoul2 Collision check
oldTrace = clip->trace;
}
if ( trace.allsolid ) {
clip->trace.allsolid = qtrue;
trace.entityNum = touch->s.number;
} else if ( trace.startsolid ) {
clip->trace.startsolid = qtrue;
trace.entityNum = touch->s.number;
//rww - added this because we want to get the number of an ent even if our trace starts inside it.
clip->trace.entityNum = touch->s.number;
}
if ( trace.fraction < clip->trace.fraction ) {
byte oldStart;
// make sure we keep a startsolid from a previous trace
oldStart = clip->trace.startsolid;
trace.entityNum = touch->s.number;
clip->trace = trace;
clip->trace.startsolid = (qboolean)((unsigned)clip->trace.startsolid | (unsigned)oldStart);
}
/*
Ghoul2 Insert Start
*/
#if 0
// decide if we should do the ghoul2 collision detection right here
if ((trace.entityNum == touch->s.number) && (clip->traceFlags))
{
// do we actually have a ghoul2 model here?
if (touch->s.ghoul2)
{
int oldTraceRecSize = 0;
int newTraceRecSize = 0;
int z;
// we have to do this because sometimes you may hit a model's bounding box, but not actually penetrate the Ghoul2 Models polygons
// this is, needless to say, not good. So we must check to see if we did actually hit the model, and if not, reset the trace stuff
// to what it was to begin with
// set our trace record size
for (z=0;z<MAX_G2_COLLISIONS;z++)
{
if (clip->trace.G2CollisionMap[z].mEntityNum != -1)
{
oldTraceRecSize++;
}
}
G2API_CollisionDetect(&clip->trace.G2CollisionMap[0], *((CGhoul2Info_v *)touch->s.ghoul2),
touch->s.angles, touch->s.origin, svs.time, touch->s.number, clip->start, clip->end, touch->s.modelScale, G2VertSpaceServer, clip->traceFlags, clip->useLod);
// set our new trace record size
for (z=0;z<MAX_G2_COLLISIONS;z++)
{
if (clip->trace.G2CollisionMap[z].mEntityNum != -1)
{
newTraceRecSize++;
}
}
// did we actually touch this model? If not, lets reset this ent as being hit..
if (newTraceRecSize == oldTraceRecSize)
{
clip->trace = oldTrace;
}
}
}
#else
//rww - since this is multiplayer and we don't have the luxury of violating networking rules in horrible ways,
//this must be done somewhat differently.
if ((clip->traceFlags & G2TRFLAG_DOGHOULTRACE) && trace.entityNum == touch->s.number && touch->ghoul2 && ((clip->traceFlags & G2TRFLAG_HITCORPSES) || !(touch->s.eFlags & EF_DEAD)))
{ //standard behavior will be to ignore g2 col on dead ents, but if traceFlags is set to allow, then we'll try g2 col on EF_DEAD people too.
static G2Trace_t G2Trace;
vec3_t angles;
float fRadius = 0.0f;
int tN = 0;
int bestTr = -1;
if (clip->mins[0] ||
clip->maxs[0])
{
fRadius=(clip->maxs[0]-clip->mins[0])/2.0f;
}
if (clip->traceFlags & G2TRFLAG_THICK)
{ //if using this flag, make sure it's at least 1.0f
if (fRadius < 1.0f)
{
fRadius = 1.0f;
}
}
memset (&G2Trace, 0, sizeof(G2Trace));
while (tN < MAX_G2_COLLISIONS)
{
G2Trace[tN].mEntityNum = -1;
tN++;
}
if (touch->s.number < MAX_CLIENTS)
{
VectorCopy(touch->s.apos.trBase, angles);
}
else
{
VectorCopy(touch->r.currentAngles, angles);
}
angles[ROLL] = angles[PITCH] = 0;
//I would think that you could trace from trace.endpos instead of clip->start, but that causes it to miss sometimes.. Not sure what it's off, but if it could be done like that, it would probably
//be faster.
#ifndef FINAL_BUILD
if (sv_showghoultraces->integer)
{
Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end),(*((CGhoul2Info_v *)touch->ghoul2))[0].mFileName);
}
#endif
G2API_CollisionDetect(G2Trace, *((CGhoul2Info_v *)touch->ghoul2), angles, touch->r.currentOrigin, svs.time, touch->s.number, clip->start, clip->end, touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius);
tN = 0;
while (tN < MAX_G2_COLLISIONS)
{
if (G2Trace[tN].mEntityNum == touch->s.number)
{ //ok, valid
bestTr = tN;
break;
}
else if (G2Trace[tN].mEntityNum == -1)
{ //there should not be any after the first -1
break;
}
tN++;
}
if (bestTr == -1)
{ //Well then, put the trace back to the old one.
clip->trace = oldTrace;
}
else
{ //Otherwise, set the endpos/normal/etc. to the model location hit instead of leaving it out in space.
VectorCopy(G2Trace[bestTr].mCollisionPosition, clip->trace.endpos);
VectorCopy(G2Trace[bestTr].mCollisionNormal, clip->trace.plane.normal);
if (clip->traceFlags & G2TRFLAG_GETSURFINDEX)
{ //we have requested that surfaceFlags be stomped over with the g2 hit surface index.
if (clip->trace.entityNum == G2Trace[bestTr].mEntityNum)
{
clip->trace.surfaceFlags = G2Trace[bestTr].mSurfaceIndex;
}
}
}
}
#endif
/*
Ghoul2 Insert End
*/
}
}
#pragma warning(default : 4701) //local variable used without having been init
/*
==================
SV_Trace
Moves the given mins/maxs volume through the world from start to end.
passEntityNum and entities owned by passEntityNum are explicitly not checked.
==================
*/
/*
Ghoul2 Insert Start
*/
void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule, int traceFlags, int useLod ) {
/*
Ghoul2 Insert End
*/
moveclip_t clip;
int i;
if ( !mins ) {
mins = vec3_origin;
}
if ( !maxs ) {
maxs = vec3_origin;
}
Com_Memset ( &clip, 0, sizeof ( moveclip_t ) );
// clip to world
CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, capsule );
clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
if ( clip.trace.fraction == 0 ) {
*results = clip.trace;
return; // blocked immediately by the world
}
clip.contentmask = contentmask;
/*
Ghoul2 Insert Start
*/
VectorCopy( start, clip.start );
clip.traceFlags = traceFlags;
clip.useLod = useLod;
/*
Ghoul2 Insert End
*/
// VectorCopy( clip.trace.endpos, clip.end );
VectorCopy( end, clip.end );
clip.mins = mins;
clip.maxs = maxs;
clip.passEntityNum = passEntityNum;
clip.capsule = capsule;
// create the bounding box of the entire move
// we can limit it to the part of the move not
// already clipped off by the world, which can be
// a significant savings for line of sight and shot traces
for ( i=0 ; i<3 ; i++ ) {
if ( end[i] > start[i] ) {
clip.boxmins[i] = clip.start[i] + clip.mins[i] - 1;
clip.boxmaxs[i] = clip.end[i] + clip.maxs[i] + 1;
} else {
clip.boxmins[i] = clip.end[i] + clip.mins[i] - 1;
clip.boxmaxs[i] = clip.start[i] + clip.maxs[i] + 1;
}
}
// clip to other solid entities
SV_ClipMoveToEntities ( &clip );
*results = clip.trace;
}
/*
=============
SV_PointContents
=============
*/
int SV_PointContents( const vec3_t p, int passEntityNum ) {
int touch[MAX_GENTITIES];
sharedEntity_t *hit;
int i, num;
int contents, c2;
clipHandle_t clipHandle;
float *angles;
// get base contents from world
contents = CM_PointContents( p, 0 );
// or in contents from all the other entities
num = SV_AreaEntities( p, p, touch, MAX_GENTITIES );
for ( i=0 ; i<num ; i++ ) {
if ( touch[i] == passEntityNum ) {
continue;
}
hit = SV_GentityNum( touch[i] );
// might intersect, so do an exact clip
clipHandle = SV_ClipHandleForEntity( hit );
angles = hit->s.angles;
if ( !hit->r.bmodel ) {
angles = vec3_origin; // boxes don't rotate
}
c2 = CM_TransformedPointContents (p, clipHandle, hit->s.origin, hit->s.angles);
contents |= c2;
}
return contents;
}