/* Copyright (C) 1996-1997 Id Software, Inc. This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "qwsvdef.h" #ifndef CLIENTONLY extern int total_loading_size, current_loading_size, loading_stage; char *T_GetString(int num); #define Q2EDICT_NUM(i) (q2edict_t*)((char *)ge->edicts+i*ge->edict_size) server_static_t svs; // persistant server info server_t sv; // local server char localmodels[MAX_MODELS][5]; // inline model names for precache char localinfo[MAX_LOCALINFO_STRING+1]; // local game info extern cvar_t skill, sv_loadentfiles; extern cvar_t sv_cheats; extern cvar_t sv_bigcoords; extern qboolean sv_allow_cheats; /* ================ SV_ModelIndex ================ */ int SV_ModelIndex (char *name) { int i; if (!name || !name[0]) return 0; for (i=1 ; ifree) continue; // create baselines for all player slots, // and any other edict that has a visible model if (entnum > MAX_CLIENTS && !svent->v.modelindex) continue; // // create entity baseline // VectorCopy (svent->v.origin, svent->baseline.origin); VectorCopy (svent->v.angles, svent->baseline.angles); svent->baseline.frame = svent->v.frame; svent->baseline.skinnum = svent->v.skin; if (entnum > 0 && entnum <= MAX_CLIENTS) { svent->baseline.colormap = entnum; svent->baseline.modelindex = SV_ModelIndex("progs/player.mdl")&255; } else { svent->baseline.colormap = 0; svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svent->v.model))&255; } #ifdef PEXT_SCALE svent->baseline.scale = 1; #endif #ifdef PEXT_TRANS svent->baseline.trans = 1; #endif // // flush the signon message out to a seperate buffer if // nearly full // SV_FlushSignon (); // // add to the message // MSG_WriteByte (&sv.signon,svc_spawnbaseline); MSG_WriteShort (&sv.signon,entnum); MSG_WriteByte (&sv.signon, svent->baseline.modelindex); MSG_WriteByte (&sv.signon, svent->baseline.frame); MSG_WriteByte (&sv.signon, svent->baseline.colormap); MSG_WriteByte (&sv.signon, svent->baseline.skinnum); for (i=0 ; i<3 ; i++) { MSG_WriteCoord(&sv.signon, svent->baseline.origin[i]); MSG_WriteAngle(&sv.signon, svent->baseline.angles[i]); } } } */ void SVNQ_CreateBaseline (void) { edict_t *svent; int entnum; int playermodel = SV_SafeModelIndex("progs/player.mdl"); for (entnum = 0; entnum < sv.num_edicts ; entnum++) { svent = EDICT_NUM(svprogfuncs, entnum); memset(&svent->baseline, 0, sizeof(entity_state_t)); svent->baseline.number = entnum; #ifdef PEXT_SCALE svent->baseline.scale = 1; #endif #ifdef PEXT_TRANS svent->baseline.trans = 1; #endif if (svent->isfree) continue; // create baselines for all player slots, // and any other edict that has a visible model if (entnum > sv.allocated_client_slots && !svent->v.modelindex) continue; // // create entity baseline // VectorCopy (svent->v.origin, svent->baseline.origin); VectorCopy (svent->v.angles, svent->baseline.angles); svent->baseline.frame = svent->v.frame; svent->baseline.skinnum = svent->v.skin; if (entnum > 0 && entnum <= sv.allocated_client_slots) { if (entnum > 0 && entnum <= 16) svent->baseline.colormap = entnum; else svent->baseline.colormap = 0; //this would crash NQ. svent->baseline.modelindex = playermodel; } else { svent->baseline.colormap = 0; svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svprogfuncs, svent->v.model)); } svent->baseline.modelindex&=255; } } /* ================ SV_SaveSpawnparms Grabs the current state of the progs serverinfo flags and each client for saving across the transition to another level ================ */ void SV_SaveSpawnparms (qboolean dontsave) { int i, j; if (!sv.state) return; // no progs loaded yet if (!svprogfuncs) return; // serverflags is the only game related thing maintained svs.serverflags = pr_global_struct->serverflags; for (i=0, host_client = svs.clients ; istate != cs_spawned) continue; // needs to reconnect host_client->state = cs_connected; if (dontsave) //level restart requires that stats can be reset continue; // call the progs to get default spawn parms for the new client if (PR_FindGlobal(svprogfuncs, "ClientReEnter", 0)) {//oooh, evil. char buffer[65536]; int bufsize = sizeof(buffer); char *buf; for (j=0 ; jspawn_parms[j] = 0; buf = svprogfuncs->saveent(svprogfuncs, buffer, &bufsize, host_client->edict); if (host_client->spawninfo) Z_Free(host_client->spawninfo); host_client->spawninfo = Z_Malloc(bufsize+1); memcpy(host_client->spawninfo, buf, bufsize); host_client->spawninfotime = sv.time; } else if (pr_nqglobal_struct->SetChangeParms) { pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, host_client->edict); PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetChangeParms); for (j=0 ; jspawn_parms[j] = (&pr_global_struct->parm1)[j]; } #ifdef SVRANKING if (host_client->rankid) { rankstats_t rs; if (Rank_GetPlayerStats(host_client->rankid, &rs)) { rs.timeonserver += realtime - host_client->stats_started; host_client->stats_started = realtime; rs.kills += host_client->kills; rs.deaths += host_client->deaths; host_client->kills=0; host_client->deaths=0; for (j=0 ; jparm1)[j]; Rank_SetPlayerStats(host_client->rankid, &rs); } } #endif } } /* ================ SV_CalcPHS Expands the PVS and calculates the PHS (Potentially Hearable Set) ================ */ void SV_CalcPHS (void) { int rowbytes, rowwords; int i, j, k, l, index, num; int bitbyte; unsigned *dest, *src; qbyte *scan; int count, vcount; if (sv.worldmodel->fromgame == fg_quake2 || sv.worldmodel->fromgame == fg_quake3) { //PHS calcs are pointless with Q2 bsps return; } if (developer.value) Con_TPrintf (STL_BUILDINGPHS); num = sv.worldmodel->numleafs; rowwords = (num+31)>>5; rowbytes = rowwords*4; sv.pvs = Hunk_AllocName (rowbytes*num, "phs vis"); scan = sv.pvs; vcount = 0; for (i=0 ; ifuncs.LeafPVS(i, sv.worldmodel, NULL), rowbytes); if (i == 0) continue; for (j=0 ; j>3] & (1<<(j&7)) ) { vcount++; } } } sv.phs = Hunk_AllocName (rowbytes*num, "phs hear"); count = 0; scan = sv.pvs; dest = (unsigned *)sv.phs; for (i=0 ; i= num) continue; src = (unsigned *)sv.pvs + index*rowwords; for (l=0 ; l>3] & (1<<(j&7)) ) count++; } if (num) if (developer.value) Con_TPrintf (STL_PHSINFO, vcount/num, count/num, num); } unsigned SV_CheckModel(char *mdl) { qbyte stackbuf[1024]; // avoid dirtying the cache heap qbyte *buf; unsigned short crc; // int len; buf = (qbyte *)COM_LoadStackFile (mdl, stackbuf, sizeof(stackbuf)); if (!buf) return 0; crc = CRC_Block(buf, com_filesize); // for (len = com_filesize; len; len--, buf++) // CRC_ProcessByte(&crc, *buf); return crc; } void SV_UnspawnServer (void) //terminate the running server. { int i; if (sv.state) { Con_TPrintf(STL_SERVERUNSPAWNED); SV_FinalMessage("Server unspawned\n"); for (i = 0; i < sv.allocated_client_slots; i++) { if (svs.clients[i].state) SV_DropClient(svs.clients+i); } sv.worldmodel = NULL; sv.state = ss_dead; *sv.name = '\0'; } for (i = 0; i < MAX_CLIENTS; i++) { svs.clients[i].state = 0; *svs.clients[i].name = '\0'; } NET_CloseServer (); } /* ================ SV_SpawnServer Change the server to a new map, taking all connected clients along with it. This is only called from the SV_Map_f() function. ================ */ extern cvar_t progs; void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean usecinematic) { func_t f; char *file; edict_t *ent; #ifdef Q2SERVER q2edict_t *q2ent; #endif int i; int spawnflagmask; #ifndef SERVERONLY if (!isDedicated && !qrenderer) { R_RestartRenderer_f(); } #endif Con_DPrintf ("SpawnServer: %s\n",server); svs.spawncount++; // any partially connected client will be // restarted #ifndef SERVERONLY total_loading_size = 100; current_loading_size = 0; loading_stage = 1; SCR_BeginLoadingPlaque(); #endif NET_InitServer(); sv.state = ss_dead; if (sv.gamedirchanged) { sv.gamedirchanged = false; #ifndef SERVERONLY Wads_Flush(); //server code is responsable for flushing old state #endif Rank_Flush(); for (i = 0; i < MAX_CLIENTS; i++) { if (svs.clients[i].state #ifdef NQPROT && !svs.clients[i].nqprot #endif ) ReloadRanking(&svs.clients[i], svs.clients[i].name); if (svs.clients[i].spawninfo) //don't remember this stuff. Z_Free(svs.clients[i].spawninfo); svs.clients[i].spawninfo = NULL; } T_FreeStrings(); } for (i = 0; i < MAX_CLIENTS; i++) { svs.clients[i].nextservertimeupdate = 0; if (!svs.clients[i].state) //bots with the net_preparse module. svs.clients[i].userinfo[0] = '\0'; //clear the userinfo to clear the name if (svs.clients[i].netchan.remote_address.type == NA_LOOPBACK) { //forget this client's message buffers, so that any shared client/server network state persists (eg: float coords) svs.clients[i].num_backbuf = 0; svs.clients[i].datagram.cursize = 0; } } if (sv_bigcoords.value) sizeofcoord = 4; else sizeofcoord = 2; sizeofangle = 1; VoteFlushAll(); #ifndef SERVERONLY D_FlushCaches(); cl.worldmodel = NULL; #endif Mod_ClearAll (); Hunk_FreeToLowMark (host_hunklevel); for (i = 0; i < MAX_LIGHTSTYLES; i++) { if (sv.lightstyles[i]) Z_Free(sv.lightstyles[i]); sv.lightstyles[i] = NULL; } // wipe the entire per-level structure memset (&sv, 0, sizeof(sv)); sv.datagram.maxsize = sizeof(sv.datagram_buf); sv.datagram.data = sv.datagram_buf; sv.datagram.allowoverflow = true; sv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf); sv.reliable_datagram.data = sv.reliable_datagram_buf; sv.multicast.maxsize = sizeof(sv.multicast_buf); sv.multicast.data = sv.multicast_buf; #ifdef NQPROT sv.nqdatagram.maxsize = sizeof(sv.nqdatagram_buf); sv.nqdatagram.data = sv.nqdatagram_buf; sv.nqdatagram.allowoverflow = true; sv.nqreliable_datagram.maxsize = sizeof(sv.nqreliable_datagram_buf); sv.nqreliable_datagram.data = sv.nqreliable_datagram_buf; sv.nqmulticast.maxsize = sizeof(sv.nqmulticast_buf); sv.nqmulticast.data = sv.nqmulticast_buf; #endif sv.q2datagram.maxsize = sizeof(sv.q2datagram_buf); sv.q2datagram.data = sv.q2datagram_buf; sv.q2datagram.allowoverflow = true; sv.q2reliable_datagram.maxsize = sizeof(sv.q2reliable_datagram_buf); sv.q2reliable_datagram.data = sv.q2reliable_datagram_buf; sv.q2multicast.maxsize = sizeof(sv.q2multicast_buf); sv.q2multicast.data = sv.q2multicast_buf; sv.master.maxsize = sizeof(sv.master_buf); sv.master.data = sv.master_buf; sv.signon.maxsize = sizeof(sv.signon_buffers[0]); sv.signon.data = sv.signon_buffers[0]; sv.num_signon_buffers = 1; sv.numextrastatics = 0; strcpy (sv.name, server); #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif Cvar_ApplyLatches(CVAR_LATCH); sv.time = 0.1; //some progs don't like time starting at 0. //cos of spawn funcs like self.nextthink = time... COM_FlushTempoaryPacks(); if (usecinematic) { strcpy (sv.name, server); strcpy (sv.modelname, ""); } else { strcpy (sv.name, server); sprintf (sv.modelname,"maps/%s.bsp", server); } sv.state = ss_loading; sv.worldmodel = Mod_ForName (sv.modelname, true); if (sv.worldmodel->needload) Sys_Error("%s is missing\n", sv.modelname); sv.state = ss_dead; #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif SV_CalcPHS (); #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif if (sv.worldmodel->fromgame == fg_doom) Info_SetValueForStarKey(svs.info, "*bspversion", "1", MAX_SERVERINFO_STRING); else if (sv.worldmodel->fromgame == fg_halflife) Info_SetValueForStarKey(svs.info, "*bspversion", "30", MAX_SERVERINFO_STRING); else if (sv.worldmodel->fromgame == fg_quake2) Info_SetValueForStarKey(svs.info, "*bspversion", "38", MAX_SERVERINFO_STRING); else if (sv.worldmodel->fromgame == fg_quake3) Info_SetValueForStarKey(svs.info, "*bspversion", "46", MAX_SERVERINFO_STRING); else Info_SetValueForStarKey(svs.info, "*bspversion", "", MAX_SERVERINFO_STRING); if (startspot) Info_SetValueForStarKey(svs.info, "*startspot", startspot, MAX_SERVERINFO_STRING); else Info_SetValueForStarKey(svs.info, "*startspot", "", MAX_SERVERINFO_STRING); // // clear physics interaction links // SV_ClearWorld (); strcpy(sv.sound_precache[0], ""); strcpy(sv.model_precache[0], ""); strcpy(sv.model_precache[1], sv.modelname); sv.models[1] = sv.worldmodel; for (i=1 ; inumsubmodels ; i++) { strcpy(sv.model_precache[1+i], localmodels[i]); sv.models[i+1] = Mod_ForName (localmodels[i], false); } //check player/eyes models for hacks sv.model_player_checksum = SV_CheckModel("progs/player.mdl"); sv.eyes_player_checksum = SV_CheckModel("progs/eyes.mdl"); if (sv_cheats.value) { sv_allow_cheats = true; Info_SetValueForStarKey(svs.info, "*cheats", "ON", MAX_SERVERINFO_STRING); } else { sv_allow_cheats = false; Info_SetValueForStarKey(svs.info, "*cheats", "", MAX_SERVERINFO_STRING); } if (svprogfuncs) //we don't want the q1 stuff anymore. { CloseProgs(svprogfuncs); svprogfuncs = NULL; } #ifdef Q2SERVER if ((sv.worldmodel->fromgame == fg_quake2 || sv.worldmodel->fromgame == fg_quake3) && !*progs.string && SVQ2_InitGameProgs()) //full q2 dll decision in one if statement { //quake2 game support depends upon q2 style bsp clipping (fixable if we tried) also, don't use q2 dlls if a progs.dat was specified. for (i=0 ; iisfree = false; // leave slots at start for clients only // sv.num_edicts = MAX_CLIENTS+1; for (i=0 ; iisfree = false; //ZOID - make sure we update frags right svs.clients[i].old_frags = 0; if (!svs.clients[i].state && svs.clients[i].name[0]) //this is a bot. svs.clients[i].name[0] = '\0'; //make it go away } sv.allocated_client_slots = i; } #ifdef Q2SERVER else { for (i=0 ; is.number = i+1; svs.clients[i].q2edict = q2ent; } sv.allocated_client_slots = i; } #endif #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif // // spawn the rest of the entities on the map // // precache and static commands can be issued during // map initialization sv.state = ss_loading; if (svprogfuncs) { extern cvar_t coop, pr_imitatemvdsv; char *s; eval_t *eval; ent = EDICT_NUM(svprogfuncs, 0); ent->isfree = false; ent->v.model = PR_SetString(svprogfuncs, sv.worldmodel->name); ent->v.modelindex = 1; // world model ent->v.solid = SOLID_BSP; ent->v.movetype = MOVETYPE_PUSH; if (progstype == PROG_QW && pr_imitatemvdsv.value>0) { ent->v.targetname = PR_SetString(svprogfuncs, "mvdsv"); s = DISTRIBUTIONLONG; ent->v.netname = PR_NewString(svprogfuncs, va("%s %f %s, build %d\nBuild date: " __DATE__ ", " __TIME__ "", DISTRIBUTIONLONG, VERSION, PLATFORM, build_number())); ent->v.impulse = 0;//QWE_VERNUM; ent->v.items = 103; } pr_global_struct->mapname = PR_SetString(svprogfuncs, sv.name); // serverflags are for cross level information (sigils) pr_global_struct->serverflags = svs.serverflags; pr_global_struct->time = 0.1; //HACK!!!! A few QuakeC mods expect time to be non-zero in spawn funcs - like prydon gate... if (progstype == PROG_H2) { if (coop.value) { eval = PR_FindGlobal(svprogfuncs, "coop", 0); if (eval) eval->_float = coop.value; } else { eval = PR_FindGlobal(svprogfuncs, "deathmatch", 0); if (eval) eval->_float = deathmatch.value; } eval = PR_FindGlobal(svprogfuncs, "randomclass", 0); if (eval) eval->_float = 1; eval = PR_FindGlobal(svprogfuncs, "cl_playerclass", 0); if (eval) eval->_float = 1; } else { if (pr_nqglobal_struct->coop && coop.value) pr_global_struct->coop = coop.value; else if (pr_nqglobal_struct->deathmatch) pr_global_struct->deathmatch = deathmatch.value; } if (progstype == PROG_QW) // run the frame start qc function to let progs check cvars SV_ProgStartFrame (); //prydon gate seems to fail because of this allowance for (i = 0; i < svs.numprogs; i++) //do this AFTER precaches have been played with... { f = PR_FindFunction (svprogfuncs, "initents", svs.progsnum[i]); if (f) { PR_ExecuteProgram(svprogfuncs, f); } } } // load and spawn all other entities if (progstype == PROG_H2) { extern cvar_t coop; spawnflagmask = 0; if (deathmatch.value) spawnflagmask |= SPAWNFLAG_NOT_H2DEATHMATCH; else if (coop.value) spawnflagmask |= SPAWNFLAG_NOT_H2COOP; else spawnflagmask |= SPAWNFLAG_NOT_H2SINGLE; if (skill.value < 0.5) spawnflagmask |= SPAWNFLAG_NOT_H2EASY; else if (skill.value > 1.5) spawnflagmask |= SPAWNFLAG_NOT_H2HARD; else spawnflagmask |= SPAWNFLAG_NOT_H2MEDIUM; } else if (!deathmatch.value) //decide if we are to inhibit single player game ents instead { spawnflagmask = SPAWNFLAG_NOT_DEATHMATCH; if (skill.value < 0.5) spawnflagmask = SPAWNFLAG_NOT_EASY; else if (skill.value > 1.5) spawnflagmask = SPAWNFLAG_NOT_HARD; else spawnflagmask = SPAWNFLAG_NOT_MEDIUM; } else spawnflagmask = SPAWNFLAG_NOT_DEATHMATCH; //do this and get the precaches/start up the game if (sv_loadentfiles.value) file = COM_LoadMallocFile(va("maps/%s.ent", server)); else file = NULL; if (file) { char crc[12]; sprintf(crc, "%i", CRC_Block(file, com_filesize)); Info_SetValueForStarKey(svs.info, "*entfile", crc, MAX_SERVERINFO_STRING); if (svprogfuncs) pr_edict_size = PR_LoadEnts(svprogfuncs, file, spawnflagmask); #ifdef Q2SERVER else ge->SpawnEntities(sv.name, file, startspot?startspot:""); #endif BZ_Free(file); } else { Info_SetValueForStarKey(svs.info, "*entfile", "", MAX_SERVERINFO_STRING); if (svprogfuncs) pr_edict_size = PR_LoadEnts(svprogfuncs, sv.worldmodel->entities, spawnflagmask); #ifdef Q2SERVER else ge->SpawnEntities(sv.name, sv.worldmodel->entities, startspot?startspot:""); #endif } #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif Q_strncpyz(sv.mapname, sv.name, sizeof(sv.mapname)); if (svprogfuncs) { eval_t *val; ent = EDICT_NUM(svprogfuncs, 0); ent->v.angles[0] = ent->v.angles[1] = ent->v.angles[2] = 0; val = svprogfuncs->GetEdictFieldValue(svprogfuncs, ent, "message", NULL); if (val) { if (progstype == PROG_H2) sprintf(sv.mapname, "%s", T_GetString(val->_float-1)); else sprintf(sv.mapname, "%s", PR_GetString(svprogfuncs, val->string)); } ent->readonly = true; //lock it down! } // look up some model indexes for specialized message compression SV_FindModelNumbers (); #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif // run two frames to allow everything to settle realtime += 0.1; SV_Physics (); #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif realtime += 0.1; sv.time += 0.1; SV_Physics (); #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif // save movement vars SV_SetMoveVars(); // create a baseline for more efficient communications // SV_CreateBaseline (); SVNQ_CreateBaseline(); sv.signon_buffer_size[sv.num_signon_buffers-1] = sv.signon.cursize; // all spawning is completed, any further precache statements // or prog writes to the signon message are errors if (usecinematic) sv.state = ss_cinematic; else sv.state = ss_active; SV_GibFilterInit(); SV_FilterImpulseInit(); Info_SetValueForKey (svs.info, "map", sv.name, MAX_SERVERINFO_STRING); Con_TPrintf (STL_SERVERSPAWNED); //misc filenotfounds can be misleading. if (!startspot) { SV_FlushLevelCache(); //to make sure it's caught for (i=0 ; istring = PR_NewString(svprogfuncs, startspot); } if (Cmd_AliasExist("f_svnewmap", RESTRICT_LOCAL)) Cbuf_AddText("f_svnewmap\n", RESTRICT_LOCAL); #ifndef SERVERONLY current_loading_size+=10; SCR_BeginLoadingPlaque(); #endif } #endif