2817 lines
72 KiB
C
2817 lines
72 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2000-2006 Tim Angus
|
|
|
|
This file is part of Tremulous.
|
|
|
|
Tremulous 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.
|
|
|
|
Tremulous 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 Tremulous; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
|
|
#define QVM_VARIANT "P-G-QVM"
|
|
#define QVM_VERSION "SVN r61"
|
|
#define QVM_URL "http://p-g-qvm.googlecode.com"
|
|
|
|
level_locals_t level;
|
|
|
|
typedef struct
|
|
{
|
|
vmCvar_t *vmCvar;
|
|
char *cvarName;
|
|
char *defaultString;
|
|
int cvarFlags;
|
|
int modificationCount; // for tracking changes
|
|
qboolean trackChange; // track this variable, and announce if changed
|
|
qboolean teamShader; // track and if changed, update shader state
|
|
} cvarTable_t;
|
|
|
|
gentity_t g_entities[ MAX_GENTITIES ];
|
|
gclient_t g_clients[ MAX_CLIENTS ];
|
|
|
|
vmCvar_t g_fraglimit;
|
|
vmCvar_t g_timelimit;
|
|
vmCvar_t g_suddenDeathTime;
|
|
vmCvar_t g_suddenDeath;
|
|
vmCvar_t g_suddenDeathMode;
|
|
vmCvar_t g_capturelimit;
|
|
vmCvar_t g_friendlyFire;
|
|
vmCvar_t g_friendlyFireAliens;
|
|
vmCvar_t g_friendlyFireHumans;
|
|
vmCvar_t g_friendlyFireMovementAttacks;
|
|
vmCvar_t g_retribution;
|
|
vmCvar_t g_friendlyBuildableFire;
|
|
vmCvar_t g_password;
|
|
vmCvar_t g_needpass;
|
|
vmCvar_t g_maxclients;
|
|
vmCvar_t g_maxGameClients;
|
|
vmCvar_t g_dedicated;
|
|
vmCvar_t g_speed;
|
|
vmCvar_t g_gravity;
|
|
vmCvar_t g_cheats;
|
|
vmCvar_t g_knockback;
|
|
vmCvar_t g_quadfactor;
|
|
vmCvar_t g_forcerespawn;
|
|
vmCvar_t g_inactivity;
|
|
vmCvar_t g_debugMove;
|
|
vmCvar_t g_debugDamage;
|
|
vmCvar_t g_debugAlloc;
|
|
vmCvar_t g_weaponRespawn;
|
|
vmCvar_t g_weaponTeamRespawn;
|
|
vmCvar_t g_motd;
|
|
vmCvar_t g_synchronousClients;
|
|
vmCvar_t g_warmup;
|
|
vmCvar_t g_doWarmup;
|
|
vmCvar_t g_proximityMines;
|
|
vmCvar_t g_proximityMinesPrice;
|
|
vmCvar_t g_proximityMinesLiveTime;
|
|
vmCvar_t g_restarted;
|
|
vmCvar_t g_lockTeamsAtStart;
|
|
vmCvar_t g_logFile;
|
|
vmCvar_t g_logFileSync;
|
|
vmCvar_t g_blood;
|
|
vmCvar_t g_podiumDist;
|
|
vmCvar_t g_podiumDrop;
|
|
vmCvar_t g_allowVote;
|
|
vmCvar_t g_voteLimit;
|
|
vmCvar_t g_suddenDeathVotePercent;
|
|
vmCvar_t g_pollVotes;
|
|
vmCvar_t g_extendVotesPercent;
|
|
vmCvar_t g_extendVotesTime;
|
|
vmCvar_t g_extendVotesCount;
|
|
vmCvar_t g_mapVotesPercent;
|
|
vmCvar_t g_layoutVotePercent;
|
|
vmCvar_t g_designateVotes;
|
|
vmCvar_t g_admitDefeatVotePercent;
|
|
vmCvar_t g_teamAutoJoin;
|
|
vmCvar_t g_teamForceBalance;
|
|
vmCvar_t g_banIPs;
|
|
vmCvar_t g_filterBan;
|
|
vmCvar_t g_smoothClients;
|
|
vmCvar_t g_clientUpgradeNotice;
|
|
vmCvar_t pmove_fixed;
|
|
vmCvar_t pmove_msec;
|
|
vmCvar_t g_rankings;
|
|
vmCvar_t g_listEntity;
|
|
vmCvar_t g_minCommandPeriod;
|
|
vmCvar_t g_minNameChangePeriod;
|
|
vmCvar_t g_maxNameChanges;
|
|
vmCvar_t g_newbieNumbering;
|
|
vmCvar_t g_newbieNamePrefix;
|
|
|
|
vmCvar_t g_humanBuildPoints;
|
|
vmCvar_t g_alienBuildPoints;
|
|
vmCvar_t g_humanStage;
|
|
vmCvar_t g_humanKills;
|
|
vmCvar_t g_humanMaxStage;
|
|
vmCvar_t g_humanStage2Threshold;
|
|
vmCvar_t g_humanStage3Threshold;
|
|
vmCvar_t g_alienStage;
|
|
vmCvar_t g_alienKills;
|
|
vmCvar_t g_alienMaxStage;
|
|
vmCvar_t g_alienStage2Threshold;
|
|
vmCvar_t g_alienStage3Threshold;
|
|
vmCvar_t g_teamImbalanceWarnings;
|
|
|
|
vmCvar_t g_unlagged;
|
|
|
|
vmCvar_t g_disabledEquipment;
|
|
vmCvar_t g_disabledClasses;
|
|
vmCvar_t g_disabledBuildables;
|
|
|
|
vmCvar_t g_markDeconstruct;
|
|
vmCvar_t g_deconDead;
|
|
|
|
vmCvar_t g_debugMapRotation;
|
|
vmCvar_t g_currentMapRotation;
|
|
vmCvar_t g_currentMap;
|
|
vmCvar_t g_initialMapRotation;
|
|
|
|
vmCvar_t g_shove;
|
|
|
|
vmCvar_t g_mapConfigs;
|
|
vmCvar_t g_chatTeamPrefix;
|
|
vmCvar_t g_floodMaxDemerits;
|
|
vmCvar_t g_floodMinTime;
|
|
|
|
vmCvar_t g_layouts;
|
|
vmCvar_t g_layoutAuto;
|
|
|
|
vmCvar_t g_admin;
|
|
vmCvar_t g_adminLog;
|
|
vmCvar_t g_adminParseSay;
|
|
vmCvar_t g_adminSayFilter;
|
|
vmCvar_t g_adminNameProtect;
|
|
vmCvar_t g_adminTempBan;
|
|
vmCvar_t g_adminMaxBan;
|
|
vmCvar_t g_adminMapLog;
|
|
vmCvar_t g_adminRegisterLevel;
|
|
vmCvar_t g_adminRegisterAdminPass;
|
|
vmCvar_t g_adminRegisterAdminLevel;
|
|
vmCvar_t g_minLevelToJoinTeam;
|
|
vmCvar_t g_chat;
|
|
|
|
vmCvar_t g_privateMessages;
|
|
vmCvar_t g_buildLogMaxLength;
|
|
vmCvar_t g_minLevelToSpecMM1;
|
|
vmCvar_t g_publicSayadmins;
|
|
vmCvar_t g_myStats;
|
|
vmCvar_t g_antiSpawnBlock;
|
|
|
|
vmCvar_t g_killingSpree;
|
|
vmCvar_t g_feedingSpree;
|
|
vmCvar_t g_bleedingSpree;
|
|
|
|
vmCvar_t g_KillerHP;
|
|
|
|
vmCvar_t g_decolorLogfiles;
|
|
|
|
vmCvar_t g_tag;
|
|
|
|
vmCvar_t g_sayAreaLocations;
|
|
|
|
vmCvar_t g_radiationDamage;
|
|
vmCvar_t g_radiationTime;
|
|
vmCvar_t g_radiationCredits;
|
|
vmCvar_t g_radiationCreditsCount;
|
|
|
|
vmCvar_t g_dretchPunt;
|
|
|
|
vmCvar_t g_allowActions;
|
|
vmCvar_t g_actionPrefix;
|
|
|
|
vmCvar_t g_allowShare;
|
|
vmCvar_t g_creditOverflow;
|
|
|
|
vmCvar_t g_devmapNoGod;
|
|
vmCvar_t g_devmapNoStructDmg;
|
|
|
|
vmCvar_t g_slapKnockback;
|
|
vmCvar_t g_slapDamage;
|
|
|
|
vmCvar_t g_practiceText;
|
|
vmCvar_t g_practiceCount;
|
|
|
|
vmCvar_t g_msg;
|
|
vmCvar_t g_msgTime;
|
|
|
|
vmCvar_t g_buyAll;
|
|
vmCvar_t g_multipleWeapons;
|
|
vmCvar_t g_devmapVotes;
|
|
|
|
vmCvar_t g_banNotice;
|
|
|
|
vmCvar_t g_voteMinTime;
|
|
vmCvar_t g_mapvoteMaxTime;
|
|
|
|
vmCvar_t g_deconTime;
|
|
|
|
vmCvar_t g_specAspec;
|
|
|
|
vmCvar_t g_healShove;
|
|
|
|
vmCvar_t g_modBuildableHealth;
|
|
vmCvar_t g_modBuildableSpeed;
|
|
vmCvar_t g_modHumanStamina;
|
|
vmCvar_t g_modHumanHealth;
|
|
vmCvar_t g_modAlienHealth;
|
|
vmCvar_t g_modHumanRate;
|
|
vmCvar_t g_modAlienRate;
|
|
vmCvar_t g_modWeaponAmmo;
|
|
vmCvar_t g_modWeaponReload;
|
|
vmCvar_t g_modTurretAngle;
|
|
|
|
static cvarTable_t gameCvarTable[ ] =
|
|
{
|
|
// don't override the cheat state set by the system
|
|
{ &g_cheats, "sv_cheats", "", 0, 0, qfalse },
|
|
|
|
// noset vars
|
|
{ NULL, "gamename", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
|
|
{ NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse },
|
|
{ &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse },
|
|
{ &g_lockTeamsAtStart, "g_lockTeamsAtStart", "0", CVAR_ROM, 0, qfalse },
|
|
{ NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
|
|
{ NULL, "P", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
|
|
{ NULL, "ff", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
|
|
|
|
{ NULL, "qvm_version", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
|
|
|
|
// latched vars
|
|
|
|
{ &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
|
|
|
|
// change anytime vars
|
|
{ &g_timelimit, "timelimit", "45", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
|
|
{ &g_suddenDeathTime, "g_suddenDeathTime", "30", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
|
|
{ &g_suddenDeathMode, "g_suddenDeathMode", "2", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
|
|
{ &g_suddenDeath, "g_suddenDeath", "0", CVAR_SERVERINFO | CVAR_NORESTART, 0, qtrue },
|
|
|
|
{ &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse },
|
|
|
|
{ &g_sayAreaLocations, "g_sayAreaLocations", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
|
|
{ &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue },
|
|
{ &g_friendlyFireAliens, "g_friendlyFireAliens", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_friendlyFireHumans, "g_friendlyFireHumans", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_retribution, "g_retribution", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue },
|
|
{ &g_friendlyFireMovementAttacks, "g_friendlyFireMovementAttacks", "1", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_devmapNoGod, "g_devmapNoGod", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_devmapNoStructDmg, "g_devmapNoStructDmg", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
|
|
{ &g_specAspec, "g_specAspec", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
|
|
{ &g_healShove, "g_healShove", "1", CVAR_ARCHIVE, 0, qtrue },
|
|
|
|
{ &g_modBuildableHealth, "g_modBuildableHealth", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modBuildableSpeed, "g_modBuildableSpeed", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modHumanStamina, "g_modHumanStamina", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modHumanHealth, "g_modHumanHealth", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modAlienHealth, "g_modAlienHealth", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modHumanRate, "g_modHumanRate", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modAlienRate, "g_modAlienRate", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modWeaponAmmo, "g_modWeaponAmmo", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modWeaponReload, "g_modWeaponReload", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_modTurretAngle, "g_modTurretAngle", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_proximityMines, "g_proximityMines", "0", 0, 0, qtrue },
|
|
{ &g_proximityMinesPrice, "g_proximityMinesPrice", "800", 0, 0, qtrue },
|
|
{ &g_proximityMinesLiveTime, "g_proximityMinesLiveTime", "60000", 0, 0, qtrue },
|
|
{ &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE },
|
|
{ &g_teamForceBalance, "g_teamForceBalance", "1", CVAR_ARCHIVE },
|
|
{ &g_buyAll, "g_buyAll", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_devmapVotes, "g_devmapVotes", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
|
|
{ &g_multipleWeapons, "g_multipleWeapons", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_doWarmup, "g_doWarmup", "1", CVAR_ARCHIVE, 0, qtrue },
|
|
{ &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse },
|
|
|
|
{ &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
|
|
|
|
{ &g_dedicated, "dedicated", "0", 0, 0, qfalse },
|
|
|
|
{ &g_speed, "g_speed", "320", CVAR_SERVERINFO, 0, qtrue },
|
|
{ &g_gravity, "g_gravity", "800", CVAR_SERVERINFO, 0, qtrue },
|
|
{ &g_knockback, "g_knockback", "1000", CVAR_SERVERINFO, 0, qtrue },
|
|
{ &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue },
|
|
{ &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue },
|
|
{ &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue },
|
|
{ &g_forcerespawn, "g_forcerespawn", "20", 0, 0, qtrue },
|
|
{ &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },
|
|
{ &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },
|
|
{ &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse },
|
|
{ &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },
|
|
{ &g_motd, "g_motd", "", 0, 0, qfalse },
|
|
{ &g_blood, "com_blood", "1", 0, 0, qfalse },
|
|
|
|
{ &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse },
|
|
{ &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },
|
|
|
|
{ &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_voteMinTime, "g_voteMinTime", "120", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_mapvoteMaxTime, "g_mapvoteMaxTime", "240", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "75", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_pollVotes, "g_pollVotes", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_extendVotesPercent, "g_extendVotesPercent", "75", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_extendVotesTime, "g_extendVotesTime", "10", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_extendVotesCount, "g_extendVotesCount", "3", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_mapVotesPercent, "g_mapVotesPercent", "50", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_layoutVotePercent, "g_layoutVotePercent", "50", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_designateVotes, "g_designateVotes", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_admitDefeatVotePercent, "g_admitDefeatVotePercent", "50", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_listEntity, "g_listEntity", "0", 0, 0, qfalse },
|
|
{ &g_minCommandPeriod, "g_minCommandPeriod", "500", 0, 0, qfalse},
|
|
{ &g_minNameChangePeriod, "g_minNameChangePeriod", "5", 0, 0, qfalse},
|
|
{ &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse},
|
|
{ &g_newbieNumbering, "g_newbieNumbering", "0", CVAR_ARCHIVE, 0, qfalse},
|
|
{ &g_newbieNamePrefix, "g_newbieNamePrefix", "Newbie#", CVAR_ARCHIVE, 0, qfalse},
|
|
|
|
{ &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse},
|
|
{ &g_clientUpgradeNotice, "g_clientUpgradeNotice", "1", 0, 0, qfalse},
|
|
{ &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},
|
|
{ &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
|
|
|
|
{ &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, CVAR_SERVERINFO, 0, qfalse },
|
|
{ &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, CVAR_SERVERINFO, 0, qfalse },
|
|
{ &g_humanStage, "g_humanStage", "0", 0, 0, qfalse },
|
|
{ &g_humanKills, "g_humanKills", "0", 0, 0, qfalse },
|
|
{ &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse },
|
|
{ &g_humanStage2Threshold, "g_humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, 0, 0, qfalse },
|
|
{ &g_humanStage3Threshold, "g_humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, 0, 0, qfalse },
|
|
{ &g_alienStage, "g_alienStage", "0", 0, 0, qfalse },
|
|
{ &g_alienKills, "g_alienKills", "0", 0, 0, qfalse },
|
|
{ &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse },
|
|
{ &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse },
|
|
{ &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse },
|
|
|
|
{ &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse },
|
|
{ &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse },
|
|
{ &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM, 0, qfalse },
|
|
|
|
{ &g_chatTeamPrefix, "g_chatTeamPrefix", "1", CVAR_ARCHIVE },
|
|
{ &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_markDeconstruct, "g_markDeconstruct", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_deconDead, "g_deconDead", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
|
|
{ &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse },
|
|
{ &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING
|
|
{ &g_currentMap, "g_currentMap", "0", 0, 0, qfalse },
|
|
{ &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_shove, "g_shove", "15", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse },
|
|
{ NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse },
|
|
|
|
{ &g_layouts, "g_layouts", "", CVAR_LATCH, 0, qfalse },
|
|
{ &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminLog, "g_adminLog", "admin.log", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminParseSay, "g_adminParseSay", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminSayFilter, "g_adminSayFilter", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminTempBan, "g_adminTempBan", "120", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminMaxBan, "g_adminMaxBan", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_minLevelToJoinTeam, "g_minLevelToJoinTeam", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminMapLog, "g_adminMapLog", "", CVAR_ROM, 0, qfalse },
|
|
{ &g_adminRegisterLevel, "g_adminRegisterLevel", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminRegisterAdminPass, "g_adminRegisterAdminPass", "", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_adminRegisterAdminLevel, "g_adminRegisterAdminLevel", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_chat, "g_chat", "chat.dat", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_radiationDamage, "g_radiationDamage", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_radiationTime, "g_radiationTime", "60", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_radiationCredits, "g_radiationCredits", "1000", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_radiationCreditsCount, "g_radiationCreditsCount", "2", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_deconTime, "g_deconTime", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_buildLogMaxLength, "g_buildLogMaxLength", "25", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_myStats, "g_myStats", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_publicSayadmins, "g_publicSayadmins", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_minLevelToSpecMM1, "g_minLevelToSpecMM1", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_antiSpawnBlock, "g_antiSpawnBlock", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_killingSpree, "g_killingSpree", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_feedingSpree, "g_feedingSpree", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_bleedingSpree, "g_bleedingSpree", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_KillerHP, "g_KillerHP", "0", CVAR_ARCHIVE, 0, qtrue },
|
|
|
|
{ &g_decolorLogfiles, "g_decolorLogfiles", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse },
|
|
|
|
{ &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_slapKnockback, "g_slapKnockback", "200", CVAR_ARCHIVE, 0, qfalse},
|
|
{ &g_slapDamage, "g_slapDamage", "5", CVAR_ARCHIVE, 0, qfalse},
|
|
|
|
{ &g_practiceText, "g_practiceText", "", 0, 0, qfalse},
|
|
{ &g_practiceCount, "g_practiceCount", "0", 0, 0, qfalse},
|
|
|
|
{ &g_msg, "g_msg", "", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_msgTime, "g_msgTime", "0", CVAR_ARCHIVE, 0, qfalse },
|
|
|
|
{ &g_rankings, "g_rankings", "0", 0, 0, qfalse },
|
|
{ &g_allowShare, "g_allowShare", "1", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse},
|
|
{ &g_creditOverflow, "g_creditOverflow", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse},
|
|
{ &g_allowActions, "g_allowActions", "1", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_actionPrefix, "g_actionPrefix", "***", CVAR_ARCHIVE, 0, qfalse },
|
|
{ &g_banNotice, "g_banNotice", "", CVAR_ARCHIVE, 0, qtrue },
|
|
};
|
|
|
|
static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] );
|
|
|
|
|
|
void G_InitGame( int levelTime, int randomSeed, int restart );
|
|
void G_RunFrame( int levelTime );
|
|
void G_ShutdownGame( int restart );
|
|
void CheckExitRules( void );
|
|
|
|
void G_CountSpawns( void );
|
|
void G_CalculateBuildPoints( void );
|
|
|
|
/*
|
|
================
|
|
vmMain
|
|
|
|
This is the only way control passes into the module.
|
|
This must be the very first function compiled into the .q3vm file
|
|
================
|
|
*/
|
|
intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4,
|
|
int arg5, int arg6, int arg7, int arg8, int arg9,
|
|
int arg10, int arg11 )
|
|
{
|
|
switch( command )
|
|
{
|
|
case GAME_INIT:
|
|
G_InitGame( arg0, arg1, arg2 );
|
|
return 0;
|
|
|
|
case GAME_SHUTDOWN:
|
|
G_ShutdownGame( arg0 );
|
|
return 0;
|
|
|
|
case GAME_CLIENT_CONNECT:
|
|
return (intptr_t)ClientConnect( arg0, arg1 );
|
|
|
|
case GAME_CLIENT_THINK:
|
|
ClientThink( arg0 );
|
|
return 0;
|
|
|
|
case GAME_CLIENT_USERINFO_CHANGED:
|
|
ClientUserinfoChanged( arg0 );
|
|
return 0;
|
|
|
|
case GAME_CLIENT_DISCONNECT:
|
|
ClientDisconnect( arg0 );
|
|
return 0;
|
|
|
|
case GAME_CLIENT_BEGIN:
|
|
ClientBegin( arg0 );
|
|
return 0;
|
|
|
|
case GAME_CLIENT_COMMAND:
|
|
ClientCommand( arg0 );
|
|
return 0;
|
|
|
|
case GAME_RUN_FRAME:
|
|
G_RunFrame( arg0 );
|
|
return 0;
|
|
|
|
case GAME_CONSOLE_COMMAND:
|
|
return ConsoleCommand( );
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
void QDECL G_Printf( const char *fmt, ... )
|
|
{
|
|
va_list argptr;
|
|
char text[ 1024 ];
|
|
|
|
va_start( argptr, fmt );
|
|
vsprintf( text, fmt, argptr );
|
|
va_end( argptr );
|
|
|
|
trap_Printf( text );
|
|
}
|
|
|
|
void QDECL G_Error( const char *fmt, ... )
|
|
{
|
|
va_list argptr;
|
|
char text[ 1024 ];
|
|
|
|
va_start( argptr, fmt );
|
|
vsprintf( text, fmt, argptr );
|
|
va_end( argptr );
|
|
|
|
trap_Error( text );
|
|
}
|
|
|
|
/*
|
|
================
|
|
G_FindTeams
|
|
|
|
Chain together all entities with a matching team field.
|
|
Entity teams are used for item groups and multi-entity mover groups.
|
|
|
|
All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
|
|
All but the last will have the teamchain field set to the next one
|
|
================
|
|
*/
|
|
void G_FindTeams( void )
|
|
{
|
|
gentity_t *e, *e2;
|
|
int i, j;
|
|
int c, c2;
|
|
|
|
c = 0;
|
|
c2 = 0;
|
|
|
|
for( i = 1, e = g_entities+i; i < level.num_entities; i++, e++ )
|
|
{
|
|
if( !e->inuse )
|
|
continue;
|
|
|
|
if( !e->team )
|
|
continue;
|
|
|
|
if( e->flags & FL_TEAMSLAVE )
|
|
continue;
|
|
|
|
e->teammaster = e;
|
|
c++;
|
|
c2++;
|
|
|
|
for( j = i + 1, e2 = e + 1; j < level.num_entities; j++, e2++ )
|
|
{
|
|
if( !e2->inuse )
|
|
continue;
|
|
|
|
if( !e2->team )
|
|
continue;
|
|
|
|
if( e2->flags & FL_TEAMSLAVE )
|
|
continue;
|
|
|
|
if( !strcmp( e->team, e2->team ) )
|
|
{
|
|
c2++;
|
|
e2->teamchain = e->teamchain;
|
|
e->teamchain = e2;
|
|
e2->teammaster = e;
|
|
e2->flags |= FL_TEAMSLAVE;
|
|
|
|
// make sure that targets only point at the master
|
|
if( e2->targetname )
|
|
{
|
|
e->targetname = e2->targetname;
|
|
e2->targetname = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
G_Printf( "%i teams with %i entities\n", c, c2 );
|
|
}
|
|
|
|
void G_RemapTeamShaders( void )
|
|
{
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
G_RegisterCvars
|
|
=================
|
|
*/
|
|
void G_RegisterCvars( void )
|
|
{
|
|
int i;
|
|
cvarTable_t *cv;
|
|
qboolean remapped = qfalse;
|
|
|
|
for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ )
|
|
{
|
|
trap_Cvar_Register( cv->vmCvar, cv->cvarName,
|
|
cv->defaultString, cv->cvarFlags );
|
|
|
|
if( cv->vmCvar )
|
|
cv->modificationCount = cv->vmCvar->modificationCount;
|
|
|
|
if( cv->teamShader )
|
|
remapped = qtrue;
|
|
}
|
|
|
|
if( remapped )
|
|
G_RemapTeamShaders( );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_UpdateCvars
|
|
=================
|
|
*/
|
|
void G_UpdateCvars( void )
|
|
{
|
|
int i;
|
|
cvarTable_t *cv;
|
|
qboolean remapped = qfalse;
|
|
|
|
for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ )
|
|
{
|
|
if( cv->vmCvar )
|
|
{
|
|
trap_Cvar_Update( cv->vmCvar );
|
|
|
|
if( cv->modificationCount != cv->vmCvar->modificationCount )
|
|
{
|
|
cv->modificationCount = cv->vmCvar->modificationCount;
|
|
|
|
if( cv->trackChange )
|
|
{
|
|
trap_SendServerCommand( -1, va( "print \"Server: %s changed to %s\n\"",
|
|
cv->cvarName, cv->vmCvar->string ) );
|
|
// update serverinfo in case this cvar is passed to clients indirectly
|
|
CalculateRanks( );
|
|
}
|
|
|
|
if( cv->teamShader )
|
|
remapped = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( remapped )
|
|
G_RemapTeamShaders( );
|
|
}
|
|
|
|
static void G_InitModCvars( void )
|
|
{
|
|
BG_MOD_set( MOD_BG_BUILDABLE_HEALTH, g_modBuildableHealth.integer );
|
|
BG_MOD_set( MOD_BG_BUILDABLE_SPEED, g_modBuildableSpeed.integer );
|
|
BG_MOD_set( MOD_BG_HUMAN_STAMINA, g_modHumanStamina.integer );
|
|
BG_MOD_set( MOD_BG_HUMAN_HEALTH, g_modHumanHealth.integer );
|
|
BG_MOD_set( MOD_BG_ALIEN_HEALTH, g_modAlienHealth.integer );
|
|
BG_MOD_set( MOD_BG_HUMAN_RATE, g_modHumanRate.integer );
|
|
BG_MOD_set( MOD_BG_ALIEN_RATE, g_modAlienRate.integer );
|
|
BG_MOD_set( MOD_BG_WEAPON_AMMO, g_modWeaponAmmo.integer );
|
|
BG_MOD_set( MOD_BG_WEAPON_RELOAD, g_modWeaponReload.integer );
|
|
BG_MOD_set( MOD_BG_TURRET_ANGLE, g_modTurretAngle.integer );
|
|
|
|
BG_MOD_update( );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_MapConfigs
|
|
=================
|
|
*/
|
|
void G_MapConfigs( const char *mapname )
|
|
{
|
|
|
|
if( !g_mapConfigs.string[0] )
|
|
return;
|
|
|
|
if( trap_Cvar_VariableIntegerValue( "g_mapConfigsLoaded" ) )
|
|
return;
|
|
|
|
trap_SendConsoleCommand( EXEC_APPEND,
|
|
va( "exec \"%s/default.cfg\"\n", g_mapConfigs.string ) );
|
|
|
|
trap_SendConsoleCommand( EXEC_APPEND,
|
|
va( "exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname ) );
|
|
|
|
trap_Cvar_Set( "g_mapConfigsLoaded", "1" );
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_InitGame
|
|
|
|
============
|
|
*/
|
|
void G_InitGame( int levelTime, int randomSeed, int restart )
|
|
{
|
|
int i;
|
|
|
|
srand( randomSeed );
|
|
|
|
G_RegisterCvars( );
|
|
|
|
G_Printf( "------- Game Initialization -------\n" );
|
|
G_Printf( "gamename: %s\n", GAME_VERSION );
|
|
G_Printf( "gamedate: %s\n", __DATE__ );
|
|
|
|
G_ProcessIPBans( );
|
|
|
|
G_InitMemory( );
|
|
|
|
// set some level globals
|
|
memset( &level, 0, sizeof( level ) );
|
|
level.time = levelTime;
|
|
level.startTime = levelTime;
|
|
level.alienStage2Time = level.alienStage3Time =
|
|
level.humanStage2Time = level.humanStage3Time = level.startTime;
|
|
|
|
level.snd_fry = G_SoundIndex( "sound/misc/fry.wav" ); // FIXME standing in lava / slime
|
|
|
|
trap_Cvar_Set( "qvm_version", va( "%s %s %s %s",
|
|
QVM_VARIANT,
|
|
QVM_VERSION,
|
|
QVM_URL,
|
|
__DATE__ ) );
|
|
|
|
if( g_logFile.string[ 0 ] )
|
|
{
|
|
if( g_logFileSync.integer )
|
|
trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND_SYNC );
|
|
else
|
|
trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND );
|
|
|
|
if( !level.logFile )
|
|
G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logFile.string );
|
|
else
|
|
{
|
|
char serverinfo[ MAX_INFO_STRING ];
|
|
qtime_t qt;
|
|
int t;
|
|
|
|
|
|
trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );
|
|
|
|
G_LogPrintf( "------------------------------------------------------------\n" );
|
|
G_LogPrintf( "InitGame: %s\n", serverinfo );
|
|
|
|
t = trap_RealTime( &qt );
|
|
G_LogPrintf("RealTime: %04i/%02i/%02i %02i:%02i:%02i\n",
|
|
qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday,
|
|
qt.tm_hour, qt.tm_min, qt.tm_sec );
|
|
|
|
}
|
|
}
|
|
else
|
|
G_Printf( "Not logging to disk\n" );
|
|
|
|
{
|
|
char map[ MAX_CVAR_VALUE_STRING ] = {""};
|
|
|
|
trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
|
|
G_MapConfigs( map );
|
|
}
|
|
|
|
// we're done with g_mapConfigs, so reset this for the next map
|
|
trap_Cvar_Set( "g_mapConfigsLoaded", "0" );
|
|
|
|
if ( g_admin.string[ 0 ] ) {
|
|
G_admin_readconfig( NULL, 0 );
|
|
}
|
|
|
|
// initialize all entities for this game
|
|
memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[ 0 ] ) );
|
|
level.gentities = g_entities;
|
|
|
|
// initialize all clients for this game
|
|
level.maxclients = g_maxclients.integer;
|
|
memset( g_clients, 0, MAX_CLIENTS * sizeof( g_clients[ 0 ] ) );
|
|
level.clients = g_clients;
|
|
|
|
// set client fields on player ents
|
|
for( i = 0; i < level.maxclients; i++ )
|
|
g_entities[ i ].client = level.clients + i;
|
|
|
|
// always leave room for the max number of clients,
|
|
// even if they aren't all used, so numbers inside that
|
|
// range are NEVER anything but clients
|
|
level.num_entities = MAX_CLIENTS;
|
|
|
|
// let the server system know where the entites are
|
|
trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
|
|
&level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) );
|
|
|
|
trap_SetConfigstring( CS_INTERMISSION, "0" );
|
|
|
|
// update maplog
|
|
G_admin_maplog_update( );
|
|
|
|
// test to see if a custom buildable layout will be loaded
|
|
G_LayoutSelect( );
|
|
|
|
// parse the key/value pairs and spawn gentities
|
|
G_SpawnEntitiesFromString( );
|
|
|
|
// load up a custom building layout if there is one
|
|
G_LayoutLoad( );
|
|
|
|
// the map might disable some things
|
|
BG_InitAllowedGameElements( );
|
|
|
|
// syncronize mod cvars
|
|
G_InitModCvars( );
|
|
|
|
// practice counter
|
|
if( g_practiceCount.integer > 0 )
|
|
{
|
|
trap_Cvar_Set( "g_practiceCount", va( "%d", g_practiceCount.integer - 1 ) );
|
|
}
|
|
|
|
// general initialization
|
|
G_FindTeams( );
|
|
|
|
//TA:
|
|
BG_InitClassOverrides( );
|
|
BG_InitBuildableOverrides( );
|
|
G_InitDamageLocations( );
|
|
G_InitMapRotations( );
|
|
G_InitSpawnQueue( &level.alienSpawnQueue );
|
|
G_InitSpawnQueue( &level.humanSpawnQueue );
|
|
|
|
if( g_debugMapRotation.integer )
|
|
G_PrintRotations( );
|
|
|
|
//reset stages
|
|
trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) );
|
|
trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) );
|
|
trap_Cvar_Set( "g_alienKills", 0 );
|
|
trap_Cvar_Set( "g_humanKills", 0 );
|
|
trap_Cvar_Set( "g_suddenDeath", 0 );
|
|
|
|
G_Printf( "-----------------------------------\n" );
|
|
|
|
G_RemapTeamShaders( );
|
|
|
|
//TA: so the server counts the spawns without a client attached
|
|
G_CountSpawns( );
|
|
|
|
G_ResetPTRConnections( );
|
|
|
|
if(g_lockTeamsAtStart.integer)
|
|
{
|
|
level.alienTeamLocked=qtrue;
|
|
level.humanTeamLocked=qtrue;
|
|
trap_Cvar_Set( "g_lockTeamsAtStart", "0" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
G_ClearVotes
|
|
|
|
remove all currently active votes
|
|
==================
|
|
*/
|
|
static void G_ClearVotes( void )
|
|
{
|
|
level.voteTime = 0;
|
|
trap_SetConfigstring( CS_VOTE_TIME, "" );
|
|
level.teamVoteTime[ 0 ] = 0;
|
|
trap_SetConfigstring( CS_TEAMVOTE_TIME, "" );
|
|
level.teamVoteTime[ 1 ] = 0;
|
|
trap_SetConfigstring( CS_TEAMVOTE_TIME + 1, "" );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_ShutdownGame
|
|
=================
|
|
*/
|
|
void G_ShutdownGame( int restart )
|
|
{
|
|
// in case of a map_restart
|
|
G_ClearVotes( );
|
|
|
|
G_Printf( "==== ShutdownGame ====\n" );
|
|
|
|
if( level.logFile )
|
|
{
|
|
G_LogPrintf( "ShutdownGame:\n" );
|
|
G_LogPrintf( "------------------------------------------------------------\n" );
|
|
trap_FS_FCloseFile( level.logFile );
|
|
}
|
|
|
|
// write all the client session data so we can get it back
|
|
G_WriteSessionData( );
|
|
|
|
G_admin_chat_writeconfig();
|
|
|
|
G_admin_cleanup( );
|
|
G_admin_namelog_cleanup( );
|
|
G_admin_adminlog_cleanup( );
|
|
G_admin_tklog_cleanup( );
|
|
|
|
level.restarted = qfalse;
|
|
level.surrenderTeam = PTE_NONE;
|
|
trap_SetConfigstring( CS_WINNER, "" );
|
|
}
|
|
|
|
|
|
|
|
//===================================================================
|
|
|
|
void QDECL Com_Error( int level, const char *error, ... )
|
|
{
|
|
va_list argptr;
|
|
char text[ 1024 ];
|
|
|
|
va_start( argptr, error );
|
|
vsprintf( text, error, argptr );
|
|
va_end( argptr );
|
|
|
|
G_Error( "%s", text );
|
|
}
|
|
|
|
void QDECL Com_Printf( const char *msg, ... )
|
|
{
|
|
va_list argptr;
|
|
char text[ 1024 ];
|
|
|
|
va_start( argptr, msg );
|
|
vsprintf( text, msg, argptr );
|
|
va_end( argptr );
|
|
|
|
G_Printf( "%s", text );
|
|
}
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
PLAYER COUNTING / SCORE SORTING
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
=============
|
|
SortRanks
|
|
|
|
=============
|
|
*/
|
|
int QDECL SortRanks( const void *a, const void *b )
|
|
{
|
|
gclient_t *ca, *cb;
|
|
|
|
ca = &level.clients[ *(int *)a ];
|
|
cb = &level.clients[ *(int *)b ];
|
|
|
|
// then sort by score
|
|
if( ca->ps.persistant[ PERS_SCORE ] > cb->ps.persistant[ PERS_SCORE ] )
|
|
return -1;
|
|
else if( ca->ps.persistant[ PERS_SCORE ] < cb->ps.persistant[ PERS_SCORE ] )
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_InitSpawnQueue
|
|
|
|
Initialise a spawn queue
|
|
============
|
|
*/
|
|
void G_InitSpawnQueue( spawnQueue_t *sq )
|
|
{
|
|
int i;
|
|
|
|
sq->back = sq->front = 0;
|
|
sq->back = QUEUE_MINUS1( sq->back );
|
|
|
|
//0 is a valid clientNum, so use something else
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
sq->clients[ i ] = -1;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_GetSpawnQueueLength
|
|
|
|
Return tha length of a spawn queue
|
|
============
|
|
*/
|
|
int G_GetSpawnQueueLength( spawnQueue_t *sq )
|
|
{
|
|
int length = sq->back - sq->front + 1;
|
|
|
|
while( length < 0 )
|
|
length += MAX_CLIENTS;
|
|
|
|
while( length >= MAX_CLIENTS )
|
|
length -= MAX_CLIENTS;
|
|
|
|
return length;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_PopSpawnQueue
|
|
|
|
Remove from front element from a spawn queue
|
|
============
|
|
*/
|
|
int G_PopSpawnQueue( spawnQueue_t *sq )
|
|
{
|
|
int clientNum = sq->clients[ sq->front ];
|
|
|
|
if( G_GetSpawnQueueLength( sq ) > 0 )
|
|
{
|
|
sq->clients[ sq->front ] = -1;
|
|
sq->front = QUEUE_PLUS1( sq->front );
|
|
g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED;
|
|
|
|
return clientNum;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_PeekSpawnQueue
|
|
|
|
Look at front element from a spawn queue
|
|
============
|
|
*/
|
|
int G_PeekSpawnQueue( spawnQueue_t *sq )
|
|
{
|
|
return sq->clients[ sq->front ];
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_PushSpawnQueue
|
|
|
|
Add an element to the back of the spawn queue
|
|
============
|
|
*/
|
|
void G_PushSpawnQueue( spawnQueue_t *sq, int clientNum )
|
|
{
|
|
sq->back = QUEUE_PLUS1( sq->back );
|
|
sq->clients[ sq->back ] = clientNum;
|
|
|
|
g_entities[ clientNum ].client->ps.pm_flags |= PMF_QUEUED;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_RemoveFromSpawnQueue
|
|
|
|
remove a specific client from a spawn queue
|
|
============
|
|
*/
|
|
qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum )
|
|
{
|
|
int i = sq->front;
|
|
|
|
if( G_GetSpawnQueueLength( sq ) )
|
|
{
|
|
do
|
|
{
|
|
if( sq->clients[ i ] == clientNum )
|
|
{
|
|
//and this kids is why it would have
|
|
//been better to use an LL for internal
|
|
//representation
|
|
do
|
|
{
|
|
sq->clients[ i ] = sq->clients[ QUEUE_PLUS1( i ) ];
|
|
|
|
i = QUEUE_PLUS1( i );
|
|
} while( i != QUEUE_PLUS1( sq->back ) );
|
|
|
|
sq->back = QUEUE_MINUS1( sq->back );
|
|
g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
i = QUEUE_PLUS1( i );
|
|
} while( i != QUEUE_PLUS1( sq->back ) );
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_GetPosInSpawnQueue
|
|
|
|
Get the position of a client in a spawn queue
|
|
============
|
|
*/
|
|
int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum )
|
|
{
|
|
int i = sq->front;
|
|
|
|
if( G_GetSpawnQueueLength( sq ) )
|
|
{
|
|
do
|
|
{
|
|
if( sq->clients[ i ] == clientNum )
|
|
{
|
|
if( i < sq->front )
|
|
return i + MAX_CLIENTS - sq->front;
|
|
else
|
|
return i - sq->front;
|
|
}
|
|
|
|
i = QUEUE_PLUS1( i );
|
|
} while( i != QUEUE_PLUS1( sq->back ) );
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_PrintSpawnQueue
|
|
|
|
Print the contents of a spawn queue
|
|
============
|
|
*/
|
|
void G_PrintSpawnQueue( spawnQueue_t *sq )
|
|
{
|
|
int i = sq->front;
|
|
int length = G_GetSpawnQueueLength( sq );
|
|
|
|
G_Printf( "l:%d f:%d b:%d :", length, sq->front, sq->back );
|
|
|
|
if( length > 0 )
|
|
{
|
|
do
|
|
{
|
|
if( sq->clients[ i ] == -1 )
|
|
G_Printf( "*:" );
|
|
else
|
|
G_Printf( "%d:", sq->clients[ i ] );
|
|
|
|
i = QUEUE_PLUS1( i );
|
|
} while( i != QUEUE_PLUS1( sq->back ) );
|
|
}
|
|
|
|
G_Printf( "\n" );
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_SpawnClients
|
|
|
|
Spawn queued clients
|
|
============
|
|
*/
|
|
void G_SpawnClients( pTeam_t team )
|
|
{
|
|
int clientNum;
|
|
gentity_t *ent, *spawn;
|
|
vec3_t spawn_origin, spawn_angles;
|
|
spawnQueue_t *sq = NULL;
|
|
int numSpawns = 0;
|
|
|
|
if( team == PTE_ALIENS )
|
|
{
|
|
sq = &level.alienSpawnQueue;
|
|
numSpawns = level.numAlienSpawns;
|
|
}
|
|
else if( team == PTE_HUMANS )
|
|
{
|
|
sq = &level.humanSpawnQueue;
|
|
numSpawns = level.numHumanSpawns;
|
|
}
|
|
|
|
if( G_GetSpawnQueueLength( sq ) > 0 && numSpawns > 0 )
|
|
{
|
|
clientNum = G_PeekSpawnQueue( sq );
|
|
ent = &g_entities[ clientNum ];
|
|
|
|
if( ( spawn = SelectTremulousSpawnPoint( team,
|
|
ent->client->pers.lastDeathLocation,
|
|
spawn_origin, spawn_angles ) ) )
|
|
{
|
|
clientNum = G_PopSpawnQueue( sq );
|
|
|
|
if( clientNum < 0 )
|
|
return;
|
|
|
|
ent = &g_entities[ clientNum ];
|
|
|
|
ent->client->sess.sessionTeam = TEAM_FREE;
|
|
ClientUserinfoChanged( clientNum );
|
|
ClientSpawn( ent, spawn, spawn_origin, spawn_angles );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_CountSpawns
|
|
|
|
Counts the number of spawns for each team
|
|
============
|
|
*/
|
|
void G_CountSpawns( void )
|
|
{
|
|
int i;
|
|
gentity_t *ent;
|
|
|
|
level.numAlienSpawns = 0;
|
|
level.numHumanSpawns = 0;
|
|
|
|
for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
|
|
{
|
|
if( !ent->inuse )
|
|
continue;
|
|
|
|
if( ent->s.modelindex == BA_A_SPAWN && ent->health > 0 )
|
|
level.numAlienSpawns++;
|
|
|
|
if( ent->s.modelindex == BA_H_SPAWN && ent->health > 0 )
|
|
level.numHumanSpawns++;
|
|
}
|
|
|
|
//let the client know how many spawns there are
|
|
trap_SetConfigstring( CS_SPAWNS, va( "%d %d",
|
|
level.numAlienSpawns, level.numHumanSpawns ) );
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_TimeTilSuddenDeath
|
|
============
|
|
*/
|
|
int G_TimeTilSuddenDeath( void )
|
|
{
|
|
if( !g_suddenDeathTime.integer )
|
|
return 1; // Always some time away
|
|
|
|
return ( g_suddenDeathTime.integer * 60000 ) -
|
|
( level.time - level.startTime );
|
|
}
|
|
|
|
|
|
#define PLAYER_COUNT_MOD 5.0f
|
|
|
|
/*
|
|
============
|
|
G_CalculateBuildPoints
|
|
|
|
Recalculate the quantity of building points available to the teams
|
|
============
|
|
*/
|
|
void G_CalculateBuildPoints( void )
|
|
{
|
|
int i;
|
|
buildable_t buildable;
|
|
gentity_t *ent;
|
|
int localHTP = g_humanBuildPoints.integer,
|
|
localATP = g_alienBuildPoints.integer;
|
|
|
|
//g_suddenDeath sets what state we want it to be. level.suddenDeath says whether we've calculated BPs at the 'start' of SD or not
|
|
|
|
//reset if SD was on, but now it's off
|
|
if(!g_suddenDeath.integer && level.suddenDeath) {
|
|
level.suddenDeath=qfalse;
|
|
level.suddenDeathWarning=0;
|
|
}
|
|
|
|
if(!level.suddenDeath){
|
|
if(g_suddenDeath.integer || G_TimeTilSuddenDeath( ) <= 0 ) //Conditions to enter SD
|
|
{
|
|
//begin sudden death
|
|
if( level.suddenDeathWarning < TW_PASSED )
|
|
{
|
|
trap_SendServerCommand( -1, "cp \"Sudden Death!\"" );
|
|
localHTP = 0;
|
|
localATP = 0;
|
|
|
|
if( g_suddenDeathMode.integer == SDMODE_SELECTIVE ) {
|
|
for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
|
|
{
|
|
if( ent->s.eType != ET_BUILDABLE )
|
|
continue;
|
|
|
|
if( BG_FindReplaceableTestForBuildable( ent->s.modelindex ) )
|
|
{
|
|
int t = BG_FindTeamForBuildable( ent->s.modelindex );
|
|
|
|
if( t == BIT_HUMANS )
|
|
localHTP += BG_FindBuildPointsForBuildable( ent->s.modelindex );
|
|
else if( t == BIT_ALIENS )
|
|
localATP += BG_FindBuildPointsForBuildable( ent->s.modelindex );
|
|
}
|
|
}
|
|
}
|
|
level.suddenDeathHBuildPoints = localHTP;
|
|
level.suddenDeathABuildPoints = localATP;
|
|
level.suddenDeath=qtrue;
|
|
//trap_Cvar_Set( "g_suddenDeath", "1" );
|
|
g_suddenDeath.integer=1;
|
|
|
|
level.suddenDeathWarning = TW_PASSED;
|
|
}
|
|
} else {
|
|
|
|
//warn about sudden death
|
|
if( G_TimeTilSuddenDeath( ) <= 60000 &&
|
|
level.suddenDeathWarning < TW_IMMINENT )
|
|
{
|
|
trap_SendServerCommand( -1, "cp \"Sudden Death in 1 minute!\"" );
|
|
level.suddenDeathWarning = TW_IMMINENT;
|
|
}
|
|
}
|
|
}
|
|
|
|
//set BP at each cycle
|
|
if( g_suddenDeath.integer )
|
|
{
|
|
localHTP = level.suddenDeathHBuildPoints;
|
|
localATP = level.suddenDeathABuildPoints;
|
|
}
|
|
else
|
|
{
|
|
localHTP = g_humanBuildPoints.integer;
|
|
localATP = g_alienBuildPoints.integer;
|
|
}
|
|
|
|
level.humanBuildPoints = level.humanBuildPointsPowered = localHTP;
|
|
level.alienBuildPoints = localATP;
|
|
|
|
level.reactorPresent = qfalse;
|
|
level.overmindPresent = qfalse;
|
|
|
|
for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
|
|
{
|
|
if( !ent->inuse )
|
|
continue;
|
|
|
|
if( ent->s.eType != ET_BUILDABLE )
|
|
continue;
|
|
|
|
buildable = ent->s.modelindex;
|
|
|
|
if( buildable != BA_NONE )
|
|
{
|
|
if( buildable == BA_H_REACTOR && ent->spawned && ent->health > 0 )
|
|
level.reactorPresent = qtrue;
|
|
|
|
if( buildable == BA_A_OVERMIND && ent->spawned && ent->health > 0 )
|
|
level.overmindPresent = qtrue;
|
|
|
|
if( !g_suddenDeath.integer || BG_FindReplaceableTestForBuildable( buildable ) )
|
|
{
|
|
if( BG_FindTeamForBuildable( buildable ) == BIT_HUMANS )
|
|
{
|
|
level.humanBuildPoints -= BG_FindBuildPointsForBuildable( buildable );
|
|
if( ent->powered )
|
|
level.humanBuildPointsPowered -= BG_FindBuildPointsForBuildable( buildable );
|
|
}
|
|
else
|
|
{
|
|
level.alienBuildPoints -= BG_FindBuildPointsForBuildable( buildable );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( level.humanBuildPoints < 0 )
|
|
{
|
|
localHTP -= level.humanBuildPoints;
|
|
level.humanBuildPointsPowered -= level.humanBuildPoints;
|
|
level.humanBuildPoints = 0;
|
|
}
|
|
|
|
if( level.alienBuildPoints < 0 )
|
|
{
|
|
localATP -= level.alienBuildPoints;
|
|
level.alienBuildPoints = 0;
|
|
}
|
|
|
|
trap_SetConfigstring( CS_BUILDPOINTS, va( "%d %d %d %d %d",
|
|
level.alienBuildPoints, localATP,
|
|
level.humanBuildPoints, localHTP,
|
|
level.humanBuildPointsPowered ) );
|
|
|
|
//may as well pump the stages here too
|
|
{
|
|
float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD;
|
|
float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD;
|
|
int alienNextStageThreshold, humanNextStageThreshold;
|
|
|
|
if( alienPlayerCountMod < 0.1f )
|
|
alienPlayerCountMod = 0.1f;
|
|
|
|
if( humanPlayerCountMod < 0.1f )
|
|
humanPlayerCountMod = 0.1f;
|
|
|
|
if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 )
|
|
alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) );
|
|
else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 )
|
|
alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) );
|
|
else
|
|
alienNextStageThreshold = -1;
|
|
|
|
if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 )
|
|
humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) );
|
|
else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 )
|
|
humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) );
|
|
else
|
|
humanNextStageThreshold = -1;
|
|
|
|
trap_SetConfigstring( CS_STAGES, va( "%d %d %d %d %d %d",
|
|
g_alienStage.integer, g_humanStage.integer,
|
|
g_alienKills.integer, g_humanKills.integer,
|
|
alienNextStageThreshold, humanNextStageThreshold ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_CalculateStages
|
|
============
|
|
*/
|
|
void G_CalculateStages( void )
|
|
{
|
|
float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD;
|
|
float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD;
|
|
|
|
if( alienPlayerCountMod < 0.1f )
|
|
alienPlayerCountMod = 0.1f;
|
|
|
|
if( humanPlayerCountMod < 0.1f )
|
|
humanPlayerCountMod = 0.1f;
|
|
|
|
if( g_alienKills.integer >=
|
|
(int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ) &&
|
|
g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 )
|
|
{
|
|
G_Checktrigger_stages( PTE_ALIENS, S2 );
|
|
trap_Cvar_Set( "g_alienStage", va( "%d", S2 ) );
|
|
level.alienStage2Time = level.time;
|
|
}
|
|
|
|
if( g_alienKills.integer >=
|
|
(int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ) &&
|
|
g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 )
|
|
{
|
|
G_Checktrigger_stages( PTE_ALIENS, S3 );
|
|
trap_Cvar_Set( "g_alienStage", va( "%d", S3 ) );
|
|
level.alienStage3Time = level.time;
|
|
}
|
|
|
|
if( g_humanKills.integer >=
|
|
(int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ) &&
|
|
g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 )
|
|
{
|
|
G_Checktrigger_stages( PTE_HUMANS, S2 );
|
|
trap_Cvar_Set( "g_humanStage", va( "%d", S2 ) );
|
|
level.humanStage2Time = level.time;
|
|
}
|
|
|
|
if( g_humanKills.integer >=
|
|
(int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ) &&
|
|
g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 )
|
|
{
|
|
G_Checktrigger_stages( PTE_HUMANS, S3 );
|
|
trap_Cvar_Set( "g_humanStage", va( "%d", S3 ) );
|
|
level.humanStage3Time = level.time;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
CalculateAvgPlayers
|
|
|
|
Calculates the average number of players playing this game
|
|
============
|
|
*/
|
|
void G_CalculateAvgPlayers( void )
|
|
{
|
|
//there are no clients or only spectators connected, so
|
|
//reset the number of samples in order to avoid the situation
|
|
//where the average tends to 0
|
|
if( !level.numAlienClients )
|
|
{
|
|
level.numAlienSamples = 0;
|
|
trap_Cvar_Set( "g_alienKills", "0" );
|
|
}
|
|
|
|
if( !level.numHumanClients )
|
|
{
|
|
level.numHumanSamples = 0;
|
|
trap_Cvar_Set( "g_humanKills", "0" );
|
|
}
|
|
|
|
//calculate average number of clients for stats
|
|
level.averageNumAlienClients =
|
|
( ( level.averageNumAlienClients * level.numAlienSamples )
|
|
+ level.numAlienClients ) /
|
|
(float)( level.numAlienSamples + 1 );
|
|
level.numAlienSamples++;
|
|
|
|
level.averageNumHumanClients =
|
|
( ( level.averageNumHumanClients * level.numHumanSamples )
|
|
+ level.numHumanClients ) /
|
|
(float)( level.numHumanSamples + 1 );
|
|
level.numHumanSamples++;
|
|
}
|
|
|
|
/*
|
|
============
|
|
CalculateRanks
|
|
|
|
Recalculates the score ranks of all players
|
|
This will be called on every client connect, begin, disconnect, death,
|
|
and team change.
|
|
============
|
|
*/
|
|
void CalculateRanks( void )
|
|
{
|
|
int i;
|
|
int rank;
|
|
int score;
|
|
int newScore;
|
|
gclient_t *cl;
|
|
|
|
char P[ MAX_CLIENTS + 1 ] = {""};
|
|
int ff = 0;
|
|
|
|
level.numConnectedClients = 0;
|
|
level.numNonSpectatorClients = 0;
|
|
level.numPlayingClients = 0;
|
|
level.numVotingClients = 0; // don't count bots
|
|
level.numAlienClients = 0;
|
|
level.numHumanClients = 0;
|
|
level.numLiveAlienClients = 0;
|
|
level.numLiveHumanClients = 0;
|
|
level.bleeders = 0;
|
|
|
|
for( i = 0; i < level.maxclients; i++ )
|
|
{
|
|
P[ i ] = '-';
|
|
if ( level.clients[ i ].pers.connected != CON_DISCONNECTED )
|
|
{
|
|
level.sortedClients[ level.numConnectedClients ] = i;
|
|
level.numConnectedClients++;
|
|
P[ i ] = (char)'0' + level.clients[ i ].pers.teamSelection;
|
|
|
|
if( level.clients[ i ].pers.connected != CON_CONNECTED )
|
|
continue;
|
|
|
|
level.numVotingClients++;
|
|
if( level.clients[ i ].pers.teamSelection != PTE_NONE )
|
|
{
|
|
level.numPlayingClients++;
|
|
if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
|
|
level.numNonSpectatorClients++;
|
|
|
|
if( level.clients[ i ].pers.teamSelection == PTE_ALIENS )
|
|
{
|
|
level.numAlienClients++;
|
|
if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
|
|
level.numLiveAlienClients++;
|
|
}
|
|
else if( level.clients[ i ].pers.teamSelection == PTE_HUMANS )
|
|
{
|
|
level.numHumanClients++;
|
|
if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
|
|
level.numLiveHumanClients++;
|
|
}
|
|
|
|
if( level.clients[ i ].pers.bleeder )
|
|
level.bleeders++;
|
|
}
|
|
}
|
|
}
|
|
level.numteamVotingClients[ 0 ] = level.numHumanClients;
|
|
level.numteamVotingClients[ 1 ] = level.numAlienClients;
|
|
P[ i + 1 ] = '\0';
|
|
trap_Cvar_Set( "P", P );
|
|
|
|
if( g_friendlyFire.integer )
|
|
ff |= ( FFF_HUMANS | FFF_ALIENS );
|
|
if( g_friendlyFireHumans.integer )
|
|
ff |= FFF_HUMANS;
|
|
if( g_friendlyFireAliens.integer )
|
|
ff |= FFF_ALIENS;
|
|
if( g_friendlyBuildableFire.integer )
|
|
ff |= FFF_BUILDABLES;
|
|
trap_Cvar_Set( "ff", va( "%i", ff ) );
|
|
|
|
qsort( level.sortedClients, level.numConnectedClients,
|
|
sizeof( level.sortedClients[ 0 ] ), SortRanks );
|
|
|
|
// set the rank value for all clients that are connected and not spectators
|
|
rank = -1;
|
|
score = 0;
|
|
for( i = 0; i < level.numPlayingClients; i++ )
|
|
{
|
|
cl = &level.clients[ level.sortedClients[ i ] ];
|
|
newScore = cl->ps.persistant[ PERS_SCORE ];
|
|
|
|
if( i == 0 || newScore != score )
|
|
{
|
|
rank = i;
|
|
// assume we aren't tied until the next client is checked
|
|
level.clients[ level.sortedClients[ i ] ].ps.persistant[ PERS_RANK ] = rank;
|
|
}
|
|
else
|
|
{
|
|
// we are tied with the previous client
|
|
level.clients[ level.sortedClients[ i - 1 ] ].ps.persistant[ PERS_RANK ] = rank;
|
|
level.clients[ level.sortedClients[ i ] ].ps.persistant[ PERS_RANK ] = rank;
|
|
}
|
|
|
|
score = newScore;
|
|
}
|
|
|
|
// see if it is time to end the level
|
|
CheckExitRules( );
|
|
|
|
// if we are at the intermission, send the new info to everyone
|
|
if( level.intermissiontime )
|
|
SendScoreboardMessageToAllClients( );
|
|
}
|
|
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
MAP CHANGING
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
/*
|
|
========================
|
|
SendScoreboardMessageToAllClients
|
|
|
|
Do this at BeginIntermission time and whenever ranks are recalculated
|
|
due to enters/exits/forced team changes
|
|
========================
|
|
*/
|
|
void SendScoreboardMessageToAllClients( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < level.maxclients; i++ )
|
|
{
|
|
if( level.clients[ i ].pers.connected == CON_CONNECTED )
|
|
ScoreboardMessage( g_entities + i );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
MoveClientToIntermission
|
|
|
|
When the intermission starts, this will be called for all players.
|
|
If a new client connects, this will be called after the spawn function.
|
|
========================
|
|
*/
|
|
void MoveClientToIntermission( gentity_t *ent )
|
|
{
|
|
// take out of follow mode if needed
|
|
if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
|
|
G_StopFollowing( ent );
|
|
|
|
// move to the spot
|
|
VectorCopy( level.intermission_origin, ent->s.origin );
|
|
VectorCopy( level.intermission_origin, ent->client->ps.origin );
|
|
VectorCopy( level.intermission_angle, ent->client->ps.viewangles );
|
|
ent->client->ps.pm_type = PM_INTERMISSION;
|
|
|
|
// clean up powerup info
|
|
memset( ent->client->ps.powerups, 0, sizeof( ent->client->ps.powerups ) );
|
|
|
|
ent->client->ps.eFlags = 0;
|
|
ent->s.eFlags = 0;
|
|
ent->s.eType = ET_GENERAL;
|
|
ent->s.modelindex = 0;
|
|
ent->s.loopSound = 0;
|
|
ent->s.event = 0;
|
|
ent->r.contents = 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
FindIntermissionPoint
|
|
|
|
This is also used for spectator spawns
|
|
==================
|
|
*/
|
|
void FindIntermissionPoint( void )
|
|
{
|
|
gentity_t *ent, *target;
|
|
vec3_t dir;
|
|
|
|
// find the intermission spot
|
|
ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" );
|
|
|
|
if( !ent )
|
|
{ // the map creator forgot to put in an intermission point...
|
|
SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( ent->s.origin, level.intermission_origin );
|
|
VectorCopy( ent->s.angles, level.intermission_angle );
|
|
// if it has a target, look towards it
|
|
if( ent->target )
|
|
{
|
|
target = G_PickTarget( ent->target );
|
|
|
|
if( target )
|
|
{
|
|
VectorSubtract( target->s.origin, level.intermission_origin, dir );
|
|
vectoangles( dir, level.intermission_angle );
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
BeginIntermission
|
|
==================
|
|
*/
|
|
void BeginIntermission( void )
|
|
{
|
|
int i;
|
|
gentity_t *client;
|
|
|
|
if( level.intermissiontime )
|
|
return; // already active
|
|
|
|
level.intermissiontime = level.time;
|
|
|
|
G_ClearVotes( );
|
|
|
|
FindIntermissionPoint( );
|
|
|
|
// move all clients to the intermission point
|
|
for( i = 0; i < level.maxclients; i++ )
|
|
{
|
|
client = g_entities + i;
|
|
|
|
if( !client->inuse )
|
|
continue;
|
|
|
|
// respawn if dead
|
|
if( client->health <= 0 )
|
|
respawn(client);
|
|
|
|
MoveClientToIntermission( client );
|
|
}
|
|
|
|
// send the current scoring to all clients
|
|
SendScoreboardMessageToAllClients( );
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
ExitLevel
|
|
|
|
When the intermission has been exited, the server is either moved
|
|
to a new map based on the map rotation or the current map restarted
|
|
=============
|
|
*/
|
|
void ExitLevel( void )
|
|
{
|
|
int i;
|
|
gclient_t *cl;
|
|
|
|
buildHistory_t *tmp, *mark;
|
|
|
|
while( ( tmp = level.buildHistory ) )
|
|
{
|
|
level.buildHistory = level.buildHistory->next;
|
|
while( ( mark = tmp ) )
|
|
{
|
|
tmp = tmp->marked;
|
|
G_Free( mark );
|
|
}
|
|
}
|
|
|
|
|
|
if( G_MapRotationActive( ) )
|
|
G_AdvanceMapRotation( );
|
|
else
|
|
trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" );
|
|
|
|
level.restarted = qtrue;
|
|
level.changemap = NULL;
|
|
level.intermissiontime = 0;
|
|
|
|
// reset all the scores so we don't enter the intermission again
|
|
for( i = 0; i < g_maxclients.integer; i++ )
|
|
{
|
|
cl = level.clients + i;
|
|
if( cl->pers.connected != CON_CONNECTED )
|
|
continue;
|
|
|
|
cl->ps.persistant[ PERS_SCORE ] = 0;
|
|
}
|
|
|
|
// we need to do this here before chaning to CON_CONNECTING
|
|
G_WriteSessionData( );
|
|
|
|
// change all client states to connecting, so the early players into the
|
|
// next level will know the others aren't done reconnecting
|
|
for( i = 0; i < g_maxclients.integer; i++ )
|
|
{
|
|
if( level.clients[ i ].pers.connected == CON_CONNECTED )
|
|
level.clients[ i ].pers.connected = CON_CONNECTING;
|
|
}
|
|
|
|
}
|
|
/*
|
|
=================
|
|
G_AdminsPrintf
|
|
|
|
Print to all active admins, and the logfile with a time stamp if it is open, and to the console
|
|
=================
|
|
*/
|
|
void QDECL G_AdminsPrintf( const char *fmt, ... )
|
|
{
|
|
va_list argptr;
|
|
char string[ 1024 ];
|
|
gentity_t *tempent;
|
|
int j;
|
|
|
|
va_start( argptr, fmt );
|
|
vsprintf( string, fmt,argptr );
|
|
va_end( argptr );
|
|
|
|
for( j = 0; j < level.maxclients; j++ )
|
|
{
|
|
tempent = &g_entities[ j ];
|
|
if( G_admin_permission( tempent, ADMF_ADMINCHAT) &&
|
|
!tempent->client->pers.ignoreAdminWarnings )
|
|
{
|
|
trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Admins]^7 %s\"", string) );
|
|
}
|
|
}
|
|
|
|
G_LogPrintf("%s",string);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
G_LogPrintf
|
|
|
|
Print to the logfile with a time stamp if it is open
|
|
=================
|
|
*/
|
|
void QDECL G_LogPrintf( const char *fmt, ... )
|
|
{
|
|
va_list argptr;
|
|
char string[ 1024 ], decolored[ 1024 ];
|
|
int min, tens, sec;
|
|
|
|
sec = level.time / 1000;
|
|
|
|
min = sec / 60;
|
|
sec -= min * 60;
|
|
tens = sec / 10;
|
|
sec -= tens * 10;
|
|
|
|
Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );
|
|
|
|
va_start( argptr, fmt );
|
|
vsprintf( string +7 , fmt,argptr );
|
|
va_end( argptr );
|
|
|
|
if( g_dedicated.integer )
|
|
G_Printf( "%s", string + 7 );
|
|
|
|
if( !level.logFile )
|
|
return;
|
|
|
|
if( g_decolorLogfiles.integer )
|
|
{
|
|
G_DecolorString( string, decolored );
|
|
trap_FS_Write( decolored, strlen( decolored ), level.logFile );
|
|
}
|
|
else
|
|
{
|
|
trap_FS_Write( string, strlen( string ), level.logFile );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_LogPrintfColored
|
|
|
|
Bypasses g_decolorLogfiles for events that need colors in the logs
|
|
=================
|
|
*/
|
|
void QDECL G_LogPrintfColored( const char *fmt, ... )
|
|
{
|
|
va_list argptr;
|
|
char string[ 1024 ];
|
|
int min, tens, sec;
|
|
|
|
sec = level.time / 1000;
|
|
|
|
min = sec / 60;
|
|
sec -= min * 60;
|
|
tens = sec / 10;
|
|
sec -= tens * 10;
|
|
|
|
Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );
|
|
|
|
va_start( argptr, fmt );
|
|
vsprintf( string +7 , fmt,argptr );
|
|
va_end( argptr );
|
|
|
|
if( g_dedicated.integer )
|
|
G_Printf( "%s", string + 7 );
|
|
|
|
if( !level.logFile )
|
|
return;
|
|
|
|
trap_FS_Write( string, strlen( string ), level.logFile );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_LogOnlyPrintf
|
|
|
|
Print to the logfile only (not console) with a time stamp if it is open
|
|
=================
|
|
*/
|
|
void QDECL G_LogOnlyPrintf( const char *fmt, ... )
|
|
{
|
|
va_list argptr;
|
|
char string[ 1024 ];
|
|
int min, tens, sec;
|
|
|
|
sec = level.time / 1000;
|
|
|
|
min = sec / 60;
|
|
sec -= min * 60;
|
|
tens = sec / 10;
|
|
sec -= tens * 10;
|
|
|
|
Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );
|
|
|
|
va_start( argptr, fmt );
|
|
vsprintf( string +7 , fmt,argptr );
|
|
va_end( argptr );
|
|
|
|
if( !level.logFile )
|
|
return;
|
|
|
|
trap_FS_Write( string, strlen( string ), level.logFile );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_SendGameStat
|
|
=================
|
|
*/
|
|
void G_SendGameStat( pTeam_t team )
|
|
{
|
|
char map[ MAX_STRING_CHARS ];
|
|
char teamChar;
|
|
char data[ BIG_INFO_STRING ];
|
|
char entry[ MAX_STRING_CHARS ];
|
|
int i, dataLength, entryLength;
|
|
gclient_t *cl;
|
|
|
|
trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
|
|
|
|
switch( team )
|
|
{
|
|
case PTE_ALIENS: teamChar = 'A'; break;
|
|
case PTE_HUMANS: teamChar = 'H'; break;
|
|
case PTE_NONE: teamChar = 'L'; break;
|
|
default: return;
|
|
}
|
|
|
|
Com_sprintf( data, BIG_INFO_STRING,
|
|
"%s %s T:%c A:%f H:%f M:%s D:%d SD:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d",
|
|
Q3_VERSION,
|
|
g_tag.string,
|
|
teamChar,
|
|
level.averageNumAlienClients,
|
|
level.averageNumHumanClients,
|
|
map,
|
|
level.time - level.startTime,
|
|
G_TimeTilSuddenDeath( ),
|
|
g_alienStage.integer,
|
|
level.alienStage2Time - level.startTime,
|
|
level.alienStage3Time - level.startTime,
|
|
g_humanStage.integer,
|
|
level.humanStage2Time - level.startTime,
|
|
level.humanStage3Time - level.startTime,
|
|
level.numConnectedClients );
|
|
|
|
dataLength = strlen( data );
|
|
|
|
for( i = 0; i < level.numConnectedClients; i++ )
|
|
{
|
|
int ping;
|
|
|
|
cl = &level.clients[ level.sortedClients[ i ] ];
|
|
|
|
if( cl->pers.connected == CON_CONNECTING )
|
|
ping = -1;
|
|
else
|
|
ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
|
|
|
|
switch( cl->ps.stats[ STAT_PTEAM ] )
|
|
{
|
|
case PTE_ALIENS: teamChar = 'A'; break;
|
|
case PTE_HUMANS: teamChar = 'H'; break;
|
|
case PTE_NONE: teamChar = 'S'; break;
|
|
default: return;
|
|
}
|
|
|
|
Com_sprintf( entry, MAX_STRING_CHARS,
|
|
" \"%s\" %c %d %d %d",
|
|
cl->pers.netname,
|
|
teamChar,
|
|
cl->ps.persistant[ PERS_SCORE ],
|
|
ping,
|
|
( level.time - cl->pers.enterTime ) / 60000 );
|
|
|
|
entryLength = strlen( entry );
|
|
|
|
if( dataLength + entryLength > MAX_STRING_CHARS )
|
|
break;
|
|
|
|
Q_strncpyz( data + dataLength, entry, BIG_INFO_STRING );
|
|
dataLength += entryLength;
|
|
}
|
|
|
|
trap_SendGameStat( data );
|
|
}
|
|
|
|
/*
|
|
================
|
|
LogExit
|
|
|
|
Append information about this game to the log file
|
|
================
|
|
*/
|
|
void LogExit( const char *string )
|
|
{
|
|
int i, numSorted;
|
|
gclient_t *cl;
|
|
gentity_t *ent;
|
|
|
|
G_LogPrintf( "Exit: %s\n", string );
|
|
|
|
level.intermissionQueued = level.time;
|
|
|
|
// this will keep the clients from playing any voice sounds
|
|
// that will get cut off when the queued intermission starts
|
|
trap_SetConfigstring( CS_INTERMISSION, "1" );
|
|
|
|
// don't send more than 32 scores (FIXME?)
|
|
numSorted = level.numConnectedClients;
|
|
if( numSorted > 32 )
|
|
numSorted = 32;
|
|
|
|
for( i = 0; i < numSorted; i++ )
|
|
{
|
|
int ping;
|
|
|
|
cl = &level.clients[ level.sortedClients[ i ] ];
|
|
|
|
if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE )
|
|
continue;
|
|
|
|
if( cl->pers.connected == CON_CONNECTING )
|
|
continue;
|
|
|
|
ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
|
|
|
|
G_LogPrintf( "score: %i ping: %i client: %i %s\n",
|
|
cl->ps.persistant[ PERS_SCORE ], ping, level.sortedClients[ i ],
|
|
cl->pers.netname );
|
|
|
|
}
|
|
|
|
for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
|
|
{
|
|
if( !ent->inuse )
|
|
continue;
|
|
|
|
if( !Q_stricmp( ent->classname, "trigger_win" ) )
|
|
{
|
|
if( level.lastWin == ent->stageTeam )
|
|
ent->use( ent, ent, ent );
|
|
}
|
|
}
|
|
|
|
G_SendGameStat( level.lastWin );
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
CheckIntermissionExit
|
|
|
|
The level will stay at the intermission for a minimum of 5 seconds
|
|
If all players wish to continue, the level will then exit.
|
|
If one or more players have not acknowledged the continue, the game will
|
|
wait 10 seconds before going on.
|
|
=================
|
|
*/
|
|
void CheckIntermissionExit( void )
|
|
{
|
|
int ready, notReady, numPlayers;
|
|
int i;
|
|
gclient_t *cl;
|
|
int readyMask;
|
|
|
|
//if no clients are connected, just exit
|
|
if( !level.numConnectedClients )
|
|
{
|
|
ExitLevel( );
|
|
return;
|
|
}
|
|
|
|
// see which players are ready
|
|
ready = 0;
|
|
notReady = 0;
|
|
readyMask = 0;
|
|
numPlayers = 0;
|
|
for( i = 0; i < g_maxclients.integer; i++ )
|
|
{
|
|
cl = level.clients + i;
|
|
if( cl->pers.connected != CON_CONNECTED )
|
|
continue;
|
|
|
|
if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE )
|
|
continue;
|
|
|
|
if( cl->readyToExit )
|
|
{
|
|
ready++;
|
|
if( i < 16 )
|
|
readyMask |= 1 << i;
|
|
}
|
|
else
|
|
notReady++;
|
|
|
|
numPlayers++;
|
|
}
|
|
|
|
trap_SetConfigstring( CS_CLIENTS_READY, va( "%d", readyMask ) );
|
|
|
|
// never exit in less than five seconds
|
|
if( level.time < level.intermissiontime + 5000 )
|
|
return;
|
|
|
|
// never let intermission go on for over 1 minute
|
|
if( level.time > level.intermissiontime + 60000 )
|
|
{
|
|
ExitLevel( );
|
|
return;
|
|
}
|
|
|
|
// if nobody wants to go, clear timer
|
|
if( !ready && numPlayers )
|
|
{
|
|
level.readyToExit = qfalse;
|
|
return;
|
|
}
|
|
|
|
// if everyone wants to go, go now
|
|
if( !notReady )
|
|
{
|
|
ExitLevel( );
|
|
return;
|
|
}
|
|
|
|
// the first person to ready starts the thirty second timeout
|
|
if( !level.readyToExit )
|
|
{
|
|
level.readyToExit = qtrue;
|
|
level.exitTime = level.time;
|
|
}
|
|
|
|
// if we have waited thirty seconds since at least one player
|
|
// wanted to exit, go ahead
|
|
if( level.time < level.exitTime + 30000 )
|
|
return;
|
|
|
|
ExitLevel( );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
ScoreIsTied
|
|
=============
|
|
*/
|
|
qboolean ScoreIsTied( void )
|
|
{
|
|
int a, b;
|
|
|
|
if( level.numPlayingClients < 2 )
|
|
return qfalse;
|
|
|
|
a = level.clients[ level.sortedClients[ 0 ] ].ps.persistant[ PERS_SCORE ];
|
|
b = level.clients[ level.sortedClients[ 1 ] ].ps.persistant[ PERS_SCORE ];
|
|
|
|
return a == b;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CheckExitRules
|
|
|
|
There will be a delay between the time the exit is qualified for
|
|
and the time everyone is moved to the intermission spot, so you
|
|
can see the last frag.
|
|
=================
|
|
*/
|
|
void CheckExitRules( void )
|
|
{
|
|
// if at the intermission, wait for all non-bots to
|
|
// signal ready, then go to next level
|
|
if( level.intermissiontime )
|
|
{
|
|
CheckIntermissionExit( );
|
|
return;
|
|
}
|
|
|
|
if( level.intermissionQueued )
|
|
{
|
|
if( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME )
|
|
{
|
|
level.intermissionQueued = 0;
|
|
BeginIntermission( );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if( g_timelimit.integer )
|
|
{
|
|
if( level.time - level.startTime >= g_timelimit.integer * 60000 )
|
|
{
|
|
level.lastWin = PTE_NONE;
|
|
trap_SendServerCommand( -1, "print \"Timelimit hit\n\"" );
|
|
trap_SetConfigstring( CS_WINNER, "Stalemate" );
|
|
LogExit( "Timelimit hit." );
|
|
G_admin_maplog_result( "t" );
|
|
return;
|
|
}
|
|
else if( level.time - level.startTime >= ( g_timelimit.integer - 5 ) * 60000 &&
|
|
level.timelimitWarning < TW_IMMINENT )
|
|
{
|
|
trap_SendServerCommand( -1, "cp \"5 minutes remaining!\"" );
|
|
level.timelimitWarning = TW_IMMINENT;
|
|
}
|
|
else if( level.time - level.startTime >= ( g_timelimit.integer - 1 ) * 60000 &&
|
|
level.timelimitWarning < TW_PASSED )
|
|
{
|
|
trap_SendServerCommand( -1, "cp \"1 minute remaining!\"" );
|
|
level.timelimitWarning = TW_PASSED;
|
|
}
|
|
}
|
|
|
|
if( level.uncondHumanWin ||
|
|
( ( level.time > level.startTime + 1000 ) &&
|
|
( level.numAlienSpawns == 0 ) &&
|
|
( level.numLiveAlienClients == 0 ) ) )
|
|
{
|
|
//humans win
|
|
level.lastWin = PTE_HUMANS;
|
|
trap_SendServerCommand( -1, "print \"Humans win\n\"");
|
|
trap_SetConfigstring( CS_WINNER, "Humans Win" );
|
|
LogExit( "Humans win." );
|
|
G_admin_maplog_result( "h" );
|
|
}
|
|
else if( level.uncondAlienWin ||
|
|
( ( level.time > level.startTime + 1000 ) &&
|
|
( level.numHumanSpawns == 0 ) &&
|
|
( level.numLiveHumanClients == 0 ) ) )
|
|
{
|
|
//aliens win
|
|
level.lastWin = PTE_ALIENS;
|
|
trap_SendServerCommand( -1, "print \"Aliens win\n\"");
|
|
trap_SetConfigstring( CS_WINNER, "Aliens Win" );
|
|
LogExit( "Aliens win." );
|
|
G_admin_maplog_result( "a" );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
FUNCTIONS CALLED EVERY FRAME
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
==================
|
|
CheckVote
|
|
==================
|
|
*/
|
|
void CheckVote( void )
|
|
{
|
|
int votePercentToPass=level.votePercentToPass;
|
|
int voteYesPercent = 0;
|
|
char *result = "";
|
|
|
|
if( level.voteExecuteTime && level.voteExecuteTime < level.time )
|
|
{
|
|
level.voteExecuteTime = 0;
|
|
|
|
if( !Q_stricmp( level.voteString, "map_restart" ) )
|
|
{
|
|
G_admin_maplog_result( "r" );
|
|
}
|
|
else if( !Q_stricmpn( level.voteString, "map", 3 ) )
|
|
{
|
|
G_admin_maplog_result( "m" );
|
|
}
|
|
else if( !Q_stricmpn( level.voteString, "!restart", 8 ) )
|
|
{
|
|
G_admin_maplog_result( "l" );
|
|
}
|
|
|
|
trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) );
|
|
if( !Q_stricmp( level.voteString, "map_restart" ) ||
|
|
!Q_stricmpn( level.voteString, "map", 3 ) )
|
|
{
|
|
level.restarted = qtrue;
|
|
}
|
|
}
|
|
|
|
if( !level.voteTime )
|
|
return;
|
|
|
|
if( level.voteYes || level.voteNo )
|
|
{
|
|
voteYesPercent = (int)(100* (level.voteYes)/(level.voteYes + level.voteNo));
|
|
}
|
|
if( level.time - level.voteTime >= VOTE_TIME || ( level.voteYes + level.voteNo == level.numConnectedClients ) )
|
|
{
|
|
if( voteYesPercent> votePercentToPass || level.voteNo == 0 )
|
|
{
|
|
// execute the command, then remove the vote
|
|
result = "^2passed";
|
|
level.voteExecuteTime = level.time + 3000;
|
|
}
|
|
else
|
|
{
|
|
result = "^1failed";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( level.voteYes > (int)((double)level.numConnectedClients * ((double)votePercentToPass/100.0)) )
|
|
{
|
|
// execute the command, then remove the vote
|
|
result = "^2passed";
|
|
level.voteExecuteTime = level.time + 3000;
|
|
}
|
|
else if( level.voteNo > (int)((double)level.numConnectedClients * ((double)(100.0-votePercentToPass)/100.0)) )
|
|
{
|
|
// same behavior as a timeout
|
|
result = "^1failed";
|
|
}
|
|
else
|
|
{
|
|
// still waiting for a majority
|
|
return;
|
|
}
|
|
}
|
|
|
|
trap_SendServerCommand(-1, va("print \"Vote %s^7 (^2Y:%d^7-^1N:%d^7, %d percent) (%s)\n\"",
|
|
result, level.voteYes, level.voteNo, voteYesPercent, level.voteDisplayString ) );
|
|
|
|
G_admin_adminlog_log( NULL, "vote",
|
|
va( "%s^7 (^2Y:%d^7-^1N:%d^7, %d percent)",
|
|
result, level.voteYes, level.voteNo, voteYesPercent ),
|
|
0, ( level.voteExecuteTime ) );
|
|
|
|
level.voteTime = 0;
|
|
trap_SetConfigstring( CS_VOTE_TIME, "" );
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
CheckTeamVote
|
|
==================
|
|
*/
|
|
void CheckTeamVote( int team )
|
|
{
|
|
int cs_offset;
|
|
int votePercentToPass;
|
|
int voteYesPercent = 0;
|
|
char *result = "";
|
|
|
|
if ( team == PTE_HUMANS )
|
|
cs_offset = 0;
|
|
else if ( team == PTE_ALIENS )
|
|
cs_offset = 1;
|
|
else
|
|
return;
|
|
|
|
if( !level.teamVoteTime[ cs_offset ] )
|
|
return;
|
|
|
|
votePercentToPass = level.teamVotePercentToPass[ cs_offset ];
|
|
if( level.teamVoteYes[ cs_offset ] || level.teamVoteNo[ cs_offset ] )
|
|
{
|
|
voteYesPercent = (int)(100* (level.teamVoteYes[ cs_offset ])/(level.teamVoteYes[ cs_offset ] + level.teamVoteNo[ cs_offset ]));
|
|
}
|
|
|
|
if( level.time - level.teamVoteTime[ cs_offset ] >= VOTE_TIME ||
|
|
level.teamVoteYes[ cs_offset ] + level.teamVoteNo[ cs_offset ] == level.numteamVotingClients[ cs_offset ] )
|
|
{
|
|
if( voteYesPercent > votePercentToPass &&
|
|
level.teamVoteYes[ cs_offset ] + level.teamVoteNo[ cs_offset ] > level.numteamVotingClients[ cs_offset ] / 2 )
|
|
{
|
|
// execute the command, then remove the vote
|
|
result = "^2passed";
|
|
trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) );
|
|
}
|
|
else
|
|
{
|
|
result = "^1failed";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( level.teamVoteYes[ cs_offset ] > (int)((double)level.numteamVotingClients[ cs_offset ] * ((double)votePercentToPass/100.0)) )
|
|
{
|
|
// execute the command, then remove the vote
|
|
result = "^2passed";
|
|
trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) );
|
|
}
|
|
else if( level.teamVoteNo[ cs_offset ] > (int)((double)level.numteamVotingClients[ cs_offset ] * ((double)(100.0-votePercentToPass)/100.0)) )
|
|
{
|
|
// same behavior as a timeout
|
|
result = "^1Failed";
|
|
}
|
|
else
|
|
{
|
|
// still waiting for a majority
|
|
return;
|
|
}
|
|
}
|
|
|
|
trap_SendServerCommand( -1, va( "print \"Team vote %s^7 (^2Y:%d^7-^1N:%d^7, %d percent)\n\"",
|
|
result, level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ], voteYesPercent ) );
|
|
|
|
G_admin_adminlog_log( NULL, "teamvote",
|
|
va( "%s^7 (^2Y:%d^7-^1N:%d^7, %d percent)",
|
|
result, level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ], voteYesPercent ),
|
|
0, ( strstr( result, "pass" ) != NULL ) );
|
|
|
|
level.teamVoteTime[ cs_offset ] = 0;
|
|
trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CheckMsgTimer
|
|
==================
|
|
*/
|
|
void CheckMsgTimer( void )
|
|
{
|
|
if( !g_msgTime.integer )
|
|
return;
|
|
|
|
if( level.time - level.lastMsgTime < abs( g_msgTime.integer ) * 60000 )
|
|
return;
|
|
|
|
// negative settings only print once per map
|
|
if( ( level.lastMsgTime ) && g_msgTime.integer < 0 )
|
|
return;
|
|
|
|
level.lastMsgTime = level.time;
|
|
|
|
if( g_msg.string[0] )
|
|
{
|
|
char buffer[ MAX_STRING_CHARS ];
|
|
|
|
Q_strncpyz( buffer, g_msg.string, sizeof( buffer ) );
|
|
G_ParseEscapedString( buffer );
|
|
trap_SendServerCommand( -1, va( "cp \"%s\"", buffer ) );
|
|
trap_SendServerCommand( -1, va( "print \"%s\n\"", buffer ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CheckCvars
|
|
==================
|
|
*/
|
|
void CheckCvars( void )
|
|
{
|
|
static int lastPasswordModCount = -1;
|
|
static int lastMarkDeconModCount = -1;
|
|
|
|
if( g_password.modificationCount != lastPasswordModCount )
|
|
{
|
|
lastPasswordModCount = g_password.modificationCount;
|
|
|
|
if( *g_password.string && Q_stricmp( g_password.string, "none" ) )
|
|
trap_Cvar_Set( "g_needpass", "1" );
|
|
else
|
|
trap_Cvar_Set( "g_needpass", "0" );
|
|
}
|
|
|
|
// Unmark any structures for deconstruction when
|
|
// the server setting is changed
|
|
if( g_markDeconstruct.modificationCount != lastMarkDeconModCount )
|
|
{
|
|
int i;
|
|
gentity_t *ent;
|
|
|
|
lastMarkDeconModCount = g_markDeconstruct.modificationCount;
|
|
|
|
for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
|
|
{
|
|
if( !ent->inuse )
|
|
continue;
|
|
|
|
if( ent->s.eType != ET_BUILDABLE )
|
|
continue;
|
|
|
|
ent->deconstruct = qfalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
G_RunThink
|
|
|
|
Runs thinking code for this frame if necessary
|
|
=============
|
|
*/
|
|
void G_RunThink( gentity_t *ent )
|
|
{
|
|
float thinktime;
|
|
|
|
thinktime = ent->nextthink;
|
|
if( thinktime <= 0 )
|
|
return;
|
|
|
|
if( thinktime > level.time )
|
|
return;
|
|
|
|
ent->nextthink = 0;
|
|
if( !ent->think )
|
|
G_Error( "NULL ent->think" );
|
|
|
|
ent->think( ent );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
G_EvaluateAcceleration
|
|
|
|
Calculates the acceleration for an entity
|
|
=============
|
|
*/
|
|
void G_EvaluateAcceleration( gentity_t *ent, int msec )
|
|
{
|
|
vec3_t deltaVelocity;
|
|
vec3_t deltaAccel;
|
|
|
|
VectorSubtract( ent->s.pos.trDelta, ent->oldVelocity, deltaVelocity );
|
|
VectorScale( deltaVelocity, 1.0f / (float)msec, ent->acceleration );
|
|
|
|
VectorSubtract( ent->acceleration, ent->oldAccel, deltaAccel );
|
|
VectorScale( deltaAccel, 1.0f / (float)msec, ent->jerk );
|
|
|
|
VectorCopy( ent->s.pos.trDelta, ent->oldVelocity );
|
|
VectorCopy( ent->acceleration, ent->oldAccel );
|
|
}
|
|
|
|
/*
|
|
================
|
|
G_RunFrame
|
|
|
|
Advances the non-player objects in the world
|
|
================
|
|
*/
|
|
void G_RunFrame( int levelTime )
|
|
{
|
|
int i;
|
|
gentity_t *ent;
|
|
int msec;
|
|
int start, end;
|
|
|
|
// if we are waiting for the level to restart, do nothing
|
|
if( level.restarted )
|
|
return;
|
|
|
|
if( level.paused )
|
|
{
|
|
if( ( levelTime % 6000 ) == 0)
|
|
trap_SendServerCommand( -1, "cp \"^3Game is paused.\"" );
|
|
|
|
level.startTime += levelTime - level.time;
|
|
trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) );
|
|
|
|
if( levelTime - level.pauseTime > 3 * 60000 )
|
|
{
|
|
trap_SendConsoleCommand( EXEC_APPEND, "!unpause" );
|
|
}
|
|
}
|
|
|
|
CheckMsgTimer( );
|
|
|
|
level.framenum++;
|
|
level.previousTime = level.time;
|
|
level.time = levelTime;
|
|
msec = level.time - level.previousTime;
|
|
|
|
//TA: seed the rng
|
|
srand( level.framenum );
|
|
|
|
// get any cvar changes
|
|
G_UpdateCvars( );
|
|
|
|
//
|
|
// go through all allocated objects
|
|
//
|
|
start = trap_Milliseconds( );
|
|
ent = &g_entities[ 0 ];
|
|
|
|
for( i = 0; i < level.num_entities; i++, ent++ )
|
|
{
|
|
if( !ent->inuse )
|
|
continue;
|
|
|
|
// clear events that are too old
|
|
if( level.time - ent->eventTime > EVENT_VALID_MSEC )
|
|
{
|
|
if( ent->s.event )
|
|
{
|
|
ent->s.event = 0; // &= EV_EVENT_BITS;
|
|
if ( ent->client )
|
|
{
|
|
ent->client->ps.externalEvent = 0;
|
|
//ent->client->ps.events[0] = 0;
|
|
//ent->client->ps.events[1] = 0;
|
|
}
|
|
}
|
|
|
|
if( ent->freeAfterEvent )
|
|
{
|
|
// tempEntities or dropped items completely go away after their event
|
|
G_FreeEntity( ent );
|
|
continue;
|
|
}
|
|
else if( ent->unlinkAfterEvent )
|
|
{
|
|
// items that will respawn will hide themselves after their pickup event
|
|
ent->unlinkAfterEvent = qfalse;
|
|
trap_UnlinkEntity( ent );
|
|
}
|
|
}
|
|
|
|
// temporary entities don't think
|
|
if( ent->freeAfterEvent )
|
|
continue;
|
|
|
|
//TA: calculate the acceleration of this entity
|
|
if( ent->evaluateAcceleration )
|
|
G_EvaluateAcceleration( ent, msec );
|
|
|
|
if( !ent->r.linked && ent->neverFree )
|
|
continue;
|
|
|
|
if( ent->s.eType == ET_MISSILE )
|
|
{
|
|
G_RunMissile( ent );
|
|
continue;
|
|
}
|
|
|
|
if( ent->s.eType == ET_BUILDABLE )
|
|
{
|
|
G_BuildableThink( ent, msec );
|
|
continue;
|
|
}
|
|
|
|
if( ent->s.eType == ET_CORPSE || ent->physicsObject )
|
|
{
|
|
G_Physics( ent, msec );
|
|
continue;
|
|
}
|
|
|
|
if( ent->s.eType == ET_MOVER )
|
|
{
|
|
G_RunMover( ent );
|
|
continue;
|
|
}
|
|
|
|
if( i < MAX_CLIENTS )
|
|
{
|
|
G_RunClient( ent );
|
|
continue;
|
|
}
|
|
|
|
G_RunThink( ent );
|
|
}
|
|
end = trap_Milliseconds();
|
|
|
|
start = trap_Milliseconds();
|
|
|
|
// perform final fixups on the players
|
|
ent = &g_entities[ 0 ];
|
|
|
|
for( i = 0; i < level.maxclients; i++, ent++ )
|
|
{
|
|
if( ent->inuse )
|
|
ClientEndFrame( ent );
|
|
}
|
|
|
|
// save position information for all active clients
|
|
G_UnlaggedStore( );
|
|
|
|
end = trap_Milliseconds();
|
|
|
|
//TA:
|
|
G_CountSpawns( );
|
|
G_CalculateBuildPoints( );
|
|
G_CalculateStages( );
|
|
G_SpawnClients( PTE_ALIENS );
|
|
G_SpawnClients( PTE_HUMANS );
|
|
G_CalculateAvgPlayers( );
|
|
G_UpdateZaps( msec );
|
|
|
|
// see if it is time to end the level
|
|
CheckExitRules( );
|
|
|
|
// update to team status?
|
|
CheckTeamStatus( );
|
|
|
|
// cancel vote if timed out
|
|
CheckVote( );
|
|
|
|
// check team votes
|
|
CheckTeamVote( PTE_HUMANS );
|
|
CheckTeamVote( PTE_ALIENS );
|
|
|
|
// for tracking changes
|
|
CheckCvars( );
|
|
|
|
if( g_listEntity.integer )
|
|
{
|
|
for( i = 0; i < MAX_GENTITIES; i++ )
|
|
G_Printf( "%4i: %s\n", i, g_entities[ i ].classname );
|
|
|
|
trap_Cvar_Set( "g_listEntity", "0" );
|
|
}
|
|
}
|
|
|