mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 21:57:57 +03:00
Hard reset
This commit is contained in:
commit
09bed43f97
1594 changed files with 892326 additions and 0 deletions
62
code/server/game.cpp
Normal file
62
code/server/game.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015 the OpenMoHAA team
|
||||
|
||||
This file is part of OpenMoHAA source code.
|
||||
|
||||
OpenMoHAA source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
OpenMoHAA source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OpenMoHAA source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
// game.cpp : TU Game.
|
||||
|
||||
#include "game.h"
|
||||
|
||||
Game game;
|
||||
|
||||
void CacheResource( const char *name )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SimpleEntity *G_FindTarget( SimpleEntity *next, const char *classname )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Vector G_GetMovedir( float angle )
|
||||
{
|
||||
if( angle == -1.0f )
|
||||
{
|
||||
return Vector( 0.0f, 0.0f, 1.0f );
|
||||
}
|
||||
else if( angle == -2.0f )
|
||||
{
|
||||
return Vector( 0.0f, 0.0f, -1.0f );
|
||||
}
|
||||
|
||||
angle *= ( M_PI * 2.0f / 360.0f );
|
||||
return Vector( cos( angle ), sin( angle ), 0.0f );
|
||||
}
|
||||
|
||||
float G_Random( float value )
|
||||
{
|
||||
return fmod( rand(), value );
|
||||
}
|
||||
|
||||
CLASS_DECLARATION( Listener, Game, NULL )
|
||||
{
|
||||
{ NULL, NULL }
|
||||
};
|
40
code/server/game.h
Normal file
40
code/server/game.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015 the OpenMoHAA team
|
||||
|
||||
This file is part of OpenMoHAA source code.
|
||||
|
||||
OpenMoHAA source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
OpenMoHAA source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OpenMoHAA source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
// game.h : TU Game.
|
||||
|
||||
#ifndef __GAME_H__
|
||||
#define __GAME_H__
|
||||
|
||||
#include <listener.h>
|
||||
|
||||
class Game : public Listener {
|
||||
CLASS_PROTOTYPE( Game );
|
||||
};
|
||||
|
||||
extern Game game;
|
||||
|
||||
SimpleEntity *G_FindTarget( SimpleEntity *next, const char *classname );
|
||||
Vector G_GetMovedir( float angle );
|
||||
float G_Random( float value );
|
||||
|
||||
#endif /* __GAME_H__*/
|
46
code/server/level.cpp
Normal file
46
code/server/level.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015 the OpenMoHAA team
|
||||
|
||||
This file is part of OpenMoHAA source code.
|
||||
|
||||
OpenMoHAA source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
OpenMoHAA source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OpenMoHAA source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
// game.cpp : TU Level.
|
||||
|
||||
#include "level.h"
|
||||
|
||||
Level level;
|
||||
|
||||
void Level::setTime( int levelTime )
|
||||
{
|
||||
svsTime = levelTime;
|
||||
inttime = levelTime - svsStartTime;
|
||||
svsFloatTime = levelTime / 1000.0f;
|
||||
time = inttime / 1000.0f;
|
||||
}
|
||||
|
||||
void Level::setFrametime( int frametime )
|
||||
{
|
||||
intframetime = frametime;
|
||||
this->frametime = frametime / 1000.0f;
|
||||
}
|
||||
|
||||
CLASS_DECLARATION( Listener, Level, NULL )
|
||||
{
|
||||
{ NULL, NULL }
|
||||
};
|
70
code/server/level.h
Normal file
70
code/server/level.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015 the OpenMoHAA team
|
||||
|
||||
This file is part of OpenMoHAA source code.
|
||||
|
||||
OpenMoHAA source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
OpenMoHAA source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OpenMoHAA source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
// level.h : TU Level.
|
||||
|
||||
#ifndef __LEVEL_H__
|
||||
#define __LEVEL_H__
|
||||
|
||||
#include <listener.h>
|
||||
|
||||
class SimpleArchivedEntity;
|
||||
|
||||
class Level : public Listener {
|
||||
public:
|
||||
bool m_LoopDrop;
|
||||
bool m_LoopProtection;
|
||||
|
||||
str m_mapscript;
|
||||
str current_map;
|
||||
|
||||
// Level time
|
||||
int framenum;
|
||||
int inttime;
|
||||
int intframetime;
|
||||
|
||||
float time;
|
||||
float frametime;
|
||||
|
||||
int spawnflags;
|
||||
|
||||
// Server time
|
||||
int svsTime;
|
||||
float svsFloatTime;
|
||||
int svsStartTime;
|
||||
int svsEndTime;
|
||||
|
||||
bool m_bScriptSpawn;
|
||||
bool m_bRejectSpawn;
|
||||
|
||||
Container< SimpleArchivedEntity * > m_SimpleArchivedEntities;
|
||||
|
||||
public:
|
||||
CLASS_PROTOTYPE( Level );
|
||||
|
||||
void setTime( int _svsTime_ );
|
||||
void setFrametime( int frameTime );
|
||||
};
|
||||
|
||||
extern Level level;
|
||||
|
||||
#endif /* __LEVEL_H__*/
|
533
code/server/server.h
Normal file
533
code/server/server.h
Normal file
|
@ -0,0 +1,533 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Quake III Arena source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// server.h
|
||||
|
||||
#ifndef __SERVER_H__
|
||||
#define __SERVER_H__
|
||||
|
||||
#include "../qcommon/q_shared.h"
|
||||
#include "../qcommon/qcommon.h"
|
||||
#include "../game/bg_public.h"
|
||||
#include "../game/g_public.h"
|
||||
|
||||
//=============================================================================
|
||||
|
||||
#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND
|
||||
// GAME BOTH REFERENCE !!!
|
||||
|
||||
#define MAX_ENT_CLUSTERS 16
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct svEntity_s {
|
||||
struct worldSector_s *worldSector;
|
||||
struct svEntity_s *nextEntityInWorldSector;
|
||||
|
||||
entityState_t baseline; // for delta compression of initial sighting
|
||||
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
|
||||
} svEntity_t;
|
||||
|
||||
typedef enum {
|
||||
SS_DEAD, // no map loaded
|
||||
SS_LOADING, // spawning level entities
|
||||
SS_LOADING2,
|
||||
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; // the feed key that we use to compute the pure checksum strings
|
||||
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
|
||||
// the serverId associated with the current checksumFeed (always <= serverId)
|
||||
int checksumFeedServerId;
|
||||
int snapshotCounter; // incremented for each snapshot built
|
||||
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];
|
||||
|
||||
int farplane;
|
||||
qboolean skyportal;
|
||||
|
||||
char *entityParsePoint; // used during game VM init
|
||||
|
||||
// the game virtual machine will update these on init and changes
|
||||
gentity_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
|
||||
} server_t;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
int areabytes;
|
||||
byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits
|
||||
playerState_t ps;
|
||||
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 netchan_buffer_s {
|
||||
msg_t msg;
|
||||
byte msgBuffer[MAX_MSGLEN];
|
||||
struct netchan_buffer_s *next;
|
||||
} netchan_buffer_t;
|
||||
|
||||
typedef struct client_s {
|
||||
clientState_t state;
|
||||
char userinfo[MAX_INFO_STRING]; // name, etc
|
||||
|
||||
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;
|
||||
|
||||
usereyes_t lastEyeinfo;
|
||||
|
||||
int lastMessageNum; // for delta compression
|
||||
int lastClientCommand; // reliable client message sequence
|
||||
char lastClientCommandString[MAX_STRING_CHARS];
|
||||
gentity_t *gentity; // SV_GentityNum(clientnum)
|
||||
char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked
|
||||
|
||||
// downloading
|
||||
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
|
||||
|
||||
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;
|
||||
qboolean gotCP; // TTimo - additional flag to distinguish between a bad pure checksum, and no cp command at all
|
||||
netchan_t netchan;
|
||||
// TTimo
|
||||
// queuing outgoing fragmented messages to send them properly, without udp packet bursts
|
||||
// in case large fragmented messages are stacking up
|
||||
// buffer them into this queue, and hand them out to netchan as needed
|
||||
netchan_buffer_t *netchan_start_queue;
|
||||
netchan_buffer_t **netchan_end_queue;
|
||||
|
||||
int oldServerTime;
|
||||
qboolean csUpdated[MAX_CONFIGSTRINGS+1];
|
||||
|
||||
server_sound_t server_sounds[ MAX_SERVER_SOUNDS ];
|
||||
int number_of_server_sounds;
|
||||
qboolean locprint;
|
||||
int XOffset;
|
||||
int YOffset;
|
||||
char centerprint[ 256 ];
|
||||
} 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
|
||||
|
||||
typedef struct {
|
||||
int iFlags;
|
||||
char szName[ 64 ];
|
||||
} soundSfx_t;
|
||||
|
||||
typedef struct {
|
||||
qboolean bPlaying;
|
||||
int iStatus;
|
||||
soundSfx_t sfx;
|
||||
int iEntNum;
|
||||
int iEntChannel;
|
||||
float vOrigin[ 3 ];
|
||||
float fVolume;
|
||||
int iBaseRate;
|
||||
float fNewPitchMult;
|
||||
float fMinDist;
|
||||
float fMaxDist;
|
||||
int iStartTime;
|
||||
int iTime;
|
||||
int iNextCheckObstructionTime;
|
||||
int iEndTime;
|
||||
int iFlags;
|
||||
int iOffset;
|
||||
int iLoopCount;
|
||||
} soundChan_t;
|
||||
|
||||
typedef struct {
|
||||
soundChan_t Channels[ 96 ];
|
||||
} soundSystem_t;
|
||||
|
||||
// this structure will be cleared only when the game dll changes
|
||||
typedef struct {
|
||||
qboolean initialized; // sv_init has completed
|
||||
|
||||
int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer()
|
||||
|
||||
int time; // will be strictly increasing across level changes
|
||||
int startTime;
|
||||
int lastTime;
|
||||
int serverLagTime;
|
||||
qboolean autosave;
|
||||
int mapTime;
|
||||
|
||||
client_t *clients; // [sv_maxclients->integer];
|
||||
int iNumClients;
|
||||
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
|
||||
char gameName[ MAX_QPATH ];
|
||||
char mapName[ MAX_QPATH ];
|
||||
char rawServerName[ MAX_QPATH ];
|
||||
int areabits_warning_time;
|
||||
qboolean soundsNeedLoad;
|
||||
char tm_filename[ MAX_QPATH ];
|
||||
int tm_loopcount;
|
||||
int tm_offset;
|
||||
|
||||
soundSystem_t soundSystem;
|
||||
} serverStatic_t;
|
||||
|
||||
//=============================================================================
|
||||
|
||||
extern cvar_t *sv_mapname;
|
||||
extern serverStatic_t svs; // persistant server info across maps
|
||||
extern server_t sv; // cleared each map
|
||||
extern gameExport_t *ge; // game exports
|
||||
|
||||
#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_showloss;
|
||||
extern cvar_t *sv_padPackets;
|
||||
extern cvar_t *sv_killserver;
|
||||
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_pure;
|
||||
extern cvar_t *sv_floodProtect;
|
||||
extern cvar_t *sv_maplist;
|
||||
extern cvar_t *sv_drawentities;
|
||||
extern cvar_t *sv_deeptracedebug;
|
||||
extern cvar_t *g_gametype;
|
||||
extern cvar_t *g_gametypestring;
|
||||
extern cvar_t *sv_chatter;
|
||||
extern cvar_t *sv_gamename;
|
||||
extern cvar_t *sv_location;
|
||||
extern cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
|
||||
extern cvar_t *sv_strictAuth;
|
||||
|
||||
extern debugline_t *DebugLines;
|
||||
extern int numDebugLines;
|
||||
extern debugstring_t *DebugStrings;
|
||||
extern int numDebugStrings;
|
||||
|
||||
//===========================================================
|
||||
|
||||
//
|
||||
// sv_main.c
|
||||
//
|
||||
void SV_FinalMessage( const 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);
|
||||
|
||||
void SV_ArchiveHudDrawElements( qboolean loading );
|
||||
void SV_HudDrawShader( int iInfo, char *name );
|
||||
void SV_HudDrawAlign( int iInfo, int iHorizontalAlign, int iVerticalAlign );
|
||||
void SV_HudDrawRect( int iInfo, int iX, int iY, int iWidth, int iHeight );
|
||||
void SV_HudDrawVirtualSize( int iInfo, qboolean bVirtualScreen );
|
||||
void SV_HudDrawColor( int iInfo, vec3_t vColor );
|
||||
void SV_HudDrawAlpha( int iInfo, float alpha );
|
||||
void SV_HudDrawString( int iInfo, const char *string );
|
||||
void SV_HudDrawFont( int iInfo, const char *name );
|
||||
void SV_ArchiveViewModelAnimation( qboolean loading /* 0x8 */ );
|
||||
void SV_ArchiveStopwatch( qboolean loading );
|
||||
void SV_ArchivePersistantFile( qboolean loading );
|
||||
void SV_ArchiveLevel( qboolean loading );
|
||||
qboolean SV_ArchiveLevelFile( qboolean loading, qboolean autosave );
|
||||
void S_Save( fileHandle_t f );
|
||||
void S_Load( fileHandle_t f );
|
||||
qboolean SV_ArchiveServerFile( qboolean loading, qboolean autosave );
|
||||
void SV_Loadgame_f( void );
|
||||
void SV_SavegameFilename( const char *name, char *fileName, int length );
|
||||
qboolean SV_AllowSaveGame( void );
|
||||
qboolean SV_DoSaveGame();
|
||||
void SV_SaveGame( const char *gamename, qboolean autosave );
|
||||
void SV_Savegame_f( void );
|
||||
void SV_CheckSaveGame( void );
|
||||
void SV_Autosavegame_f( void );
|
||||
|
||||
//
|
||||
// sv_init.c
|
||||
//
|
||||
void SV_ClearSvsTimeFixups( void );
|
||||
void SV_FinishSvsTimeFixups( void );
|
||||
void SV_AddSvsTimeFixup( int *piTime );
|
||||
void SV_SetConfigstring( int index, const char *val );
|
||||
char *SV_GetConfigstring( int index );
|
||||
int SV_FindIndex( const char *name, int start, int max, qboolean create );
|
||||
int SV_ModelIndex( const char *name );
|
||||
void SV_ClearModel( int index );
|
||||
int SV_SoundIndex( const char *name, qboolean streamed );
|
||||
int SV_ImageIndex( const char *name );
|
||||
int SV_ItemIndex( const char *name );
|
||||
void SV_SetLightStyle( int index, const char *data );
|
||||
void SV_UpdateConfigstrings( client_t *client );
|
||||
|
||||
void SV_SetUserinfo( int index, const char *val );
|
||||
void SV_GetUserinfo( int index, char *buffer, int bufferSize );
|
||||
|
||||
void SV_ChangeMaxClients( void );
|
||||
void SV_SpawnServer( const char *server, qboolean loadgame, qboolean restart, qboolean bTransition );
|
||||
|
||||
|
||||
|
||||
//
|
||||
// sv_client.c
|
||||
//
|
||||
void SV_GetChallenge( netadr_t from );
|
||||
|
||||
void SV_DirectConnect( netadr_t from );
|
||||
|
||||
void SV_AuthorizeIpPacket( netadr_t from );
|
||||
|
||||
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 );
|
||||
|
||||
//
|
||||
// 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
|
||||
//
|
||||
void SV_ClearModelUserCounts( void );
|
||||
int SV_NumForGentity( gentity_t *ent );
|
||||
gentity_t *SV_GentityNum( int num );
|
||||
playerState_t *SV_GameClientNum( int num );
|
||||
svEntity_t *SV_SvEntityForGentity( gentity_t *gEnt );
|
||||
gentity_t *SV_GEntityForSvEntity( svEntity_t *svEnt );
|
||||
void SV_InitGameProgs ( void );
|
||||
void SV_ShutdownGameProgs ( void );
|
||||
qboolean SV_inPVS (const vec3_t p1, const vec3_t p2);
|
||||
// su44: MoHAA game -> cgame messages
|
||||
void SV_WriteCGMToClient (client_t *client, msg_t *msg);
|
||||
void SV_InitAllCGMessages ();
|
||||
|
||||
//
|
||||
// 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 );
|
||||
|
||||
//
|
||||
// sv_snd.c
|
||||
//
|
||||
void SV_Sound( vec3_t *org, int entnum, int channel, const char *sound_name, float volume, float mindist, float pitch, float maxdist, qboolean streamed );
|
||||
void SV_ClearSounds( client_t *client );
|
||||
void SV_StopSound( int entnum, int channel );
|
||||
|
||||
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( gentity_t *ent );
|
||||
// call before removing an entity, and before trying to move one,
|
||||
// so it doesn't clip against itself
|
||||
|
||||
void SV_LinkEntity( gentity_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 gentity_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.
|
||||
|
||||
baseshader_t *SV_GetShaderPointer( int iShaderNum );
|
||||
int SV_PointContents( const vec3_t p, int passEntityNum );
|
||||
// returns the CONTENTS_* value from the world and all entities at the given point.
|
||||
|
||||
qboolean SV_SightTraceEntity( gentity_t *touch, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int contentmask, qboolean cylinder );
|
||||
qboolean SV_SightTrace( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int passEntityNum2, int contentmask, qboolean cylinder );
|
||||
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, qboolean cylinder, qboolean traceDeep );
|
||||
void SV_TraceDeep( trace_t *results, const vec3_t vStart, const vec3_t vEnd, int iBrushMask, gentity_t *touch );
|
||||
// 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 );
|
||||
// clip to a specific entity
|
||||
|
||||
//
|
||||
// sv_net_chan.c
|
||||
//
|
||||
void SV_Netchan_Transmit( client_t *client, msg_t *msg);
|
||||
void SV_Netchan_TransmitNextFragment( client_t *client );
|
||||
qboolean SV_Netchan_Process( client_t *client, msg_t *msg );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // __SERVER_H__
|
632
code/server/sv_bot.c
Normal file
632
code/server/sv_bot.c
Normal file
|
@ -0,0 +1,632 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Quake III Arena source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// sv_bot.c
|
||||
|
||||
#include "server.h"
|
||||
#include "../botlib/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;
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotAllocateClient
|
||||
==================
|
||||
*/
|
||||
int SV_BotAllocateClient(void) {
|
||||
int i;
|
||||
client_t *cl;
|
||||
|
||||
// find a client slot
|
||||
for ( i = 0, cl = svs.clients; 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,qfalse);
|
||||
//copy the trace information
|
||||
bsptrace->allsolid = trace.allsolid;
|
||||
bsptrace->startsolid = 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);
|
||||
//copy the trace information
|
||||
bsptrace->allsolid = trace.allsolid;
|
||||
bsptrace->startsolid = 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_GetMemory
|
||||
==================
|
||||
*/
|
||||
void *BotImport_GetMemory(int size) {
|
||||
void *ptr;
|
||||
|
||||
ptr = Z_TagMalloc( size, TAG_BOTLIB );
|
||||
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 (!ge) 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 //wombat: disabled for now
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_BotInitBotLib
|
||||
==================
|
||||
*/
|
||||
void SV_BotInitBotLib(void) {
|
||||
botlib_import_t botlib_import;
|
||||
|
||||
if (debugpolygons) Z_Free(debugpolygons);
|
||||
bot_maxdebugpolys = Cvar_VariableIntegerValue("bot_maxdebugpolys");
|
||||
debugpolygons = Z_Malloc(sizeof(bot_debugpoly_t) * bot_maxdebugpolys);
|
||||
|
||||
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 = 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); // 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;
|
||||
}
|
||||
|
1526
code/server/sv_ccmds.c
Normal file
1526
code/server/sv_ccmds.c
Normal file
File diff suppressed because it is too large
Load diff
1580
code/server/sv_client.c
Normal file
1580
code/server/sv_client.c
Normal file
File diff suppressed because it is too large
Load diff
1687
code/server/sv_game.c
Normal file
1687
code/server/sv_game.c
Normal file
File diff suppressed because it is too large
Load diff
1086
code/server/sv_init.c
Normal file
1086
code/server/sv_init.c
Normal file
File diff suppressed because it is too large
Load diff
996
code/server/sv_main.c
Normal file
996
code/server/sv_main.c
Normal file
|
@ -0,0 +1,996 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Quake III Arena source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
cvar_t *sv_mapname;
|
||||
serverStatic_t svs; // persistant server info
|
||||
server_t sv; // local server
|
||||
//vm_t *gvm = NULL; // game virtual machine
|
||||
gameExport_t *ge = NULL;
|
||||
|
||||
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_allowDownload;
|
||||
cvar_t *sv_maxclients;
|
||||
|
||||
cvar_t *sv_privateClients; // number of clients reserved for password
|
||||
cvar_t *sv_hostname;
|
||||
cvar_t *sv_master[ MAX_MASTER_SERVERS ]; // master server ip address
|
||||
cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
|
||||
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_mapChecksum;
|
||||
cvar_t *sv_serverid;
|
||||
cvar_t *sv_maxRate;
|
||||
cvar_t *sv_minPing;
|
||||
cvar_t *sv_maxPing;
|
||||
cvar_t *sv_pure;
|
||||
cvar_t *sv_floodProtect;
|
||||
cvar_t *sv_maplist;
|
||||
cvar_t *sv_drawentities;
|
||||
cvar_t *sv_deeptracedebug;
|
||||
cvar_t *g_gametype;
|
||||
cvar_t *g_gametypestring;
|
||||
cvar_t *sv_chatter;
|
||||
cvar_t *sv_gamename;
|
||||
cvar_t *sv_location;
|
||||
|
||||
// FIXME: use another global network ?
|
||||
//cvar_t *sv_debug_gamepsy;
|
||||
//cvar_t *sv_gamespy;
|
||||
cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
|
||||
cvar_t *sv_strictAuth;
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
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;
|
||||
// }
|
||||
|
||||
// do not send commands until the gamestate has been sent
|
||||
if( client->state < CS_PRIMED )
|
||||
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 ) {
|
||||
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 );
|
||||
SV_DropClient( client, "Server command overflow" );
|
||||
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);
|
||||
Q_vsnprintf ((char *)message, sizeof(message), fmt,argptr);
|
||||
va_end (argptr);
|
||||
|
||||
// Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt
|
||||
// The actual cause of the bug is probably further downstream
|
||||
// and should maybe be addressed later, but this certainly
|
||||
// fixes the problem for now
|
||||
if ( strlen ((char *)message) > 1022 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
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++) {
|
||||
SV_AddServerCommand( client, (char *)message );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
MASTER SERVER FUNCTIONS
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
================
|
||||
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 "mohaa-1"
|
||||
void SV_MasterHeartbeat( void ) {
|
||||
static netadr_t adr[MAX_MASTER_SERVERS];
|
||||
int i;
|
||||
|
||||
// "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;
|
||||
|
||||
|
||||
// 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_master[i]->modified = qfalse;
|
||||
|
||||
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 ( !strchr( 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
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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) );
|
||||
|
||||
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",
|
||||
// su44: ps->persistant is not avaible in MoHAA
|
||||
// ps->persistant[PERS_SCORE], cl->ping, cl->name);
|
||||
0, 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
|
||||
|
||||
Responds with a short info message that should be enough to determine
|
||||
if a user is interested in a server to do a full status
|
||||
================
|
||||
*/
|
||||
void SVC_Info( netadr_t from ) {
|
||||
int i, count;
|
||||
char *gamedir;
|
||||
char infostring[MAX_INFO_STRING];
|
||||
|
||||
// ignore if we are in single player
|
||||
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led
|
||||
* to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory.
|
||||
*/
|
||||
|
||||
// A maximum challenge length of 128 should be more than plenty.
|
||||
if(strlen(Cmd_Argv(1)) > 128)
|
||||
return;
|
||||
|
||||
// don't count privateclients
|
||||
count = 0;
|
||||
for ( i = sv_privateClients->integer ; i < svs.iNumClients ; i++ ) {
|
||||
if ( svs.clients[i].state >= CS_CONNECTED ) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
infostring[0] = 0;
|
||||
|
||||
// echo back the parameter to status. so servers can use it as a challenge
|
||||
// to prevent timed spoofed reply packets that add ghost servers
|
||||
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
|
||||
|
||||
Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) );
|
||||
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", svs.iNumClients - sv_privateClients->integer ) );
|
||||
Info_SetValueForKey( infostring, "gametype", va("%i", g_gametype->integer ) );
|
||||
Info_SetValueForKey( infostring, "gametypestring", g_gametypestring->string );
|
||||
Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
|
||||
|
||||
if( sv_minPing->integer ) {
|
||||
Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
|
||||
}
|
||||
if( sv_maxPing->integer ) {
|
||||
Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) );
|
||||
}
|
||||
gamedir = Cvar_VariableString( "fs_game" );
|
||||
if( *gamedir ) {
|
||||
Info_SetValueForKey( infostring, "game", gamedir );
|
||||
}
|
||||
|
||||
NET_OutOfBandPrint( NS_SERVER, from, "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 time;
|
||||
char remaining[1024];
|
||||
// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
|
||||
// (OOB messages are the bottleneck here)
|
||||
#define SV_OUTPUTBUF_LENGTH (1024 - 16)
|
||||
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
|
||||
static unsigned int lasttime = 0;
|
||||
char *cmd_aux;
|
||||
|
||||
// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
|
||||
time = Com_Milliseconds();
|
||||
if ( (unsigned)( time - lasttime ) < 500u ) {
|
||||
return;
|
||||
}
|
||||
lasttime = time;
|
||||
|
||||
if ( !strlen( sv_rconPassword->string ) ||
|
||||
strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
|
||||
valid = qfalse;
|
||||
Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
|
||||
} else {
|
||||
valid = qtrue;
|
||||
Com_Printf ("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 on the server.\n");
|
||||
} else if ( !valid ) {
|
||||
Com_Printf ("Bad rconpassword.\n");
|
||||
} else {
|
||||
remaining[0] = 0;
|
||||
|
||||
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
|
||||
// get the command directly, "rcon <pass> <command>" to avoid quoting issues
|
||||
// extract the command by walking
|
||||
// since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing
|
||||
cmd_aux = Cmd_Cmd();
|
||||
cmd_aux+=4;
|
||||
while(cmd_aux[0]==' ')
|
||||
cmd_aux++;
|
||||
while(cmd_aux[0] && cmd_aux[0]!=' ') // password
|
||||
cmd_aux++;
|
||||
while(cmd_aux[0]==' ')
|
||||
cmd_aux++;
|
||||
|
||||
Q_strcat( remaining, sizeof(remaining), cmd_aux);
|
||||
|
||||
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
|
||||
|
||||
MSG_ReadByte( msg ); // skip the direction byte
|
||||
|
||||
if (!Q_strncmp("connect", (char *) &msg->data[5], 7)) {
|
||||
Huff_Decompress(msg, 13);
|
||||
}
|
||||
|
||||
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 );
|
||||
} else if (!Q_stricmp(c, "getchallenge")) {
|
||||
SV_GetChallenge( from );
|
||||
} else if (!Q_stricmp(c, "connect")) {
|
||||
SV_DirectConnect( from );
|
||||
} else if (!Q_stricmp(c, "ipAuthorize")) {
|
||||
SV_AuthorizeIpPacket( from );
|
||||
} 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;
|
||||
}
|
||||
|
||||
// read the qport out of the message so we can fix up
|
||||
// stupid address translating routers
|
||||
MSG_BeginReadingOOB( msg );
|
||||
i=MSG_ReadLong( msg ); // sequence number
|
||||
qport = MSG_ReadShort( msg ) & 0xffff;
|
||||
|
||||
// find which client the message is from
|
||||
for (i=0, cl=svs.clients ; i < svs.iNumClients; i++,cl++) {
|
||||
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_PacketEvent: 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.lastTime; // don't timeout
|
||||
SV_ExecuteClientMessage( cl, msg );
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// if we received a sequenced packet from an address we don't recognize,
|
||||
// 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) {
|
||||
// using the client id cause the cl->name is empty at this point
|
||||
Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i );
|
||||
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");
|
||||
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 ( !paused->integer && !SV_DoSaveGame() ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if( !ge->AllowPaused() ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
// only pause if there is just a single client connected
|
||||
count = 0;
|
||||
for (i=0,cl=svs.clients ; i < svs.iNumClients ; i++,cl++) {
|
||||
if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( count > 1 ) {
|
||||
Com_Unpause();
|
||||
}
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
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");
|
||||
Cvar_Set( "sv_killserver", "0" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( !com_sv_running->integer )
|
||||
{
|
||||
// Running as a server, but no map loaded
|
||||
#ifdef DEDICATED
|
||||
// Block until something interesting happens
|
||||
Sys_Sleep(-1);
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 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", "20" );
|
||||
}
|
||||
|
||||
frameMsec = 1000 / sv_fps->integer;
|
||||
sv.timeResidual += msec;
|
||||
|
||||
if ( com_dedicated->integer && sv.timeResidual < frameMsec ) {
|
||||
// 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( va( "map %s\n", Cvar_VariableString( "mapname" ) ) );
|
||||
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( va( "map %s\n", Cvar_VariableString( "mapname" ) ) );
|
||||
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( CVAR_SYSTEMINFO ) );
|
||||
cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
|
||||
}
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
startTime = Sys_Milliseconds ();
|
||||
} else {
|
||||
startTime = 0; // quite a compiler warning
|
||||
}
|
||||
|
||||
if( ( sv_fps->integer * msec > 1100 ) && ( svs.time > svs.serverLagTime + 2500 ) )
|
||||
{
|
||||
svs.serverLagTime = svs.time;
|
||||
SV_SendServerCommand( NULL, "svlag" );
|
||||
}
|
||||
|
||||
// update ping based on the all received frames
|
||||
SV_CalcPings();
|
||||
|
||||
// run the game simulation in chunks
|
||||
while ( sv.timeResidual >= frameMsec ) {
|
||||
sv.timeResidual -= frameMsec;
|
||||
svs.time += frameMsec;
|
||||
|
||||
if( sv.state == SS_GAME )
|
||||
{
|
||||
const char *err;
|
||||
|
||||
// let everything in the world think and move
|
||||
ge->RunFrame( svs.time, frameMsec );
|
||||
|
||||
err = ge->errorMessage;
|
||||
if( err )
|
||||
{
|
||||
ge->errorMessage = NULL;
|
||||
Com_Error( ERR_DROP, err );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( com_speeds->integer ) {
|
||||
time_game = Sys_Milliseconds () - startTime;
|
||||
}
|
||||
|
||||
// check timeouts
|
||||
SV_CheckTimeouts();
|
||||
|
||||
// send messages back to the clients
|
||||
SV_SendClientMessages();
|
||||
|
||||
// send a heartbeat to the master if needed
|
||||
SV_MasterHeartbeat();
|
||||
|
||||
svs.lastTime = svs.time;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
Com_Pause
|
||||
==================
|
||||
*/
|
||||
void Com_Pause()
|
||||
{
|
||||
if( deathmatch->integer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( !com_sv_running->integer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( sv.state != SS_GAME ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( paused->integer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetBelowNormalThreadPriority();
|
||||
Cvar_Set( "paused", "1" );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
Com_Unpause
|
||||
==================
|
||||
*/
|
||||
void Com_Unpause()
|
||||
{
|
||||
if( paused->integer )
|
||||
{
|
||||
if( paused->integer == 1 ) {
|
||||
SetNormalThreadPriority();
|
||||
}
|
||||
|
||||
Cvar_Set( "paused", "0" );
|
||||
CL_ClearButtons();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
Com_FakePause
|
||||
==================
|
||||
*/
|
||||
void Com_FakePause()
|
||||
{
|
||||
if( !paused->integer ) {
|
||||
Cvar_Set( "paused", "2" );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
Com_FakeUnpause
|
||||
==================
|
||||
*/
|
||||
void Com_FakeUnpause()
|
||||
{
|
||||
if( paused->integer >= 2 )
|
||||
{
|
||||
Cvar_Set( "paused", "0" );
|
||||
CL_ClearButtons();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
Com_Pause_f
|
||||
==================
|
||||
*/
|
||||
void Com_Pause_f()
|
||||
{
|
||||
if( paused->integer == 1 ) {
|
||||
Com_Unpause();
|
||||
} else {
|
||||
Com_Pause();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_SoundCallback
|
||||
==================
|
||||
*/
|
||||
void SV_SoundCallback( int entnum, int channel_number, const char *name )
|
||||
{
|
||||
if( ge ) {
|
||||
ge->SoundCallback( entnum, channel_number, name );
|
||||
} else {
|
||||
Com_Printf( "^~^~^ Failed SV_SoundCallback for (entnum %d, channel %d, sound '%s')\n", entnum, channel_number, name );
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
207
code/server/sv_net_chan.c
Normal file
207
code/server/sv_net_chan.c
Normal file
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Quake III Arena source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "../qcommon/q_shared.h"
|
||||
#include "../qcommon/qcommon.h"
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
==============
|
||||
SV_Netchan_Encode
|
||||
|
||||
// first four bytes of the data are always:
|
||||
long reliableAcknowledge;
|
||||
|
||||
==============
|
||||
*/
|
||||
static void SV_Netchan_Encode( client_t *client, msg_t *msg ) {
|
||||
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 = 0;
|
||||
|
||||
reliableAcknowledge = MSG_ReadLong(msg);
|
||||
|
||||
msg->oob = 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 || 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 ) {
|
||||
int serverId, messageAcknowledge, reliableAcknowledge;
|
||||
int i, index, srdc, sbit, soob;
|
||||
byte key, *string;
|
||||
|
||||
srdc = msg->readcount;
|
||||
sbit = msg->bit;
|
||||
soob = msg->oob;
|
||||
|
||||
msg->oob = 0;
|
||||
|
||||
serverId = MSG_ReadLong(msg);
|
||||
messageAcknowledge = MSG_ReadLong(msg);
|
||||
reliableAcknowledge = MSG_ReadLong(msg);
|
||||
|
||||
msg->oob = 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 || 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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_Netchan_TransmitNextFragment
|
||||
=================
|
||||
*/
|
||||
void SV_Netchan_TransmitNextFragment( client_t *client ) {
|
||||
Netchan_TransmitNextFragment( &client->netchan );
|
||||
if (!client->netchan.unsentFragments)
|
||||
{
|
||||
// make sure the netchan queue has been properly initialized (you never know)
|
||||
if (!client->netchan_end_queue) {
|
||||
Com_Error(ERR_DROP, "netchan queue is not properly initialized in SV_Netchan_TransmitNextFragment\n");
|
||||
}
|
||||
// the last fragment was transmitted, check wether we have queued messages
|
||||
if (client->netchan_start_queue) {
|
||||
netchan_buffer_t *netbuf;
|
||||
Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n");
|
||||
netbuf = client->netchan_start_queue;
|
||||
SV_Netchan_Encode( client, &netbuf->msg );
|
||||
Netchan_Transmit( &client->netchan, netbuf->msg.cursize, netbuf->msg.data );
|
||||
// pop from queue
|
||||
client->netchan_start_queue = netbuf->next;
|
||||
if (!client->netchan_start_queue) {
|
||||
Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n");
|
||||
client->netchan_end_queue = &client->netchan_start_queue;
|
||||
}
|
||||
else
|
||||
Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n");
|
||||
Z_Free(netbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_Netchan_Transmit
|
||||
TTimo
|
||||
https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462
|
||||
if there are some unsent fragments (which may happen if the snapshots
|
||||
and the gamestate are fragmenting, and collide on send for instance)
|
||||
then buffer them and make sure they get sent in correct order
|
||||
================
|
||||
*/
|
||||
|
||||
void SV_Netchan_Transmit( client_t *client, msg_t *msg) { //int length, const byte *data ) {
|
||||
MSG_WriteByte( msg, svc_EOF );
|
||||
if (client->netchan.unsentFragments) {
|
||||
netchan_buffer_t *netbuf;
|
||||
Com_DPrintf("#462 SV_Netchan_Transmit: unsent fragments, stacked\n");
|
||||
netbuf = (netchan_buffer_t *)Z_Malloc(sizeof(netchan_buffer_t));
|
||||
// store the msg, we can't store it encoded, as the encoding depends on stuff we still have to finish sending
|
||||
MSG_Copy(&netbuf->msg, netbuf->msgBuffer, sizeof( netbuf->msgBuffer ), msg);
|
||||
netbuf->next = NULL;
|
||||
// insert it in the queue, the message will be encoded and sent later
|
||||
*client->netchan_end_queue = netbuf;
|
||||
client->netchan_end_queue = &(*client->netchan_end_queue)->next;
|
||||
// emit the next fragment of the current message for now
|
||||
Netchan_TransmitNextFragment(&client->netchan);
|
||||
} else {
|
||||
SV_Netchan_Encode( client, msg );
|
||||
Netchan_Transmit( &client->netchan, msg->cursize, msg->data );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Netchan_SV_Process
|
||||
=================
|
||||
*/
|
||||
qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) {
|
||||
int ret;
|
||||
ret = Netchan_Process( &client->netchan, msg );
|
||||
if (!ret)
|
||||
return qfalse;
|
||||
SV_Netchan_Decode( client, msg );
|
||||
return qtrue;
|
||||
}
|
||||
|
1537
code/server/sv_rankings.c
Normal file
1537
code/server/sv_rankings.c
Normal file
File diff suppressed because it is too large
Load diff
728
code/server/sv_snapshot.c
Normal file
728
code/server/sv_snapshot.c
Normal file
|
@ -0,0 +1,728 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Quake III Arena source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
==================
|
||||
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_WriteSVC(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
|
||||
if( client->oldServerTime ) {
|
||||
// The server has not yet got an acknowledgement of the
|
||||
// new gamestate from this client, so continue to send it
|
||||
// a time as if the server has not restarted. Note from
|
||||
// the client's perspective this time is strictly speaking
|
||||
// incorrect, but since it'll be busy loading a map at
|
||||
// the time it doesn't really matter.
|
||||
MSG_WriteLong (msg, svs.time + client->oldServerTime);
|
||||
} else {
|
||||
// WOMBAT: note that MOHAA always goes into this else.
|
||||
// therefore we are deviating from the MOHAA protocol but i don't think this is a problem
|
||||
MSG_WriteLong (msg, svs.time);
|
||||
}
|
||||
|
||||
if ( sv.timeResidual > 254 )
|
||||
MSG_WriteByte( msg, 255 );
|
||||
else MSG_WriteByte( msg, sv.timeResidual );
|
||||
|
||||
// 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
|
||||
if ( frame->areabytes > 255 ) {
|
||||
Com_DPrintf( "WARNING: area bytes exceeds 255! Bad! Bad!" ); // 2015 actually had humour
|
||||
MSG_WriteByte( msg, 0 );
|
||||
} else {
|
||||
MSG_WriteByte (msg, frame->areabytes);
|
||||
MSG_WriteData (msg, frame->areabits, frame->areabytes);
|
||||
}
|
||||
|
||||
// delta encode the playerstate
|
||||
if ( oldframe ) {
|
||||
MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps );
|
||||
} else {
|
||||
MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps );
|
||||
}
|
||||
|
||||
// delta encode the entities
|
||||
SV_EmitPacketEntities (oldframe, frame, msg);
|
||||
|
||||
MSG_WriteSounds( msg, client->server_sounds, client->number_of_server_sounds );
|
||||
|
||||
if ( client->centerprint ) {
|
||||
if ( client->locprint ) {
|
||||
MSG_WriteSVC( msg, svc_locprint );
|
||||
MSG_WriteShort( msg, client->XOffset );
|
||||
MSG_WriteShort( msg, client->YOffset );
|
||||
MSG_WriteString( msg, client->centerprint );
|
||||
}
|
||||
else {
|
||||
MSG_WriteSVC( msg, svc_centerprint );
|
||||
MSG_WriteString( msg, client->centerprint );
|
||||
}
|
||||
}
|
||||
|
||||
// padding for rate debugging
|
||||
if ( sv_padPackets->integer ) {
|
||||
for ( i = 0 ; i < sv_padPackets->integer ; i++ ) {
|
||||
MSG_WriteSVC(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_WriteSVC( 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, gentity_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
|
||||
===============
|
||||
*/
|
||||
static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame,
|
||||
snapshotEntityNumbers_t *eNums, qboolean portal ) {
|
||||
int e, i;
|
||||
gentity_t *ent;
|
||||
svEntity_t *svEnt;
|
||||
int l;
|
||||
int clientarea, clientcluster;
|
||||
int leafnum;
|
||||
int c_fullsend;
|
||||
byte *clientpvs;
|
||||
byte *bitvector;
|
||||
|
||||
// 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.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;
|
||||
}
|
||||
}
|
||||
// entities can be flagged to be sent to a given mask of clients
|
||||
if ( ent->r.svFlags & SVF_CLIENTMASK ) {
|
||||
if (frame->ps.clientNum >= 32)
|
||||
Com_Error( ERR_DROP, "SVF_CLIENTMASK: clientNum > 32\n" );
|
||||
if (~ent->r.singleClient & (1 << 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
|
||||
if ( ent->r.svFlags & SVF_BROADCAST ) {
|
||||
SV_AddEntToSnapshot( svEnt, ent, eNums );
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: entities won't show sometimes
|
||||
#if 0
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// check individual leafs
|
||||
if( !svEnt->numClusters ) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
bitvector = clientpvs;
|
||||
|
||||
l = 0;
|
||||
for ( i=0 ; i < svEnt->numClusters ; i++ ) {
|
||||
l = svEnt->clusternums[i];
|
||||
if ( bitvector[l >> 3] & (1 << (l&7) ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we haven't found it to be visible,
|
||||
// check overflow clusters that coudln't be stored
|
||||
if ( i == svEnt->numClusters ) {
|
||||
if ( svEnt->lastCluster ) {
|
||||
for ( ; l <= svEnt->lastCluster ; l++ ) {
|
||||
if ( bitvector[l >> 3] & (1 << (l&7) ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( l == svEnt->lastCluster ) {
|
||||
continue; // not visible
|
||||
}
|
||||
} else {
|
||||
// FIXME (ley0k) : this seems to hide the entity at random places
|
||||
//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 ) {
|
||||
// entityState_t::generic1 is not present in MoHAA
|
||||
//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 );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
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;
|
||||
gentity_t *ent;
|
||||
entityState_t *state;
|
||||
svEntity_t *svEnt;
|
||||
gentity_t *clent;
|
||||
int clientNum;
|
||||
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 ) );
|
||||
|
||||
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=62
|
||||
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;
|
||||
|
||||
// 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 ];
|
||||
|
||||
// su44: that's not done in MoHAA
|
||||
//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 * com_timescale->value;
|
||||
|
||||
return rateMsec;
|
||||
}
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_SendMessageToClient
|
||||
|
||||
Called by SV_SendClientSnapshot and SV_SendClientGameState
|
||||
=======================
|
||||
*/
|
||||
void SV_SendMessageToClient( msg_t *msg, client_t *client ) {
|
||||
int rateMsec;
|
||||
|
||||
// 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 server frame
|
||||
// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=491
|
||||
// added sv_lanForceRate check
|
||||
if ( client->netchan.remoteAddress.type == NA_LOOPBACK || (sv_lanForceRate->integer && Sys_IsLANAddress (client->netchan.remoteAddress)) ) {
|
||||
client->nextSnapshotTime = svs.time + (1000.0 / sv_fps->integer * com_timescale->value);
|
||||
return;
|
||||
}
|
||||
|
||||
// normal rate / snapshotMsec calculation
|
||||
rateMsec = SV_RateMsec(client, msg->cursize);
|
||||
|
||||
if ( rateMsec < client->snapshotMsec * com_timescale->value) {
|
||||
// never send more packets than this, no matter what the rate is at
|
||||
rateMsec = client->snapshotMsec * com_timescale->value;
|
||||
client->rateDelayed = qfalse;
|
||||
} else {
|
||||
client->rateDelayed = qtrue;
|
||||
}
|
||||
|
||||
client->nextSnapshotTime = svs.time + rateMsec * com_timescale->value;
|
||||
|
||||
// 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
|
||||
if (!*client->downloadName && client->nextSnapshotTime < svs.time + 1000 * com_timescale->value)
|
||||
client->nextSnapshotTime = svs.time + 1000 * com_timescale->value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
=======================
|
||||
SV_SendClientSnapshot
|
||||
|
||||
Also called by SV_FinalMessage
|
||||
|
||||
=======================
|
||||
*/
|
||||
void SV_SendClientSnapshot( client_t *client ) {
|
||||
byte msg_buf[MAX_MSGLEN];
|
||||
msg_t msg;
|
||||
|
||||
// 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 );
|
||||
|
||||
// su44: write any pending MoHAA cg messages
|
||||
SV_WriteCGMToClient( client, &msg );
|
||||
|
||||
// Add any download data if the client is downloading
|
||||
SV_WriteDownloadToClient( client, &msg );
|
||||
|
||||
// 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;
|
||||
|
||||
// send a message to each connected client
|
||||
for (i=0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++) {
|
||||
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 );
|
||||
continue;
|
||||
}
|
||||
|
||||
// generate and send a new message
|
||||
SV_SendClientSnapshot( c );
|
||||
}
|
||||
}
|
||||
|
103
code/server/sv_snd.c
Normal file
103
code/server/sv_snd.c
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2015 the OpenMoHAA team
|
||||
|
||||
This file is part of OpenMoHAA source code.
|
||||
|
||||
OpenMoHAA source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
OpenMoHAA source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OpenMoHAA source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
|
||||
// sv_snd.c: Server sound
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_Sound
|
||||
===============
|
||||
*/
|
||||
void SV_Sound( vec3_t *org, int entnum, int channel, const char *sound_name, float volume, float mindist, float pitch, float maxdist, qboolean streamed )
|
||||
{
|
||||
int i;
|
||||
|
||||
for( i = 0; i < sv_maxclients->integer; i++ )
|
||||
{
|
||||
client_t *client = &svs.clients[ i ];
|
||||
server_sound_t *sound;
|
||||
|
||||
if( client->state != CS_ACTIVE )
|
||||
continue;
|
||||
|
||||
if( client->number_of_server_sounds >= MAX_SERVER_SOUNDS )
|
||||
continue;
|
||||
|
||||
sound = &client->server_sounds[ client->number_of_server_sounds ];
|
||||
sound->stop_flag = 0;
|
||||
sound->entity_number = entnum;
|
||||
|
||||
if( org )
|
||||
{
|
||||
VectorCopy( *org, sound->origin );
|
||||
}
|
||||
else
|
||||
{
|
||||
VectorClear( sound->origin );
|
||||
}
|
||||
|
||||
sound->channel = channel;
|
||||
sound->volume = volume;
|
||||
sound->min_dist = mindist;
|
||||
sound->pitch = pitch;
|
||||
sound->maxDist = maxdist;
|
||||
sound->sound_index = SV_SoundIndex( sound_name, streamed );
|
||||
sound->streamed = streamed;
|
||||
client->number_of_server_sounds++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_ClearSounds
|
||||
===============
|
||||
*/
|
||||
void SV_ClearSounds( client_t *client )
|
||||
{
|
||||
client->number_of_server_sounds = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_StopSound
|
||||
===============
|
||||
*/
|
||||
void SV_StopSound( int entnum, int channel )
|
||||
{
|
||||
server_sound_t *server_sound;
|
||||
int i;
|
||||
client_t *client;
|
||||
|
||||
for( client = svs.clients, i = 0; i < svs.iNumClients; client++, i++ )
|
||||
{
|
||||
if( client->state == CS_ACTIVE && client->number_of_server_sounds < MAX_SERVER_SOUNDS )
|
||||
{
|
||||
server_sound = &client->server_sounds[ client->number_of_server_sounds ];
|
||||
server_sound->stop_flag = qtrue;
|
||||
server_sound->entity_number = entnum;
|
||||
server_sound->channel = channel;
|
||||
client->number_of_server_sounds++;
|
||||
}
|
||||
}
|
||||
}
|
862
code/server/sv_world.c
Normal file
862
code/server/sv_world.c
Normal file
|
@ -0,0 +1,862 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 1999-2005 Id Software, Inc.
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
Quake III Arena source code is free software; you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Quake III Arena source code is distributed in the hope that it will be
|
||||
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Quake III Arena source code; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// world.c -- world query functions
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/*
|
||||
================
|
||||
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 gentity_t *ent ) {
|
||||
if ( ent->r.bmodel && ent->solid != SOLID_BBOX ) {
|
||||
// explicit hulls in the BSP model
|
||||
return CM_InlineModel( ent->s.modelindex );
|
||||
}
|
||||
|
||||
// create a temp tree from bounding box sizes
|
||||
return CM_TempBoxModel( ent->r.mins, ent->r.maxs, ent->r.contents );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
===============================================================================
|
||||
|
||||
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;
|
||||
int i;
|
||||
int num;
|
||||
char name[ 10 ];
|
||||
|
||||
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 );
|
||||
|
||||
// set inline models
|
||||
num = CM_NumInlineModels();
|
||||
for( i = 1; i < num; i++ )
|
||||
{
|
||||
sprintf( name, "*%i", i );
|
||||
SV_ModelIndex( name );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
SV_UnlinkEntity
|
||||
|
||||
===============
|
||||
*/
|
||||
void SV_UnlinkEntity( gentity_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( gentity_t *gEnt ) {
|
||||
worldSector_t *node;
|
||||
int leafs[MAX_TOTAL_ENT_LEAFS];
|
||||
int cluster;
|
||||
int num_leafs;
|
||||
int i;
|
||||
int area;
|
||||
int lastLeaf;
|
||||
float *origin;
|
||||
svEntity_t *ent;
|
||||
|
||||
ent = SV_SvEntityForGentity( gEnt );
|
||||
|
||||
if ( ent->worldSector ) {
|
||||
SV_UnlinkEntity( gEnt ); // unlink from old position
|
||||
}
|
||||
|
||||
switch( gEnt->solid )
|
||||
{
|
||||
case SOLID_TRIGGER:
|
||||
gEnt->s.solid = 0;
|
||||
break;
|
||||
case SOLID_BBOX:
|
||||
if( !gEnt->r.contents || ( gEnt->r.contents == CONTENTS_SHOOTONLY ) )
|
||||
{
|
||||
gEnt->s.solid = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// encode the size into the entityState_t for client prediction
|
||||
gEnt->s.solid = BoundingBoxToInteger( gEnt->r.mins, gEnt->r.maxs );
|
||||
}
|
||||
break;
|
||||
case SOLID_BSP:
|
||||
if( gEnt->r.contents == -1 || !( gEnt->r.contents & CONTENTS_SHOOTONLY ) )
|
||||
{
|
||||
gEnt->s.solid = SOLID_BMODEL;
|
||||
}
|
||||
else
|
||||
{
|
||||
gEnt->s.solid = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
gEnt->s.solid = gEnt->solid;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// get the position
|
||||
origin = gEnt->s.origin;
|
||||
|
||||
// set the abs box
|
||||
if( gEnt->r.currentAngles[ 0 ] || gEnt->r.currentAngles[ 1 ] || gEnt->r.currentAngles[ 2 ] ) {
|
||||
// expand for rotation
|
||||
VectorCopy( gEnt->r.mins, gEnt->r.absmin );
|
||||
VectorCopy( gEnt->r.maxs, gEnt->r.absmax );
|
||||
CalculateRotatedBounds2( gEnt->mat, gEnt->r.absmin, gEnt->r.absmax );
|
||||
VectorAdd( origin, gEnt->r.absmin, gEnt->r.absmin );
|
||||
VectorAdd( origin, gEnt->r.absmax, gEnt->r.absmax );
|
||||
} 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 the areanum to gentity
|
||||
gEnt->r.areanum = ent->areanum;
|
||||
|
||||
// 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;
|
||||
gentity_t *gcheck;
|
||||
worldSector_t *nodestack[ 8 ];
|
||||
int iStackPos;
|
||||
|
||||
iStackPos = 0;
|
||||
|
||||
while( 1 )
|
||||
{
|
||||
while( 1 )
|
||||
{
|
||||
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_Printf( "SV_AreaEntities: MAXCOUNT\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
ap->list[ ap->count ] = check - sv.svEntities;
|
||||
ap->count++;
|
||||
}
|
||||
|
||||
if( node->axis == -1 ) {
|
||||
break; // terminal node
|
||||
}
|
||||
|
||||
// recurse down both sides
|
||||
if( ap->maxs[ node->axis ] > node->dist ) {
|
||||
nodestack[ iStackPos++ ] = node->children[ 0 ];
|
||||
}
|
||||
|
||||
if( ap->mins[ node->axis ] < node->dist ) {
|
||||
node = node->children[ 1 ];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( !iStackPos ) {
|
||||
return;
|
||||
}
|
||||
|
||||
iStackPos--;
|
||||
|
||||
node = nodestack[ iStackPos ];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
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
|
||||
const float *start;
|
||||
vec3_t end;
|
||||
trace_t trace;
|
||||
int passEntityNum;
|
||||
int contentmask;
|
||||
qboolean cylinder;
|
||||
qboolean traceDeep;
|
||||
} 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 ) {
|
||||
gentity_t *touch;
|
||||
clipHandle_t clipHandle;
|
||||
|
||||
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);
|
||||
|
||||
CM_TransformedBoxTrace ( trace, start, end,
|
||||
mins, maxs, clipHandle, contentmask,
|
||||
touch->s.origin, touch->r.currentAngles, qfalse );
|
||||
|
||||
if ( trace->fraction < 1 ) {
|
||||
trace->entityNum = touch->s.number;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
====================
|
||||
SV_ClipMoveToEntities
|
||||
|
||||
====================
|
||||
*/
|
||||
void SV_ClipMoveToEntities( moveclip_t *clip ) {
|
||||
int i, num;
|
||||
int touchlist[MAX_GENTITIES];
|
||||
gentity_t *touch;
|
||||
int passOwnerNum;
|
||||
trace_t trace;
|
||||
clipHandle_t clipHandle;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for ( i=0 ; i<num ; i++ ) {
|
||||
if ( clip->trace.allsolid ) {
|
||||
return;
|
||||
}
|
||||
touch = SV_GentityNum( touchlist[i] );
|
||||
|
||||
// skip non-solids and triggers
|
||||
if( touch->solid == SOLID_NOT || touch->solid == SOLID_TRIGGER ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
continue; // don't clip against own missiles
|
||||
}
|
||||
if ( touch->r.ownerNum == passOwnerNum ) {
|
||||
continue; // don't clip against other missiles from our owner
|
||||
}
|
||||
}
|
||||
|
||||
// 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->traceDeep && touch->tiki != NULL && touch->tiki->a->bIsCharacter )
|
||||
{
|
||||
// do an extended trace with an output location
|
||||
SV_TraceDeep(
|
||||
&trace,
|
||||
clip->start,
|
||||
clip->end,
|
||||
clip->contentmask,
|
||||
touch );
|
||||
}
|
||||
else
|
||||
{
|
||||
// might intersect, so do an exact clip
|
||||
clipHandle = SV_ClipHandleForEntity( touch );
|
||||
|
||||
CM_TransformedBoxTrace( &trace, clip->start, clip->end,
|
||||
clip->mins, clip->maxs, clipHandle, clip->contentmask,
|
||||
touch->s.origin, touch->r.currentAngles, clip->cylinder );
|
||||
}
|
||||
|
||||
if ( trace.allsolid ) {
|
||||
clip->trace.allsolid = qtrue;
|
||||
clip->trace.entityNum = touch->s.number;
|
||||
trace.entityNum = touch->s.number;
|
||||
} else if ( trace.startsolid ) {
|
||||
clip->trace.startsolid = qtrue;
|
||||
clip->trace.entityNum = touch->s.number;
|
||||
trace.entityNum = touch->s.number;
|
||||
}
|
||||
|
||||
if ( trace.fraction < clip->trace.fraction ) {
|
||||
qboolean 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 |= oldStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
SV_ClipSightToEntities
|
||||
====================
|
||||
*/
|
||||
qboolean SV_ClipSightToEntities( moveclip_t *clip, int passEntityNum2 )
|
||||
{
|
||||
int i, num;
|
||||
int touchlist[ MAX_GENTITIES ];
|
||||
gentity_t *touch;
|
||||
int passOwnerNum;
|
||||
int passOwnerNum2;
|
||||
clipHandle_t clipHandle;
|
||||
|
||||
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( passEntityNum2 != ENTITYNUM_NONE ) {
|
||||
passOwnerNum2 = ( SV_GentityNum( passEntityNum2 ) )->r.ownerNum;
|
||||
if( passOwnerNum2 == ENTITYNUM_NONE ) {
|
||||
passOwnerNum2 = -1;
|
||||
}
|
||||
} else {
|
||||
passOwnerNum2 = -1;
|
||||
}
|
||||
|
||||
for( i = 0; i<num; i++ ) {
|
||||
touch = SV_GentityNum( touchlist[ i ] );
|
||||
|
||||
// skip non-solids and triggers
|
||||
if( touch->solid == SOLID_NOT || touch->solid == SOLID_TRIGGER ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
continue; // don't clip against own missiles
|
||||
}
|
||||
if( touch->r.ownerNum == passOwnerNum ) {
|
||||
continue; // don't clip against other missiles from our owner
|
||||
}
|
||||
}
|
||||
|
||||
if( passEntityNum2 != ENTITYNUM_NONE ) {
|
||||
if( touchlist[ i ] == passEntityNum2 ) {
|
||||
continue; // don't clip against the pass entity
|
||||
}
|
||||
if( touch->r.ownerNum == passEntityNum2 ) {
|
||||
continue; // don't clip against own missiles
|
||||
}
|
||||
if( touch->r.ownerNum == passOwnerNum2 ) {
|
||||
continue; // don't clip against other missiles from our owner
|
||||
}
|
||||
}
|
||||
|
||||
// if it doesn't have any brushes of a type we
|
||||
// are looking for, ignore it
|
||||
if( !( clip->contentmask & touch->r.contents ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// might intersect, so do an exact clip
|
||||
clipHandle = SV_ClipHandleForEntity( touch );
|
||||
|
||||
if( !CM_TransformedBoxSightTrace( clip->start, clip->end,
|
||||
clip->mins, clip->maxs, clipHandle, clip->contentmask,
|
||||
touch->s.origin, touch->r.currentAngles, clip->cylinder ) ) {
|
||||
return qfalse;
|
||||
}
|
||||
}
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_SightTraceEntity
|
||||
|
||||
Returns false if something was hit.
|
||||
==================
|
||||
*/
|
||||
qboolean SV_SightTraceEntity( gentity_t *touch, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int contentmask, qboolean cylinder ) {
|
||||
clipHandle_t clipHandle = SV_ClipHandleForEntity( touch );
|
||||
return CM_TransformedBoxSightTrace( start, end, mins, maxs, clipHandle, contentmask, touch->s.origin, touch->r.currentAngles, cylinder );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_SightTrace
|
||||
|
||||
Returns false if something was hit.
|
||||
==================
|
||||
*/
|
||||
qboolean SV_SightTrace( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int passEntityNum2, int contentmask, qboolean cylinder ) {
|
||||
moveclip_t clip;
|
||||
int i;
|
||||
|
||||
if( !CM_BoxSightTrace( start, end, mins, maxs, 0, contentmask, cylinder ) ) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
clip.contentmask = contentmask;
|
||||
clip.start = start;
|
||||
VectorCopy( end, clip.end );
|
||||
clip.mins = mins;
|
||||
clip.maxs = maxs;
|
||||
clip.passEntityNum = passEntityNum;
|
||||
clip.cylinder = cylinder;
|
||||
|
||||
// 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
|
||||
return SV_ClipSightToEntities( &clip, passEntityNum2 );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
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.
|
||||
==================
|
||||
*/
|
||||
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, qboolean cylinder, qboolean traceDeep ) {
|
||||
moveclip_t clip;
|
||||
int i;
|
||||
|
||||
Com_Memset( &clip, 0, sizeof( moveclip_t ) );
|
||||
|
||||
// clip to world
|
||||
CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, cylinder );
|
||||
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;
|
||||
clip.start = start;
|
||||
// VectorCopy( clip.trace.endpos, clip.end );
|
||||
VectorCopy( end, clip.end );
|
||||
clip.mins = mins;
|
||||
clip.maxs = maxs;
|
||||
clip.passEntityNum = passEntityNum;
|
||||
clip.cylinder = cylinder;
|
||||
clip.traceDeep = traceDeep;
|
||||
|
||||
// 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_GetShaderPointer
|
||||
=============
|
||||
*/
|
||||
baseshader_t *SV_GetShaderPointer( int iShaderNum )
|
||||
{
|
||||
return CM_ShaderPointer( iShaderNum );
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
SV_PointContents
|
||||
=============
|
||||
*/
|
||||
int SV_PointContents( const vec3_t p, int passEntityNum ) {
|
||||
int touch[MAX_GENTITIES];
|
||||
gentity_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;
|
||||
}
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue