script, ui: added "sv_min_startmap_health" cvar
[k8vavoom.git] / source / server / sv_main.cpp
blobfbb59de715ed3d0fa6a7a86e9caad8d692e86e3a
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "../host.h"
28 #include "../net/network.h"
29 #include "../psim/p_entity.h"
30 #include "../psim/p_worldinfo.h"
31 #include "../psim/p_levelinfo.h"
32 #include "../psim/p_playerreplicationinfo.h"
33 #include "../psim/p_player.h"
34 #include "../filesys/files.h"
35 #include "../sound/sound.h"
36 #include "../lockdefs.h"
37 #include "server.h"
38 #include "sv_local.h"
39 #include "sv_save.h"
40 #ifdef CLIENT
41 # include "../screen.h"
42 # include "../drawer.h"
43 # include "../menu.h"
44 # include "../client/client.h"
45 # include "../client/cl_local.h"
46 #else
47 # include "../render/r_public.h"
48 #endif
49 #include "../psim/p_decal.h"
50 #include "../decorate/vc_decorate.h"
51 #include "../dehacked/vc_dehacked.h"
53 #ifndef CLIENT
54 // this is for BDW mod, to have tracers
55 VCvarB r_models("r_models", true, "Allow 3d models?", CVAR_NoShadow/*|CVAR_Archive*/);
56 #endif
59 #ifdef CLIENT
60 extern VCvarB r_wipe_enabled;
61 #endif
63 // arbitrary number
64 #define INITIAL_TICK_DELAY (2)
66 bool sv_skipOneTitlemap = false;
67 int serverStartRenderFramesTic = -1;
69 static bool triedToSkipIntermission = false;
72 static double FrameTime = 1.0/35.0;
73 // round a little bit up to prevent "slow motion"
74 *(vuint64 *)&FrameTime += 1; // this is 0x1.d41d41d41d41ep-6
76 static float FrameTime = 1.0f/35.0f;
77 // round a little bit up to prevent "slow motion"
78 *(vuint32 *)&FrameTime += 1; // this is 0x1.d41d44p-6
80 #if 0
81 static constexpr double FrameTime = 0x1.d41d41d41d41ep-6; // see above
82 double SV_GetFrameTimeConstant () noexcept { return FrameTime; }
83 #else
84 static constexpr float FrameTime = 0x1.d41d44p-6; // see above
85 float SV_GetFrameTimeConstant () noexcept { return FrameTime; }
86 #endif
89 float FrameTime = 1.0f/35.0f;
90 printf("%a 0x%08x\n", FrameTime, *(const vuint32 *)&FrameTime); // 0x1.d41d42p-6 0x3cea0ea1
91 *(vuint32 *)&FrameTime += 1;
92 printf("%a 0x%08x\n", FrameTime, *(const vuint32 *)&FrameTime); // 0x1.d41d44p-6 0x3cea0ea2
94 double FrameTimeD = 1.0f/35.0f;
95 printf("%a 0x%16llx\n", FrameTimeD, *(const vuint64 *)&FrameTimeD); // 0x1.d41d42p-6 0x3f9d41d420000000
96 *(vuint64 *)&FrameTimeD += 1;
97 printf("%a 0x%16llx\n", FrameTimeD, *(const vuint64 *)&FrameTimeD); // 0x1.d41d420000001p-6 0x3f9d41d420000001
99 FrameTimeD = 1.0/35.0;
100 printf("%a 0x%16llx\n", FrameTimeD, *(const vuint64 *)&FrameTimeD); // 0x1.d41d41d41d41dp-6 0x3f9d41d41d41d41d
101 *(vuint64 *)&FrameTimeD += 1;
102 printf("%a 0x%16llx\n", FrameTimeD, *(const vuint64 *)&FrameTimeD); // 0x1.d41d41d41d41ep-6 0x3f9d41d41d41d41e
106 static double ServerLastKeepAliveTime = 0.0;
109 static int cli_SVDumpDoomEd = 0;
110 static int cli_SVDumpScriptId = 0;
111 static int cli_SVShowExecTimes = 0;
112 static int cli_SVNoTitleMap = 0;
114 /*static*/ bool cliRegister_svmain_args =
115 VParsedArgs::RegisterFlagSet("-dbg-dump-doomed", "!dump doomed numbers", &cli_SVDumpDoomEd) &&
116 VParsedArgs::RegisterFlagSet("-dbg-dump-scriptid", "!dump scriptid numbers", &cli_SVDumpScriptId) &&
117 VParsedArgs::RegisterFlagSet("-show-exec-times", "!show some developer info", &cli_SVShowExecTimes) &&
118 VParsedArgs::RegisterFlagSet("-notitlemap", "Do not load and run TITLEMAP", &cli_SVNoTitleMap);
121 static void G_DoReborn (int playernum, bool cheatReborn);
122 static void G_DoCompleted (bool ignoreNoExit);
124 extern VCvarB dbg_vm_disable_thinkers;
126 static VCvarB dbg_skipframe_player_tick("dbg_skipframe_player_tick", true, "Run player ticks on skipped frames?", CVAR_PreInit|CVAR_NoShadow);
127 static VCvarB dbg_skipframe_player_block_move("dbg_skipframe_player_block_move", false, "Keep moving on skipped player frames (this is wrong)?", CVAR_PreInit|CVAR_NoShadow);
128 static VCvarB dbg_report_orphan_weapons("dbg_report_orphan_weapons", false, "Report orphan weapon assign?", CVAR_Archive|CVAR_PreInit|CVAR_NoShadow);
130 VCvarB sv_ignore_nojump("sv_ignore_nojump", false, "Ignore \"nojump\" flag in MAPINFO?", CVAR_Archive|CVAR_NoShadow);
131 VCvarB sv_ignore_nocrouch("sv_ignore_nocrouch", false, "Ignore \"nocrouch\" flag in MAPINFO?", CVAR_Archive|CVAR_NoShadow);
132 VCvarB sv_ignore_nomlook("sv_ignore_nomlook", false, "Ignore \"nofreelook\" flag in MAPINFO?", CVAR_Archive|CVAR_NoShadow);
134 static VCvarB sv_ignore_reset_health("sv_ignore_reset_health", false, "Ignore \"resethealth\" flag in MAPINFO?", CVAR_Archive|CVAR_NoShadow);
135 static VCvarB sv_ignore_reset_inventory("sv_ignore_reset_inventory", false, "Ignore \"resetinventory\" flag in MAPINFO?", CVAR_Archive|CVAR_NoShadow);
136 static VCvarB sv_ignore_reset_items("sv_ignore_reset_items", false, "Ignore \"resetitems\" flag in MAPINFO?", CVAR_Archive|CVAR_NoShadow);
138 static VCvarB sv_force_pistol_start("sv_force_pistol_start", false, "Start each new map with default weapons?", /*CVAR_ServerInfo|CVAR_Latch|*/CVAR_PreInit|CVAR_NoShadow);
139 static VCvarB sv_force_health_reset("sv_force_health_reset", false, "Start each new map with default health?", /*CVAR_ServerInfo|CVAR_Latch|*/CVAR_PreInit|CVAR_NoShadow);
141 static VCvarI sv_min_startmap_health("sv_min_startmap_health", "0", "Restore health to this minimum limit on map start (0: don't restore).", /*CVAR_ServerInfo|CVAR_Latch|*/CVAR_PreInit|CVAR_NoShadow);
143 static VCvarB sv_transporters_absolute("sv_transporters_absolute", true, "Use absolute movement instead of velocity for Boom transporters?", /*CVAR_ServerInfo|CVAR_Latch|*/CVAR_Archive|CVAR_PreInit);
145 static VCvarB mod_allow_server_cvars("mod_allow_server_cvars", false, "Allow server cvars from CVARINFO?", CVAR_Archive|CVAR_PreInit|CVAR_NoShadow);
147 extern VCvarI host_max_skip_frames;
148 extern VCvarB NoExit;
150 static VCvarB __dbg_cl_always_allow_pause("dbg_cl_always_allow_pause", false, "Allow pausing in network games?", CVAR_PreInit|CVAR_Hidden|CVAR_NoShadow);
153 server_t sv;
154 server_static_t svs;
156 bool sv_loading = false;
157 bool sv_map_travel = false;
158 int sv_load_num_players = 0;
159 bool run_open_scripts = false;
161 VBasePlayer *GPlayersBase[MAXPLAYERS];
162 //vuint8 deathmatch = 0; // only if started as net death
163 bool TimerGameWarned = false;
164 int TimerGame = 0; // for DM timelimit, in vanilla tics
165 int FragGame = 0; // for DM fraglimit -- advance when somebody reaches this number of frags; "-1" means "calculate"
166 VLevelInfo *GLevelInfo = nullptr;
167 int LeavePosition = 0;
168 bool completed = false;
169 VNetContext *GDemoRecordingContext = nullptr;
171 static int RebornPosition = 0; // position indicator for cooperative net-play reborn
173 static bool mapteleport_issued = false;
174 static int mapteleport_flags = 0;
175 static int mapteleport_skill = -1;
176 static bool mapteleport_executed = false; // used for netgame autoteleport; reset in `SV_SpawnServer()`
178 static VCvarI TimeLimit("TimeLimit", "0", "Deathmatch time limit, in minutes (0 means 'none').", CVAR_ServerInfo|CVAR_NoShadow);
179 static VCvarI FragLimit("FragLimit", "0", "Deathmatch frag limit (0 means 'none')", CVAR_ServerInfo|CVAR_NoShadow);
180 VCvarB NoExit("NoExit", false, "Disable exiting in deathmatch?", CVAR_ServerInfo|CVAR_NoShadow/*CVAR_PreInit*/);
181 static VCvarI DeathMatch("DeathMatch", "0", "DeathMatch mode.", CVAR_ServerInfo|CVAR_NoShadow);
182 VCvarB NoMonsters("NoMonsters", false, "NoMonsters mode?", CVAR_NoShadow/*|CVAR_PreInit*/);
183 VCvarI Skill("Skill", "3", "Skill level.", CVAR_NoShadow/*|CVAR_PreInit*/);
184 VCvarB sv_cheats("sv_cheats", false, "Allow cheats in network game?", CVAR_ServerInfo|/*CVAR_Latch|*/CVAR_PreInit|CVAR_NoShadow);
186 VCvarB sv_disable_run("sv_disable_run", false, "Disable running?", CVAR_ServerInfo|CVAR_Archive|CVAR_PreInit|CVAR_NoShadow);
187 VCvarB sv_disable_crouch("sv_disable_crouch", false, "Disable crouching?", CVAR_ServerInfo|CVAR_Archive|CVAR_PreInit|CVAR_NoShadow);
188 VCvarB sv_disable_jump("sv_disable_jump", false, "Disable jumping?", CVAR_ServerInfo|CVAR_Archive|CVAR_PreInit|CVAR_NoShadow);
189 VCvarB sv_disable_mlook("sv_disable_mlook", false, "Disable mouselook?", CVAR_ServerInfo|CVAR_Archive|CVAR_PreInit|CVAR_NoShadow);
191 VCvarI sv_maxmove("sv_maxmove", "400", "Maximum allowed network movement.", CVAR_Archive|CVAR_NoShadow);
193 static VCvarB sv_barrel_respawn("sv_barrel_respawn", false, "Respawn barrels in network game?", CVAR_Archive|/*CVAR_ServerInfo|CVAR_Latch|*/CVAR_PreInit);
194 static VCvarB sv_pushable_barrels("sv_pushable_barrels", true, "Pushable barrels?", CVAR_Archive|/*CVAR_ServerInfo|CVAR_Latch|*/CVAR_PreInit);
195 VCvarB sv_decoration_block_projectiles("sv_decoration_block_projectiles", false, "Should decoration things block projectiles?", CVAR_Archive|/*CVAR_ServerInfo|CVAR_Latch|*/CVAR_PreInit);
197 static VCvarI master_heartbeat_time("master_heartbeat_time", "3", "Master server heartbeat interval, minutes.", CVAR_Archive|CVAR_PreInit|CVAR_NoShadow);
199 static VServerNetContext *ServerNetContext = nullptr;
201 static double LastMasterUpdate = 0.0;
203 static bool cliMapStartFound = false;
206 struct VCVFSSaver {
207 void *userdata;
208 VStream *(*dgOpenFile) (VStr filename, void *userdata);
210 VCVFSSaver () : userdata(VMemberBase::userdata), dgOpenFile(VMemberBase::dgOpenFile) {}
212 ~VCVFSSaver () {
213 VMemberBase::userdata = userdata;
214 VMemberBase::dgOpenFile = dgOpenFile;
219 static int vcmodCurrFileLump = -1;
221 static VStream *vcmodOpenFile (VStr filename, void * /*userdata*/) {
223 for (int flump = W_IterateFile(-1, filename); flump >= 0; flump = W_IterateFile(flump, filename)) {
224 if (vcmodCurrFile >= 0 && (vcmodCurrFile != W_LumpFile(flump))) continue;
225 //fprintf(stderr, "VC: found <%s> for <%s>\n", *W_FullLumpName(flump), *filename);
226 return W_CreateLumpReaderNum(flump);
229 int lmp = W_CheckNumForFileNameInSameFileOrLower(vcmodCurrFileLump, filename);
230 if (lmp >= 0) return W_CreateLumpReaderNum(lmp);
231 //fprintf(stderr, "VC: NOT found <%s>\n", *filename);
232 return nullptr;
236 //==========================================================================
238 // VServerNetContext::GetLevel
240 //==========================================================================
241 VLevel *VServerNetContext::GetLevel () {
242 return GLevel;
246 //==========================================================================
248 // ProcessServerModOption
250 //==========================================================================
251 static void ProcessServerModOption (VScriptParser *sc, bool single) {
252 sc->ExpectString();
253 if (single && sc->Crossed) sc->Error("option expected");
254 //sc->String = sc->String.xstrip();
255 if (sc->String.strEquCI("skipbdw")) {
256 if (!decorateSkipBDWClasses) {
257 GCon->Logf(NAME_Init, "server option: skip BDW");
258 decorateSkipBDWClasses = true;
260 } else {
261 sc->Error(va("unknown server mod option '%s'", *sc->String));
266 //==========================================================================
268 // ParseModListLine
270 //==========================================================================
271 static void ParseModListLine (VScriptParser *sc, int mode, const char *modtypestr) {
272 bool doLoad;
273 if (mode != VCMODS_CLIENT) {
274 // server mods
275 if (sc->Check("option")) {
276 ProcessServerModOption(sc, true);
277 return;
278 } else if (sc->Check("options")) {
279 sc->Expect("{");
280 while (!sc->Check("}")) {
281 if (sc->AtEnd()) sc->Error("unclosed options block");
282 ProcessServerModOption(sc, false);
284 return;
285 } else {
286 #if 0
287 GCon->Logf(NAME_Debug, " <%s>\n", *sc->String.quote());
288 #endif
289 bool doSection = false;
290 if (sc->Check("pre")) {
291 doLoad = (mode == VCMODS_SERVER_PRE);
292 doSection = true;
293 } else if (sc->Check("post")) {
294 doLoad = (mode == VCMODS_SERVER_POST);
295 doSection = true;
296 } else {
297 doLoad = (mode == VCMODS_SERVER_PRE);
299 #if 0
300 GCon->Logf(NAME_Debug, " :<%s> (doSection=%d; doLoad=%d)\n", *sc->String.quote(),
301 (int)doSection, (int)doLoad);
302 #endif
303 // section?
304 if (doSection) {
305 sc->Expect("{");
306 if (doLoad) {
307 // load
308 while (!sc->Check("}")) {
309 if (sc->AtEnd()) sc->Error("unclosed mods block");
310 sc->ExpectString();
311 if (sc->String == "{" || sc->String == "}") sc->Error("wtf?!");
312 //sc->String = sc->String.xstrip();
313 GCon->Logf(NAME_Init, "loading %s Vavoom C mod '%s'", modtypestr, *sc->String);
314 VMemberBase::StaticLoadPackage(VName(*sc->String), TLocation());
316 } else {
317 sc->SkipBracketed(true);
319 return;
322 } else {
323 // client
324 doLoad = true;
326 sc->ExpectString();
327 if (sc->String == "{" || sc->String == "}") sc->Error("wtf?!");
328 if (doLoad) {
329 //sc->String = sc->String.xstrip();
330 GCon->Logf(NAME_Init, "loading %s Vavoom C mod '%s'", modtypestr, *sc->String);
331 VMemberBase::StaticLoadPackage(VName(*sc->String), TLocation());
336 //==========================================================================
338 // LoadModList
340 //==========================================================================
341 static void LoadModList (const int ScLump, int mode, bool newStyle) {
342 const char *modtypestr;
343 switch (mode) {
344 case VCMODS_SERVER_PRE:
345 modtypestr = "server (pre)";
346 break;
347 case VCMODS_SERVER_POST:
348 modtypestr = "server (post)";
349 break;
350 case VCMODS_CLIENT:
351 modtypestr = "client";
352 break;
353 default:
354 Sys_Error("G_LoadVCMods: invalid mode %d (internal error)", mode);
357 //vcmodCurrFile = W_LumpFile(ScLump);
358 vcmodCurrFileLump = ScLump;
359 VScriptParser *sc = VScriptParser::NewWithLump(ScLump);
360 sc->SetCMode(false);
361 sc->SetEscape(false);
362 sc->SetSemicolonComments(true);
363 sc->SetHashComments(true);
365 if (newStyle) {
366 GCon->Logf(NAME_Init, "parsing Vavoom C mod list from '%s'", *W_FullLumpName(ScLump));
367 } else {
368 GCon->Logf(NAME_Init, "parsing old-style Vavoom C mod list from '%s'", *W_FullLumpName(ScLump));
371 if (newStyle) {
372 while (!sc->AtEnd()) {
373 if (sc->Check("client")) {
374 if (mode != VCMODS_CLIENT) {
375 sc->SkipBracketed(false);
376 continue;
378 } else if (sc->Check("server")) {
379 if (mode == VCMODS_CLIENT) {
380 sc->SkipBracketed(false);
381 continue;
383 } else {
384 sc->Error(va("invalid modlist section '%s'", *sc->String));
386 sc->Expect("{");
387 while (!sc->Check("}")) {
388 if (sc->AtEnd()) sc->Error("unfinished section");
389 ParseModListLine(sc, mode, modtypestr);
392 } else {
393 while (!sc->AtEnd()) {
394 ParseModListLine(sc, mode, modtypestr);
395 if (!sc->GetString()) break;
396 sc->UnGet();
400 delete sc;
404 struct ModListInfo {
405 int lump;
406 bool newStyle;
410 //==========================================================================
412 // nslist_cmp
414 // module list lump compare
416 //==========================================================================
417 static int nslist_cmp (const void *aa, const void *bb, void *udata) {
418 const ModListInfo *a = (const ModListInfo *)aa;
419 const ModListInfo *b = (const ModListInfo *)bb;
420 int diff = W_LumpFile(a->lump) - W_LumpFile(b->lump);
421 if (diff != 0) return (diff < 0 ? -1 : +1);
422 diff = a->lump - b->lump;
423 return (diff < 0 ? -1 : diff > 0 ? +1 : 0);
427 //==========================================================================
429 // G_LoadVCMods
431 // loading mods, take list from modlistfile
432 // load user-specified Vavoom C script files
434 //==========================================================================
435 void G_LoadVCMods (int mode) {
436 VName modlistfile;
437 switch (mode) {
438 case VCMODS_SERVER_PRE:
439 modlistfile = "loadvcs";
440 break;
441 case VCMODS_SERVER_POST:
442 modlistfile = "loadvcs";
443 break;
444 case VCMODS_CLIENT:
445 modlistfile = "loadvcc";
446 break;
447 default:
448 Sys_Error("G_LoadVCMods: invalid mode %d (internal error)", mode);
450 //for (unsigned f = 0; f < MAXPLAYERS; ++f) GPlayersBase[f] = nullptr;
452 // collect new-style lumps
453 TArrayNC<ModListInfo> nslist;
454 for (auto &&it : WadFileIterator("vavoomc_mods.rc")) {
455 int f = 0;
456 while (f < nslist.length() && W_LumpFile(nslist[f].lump) != W_LumpFile(it.lump)) {
457 f += 1;
459 if (f == nslist.length()) {
460 ModListInfo &nfo = nslist.Alloc();
461 nfo.lump = it.lump;
462 nfo.newStyle = true;
463 } else {
464 nslist[f].lump = it.lump;
468 // collect old-style lumps
469 for (auto &&it : WadNSNameIterator(modlistfile, WADNS_Global)) {
470 int f = 0;
471 while (f < nslist.length() && W_LumpFile(nslist[f].lump) != W_LumpFile(it.lump)) {
472 f += 1;
474 if (f == nslist.length()) {
475 ModListInfo &nfo = nslist.Alloc();
476 nfo.lump = it.lump;
477 nfo.newStyle = false;
478 } else if (!nslist[f].newStyle) {
479 nslist[f].lump = it.lump;
483 // sort
484 if (nslist.length()) {
485 smsort_r(nslist.ptr(), (size_t)nslist.length(), sizeof(nslist[0]), nslist_cmp, nullptr);
487 VCVFSSaver saver;
488 VMemberBase::dgOpenFile = &vcmodOpenFile;
490 for (const ModListInfo &nfo : nslist) {
491 LoadModList(nfo.lump, mode, nfo.newStyle);
497 //==========================================================================
499 // SV_ReplaceCustomDamageFactors
501 //==========================================================================
502 void SV_ReplaceCustomDamageFactors () {
503 if (!GGameInfo) return;
504 // put custom damage factors into gameinfo object
505 if (CustomDamageFactors.length()) {
506 GCon->Logf(NAME_Init, "setting up %d custom damage factor%s", CustomDamageFactors.length(), (CustomDamageFactors.length() != 1 ? "s" : ""));
507 VField *F = GGameInfo->GetClass()->FindFieldChecked("CustomDamageFactors");
508 TArray<VDamageFactor> &dflist = *(TArray<VDamageFactor> *)F->GetFieldPtr(GGameInfo);
509 dflist.resize(CustomDamageFactors.length()); // do not overallocate
510 for (auto &&it : CustomDamageFactors) {
511 VDamageFactor &newdf = dflist.alloc();
512 newdf = it;
514 // this can be called from mapinfo parser, so don't clear it here
515 //CustomDamageFactors.clear(); // we don't need them anymore
520 //==========================================================================
522 // SV_GetModListHash
524 //==========================================================================
525 vuint64 SV_GetModListHash (vuint32 *old) {
526 VStr modlist;
527 // get list of loaded modules
528 auto wadlist = FL_GetWadPk3ListSmall();
529 for (auto &&wadname : wadlist) {
530 modlist += wadname;
531 modlist += "\n";
533 //GCon->Logf(NAME_Debug, "modlist:\n%s", *modlist);
534 if (old) {
535 *old = XXH32(*modlist, (vint32)modlist.length(), (vuint32)wadlist.length());
537 RIPEMD160_Ctx ctx;
538 uint8_t hash[RIPEMD160_BYTES];
539 ripemd160_init(&ctx);
540 ripemd160_put(&ctx, *modlist, (unsigned)modlist.length());
541 ripemd160_finish(&ctx, hash);
542 const uint64_t val =
543 ((uint64_t)hash[0])|
544 (((uint64_t)hash[1])<<8)|
545 (((uint64_t)hash[2])<<16)|
546 (((uint64_t)hash[3])<<24)|
547 (((uint64_t)hash[4])<<32)|
548 (((uint64_t)hash[5])<<40)|
549 (((uint64_t)hash[6])<<48)|
550 (((uint64_t)hash[7])<<56);
551 return (vuint64)val;
555 //==========================================================================
557 // SV_GetModListHashOld
559 //==========================================================================
560 vuint64 SV_GetModListHashOld (vuint32 *old) {
561 VStr modlist;
562 // get list of loaded modules
563 auto wadlist = FL_GetWadPk3List();
564 for (auto &&wadname : wadlist) {
565 modlist += wadname;
566 modlist += "\n";
568 //GCon->Logf(NAME_Debug, "modlist:\n%s", *modlist);
569 if (old) {
570 *old = XXH32(*modlist, (vint32)modlist.length(), (vuint32)wadlist.length());
572 RIPEMD160_Ctx ctx;
573 uint8_t hash[RIPEMD160_BYTES];
574 ripemd160_init(&ctx);
575 ripemd160_put(&ctx, *modlist, (unsigned)modlist.length());
576 ripemd160_finish(&ctx, hash);
577 const uint64_t val =
578 ((uint64_t)hash[0])|
579 (((uint64_t)hash[1])<<8)|
580 (((uint64_t)hash[2])<<16)|
581 (((uint64_t)hash[3])<<24)|
582 (((uint64_t)hash[4])<<32)|
583 (((uint64_t)hash[5])<<40)|
584 (((uint64_t)hash[6])<<48)|
585 (((uint64_t)hash[7])<<56);
586 return (vuint64)val;
590 //==========================================================================
592 // SV_LoadMods
594 //==========================================================================
595 void SV_LoadMods () {
596 svs.max_clients = 1;
598 VMemberBase::StaticLoadPackage(NAME_game, TLocation());
599 // load user-specified Vavoom C script files
600 G_LoadVCMods(VCMODS_SERVER_PRE);
602 // sadly, we have to emit the code as this stage.
603 // this is because DECORATE compiler wants it this way
604 // (i.e. it wants classes and defaults created).
605 VPackage::StaticEmitPackages();
607 GGameInfo = (VGameInfo *)VObject::StaticSpawnWithReplace(VClass::FindClass("MainGameInfo"));
608 GCon->Logf(NAME_Init, "Spawned game info object of class '%s'", *GGameInfo->GetClass()->GetFullName());
609 GGameInfo->eventInit();
611 ProcessDecorateScripts();
612 ProcessDecalDefs();
613 ProcessDehackedFiles();
615 G_LoadVCMods(VCMODS_SERVER_POST);
619 //==========================================================================
621 // SV_CompileScripts
623 //==========================================================================
624 void SV_CompileScripts () {
625 // just in case; DECORATE should not left uncompiled things, but...
626 VPackage::StaticEmitPackages();
628 GGameInfo->eventPostDecorateInit();
630 if (cli_SVDumpDoomEd > 0) VClass::StaticDumpMObjInfo();
631 if (cli_SVDumpScriptId > 0) VClass::StaticDumpScriptIds();
633 // reports
634 if (VMethod::ReportUnusedBuiltins()) Sys_Error("found some unused builtins (internal engine error)");
635 VPackage::DumpCodeSizeStats();
637 VMemberBase::StaticCompilerShutdown();
638 CompilerReportMemory();
642 //==========================================================================
644 // SV_Init
646 //==========================================================================
647 void SV_Init () {
648 GCon->Logf(NAME_Init, "registering %d sprites", VClass::GetSpriteCount());
649 for (int i = 0; i < VClass::GetSpriteCount(); ++i) R_InstallSprite(*VClass::GetSpriteNameAt(i), i);
650 R_InstallSpriteComplete(); // why not?
652 ServerNetContext = new VServerNetContext();
654 VClass *PlayerClass = VClass::FindClass("K8VPlayer");
655 for (int i = 0; i < MAXPLAYERS; ++i) {
656 GPlayersBase[i] = (VBasePlayer *)VObject::StaticSpawnWithReplace(PlayerClass);
657 if (developer) GCon->Logf(NAME_Dev, "spawned base player object for player #%d, with actual class <%s>", i, *GPlayersBase[i]->GetClass()->GetFullName());
660 //GGameInfo->validcount = &validcount;
661 GGameInfo->skyflatnum = skyflatnum; // this should be fixed after mapinfo parsing
663 SV_ReplaceCustomDamageFactors();
665 P_InitSwitchList();
666 P_InitTerrainTypes();
667 InitLockDefs();
671 //==========================================================================
673 // SV_UpdateSkyFlat
675 // call after texture manager updated a flat
677 //==========================================================================
678 void SV_UpdateSkyFlat () {
679 if (GGameInfo) GGameInfo->skyflatnum = skyflatnum;
683 //==========================================================================
685 // SV_ResetPlayers
687 //==========================================================================
688 void SV_ResetPlayers () {
689 for (int i = 0; i < MAXPLAYERS; ++i) {
690 VBasePlayer *Player = GPlayersBase[i];
691 if (!Player) continue;
692 Player->ResetButtons();
693 Player->eventResetToDefaults();
698 //==========================================================================
700 // SV_ResetPlayerButtons
702 //==========================================================================
703 void SV_ResetPlayerButtons () {
704 for (int i = 0; i < MAXPLAYERS; ++i) {
705 VBasePlayer *Player = GPlayersBase[i];
706 if (!Player) continue;
707 Player->ResetButtons();
712 //==========================================================================
714 // SV_SendLoadedEvent
716 //==========================================================================
717 void SV_SendLoadedEvent () {
718 for (int i = 0; i < MAXPLAYERS; ++i) {
719 VBasePlayer *Player = GPlayersBase[i];
720 if (!Player || !(Player->PlayerFlags&VBasePlayer::PF_Active)) continue;
721 Player->eventOnSaveLoaded();
726 //==========================================================================
728 // SV_SendBeforeSaveEvent
730 //==========================================================================
731 void SV_SendBeforeSaveEvent (bool isAutosave, bool isCheckpoint) {
732 for (int i = 0; i < MAXPLAYERS; ++i) {
733 VBasePlayer *Player = GPlayersBase[i];
734 if (!Player || !(Player->PlayerFlags&VBasePlayer::PF_Active)) continue;
735 Player->eventOnBeforeSave(isAutosave, isCheckpoint);
740 //==========================================================================
742 // SV_SendAfterSaveEvent
744 //==========================================================================
745 void SV_SendAfterSaveEvent (bool isAutosave, bool isCheckpoint) {
746 for (int i = 0; i < MAXPLAYERS; ++i) {
747 VBasePlayer *Player = GPlayersBase[i];
748 if (!Player || !(Player->PlayerFlags&VBasePlayer::PF_Active)) continue;
749 Player->eventOnAfterSave(isAutosave, isCheckpoint);
754 //==========================================================================
756 // P_InitThinkers
758 //==========================================================================
759 void P_InitThinkers () {
763 //==========================================================================
765 // SV_Shutdown
767 //==========================================================================
768 void SV_Shutdown () {
769 if (GGameInfo) {
770 SV_ShutdownGame();
771 GGameInfo->ConditionalDestroy();
773 for (int i = 0; i < MAXPLAYERS; ++i) {
774 if (GPlayersBase[i]) {
775 delete GPlayersBase[i]->Net;
776 GPlayersBase[i]->Net = nullptr;
777 GPlayersBase[i]->ConditionalDestroy();
781 P_FreeTerrainTypes();
782 ShutdownLockDefs();
783 svs.serverinfo.Clean();
785 delete ServerNetContext;
786 ServerNetContext = nullptr;
790 //==========================================================================
792 // SV_Clear
794 //==========================================================================
795 void SV_Clear () {
796 if (GLevel) {
797 for (int i = 0; i < svs.max_clients; ++i) {
798 VBasePlayer *Player = GGameInfo->Players[i];
799 if (!Player) continue;
800 if (Player->Net) Player->Net->ResetLevel();
803 if (GDemoRecordingContext) {
804 for (int f = 0; f < GDemoRecordingContext->ClientConnections.length(); ++f) {
805 //GDemoRecordingContext->ClientConnections[f]->Driver->SetNetTime();
806 GDemoRecordingContext->ClientConnections[f]->ResetLevel();
810 GLevel->ConditionalDestroy();
811 GLevel = nullptr;
812 //Host_CollectGarbage(true); // later
814 memset(&sv, 0, sizeof(sv));
815 #ifdef CLIENT
816 // clear drawer level
817 if (Drawer) Drawer->RendLev = nullptr;
818 // make sure all sounds are stopped
819 GAudio->StopAllSound();
820 #endif
821 Host_CollectGarbage(true, true); // force-collect garbage, and set new unique id
825 //==========================================================================
827 // SV_NetworkHeartbeat
829 //==========================================================================
830 static void SV_NetworkHeartbeat (bool forced) {
831 if (!ServerNetContext) return;
832 if (GGameInfo->NetMode != NM_DedicatedServer && GGameInfo->NetMode != NM_ListenServer) return; // no need if server is local
833 #ifdef CLIENT
834 if (cls.demorecording || cls.demoplayback) return;
835 #endif
836 double currTime = Sys_Time();
837 if (currTime < 0.0) currTime = 0.0;
838 if (ServerLastKeepAliveTime > currTime) ServerLastKeepAliveTime = currTime; // wtf?!
839 if (!forced && currTime-ServerLastKeepAliveTime < 1.0/60.0) return;
840 ServerLastKeepAliveTime = currTime;
841 ServerNetContext->KeepaliveTick();
845 //==========================================================================
847 // SV_SendClientMessages
849 //==========================================================================
850 static void SV_SendClientMessages (bool full=true) {
851 if (/*full*/true) {
852 // update player replication infos
853 for (int i = 0; i < svs.max_clients; ++i) {
854 VBasePlayer *Player = GGameInfo->Players[i];
855 if (!Player) continue;
856 if (Player->IsGoingToDie()) continue;
858 VPlayerReplicationInfo *RepInfo = Player->PlayerReplicationInfo;
859 if (!RepInfo) continue;
861 RepInfo->PlayerName = Player->PlayerName;
862 RepInfo->UserInfo = Player->UserInfo;
863 RepInfo->TranslStart = Player->TranslStart;
864 RepInfo->TranslEnd = Player->TranslEnd;
865 RepInfo->Color = Player->Color;
866 RepInfo->Frags = Player->Frags;
867 RepInfo->Deaths = Player->Deaths;
868 RepInfo->KillCount = Player->KillCount;
869 RepInfo->ItemCount = Player->ItemCount;
870 RepInfo->SecretCount = Player->SecretCount;
872 // update view angle if needed
873 if (Player->PlayerFlags&VBasePlayer::PF_Spawned) {
874 Player->WriteViewData();
876 if (!full && Player->Net) {
877 Player->Net->GetMessages();
878 // server context ticker will tick all client connections
883 ServerNetContext->Tick();
884 ServerLastKeepAliveTime = Sys_Time();
886 if (full && GDemoRecordingContext) {
887 for (int i = 0; i < GDemoRecordingContext->ClientConnections.length(); ++i) {
888 //GDemoRecordingContext->ClientConnections[i]->Driver->SetNetTime();
889 GDemoRecordingContext->ClientConnections[i]->NeedsUpdate = true;
891 GDemoRecordingContext->Tick();
896 //========================================================================
898 // CheckForIntermissionSkip
900 // Check to see if any player hit a key
902 //========================================================================
903 static void CheckForIntermissionSkip () {
904 VBasePlayer *player;
905 bool skip = false;
907 //GCon->Logf(NAME_Debug, "CheckForIntermissionSkip! dm=%d; itime=%g; tsi=%d", svs.deathmatch, sv.intertime, (int)triedToSkipIntermission);
909 // if we don't have alive players, skip it forcefully
910 bool hasAlivePlayer = false;
911 for (int i = 0; i < MAXPLAYERS; ++i) {
912 player = GGameInfo->Players[i];
913 if (player) {
914 if (!(player->PlayerFlags&VBasePlayer::PF_IsBot)) {
915 if (GGameInfo->NetMode == NM_DedicatedServer || GGameInfo->NetMode == NM_ListenServer) {
916 if (!player->Net) {
917 // local player?
918 #ifdef CLIENT
919 if (!(GGameInfo->NetMode == NM_ListenServer && player == cl)) continue;
920 #endif
923 if (player->PlayerFlags&VBasePlayer::PF_Spawned) {
924 hasAlivePlayer = true; // alive
927 if (player->Buttons&BT_ATTACK) {
928 //GCon->Logf(NAME_Debug, " plr#%d: attack down", i);
929 if (!(player->PlayerFlags&VBasePlayer::PF_AttackDown)) {
930 //GCon->Logf(NAME_Debug, " plr#%d: attack SKIP", i);
931 skip = true;
933 player->PlayerFlags |= VBasePlayer::PF_AttackDown;
934 } else {
935 player->PlayerFlags &= ~VBasePlayer::PF_AttackDown;
937 if (player->Buttons&BT_USE) {
938 //GCon->Logf(NAME_Debug, " plr#%d: use down", i);
939 if (!(player->PlayerFlags&VBasePlayer::PF_UseDown)) {
940 //GCon->Logf(NAME_Debug, " plr#%d: use SKIP", i);
941 skip = true;
943 player->PlayerFlags |= VBasePlayer::PF_UseDown;
944 } else {
945 player->PlayerFlags &= ~VBasePlayer::PF_UseDown;
950 if (svs.deathmatch && sv.intertime < 4) {
951 // wait for 4 seconds before allowing a skip
952 if (skip) {
953 //GCon->Logf(NAME_Debug, " delayed intermission skip");
954 triedToSkipIntermission = true;
955 skip = false;
957 } else if (triedToSkipIntermission) {
958 skip = true;
959 triedToSkipIntermission = false;
962 // no alive players, and network game? skip intermission
963 if (!hasAlivePlayer && (GGameInfo->NetMode == NM_DedicatedServer /*|| GGameInfo->NetMode == NM_ListenServer*/)) {
964 skip = true;
965 GCon->Log(NAME_Debug, "forced intermisstion skip!");
968 if (skip) {
969 //GCon->Logf(NAME_Debug, " sending intermission skip event... (hasAlivePlayer=%d)", (int)hasAlivePlayer);
970 for (int i = 0; i < svs.max_clients; ++i) {
971 player = GGameInfo->Players[i];
972 if (!player) continue;
973 player->eventClientSkipIntermission();
975 // if no alive players, simply go to the next map
976 if (!hasAlivePlayer) {
977 //GCon->Logf(NAME_Debug, " teleporting to the next map");
978 if (VStr(GLevelInfo->NextMap).startsWithCI("EndGame")) {
979 for (int ep = 0; ep < P_GetNumEpisodes(); ++ep) {
980 VEpisodeDef *edef = P_GetEpisodeDef(ep);
981 if (!edef) continue; // just in case
982 VName map = edef->Name;
983 if (map == NAME_None || !IsMapPresent(map)) {
984 //GCon->Logf(NAME_Debug, " ep=%d; map '%s' is not here!", ep, *edef->Name);
985 map = edef->TeaserName;
986 if (map == NAME_None || !IsMapPresent(map)) continue;
988 GLevelInfo->NextMap = map;
989 break;
992 //sv.intermission = server_t::IM_No;
993 if (!mapteleport_executed) {
994 mapteleport_executed = true;
995 GCon->Logf(NAME_Debug, "*** teleporting to the new map '%s'", *GLevelInfo->NextMap);
996 GCmdBuf << "TeleportNewMap **forced**\n";
1003 //==========================================================================
1005 // SV_RunPlayerTick
1007 // do all necessary checks *BEFORE* calling this function
1009 //==========================================================================
1010 static void SV_RunPlayerTick (VBasePlayer *Player, bool skipFrame) {
1011 Player->ForwardMove = (skipFrame && dbg_skipframe_player_block_move ? 0 : Player->ClientForwardMove);
1012 Player->SideMove = (skipFrame && dbg_skipframe_player_block_move ? 0 : Player->ClientSideMove);
1013 //if (Player->ForwardMove) GCon->Logf("ffm: %f (%d)", Player->ClientForwardMove, (int)skipFrame);
1014 // don't move faster than maxmove (halved, if running is disabled)
1015 //FIXME: this should not assume anything, but take walking speed from cvars!
1016 const float maxmove = max2(0.0f, sv_maxmove.asFloat()*(sv_disable_run ? 0.5f : 1.0f));
1017 Player->ForwardMove = clampWithBound(Player->ForwardMove, maxmove);
1018 Player->SideMove = clampWithBound(Player->SideMove, maxmove);
1019 // check for disabled freelook (this is required for server)
1020 if (sv_disable_mlook) Player->ViewAngles.pitch = 0;
1021 else if (!svs.deathmatch && !sv_ignore_nomlook && (GLevelInfo->LevelInfoFlags&VLevelInfo::LIF_NoFreelook)) Player->ViewAngles.pitch = 0;
1022 //GCon->Logf(NAME_Debug, "*** 000: PLAYER TICK(%p) ***: Buttons=0x%08x; OldButtons=0x%08x; fwd=%g; side=%g", Player, Player->Buttons, Player->OldButtons, Player->ForwardMove, Player->SideMove);
1023 //GCon->Logf("*** 000: PLAYER TICK(%p) ***: Buttons=0x%08x; OldButtons=0x%08x", Player, Player->Buttons, Player->OldButtons);
1024 Player->eventPlayerTick(host_frametime);
1025 //GCon->Logf("*** 001: PLAYER TICK(%p) ***: Buttons=0x%08x; OldButtons=0x%08x", Player, Player->Buttons, Player->OldButtons);
1026 // new logic for client buttons update
1027 //Player->OldButtons = Player->AcsButtons;
1028 //Player->OldViewAngles = Player->ViewAngles;
1029 // latch logic
1030 //if ((Player->AcsNextButtonUpdate -= host_frametime) <= 0.0f)
1031 if (Player->AcsNextButtonUpdate <= GLevel->TicTime) {
1032 //Player->AcsNextButtonUpdate = 1.0f/35.0f; // once per standard DooM frame
1033 //!Player->AcsNextButtonUpdate = 1.0f/36.0f; // once per standard DooM frame
1034 Player->AcsNextButtonUpdate = GLevel->TicTime+1;
1035 Player->OldButtons = Player->AcsButtons;
1036 // now create new acs buttons
1037 Player->AcsButtons = Player->AcsCurrButtonsPressed;
1038 Player->AcsCurrButtonsPressed = Player->AcsCurrButtons;
1039 // mouse movement
1040 Player->AcsMouseX = Player->AcsPrevMouseX;
1041 Player->AcsMouseY = Player->AcsPrevMouseY;
1042 Player->AcsPrevMouseX = 0;
1043 Player->AcsPrevMouseY = 0;
1048 //==========================================================================
1050 // SV_RunClients
1052 //==========================================================================
1053 static void SV_RunClients (bool skipFrame=false) {
1054 int currMaxFrags = 0;
1056 // get commands
1057 for (int i = 0; i < MAXPLAYERS; ++i) {
1058 VBasePlayer *Player = GGameInfo->Players[i];
1059 if (!Player) continue;
1061 if ((Player->PlayerFlags&VBasePlayer::PF_IsBot) &&
1062 !(Player->PlayerFlags&VBasePlayer::PF_Spawned))
1064 Player->SpawnClient();
1067 // do player reborns if needed
1068 if (Player->PlayerState == PST_REBORN) G_DoReborn(i, false);
1069 else if (Player->PlayerState == PST_CHEAT_REBORN) G_DoReborn(i, true);
1071 if (Player->Net) {
1072 // resetting update flag is totally wrong: we may miss some important updates in combined mode!
1073 // (because they aren't sent yet)
1074 // let context ticker reset it instead
1075 //Player->Net->NeedsUpdate = false;
1076 //GCon->Logf(NAME_DevNet, "player #%d: getting messages", i);
1077 // actually, force updates; the network update code will take care of rate limiting
1078 Player->Net->NeedsUpdate = true;
1079 Player->Net->GetMessages();
1080 Player->Net->Tick();
1082 currMaxFrags = max2(currMaxFrags, Player->Frags);
1084 // pause if in menu or console and at least one tic has been run
1085 if ((Player->PlayerFlags&VBasePlayer::PF_Spawned) && !sv.intermission && !GGameInfo->IsPaused()) {
1086 if (dbg_vm_disable_thinkers) {
1087 if (Player->PlayerFlags&VBasePlayer::PF_IsBot) continue;
1089 SV_RunPlayerTick(Player, skipFrame);
1093 if (FragGame < 0) {
1094 const int fl = FragLimit.asInt();
1095 FragGame = (fl <= 0 ? 0 : currMaxFrags+fl);
1096 GCon->Logf(NAME_Debug, "*** FRAGLIMIT set to %d (current max is %d, FragLimit is %d)", FragGame, currMaxFrags, FragLimit.asInt());
1099 //GCon->Logf(NAME_Debug, "*** IMS: %d (demo=%p : %d)", (int)sv.intermission, GDemoRecordingContext, (int)cls.demorecording);
1100 if (sv.intermission) {
1101 CheckForIntermissionSkip();
1102 sv.intertime += host_frametime;
1103 } else {
1104 triedToSkipIntermission = false;
1109 //==========================================================================
1111 // SV_UpdateMaster
1113 //==========================================================================
1114 static void SV_UpdateMaster () {
1115 if (GGameInfo->NetMode != NM_DedicatedServer && GGameInfo->NetMode != NM_ListenServer) {
1116 LastMasterUpdate = 0;
1117 return;
1119 const double hbt = clampval(master_heartbeat_time.asInt(), 1, 60)*60.0;
1120 if (LastMasterUpdate <= 0.0 || host_systime-LastMasterUpdate >= hbt) {
1121 GNet->UpdateMaster();
1122 // randomise update time a little
1123 LastMasterUpdate = host_systime+(FRandomFull()-FRandomFull())*8.0;
1128 //==========================================================================
1130 // svIsInWipe
1132 //==========================================================================
1133 static inline bool svIsInWipe () noexcept {
1134 #ifdef CLIENT_NOPE_NOPE
1135 if (GGameInfo->NetMode <= NM_Standalone || GLevel->TicTime >= serverStartRenderFramesTic) return false;
1136 //if (!r_wipe_enabled.asBool()) return false;
1137 return true;
1138 #else
1139 return false;
1140 #endif
1144 //==========================================================================
1146 // SV_Ticker
1148 //==========================================================================
1149 static void SV_Ticker () {
1150 if (host_frametime <= 0.0f) return;
1152 int scap = host_max_skip_frames;
1153 if (scap < 3) scap = 3;
1155 // freestep mode
1156 if (sv_loading || sv.intermission) {
1157 GGameInfo->frametime = host_frametime;
1158 // do not run intermission while wiping
1159 SV_RunClients();
1160 } else {
1161 const float saved_frametime = host_frametime;
1162 bool frameSkipped = false;
1163 bool timeLimitReached = false;
1164 bool runClientsCalled = false;
1165 // do main actions
1166 float frametimeleft = host_frametime;
1167 int lastTick = GLevel->TicTime;
1168 while (!sv.intermission && !completed) {
1169 if (frametimeleft <= 0.0f) break;
1170 if (GLevel->TicTime != lastTick) {
1171 lastTick = GLevel->TicTime;
1172 Host_CollectGarbage();
1174 // calculate frame time
1175 // do small steps, it seems to work better this way
1176 const float currframetime = (frametimeleft > FrameTime ? FrameTime : frametimeleft);
1177 // do it this way, because of rounding
1178 GGameInfo->frametime = currframetime;
1179 host_frametime = GGameInfo->frametime;
1180 // do not allow pause if we're doing initial ticking with wiping
1181 if (GGameInfo->IsPaused() && !svIsInWipe()) {
1182 // no need to do anything more if the game is paused
1183 if (!frameSkipped) { runClientsCalled = true; SV_RunClients(); }
1184 break;
1186 // advance player states, so weapons won't slow down on frame skip
1187 if (!frameSkipped || dbg_skipframe_player_tick) {
1188 runClientsCalled = true;
1189 SV_RunClients(frameSkipped); // have to make a full run, for demos/network (k8: is it really necessary?)
1191 GLevel->TickWorld(host_frametime, /*allowVCPause*/true);
1192 //GCon->Logf("%d: ft=%f; ftleft=%f; Time=%f; tics=%d", (int)frameSkipped, host_frametime, oldft-GGameInfo->frametime, GLevel->Time, (int)GLevel->TicTime);
1193 // level timer
1194 if (TimerGame > 0) {
1195 if (GLevel->TicTime > TimerGame) {
1196 TimerGame = 0;
1197 FragGame = -1;
1198 LeavePosition = 0;
1199 completed = true;
1200 timeLimitReached = true;
1201 } else if (!TimerGameWarned && TimerGame-GLevel->TicTime <= 10*35) {
1202 TimerGameWarned = true;
1203 for (int i = 0; i < MAXPLAYERS; ++i) {
1204 VBasePlayer *Player = GGameInfo->Players[i];
1205 if (!Player || (Player->PlayerFlags&VBasePlayer::PF_IsBot)) continue;
1206 if (Player->Net) Player->CenterPrintf("TIME LIMIT WILL BE REACHED IN 10 SECONDS!");
1210 frametimeleft -= currframetime; /*host_frametime*/ /*currframetime*/ // next step
1211 frameSkipped = true;
1213 if (!runClientsCalled) SV_RunClients(true);
1214 // check frags
1215 if (!completed && FragGame > 0) {
1216 //static int lastMaxFrags = -1;
1217 int maxFrags = 0;
1218 for (int i = 0; i < MAXPLAYERS; ++i) {
1219 VBasePlayer *Player = GGameInfo->Players[i];
1220 if (!Player) continue;
1221 maxFrags = max2(maxFrags, Player->Frags);
1223 //if (maxFrags != lastMaxFrags) { GCon->Logf(NAME_Debug, "MAX FRAGS: %d; LIMIT: %d", maxFrags, FragGame); lastMaxFrags = maxFrags; }
1224 if (maxFrags >= FragGame) {
1225 // setup time limit, so we won't teleport immediately after the last kill
1226 FragGame = 0;
1227 TimerGame = GLevel->TicTime+35*3; // three seconds
1228 TimerGameWarned = true; // prevent warning
1229 GCon->Logf(NAME_Debug, "fraglimit timer activated!");
1230 for (int i = 0; i < MAXPLAYERS; ++i) {
1231 VBasePlayer *Player = GGameInfo->Players[i];
1232 if (!Player || (Player->PlayerFlags&VBasePlayer::PF_IsBot)) continue;
1233 if (Player->Net) Player->CenterPrintf("FRAGLIMIT REACHED, ACTIVATED ENDGAME TIMER!");
1237 if (completed) G_DoCompleted(timeLimitReached);
1238 // restore frame time (just in case)
1239 host_frametime = saved_frametime;
1244 //==========================================================================
1246 // CheckRedirects
1248 //==========================================================================
1249 static VName CheckRedirects (VName Map) {
1250 const VMapInfo &Info = P_GetMapInfo(Map);
1251 if (Info.RedirectType == NAME_None || Info.RedirectMap == NAME_None) return Map; // no redirect for this map
1252 // check all players
1253 for (int i = 0; i < MAXPLAYERS; ++i) {
1254 VBasePlayer *P = GGameInfo->Players[i];
1255 if (!P || !(P->PlayerFlags&VBasePlayer::PF_Spawned)) continue;
1256 // no replacements allowed
1257 if (P->MO->eventCheckInventory(Info.RedirectType, false) > 0) return CheckRedirects(Info.RedirectMap);
1259 // none of the players have required item, no redirect
1260 return Map;
1264 //==========================================================================
1266 // G_CheckWantExitText
1268 //==========================================================================
1269 bool G_CheckWantExitText () {
1270 if (svs.deathmatch) return false;
1271 if (!GLevel) return false; // just in case
1272 const VMapInfo &old_info = P_GetMapInfo(GLevel->MapName);
1273 const VClusterDef *ClusterD = P_GetClusterDef(old_info.Cluster);
1274 if (!ClusterD) return false;
1276 GCon->Logf(NAME_Debug, "MAP: %s; cluster #%d; flags=0x%04x; etext=\"%s\"; xtext=\"%s\"; flat=<%s>; mus=<%s>", *GLevel->MapName,
1277 ClusterD->Cluster, ClusterD->Flags, *ClusterD->EnterText.quote(), *ClusterD->ExitText.quote(), *ClusterD->Flat, *ClusterD->Music);
1279 return (ClusterD && !ClusterD->ExitText.xstrip().isEmpty());
1283 //==========================================================================
1285 // G_CheckFinale
1287 //==========================================================================
1288 bool G_CheckFinale () {
1289 if (!GLevelInfo) return false;
1290 return VStr(GLevelInfo->NextMap).startsWithCI("EndGame");
1294 //==========================================================================
1296 // G_StartClientFinale
1298 //==========================================================================
1299 bool G_StartClientFinale () {
1300 if (!GLevelInfo) return false;
1301 //if (!VStr(GLevelInfo->NextMap).startsWithCI("EndGame")) return false;
1302 for (int i = 0; i < svs.max_clients; ++i) {
1303 if (GGameInfo->Players[i]) GGameInfo->Players[i]->eventClientFinale(VStr(GLevelInfo->NextMap).StartsWithCI("EndGame") ? *GLevelInfo->NextMap : "");
1305 sv.intermission = server_t::IM_Finale;
1306 return true;
1310 //==========================================================================
1312 // G_DoCompleted
1314 //==========================================================================
1315 static void G_DoCompleted (bool ignoreNoExit) {
1316 completed = false;
1317 if (sv.intermission) return;
1319 if (!ignoreNoExit && NoExit /*&& svs.deathmatch*/ && (GGameInfo->NetMode == NM_DedicatedServer || GGameInfo->NetMode == NM_ListenServer)) {
1320 return;
1323 if (GGameInfo->NetMode < NM_DedicatedServer &&
1324 (!GGameInfo->Players[0] || !(GGameInfo->Players[0]->PlayerFlags&VBasePlayer::PF_Spawned)))
1326 //FIXME: some ACS left from previous visit of the level
1327 return;
1330 for (int i = 0; i < MAXPLAYERS; ++i) {
1331 if (GGameInfo->Players[i]) GGameInfo->Players[i]->eventPlayerBeforeExitMap();
1334 #ifdef CLIENT
1335 SV_AutoSaveOnLevelExit();
1336 SCR_SignalWipeStart();
1337 #endif
1339 sv.intermission = server_t::IM_EndLevel;
1340 sv.intertime = 0;
1341 GLevelInfo->CompletitionTime = GLevel->Time;
1343 GLevel->Acs->StartTypedACScripts(SCRIPT_Unloading, 0, 0, 0, nullptr, false, true);
1345 GLevelInfo->NextMap = CheckRedirects(GLevelInfo->NextMap);
1347 const VMapInfo &old_info = P_GetMapInfo(GLevel->MapName);
1348 const VMapInfo &new_info = P_GetMapInfo(GLevelInfo->NextMap);
1349 const VClusterDef *ClusterD = P_GetClusterDef(old_info.Cluster);
1350 bool HubChange = (!old_info.Cluster || !(ClusterD->Flags&CLUSTERF_Hub) || old_info.Cluster != new_info.Cluster);
1352 if (G_CheckFinale()) {
1353 HubChange = true; // no more maps
1354 if (!G_CheckWantExitText()) {
1355 G_StartClientFinale();
1356 return;
1360 for (int i = 0; i < MAXPLAYERS; ++i) {
1361 if (GGameInfo->Players[i]) {
1362 GGameInfo->Players[i]->eventPlayerExitMap(HubChange);
1363 if (svs.deathmatch || HubChange) {
1364 //GCon->Logf(NAME_Debug, "PLR#%d: HubChange=%d; next=<%s>", i, (int)HubChange, *GLevelInfo->NextMap);
1365 GGameInfo->Players[i]->eventClientIntermission(GLevelInfo->NextMap);
1370 if (!svs.deathmatch && !HubChange) GCmdBuf << "TeleportNewMap\n";
1374 //==========================================================================
1375 static const char *knownFinalesList[] = {
1376 "EndGameBunny",
1377 "EndGameCast",
1378 "EndGameChess",
1379 "EndGameDemon",
1380 "EndGamePic1",
1381 "EndGamePic2",
1382 "EndGamePic3",
1383 "EndGameStrife",
1384 "EndGameUnderwater",
1385 nullptr,
1389 //==========================================================================
1391 // COMMAND TestFinale
1393 //==========================================================================
1394 COMMAND_WITH_AC(TestFinale) {
1395 if (Args.length() != 2) return;
1397 if (GGameInfo->NetMode == NM_Client) return;
1399 CMD_FORWARD_TO_SERVER();
1401 // normalise finale name
1402 VStr fname = Args[1];
1403 for (const char **fin = knownFinalesList; *fin; ++fin) {
1404 if (fname.strEquCI(*fin)) {
1405 fname = VStr(*fin);
1406 break;
1410 if (GGameInfo->NetMode == NM_Standalone && !svs.deathmatch) {
1411 for (int i = 0; i < svs.max_clients; ++i) {
1412 if (GGameInfo->Players[i]) {
1413 GGameInfo->Players[i]->eventClientFinale(fname);
1416 sv.intermission = server_t::IM_Finale;
1419 //if (GGameInfo->NetMode == NM_Standalone) SV_UpdateRebornSlot(); // copy the base slot to the reborn slot
1423 //==========================================================================
1425 // COMMAND_AC TestFinale
1427 //==========================================================================
1428 COMMAND_AC(TestFinale) {
1429 TArray<VStr> list;
1430 VStr prefix = (aidx < args.length() ? args[aidx] : VStr());
1431 if (aidx == 1) {
1432 for (const char **fin = knownFinalesList; *fin; ++fin) list.append(VStr(*fin));
1433 return AutoCompleteFromListCmd(prefix, list);
1434 } else {
1435 return VStr::EmptyString;
1440 //==========================================================================
1442 // COMMAND TeleportNewMap
1444 // TeleportNewMap [mapname leaveposition] | [**forced**]
1446 //==========================================================================
1447 COMMAND_WITH_AC(TeleportNewMap) {
1448 int flags = 0;
1449 //mapteleport_executed = false; // used for netgame autoteleport
1451 // dumb clients must forward this
1452 if (GGameInfo->NetMode == NM_None) return;
1454 CMD_FORWARD_TO_SERVER();
1456 if (Args.length() == 3) {
1457 GLevelInfo->NextMap = VName(*Args[1], VName::AddLower8);
1458 LeavePosition = VStr::atoi(*Args[2]);
1459 } else if (sv.intermission != server_t::IM_EndLevel) { //k8: why check for `IM_EndLevel` here?
1460 if (Args.length() != 2 || !Args[1].startsWithCI("**forced**")) return;
1461 flags = CHANGELEVEL_REMOVEKEYS;
1462 if (Args[1].startsWithCI("**forced**")) mapteleport_executed = true; // block it again
1464 //GCon->Logf(NAME_Debug, "TELEPORTMAP: map=<%s>; nextmap=<%s>", (GLevel ? *GLevel->MapName : "none"), *GLevelInfo->NextMap);
1466 if (!svs.deathmatch && G_CheckFinale()) {
1467 G_StartClientFinale();
1468 return;
1471 #ifdef CLIENT
1472 Draw_TeleportIcon();
1473 #endif
1475 RebornPosition = LeavePosition;
1476 GGameInfo->RebornPosition = RebornPosition;
1477 mapteleport_issued = true;
1478 mapteleport_flags = flags;
1479 mapteleport_skill = -1;
1480 //if (GGameInfo->NetMode == NM_Standalone) SV_UpdateRebornSlot(); // copy the base slot to the reborn slot
1483 COMMAND_AC_SIMPLE_LIST(TeleportNewMap, "**forced**")
1486 // ////////////////////////////////////////////////////////////////////////// //
1487 struct TeleportMapExFlag {
1488 const char *name;
1489 int value;
1490 bool reset;
1493 static TeleportMapExFlag TMEFlags[] = {
1494 { "KeepFacing", CHANGELEVEL_KEEPFACING, false },
1495 { "KeepKeys", CHANGELEVEL_REMOVEKEYS, true },
1496 { "ResetInventory", CHANGELEVEL_RESETINVENTORY, false },
1497 { "NoMonsters", CHANGELEVEL_NOMONSTERS, false },
1498 //{ "ChangeSkill", CHANGELEVEL_CHANGESKILL, false },
1499 { "NoIntermission", CHANGELEVEL_NOINTERMISSION, false },
1500 { "ResetHealth", CHANGELEVEL_RESETHEALTH, false },
1501 { "PreraiseWeapon", CHANGELEVEL_PRERAISEWEAPON, false },
1502 { nullptr, 0, false },
1506 //==========================================================================
1508 // COMMAND ACS_TeleportNewMap
1510 // mapname posidx flags [skill]
1512 //==========================================================================
1513 COMMAND(ACS_TeleportNewMap) {
1514 mapteleport_executed = false; // used for netgame autoteleport
1516 if (GGameInfo->NetMode == NM_Client) return;
1518 if (Args.length() != 5) {
1519 GCon->Logf("ACS_TeleportNewMap mapname posidx flags skill");
1520 return;
1523 // dumb clients must forward this
1524 if (GGameInfo->NetMode == NM_None /*|| GGameInfo->NetMode == NM_Client*/) return;
1526 CMD_FORWARD_TO_SERVER();
1528 int posidx = 0;
1529 if (!Args[2].convertInt(&posidx)) {
1530 GCon->Logf(NAME_Warning, "ACS_TeleportNewMap: invalid position index '%s'", *Args[2]);
1531 posidx = 0;
1534 int flags = 0;
1535 if (!Args[3].convertInt(&flags)) {
1536 GCon->Logf(NAME_Warning, "ACS_TeleportNewMap: invalid flags value '%s'", *Args[3]);
1537 flags = 0;
1539 flags |= CHANGELEVEL_REMOVEKEYS;
1541 int skill = -1;
1542 if (!Args[4].convertInt(&flags)) {
1543 GCon->Logf(NAME_Warning, "ACS_TeleportNewMap: invalid skill value '%s'", *Args[4]);
1544 skill = -1;
1547 GCon->Logf(NAME_Debug, "ACS level teleport: map=<%s>; flags=0x%04x, skill=%d", *Args[1], (unsigned)flags, skill);
1549 VName mname = VName(*Args[1], VName::FindLower8);
1550 if (mname == NAME_None) {
1551 if (!Args[1].startsWithCI("EndGame")) {
1552 GCon->Logf(NAME_Warning, "ACS_TeleportNewMap: unknown map name '%s'", *Args[1]);
1553 return;
1555 mname = VName(*Args[1]);
1557 GLevelInfo->NextMap = mname;
1558 //GCon->Logf(NAME_Debug, "TELEPORTMAP: map=<%s>; nextmap=<%s>", (GLevel ? *GLevel->MapName : "none"), *GLevelInfo->NextMap);
1560 if (!svs.deathmatch && G_CheckFinale()) {
1561 G_StartClientFinale();
1562 return;
1565 #ifdef CLIENT
1566 Draw_TeleportIcon();
1567 #endif
1569 RebornPosition = posidx;
1570 GGameInfo->RebornPosition = RebornPosition;
1571 mapteleport_issued = true; // this will actually change the map
1572 mapteleport_flags = flags;
1573 mapteleport_skill = skill;
1574 //if (GGameInfo->NetMode == NM_Standalone) SV_UpdateRebornSlot(); // copy the base slot to the reborn slot
1578 //==========================================================================
1580 // COMMAND TeleportNewMapEx
1582 // mapname posidx flags [skill]
1584 //==========================================================================
1585 COMMAND_WITH_AC(TeleportNewMapEx) {
1586 mapteleport_executed = false; // used for netgame autoteleport
1588 if (Args.length() < 2) {
1589 GCon->Logf("TeleportNewMapEx mapname|+|* [posidx [flags [skill]]]");
1590 return;
1593 //if (GGameInfo->NetMode == NM_None || GGameInfo->NetMode == NM_Client) return;
1594 if (GGameInfo->NetMode != NM_Standalone) {
1595 GCon->Logf("TeleportNewMapEx works only in standalone games!");
1596 return;
1599 CMD_FORWARD_TO_SERVER();
1601 int posidx = 0;
1602 if (Args.length() > 2) {
1603 if (!Args[2].convertInt(&posidx)) {
1604 GCon->Logf(NAME_Warning, "TeleportNewMapEx: invalid position index '%s'", *Args[2]);
1605 return;
1609 int flags = CHANGELEVEL_REMOVEKEYS, skill = -1;
1610 for (int f = 3; f < Args.length(); ++f) {
1611 bool found = false;
1612 for (const TeleportMapExFlag *tff = TMEFlags; tff->name; ++tff) {
1613 if (Args[f].strEquCI(tff->name)) {
1614 found = true;
1615 if (tff->reset) flags &= ~tff->value; else flags |= tff->value;
1616 break;
1619 // if not found, try numeric conversion for skill
1620 if (found) continue;
1621 // try skill names too
1622 int skn = M_SkillFromName(*Args[f]);
1623 if (skn >= 0) {
1624 skill = skn;
1625 flags |= CHANGELEVEL_CHANGESKILL;
1626 continue;
1628 GCon->Logf(NAME_Warning, "TeleportNewMapEx: invalid flag '%s'", *Args[f]);
1629 return;
1632 //GCon->Logf(NAME_Debug, "TeleportNewMapEx: name=<%s>; posidx=%d; flags=0x%04x; skill=%d", *Args[1], posidx, flags, skill);
1634 if (Args[1] == "+" || Args[1] == "*") {
1635 // use default next map; i.e. do nothing
1636 if (Args.length() == 2 && Args[1] == "+") {
1637 #ifdef CLIENT
1638 C_Stop(true); // immediate
1639 #endif
1640 G_DoCompleted(false);
1641 return;
1643 } else {
1644 VName mname = VName(*Args[1], VName::FindLower8);
1645 if (mname == NAME_None) {
1646 if (!Args[1].startsWithCI("EndGame")) {
1647 GCon->Logf(NAME_Warning, "TeleportNewMapEx: unknown map name '%s'", *Args[1]);
1648 return;
1650 mname = VName(*Args[1]);
1652 GLevelInfo->NextMap = mname;
1655 if (!svs.deathmatch) {
1656 if (VStr(GLevelInfo->NextMap).startsWithCI("EndGame")) {
1657 for (int i = 0; i < svs.max_clients; ++i) {
1658 if (GGameInfo->Players[i]) GGameInfo->Players[i]->eventClientFinale(*GLevelInfo->NextMap);
1660 sv.intermission = server_t::IM_Finale;
1661 return;
1665 #ifdef CLIENT
1666 Draw_TeleportIcon();
1667 #endif
1669 RebornPosition = posidx;
1670 GGameInfo->RebornPosition = RebornPosition;
1671 mapteleport_issued = true; // this will actually change the map
1672 mapteleport_flags = flags;
1673 mapteleport_skill = skill;
1674 //if (GGameInfo->NetMode == NM_Standalone) SV_UpdateRebornSlot(); // copy the base slot to the reborn slot
1678 //==========================================================================
1680 // COMMAND_AC TeleportNewMapEx
1682 //==========================================================================
1683 COMMAND_AC(TeleportNewMapEx) {
1684 VStr prefix = (aidx < args.length() ? args[aidx] : VStr());
1685 if (aidx == 1) {
1686 int mapcount = P_GetNumMaps();
1687 TArray<VStr> list;
1688 list.resize(mapcount);
1689 for (int f = 0; f < mapcount; ++f) {
1690 VName mlump = P_GetMapLumpName(f);
1691 if (mlump != NAME_None) list.append(*mlump);
1693 if (list.length()) return AutoCompleteFromListCmd(prefix, list);
1694 } else if (aidx >= 3) {
1695 // flags
1696 TArray<VStr> list;
1697 for (const TeleportMapExFlag *tff = TMEFlags; tff->name; ++tff) {
1698 // check for duplicate
1699 bool found = false;
1700 for (int f = 1; f < args.length(); ++f) if (args[f].strEquCI(tff->name)) { found = true; break; }
1701 if (!found) list.append(tff->name);
1703 if (list.length()) return AutoCompleteFromListCmd(prefix, list);
1705 return VStr::EmptyString;
1709 //==========================================================================
1711 // G_DoReborn
1713 //==========================================================================
1714 static void G_DoReborn (int playernum, bool cheatReborn) {
1715 if (!GGameInfo->Players[playernum] ||
1716 !(GGameInfo->Players[playernum]->PlayerFlags&VBasePlayer::PF_Spawned))
1718 return;
1720 if (GGameInfo->NetMode == NM_Standalone && !cheatReborn) {
1721 GCmdBuf << "Restart\n";
1722 GGameInfo->Players[playernum]->PlayerState = PST_LIVE;
1723 } else {
1724 GGameInfo->Players[playernum]->eventNetGameReborn();
1729 //==========================================================================
1731 // NET_SendToAll
1733 //==========================================================================
1734 int NET_SendToAll (int blocktime) {
1735 double start;
1736 int count = 0;
1737 bool state1[MAXPLAYERS];
1738 bool state2[MAXPLAYERS];
1740 for (int i = 0; i < svs.max_clients; ++i) {
1741 VBasePlayer *Player = GGameInfo->Players[i];
1742 if (Player && Player->Net) {
1743 if (Player->Net->IsLocalConnection()) {
1744 state1[i] = false;
1745 state2[i] = true;
1746 continue;
1748 ++count;
1749 state1[i] = false;
1750 state2[i] = false;
1751 } else {
1752 state1[i] = true;
1753 state2[i] = true;
1757 start = Sys_Time();
1758 while (count) {
1759 count = 0;
1760 for (int i = 0; i < svs.max_clients; ++i) {
1761 VBasePlayer *Player = GGameInfo->Players[i];
1762 if (!Player) continue;
1764 if (!state1[i]) {
1765 state1[i] = true;
1766 //Player->Net->ForceAllowSendForServer();
1767 Player->Net->GetGeneralChannel()->Close();
1768 ++count;
1769 continue;
1772 if (!state2[i]) {
1773 if (Player->Net->IsClosed()) {
1774 state2[i] = true;
1775 } else {
1776 //Player->Net->ForceAllowSendForServer();
1777 Player->Net->GetMessages();
1778 Player->Net->Tick();
1780 ++count;
1781 continue;
1784 if ((Sys_Time()-start) > blocktime) break;
1787 return count;
1791 //==========================================================================
1793 // SV_SendServerInfoToClients
1795 //==========================================================================
1796 void SV_SendServerInfoToClients () {
1797 for (int i = 0; i < svs.max_clients; ++i) {
1798 VBasePlayer *Player = GGameInfo->Players[i];
1799 if (!Player) continue;
1800 Player->Level = GLevelInfo;
1801 if (Player->Net) {
1802 Player->Net->LoadedNewLevel();
1803 Player->Net->SendServerInfo();
1804 } else {
1805 //GCon->Logf("SERVER: player #%d has no network connection", i);
1809 if (GDemoRecordingContext) {
1810 for (int f = 0; f < GDemoRecordingContext->ClientConnections.length(); ++f) {
1811 //GDemoRecordingContext->ClientConnections[f]->Driver->SetNetTime();
1812 GDemoRecordingContext->ClientConnections[f]->LoadedNewLevel();
1813 GDemoRecordingContext->ClientConnections[f]->SendServerInfo();
1819 //==========================================================================
1821 // SV_SpawnServer
1823 //==========================================================================
1824 void SV_SpawnServer (const char *mapname, bool spawn_thinkers, bool titlemap) {
1825 if (GSoundManager) GSoundManager->CleanupSounds();
1827 GCon->Log("===============================================");
1828 GCon->Logf("Spawning %sserver with map \"%s\"%s", (titlemap ? "titlemap " : ""), mapname, (spawn_thinkers ? "" : " (without thinkers)"));
1829 //if (VStr::startsWithCI(mapname, "EndGame")) abort();
1831 GGameInfo->Flags &= ~VGameInfo::GIF_Paused;
1832 mapteleport_executed = false; // just in case
1833 mapteleport_issued = false;
1834 mapteleport_flags = 0;
1835 mapteleport_skill = -1;
1836 run_open_scripts = spawn_thinkers;
1837 serverStartRenderFramesTic = -1;
1839 cliMapStartFound = false;
1841 if (GGameInfo->NetMode != NM_None) {
1842 //fprintf(stderr, "SV_SpawnServer!!!\n");
1843 // level change
1844 for (int i = 0; i < MAXPLAYERS; ++i) {
1845 if (!GGameInfo->Players[i]) continue;
1847 GGameInfo->Players[i]->KillCount = 0;
1848 GGameInfo->Players[i]->SecretCount = 0;
1849 GGameInfo->Players[i]->ItemCount = 0;
1851 GGameInfo->Players[i]->PlayerFlags &= ~(VBasePlayer::PF_Spawned|VBasePlayer::PF_ExitedViaSecret);
1852 GGameInfo->Players[i]->MO = nullptr;
1853 GGameInfo->Players[i]->Frags = 0;
1854 GGameInfo->Players[i]->Deaths = 0;
1855 if (GGameInfo->Players[i]->PlayerState == PST_DEAD) GGameInfo->Players[i]->PlayerState = PST_REBORN;
1857 } else {
1858 // new game
1859 svs.deathmatch = DeathMatch;
1860 GGameInfo->deathmatch = svs.deathmatch;
1862 P_InitThinkers();
1864 #ifdef CLIENT
1865 GGameInfo->NetMode = (titlemap ? NM_TitleMap : svs.max_clients == 1 ? NM_Standalone : NM_ListenServer);
1866 #else
1867 GGameInfo->NetMode = NM_DedicatedServer;
1868 #endif
1870 GGameInfo->WorldInfo = GGameInfo->eventCreateWorldInfo();
1872 GGameInfo->WorldInfo->SetSkill(Skill);
1873 GGameInfo->eventInitNewGame(GGameInfo->WorldInfo->GameSkill);
1876 SV_Clear();
1877 //GCon->Log("*** UNLATCH ***");
1878 VCvar::Unlatch();
1880 // load it
1881 SV_LoadLevel(VName(mapname, VName::AddLower8));
1882 GLevel->NetContext = ServerNetContext;
1883 GLevel->WorldInfo = GGameInfo->WorldInfo;
1885 #ifdef CLIENT
1886 if (GGameInfo->NetMode <= NM_Standalone) SCR_SignalWipeStart();
1887 #endif
1889 const VMapInfo &info = P_GetMapInfo(GLevel->MapName);
1891 if (spawn_thinkers) {
1892 // create level info
1893 GLevelInfo = (VLevelInfo *)GLevel->SpawnThinker(GGameInfo->LevelInfoClass);
1894 if (!GLevelInfo) Sys_Error("SV_SpawnServer: cannot spawn LevelInfo");
1895 if (!GLevelInfo->IsA(GGameInfo->LevelInfoClass)) Sys_Error("SV_SpawnServer: spawned LevelInfo is of invalid class `%s`", GLevelInfo->GetClass()->GetName());
1896 GLevelInfo->Level = GLevelInfo;
1897 GLevelInfo->Game = GGameInfo;
1898 GLevelInfo->World = GGameInfo->WorldInfo;
1899 GLevel->LevelInfo = GLevelInfo;
1900 GLevelInfo->SetMapInfo(GLevel, info);
1902 // spawn things
1903 for (int i = 0; i < GLevel->NumThings; ++i) GLevelInfo->eventSpawnMapThing(&GLevel->Things[i]);
1904 if (svs.deathmatch && GLevelInfo->DeathmatchStarts.length() < 4) Host_Error("Level needs more deathmatch start spots");
1907 if (svs.deathmatch) {
1908 TimerGame = max2(0, TimeLimit.asInt()*35*60);
1909 const int fl = FragLimit.asInt();
1910 FragGame = (fl <= 0 ? 0 : -1); // calculate on the next tick
1911 } else {
1912 TimerGame = 0;
1913 FragGame = 0;
1915 TimerGameWarned = false;
1917 // set up world state
1918 Host_ResetSkipFrames();
1920 if (!spawn_thinkers) {
1921 // usually loading a save
1922 SV_SendServerInfoToClients(); // anyway
1923 if (GLevel->scriptThinkers.length() || AcsHasScripts(GLevel->Acs)) {
1924 serverStartRenderFramesTic = GLevel->TicTime+INITIAL_TICK_DELAY;
1925 GCon->Log("---");
1926 if (AcsHasScripts(GLevel->Acs)) GCon->Log("Found some ACS scripts");
1927 if (GLevel->scriptThinkers.length()) GCon->Logf("ACS thinkers: %d", GLevel->scriptThinkers.length());
1929 } else {
1930 // P_SpawnSpecials
1931 // after the map has been loaded, scan for specials that spawn thinkers
1932 GLevelInfo->eventSpawnSpecials();
1934 SV_SendServerInfoToClients();
1936 // call BeginPlay events
1937 for (TThinkerIterator<VEntity> Ent(GLevel); Ent; ++Ent) Ent->eventBeginPlay();
1938 GLevelInfo->LevelInfoFlags2 |= VLevelInfo::LIF2_BegunPlay;
1940 Host_ResetSkipFrames();
1942 if (GGameInfo->NetMode != NM_TitleMap && GGameInfo->NetMode != NM_Standalone) {
1943 GLevel->TickWorld(FrameTime, /*allowVCPause*/false);
1944 GLevel->TickWorld(FrameTime, /*allowVCPause*/false);
1945 // start open scripts
1946 GLevel->Acs->StartTypedACScripts(SCRIPT_Open, 0, 0, 0, nullptr, false, false);
1949 // delay rendering if we have ACS scripts
1950 if (GLevel->scriptThinkers.length() || AcsHasScripts(GLevel->Acs)) {
1951 if (GGameInfo->NetMode <= NM_Standalone) serverStartRenderFramesTic = GLevel->TicTime+INITIAL_TICK_DELAY;
1952 GCon->Log("---");
1953 if (AcsHasScripts(GLevel->Acs)) GCon->Log("Found some ACS scripts");
1954 if (GLevel->scriptThinkers.length()) GCon->Logf("ACS thinkers: %d", GLevel->scriptThinkers.length());
1958 Host_CollectGarbage(true); // why not?
1959 GCon->Logf("Spawned server for \"%s\".", mapname);
1963 //==========================================================================
1965 // COMMAND PreSpawn
1967 //==========================================================================
1968 COMMAND(PreSpawn) {
1969 if (Source == SRC_Command) {
1970 GCon->Log("PreSpawn is not valid from console");
1971 return;
1974 // allow server to send updates
1975 Player->Net->NeedsUpdate = true;
1977 // make sure level info is spawned on server side, since there could be some RPCs that depend on it
1978 VThinkerChannel *Chan = Player->Net->ThinkerChannels.findptr(GLevelInfo);
1979 if (!Chan) {
1980 Chan = (VThinkerChannel *)Player->Net->CreateChannel(CHANNEL_Thinker, -1, true); // local channel
1981 if (!Chan) Sys_Error("cannot allocate `LevelInfo` channel, this should NOT happen!");
1982 GCon->Logf(NAME_DevNet, "%s: created channel for `GLevelInfo`", *Player->Net->GetAddress());
1983 Chan->SetThinker(GLevelInfo);
1984 } else {
1985 GCon->Logf(NAME_DevNet, "%s: have existing channel for `GLevelInfo` (why?!)", *Chan->GetDebugName());
1987 Chan->Update();
1991 //==========================================================================
1993 // COMMAND Spawn
1995 //==========================================================================
1996 COMMAND(Client_Spawn) {
1997 if (Source == SRC_Command) {
1998 GCon->Log("Client_Spawn is not valid from console");
1999 return;
2001 Player->SpawnClient();
2005 //==========================================================================
2007 // SV_DropClient
2009 //==========================================================================
2010 void SV_DropClient (VBasePlayer *Player, bool crash) {
2011 if (!crash) {
2012 if (GLevel && GLevel->Acs) {
2013 GLevel->Acs->StartTypedACScripts(SCRIPT_Disconnect, SV_GetPlayerNum(Player), 0, 0, nullptr, true, true); // "runnow" was false
2015 if (Player->PlayerFlags&VBasePlayer::PF_Spawned) Player->eventDisconnectClient();
2016 } else {
2017 // this is sudden network disconnect, kill player mobile (if any)
2018 // if we won't do this, player mobile will be left in the game, and will become Voodoo Doll
2019 if ((Player->PlayerFlags&VBasePlayer::PF_Spawned) && Player->MO) {
2020 GCon->Logf(NAME_DevNet, "killing player #%d mobile", SV_GetPlayerNum(Player));
2021 Player->eventDisconnectClient();
2025 Player->PlayerFlags &= ~VBasePlayer::PF_Active;
2026 GGameInfo->Players[SV_GetPlayerNum(Player)] = nullptr;
2027 Player->PlayerFlags &= ~VBasePlayer::PF_Spawned;
2029 if (Player->PlayerReplicationInfo) Player->PlayerReplicationInfo->DestroyThinker();
2031 if (Player->Net) {
2032 GCon->Log(NAME_DevNet, "deleting player connection");
2033 delete Player->Net;
2034 Player->Net = nullptr;
2037 --svs.num_connected;
2038 Player->UserInfo = VStr();
2042 //==========================================================================
2044 // SV_ShutdownGame
2046 // This only happens at the end of a game, not between levels
2047 // This is also called on Host_Error, so it shouldn't cause any errors
2049 //==========================================================================
2050 void SV_ShutdownGame () {
2051 #ifdef CLIENT
2052 // so we could minimize uniqueid
2053 MN_DeactivateMenu();
2054 #endif
2056 if (GGameInfo->NetMode == NM_None) {
2057 Host_CollectGarbage(true, true); // force-collect garbage, and set new unique id
2058 return;
2061 #ifdef CLIENT
2062 if (GGameInfo->Flags&VGameInfo::GIF_Paused) {
2063 GGameInfo->Flags &= ~VGameInfo::GIF_Paused;
2064 GAudio->ResumeSound();
2067 // stop sounds (especially looping!)
2068 GAudio->StopAllSound();
2070 if (cls.demorecording) CL_StopRecording();
2072 // clear drawer level
2073 if (Drawer) Drawer->RendLev = nullptr;
2074 #endif
2076 if (GGameInfo->NetMode == NM_Client) {
2077 #ifdef CLIENT
2078 if (cls.demoplayback) GClGame->eventDemoPlaybackStopped();
2080 // sends a disconnect message to the server
2081 if (!cls.demoplayback) {
2082 GCon->Log(NAME_Dev, "Sending clc_disconnect");
2083 if (cl->Net) {
2084 if (cl->Net->GetGeneralChannel()) cl->Net->GetGeneralChannel()->Close();
2085 cl->Net->Flush();
2089 delete cl->Net;
2090 cl->Net = nullptr;
2091 cl->ConditionalDestroy();
2093 if (GClLevel) {
2094 delete GClLevel;
2095 GClLevel = nullptr;
2097 #endif
2098 } else {
2099 sv_loading = false;
2100 sv_map_travel = false;
2102 // make sure all the clients know we're disconnecting
2103 int count = NET_SendToAll(5);
2104 if (count) GCon->Logf("Shutdown server failed for %d clients", count);
2106 for (int i = 0; i < svs.max_clients; ++i) {
2107 if (GGameInfo->Players[i]) SV_DropClient(GGameInfo->Players[i], false);
2110 // clear structures
2111 if (GLevel) {
2112 delete GLevel;
2113 GLevel = nullptr;
2115 if (GGameInfo->WorldInfo) {
2116 delete GGameInfo->WorldInfo;
2117 GGameInfo->WorldInfo = nullptr;
2119 for (int i = 0; i < MAXPLAYERS; ++i) {
2120 // save net pointer
2121 VNetConnection *OldNet = GPlayersBase[i]->Net;
2122 GPlayersBase[i]->GetClass()->DestructObject(GPlayersBase[i]);
2123 if (GPlayersBase[i]->GetClass()->ClassSize > (int)sizeof(VObject)) {
2124 memset((vuint8 *)GPlayersBase[i]+sizeof(VObject), 0, GPlayersBase[i]->GetClass()->ClassSize-sizeof(VObject));
2126 // restore pointer
2127 GPlayersBase[i]->Net = OldNet;
2129 memset(GGameInfo->Players, 0, sizeof(GGameInfo->Players));
2130 memset(&sv, 0, sizeof(sv));
2132 // tell master server that this server is gone
2133 if (GGameInfo->NetMode >= NM_DedicatedServer) {
2134 GNet->QuitMaster();
2135 LastMasterUpdate = 0;
2139 #ifdef CLIENT
2140 GClLevel = nullptr;
2141 cl = nullptr;
2142 cls.clearForDisconnect(); // this resets demo playback flag too
2144 if (GGameInfo->NetMode != NM_DedicatedServer) GClGame->eventDisconnected();
2145 #endif
2147 SV_ClearBaseSlot();
2149 GGameInfo->NetMode = NM_None;
2151 Host_CollectGarbage(true, true); // force-collect garbage, and set new unique id
2155 #ifdef CLIENT
2156 //==========================================================================
2158 // COMMAND Restart
2160 //==========================================================================
2161 COMMAND(Restart) {
2162 //fprintf(stderr, "*****RESTART!\n");
2163 if (GGameInfo->NetMode != NM_Standalone) return;
2164 //if (!SV_LoadQuicksaveSlot())
2166 // reload the level from scratch
2167 SV_SpawnServer(*GLevel->MapName, true/*spawn thinkers*/);
2168 if (GGameInfo->NetMode != NM_DedicatedServer) CL_SetupLocalPlayer();
2171 #endif
2174 //==========================================================================
2176 // COMMAND Pause
2178 //==========================================================================
2179 COMMAND(Pause) {
2180 if (GGameInfo->NetMode == NM_None || GGameInfo->NetMode == NM_Client) return;
2182 CMD_FORWARD_TO_SERVER();
2184 if (!__dbg_cl_always_allow_pause) {
2185 if (GGameInfo->NetMode != NM_Standalone || svs.max_clients > 1) {
2186 if (Player) Player->Printf("%s", "You cannot pause in network game, sorry.");
2187 return;
2191 GGameInfo->Flags ^= VGameInfo::GIF_Paused;
2192 for (int i = 0; i < svs.max_clients; ++i) {
2193 if (GGameInfo->Players[i]) {
2194 GGameInfo->Players[i]->eventClientPause(!!(GGameInfo->Flags&VGameInfo::GIF_Paused));
2200 //==========================================================================
2202 // COMMAND Stats
2204 //==========================================================================
2205 COMMAND(Stats) {
2206 CMD_FORWARD_TO_SERVER();
2207 if (Player) {
2208 Player->Printf("Kills: %d of %d", Player->KillCount, GLevelInfo->TotalKills);
2209 Player->Printf("Items: %d of %d", Player->ItemCount, GLevelInfo->TotalItems);
2210 Player->Printf("Secrets: %d of %d", Player->SecretCount, GLevelInfo->TotalSecret);
2215 //==========================================================================
2217 // SV_ConnectClient
2219 // initialises a client_t for a new net connection
2220 // this will only be called once for a player each game, not once
2221 // for each level change
2223 //==========================================================================
2224 void SV_ConnectClient (VBasePlayer *player) {
2225 if (player->Net) {
2226 GCon->Logf(NAME_DevNet, "Client %s connected", *player->Net->GetAddress());
2227 ServerNetContext->ClientConnections.Append(player->Net);
2228 player->Net->NeedsUpdate = false; // we're just connected, no need to send world updates yet
2231 GGameInfo->Players[SV_GetPlayerNum(player)] = player;
2232 player->ClientNum = SV_GetPlayerNum(player);
2233 player->PlayerFlags |= VBasePlayer::PF_Active;
2235 player->PlayerFlags &= ~VBasePlayer::PF_Spawned;
2236 player->Level = GLevelInfo;
2237 if (!sv_loading) {
2238 //GCon->Logf(NAME_Debug, "player #%d: reborn!", SV_GetPlayerNum(player));
2239 player->MO = nullptr;
2240 player->PlayerState = PST_REBORN;
2241 player->eventPutClientIntoServer();
2243 player->Frags = 0;
2244 player->Deaths = 0;
2246 player->PlayerReplicationInfo = (VPlayerReplicationInfo *)GLevel->SpawnThinker(GGameInfo->PlayerReplicationInfoClass);
2247 player->PlayerReplicationInfo->Player = player;
2248 player->PlayerReplicationInfo->PlayerNum = SV_GetPlayerNum(player);
2252 //==========================================================================
2254 // SV_CheckForNewClients
2256 // check for new connections
2258 // returns `false` if we have no active connections
2260 //==========================================================================
2261 static bool SV_CheckForNewClients () {
2262 for (;;) {
2263 VSocketPublic *sock = GNet->CheckNewConnections(false); // not only rcon
2264 if (!sock) break;
2266 //GCon->Logf(NAME_DevNet, "SV_CheckForNewClients: got client at %s", *sock->Address);
2268 // init a new client structure
2269 int i;
2270 for (i = 0; i < svs.max_clients; ++i) if (!GGameInfo->Players[i]) break;
2271 if (i == svs.max_clients) Sys_Error("Host_CheckForNewClients: no free clients");
2273 VBasePlayer *Player = GPlayersBase[i];
2274 Player->Net = new VNetConnection(sock, ServerNetContext, Player);
2275 Player->Net->ObjMap->SetupClassLookup();
2276 Player->Net->GetPlayerChannel()->SetPlayer(Player);
2277 Player->Net->CreateChannel(CHANNEL_ObjectMap, -1, true); // local channel
2278 SV_ConnectClient(Player);
2279 ++svs.num_connected;
2282 #ifdef CLIENT
2283 return true; // always
2284 #else
2285 return (svs.num_connected > 0);
2286 #endif
2290 //==========================================================================
2292 // SV_ConnectBot
2294 //==========================================================================
2295 void SV_ConnectBot (const char *name) {
2296 int i;
2298 if (GGameInfo->NetMode == NM_None || GGameInfo->NetMode == NM_Client) {
2299 GCon->Log("Game is not running");
2300 return;
2303 if (svs.num_connected >= svs.max_clients) {
2304 GCon->Log("Server is full");
2305 return;
2308 // init a new client structure
2309 for (i = 0; i < svs.max_clients; ++i) if (!GGameInfo->Players[i]) break;
2310 if (i == svs.max_clients) Sys_Error("SV_ConnectBot: no free clients");
2312 VBasePlayer *Player = GPlayersBase[i];
2313 Player->PlayerFlags |= VBasePlayer::PF_IsBot;
2314 Player->PlayerName = name;
2315 SV_ConnectClient(Player);
2316 ++svs.num_connected;
2317 Player->SetUserInfo(Player->UserInfo);
2318 Player->SpawnClient();
2322 //==========================================================================
2324 // COMMAND AddBot
2326 //==========================================================================
2327 COMMAND(AddBot) {
2328 SV_ConnectBot(Args.length() > 1 ? *Args[1] : "");
2332 //==========================================================================
2334 // Map
2336 //==========================================================================
2337 COMMAND_WITH_AC(Map) {
2338 VStr mapname;
2340 if (Args.length() != 2) {
2341 GCon->Log("map <mapname> : change level");
2342 return;
2344 mapname = Args[1];
2346 SV_ShutdownGame();
2348 // default the player start spot group to 0
2349 RebornPosition = 0;
2350 GGameInfo->RebornPosition = RebornPosition;
2352 if ((int)Skill < 0) Skill = 0;
2353 else if ((int)Skill >= P_GetNumSkills()) Skill = P_GetNumSkills()-1;
2355 SV_ResetPlayers();
2356 SV_SpawnServer(*mapname, true/*spawn thinkers*/);
2358 #ifdef CLIENT
2359 if (GGameInfo->NetMode != NM_DedicatedServer) CL_SetupLocalPlayer();
2360 #endif
2363 //==========================================================================
2365 // COMMAND_AC Map
2367 //==========================================================================
2368 COMMAND_AC(Map) {
2369 VStr prefix = (aidx < args.length() ? args[aidx] : VStr());
2370 if (aidx == 1) {
2371 TArray<VStr> list;
2372 // prefer pwad maps
2373 if (fsys_PWadMaps.length()) {
2374 list.resize(fsys_PWadMaps.length());
2375 for (auto &&lmp : fsys_PWadMaps) list.append(lmp.mapname);
2376 } else {
2377 int mapcount = P_GetNumMaps();
2378 list.resize(mapcount);
2379 for (int f = 0; f < mapcount; ++f) {
2380 VName mlump = P_GetMapLumpName(f);
2381 if (mlump != NAME_None) list.append(VStr(mlump));
2384 if (list.length()) return AutoCompleteFromListCmd(prefix, list);
2386 return VStr::EmptyString;
2390 //==========================================================================
2392 // Host_CLIMapStartFound
2394 // called if CLI arguments contains some map selections command
2396 //==========================================================================
2397 void Host_CLIMapStartFound () {
2398 cliMapStartFound = true;
2402 //==========================================================================
2404 // Host_IsCLIMapStartFound
2406 //==========================================================================
2407 bool Host_IsCLIMapStartFound () {
2408 return cliMapStartFound;
2412 //==========================================================================
2414 // Host_StartTitleMap
2416 //==========================================================================
2417 bool Host_StartTitleMap () {
2418 if (cliMapStartFound) return false;
2420 static bool loadingTitlemap = false;
2422 if (cli_SVNoTitleMap > 0) return false;
2424 if (loadingTitlemap) {
2425 // it is completely fucked, ignore it
2426 static bool titlemapWarned = false;
2427 if (!titlemapWarned) {
2428 titlemapWarned = true;
2429 GCon->Log(NAME_Warning, "Your titlemap is fucked, I won't try to load it anymore.");
2431 return false;
2434 if (!FL_FileExists("maps/titlemap.wad") && W_CheckNumForName(NAME_titlemap) < 0) return false;
2436 loadingTitlemap = true;
2437 // default the player start spot group to 0
2438 RebornPosition = 0;
2439 GGameInfo->RebornPosition = RebornPosition;
2441 SV_ResetPlayers();
2442 SV_SpawnServer("titlemap", true/*spawn thinkers*/, true/*titlemap*/);
2443 #ifdef CLIENT
2444 CL_SetupLocalPlayer();
2445 #endif
2446 loadingTitlemap = false;
2447 return true;
2451 //==========================================================================
2453 // COMMAND MaxPlayers
2455 //==========================================================================
2456 COMMAND(MaxPlayers) {
2457 if (Args.length() < 2 || Args.length() > 3) {
2458 GCon->Logf("maxplayers is %d", svs.max_clients);
2459 return;
2462 if (GGameInfo->NetMode != NM_None && GGameInfo->NetMode != NM_Client) {
2463 GCon->Log("maxplayers can not be changed while a server is running.");
2464 return;
2467 int n = VStr::atoi(*Args[1]);
2468 if (n < 1) n = 1;
2469 if (n > MAXPLAYERS) {
2470 n = MAXPLAYERS;
2471 GCon->Logf("maxplayers set to %d", n);
2473 svs.max_clients = n;
2475 int dmMode = 2;
2476 if (Args.length() > 2) {
2477 dmMode = VStr::atoi(*Args[2]);
2478 if (dmMode < 0 || dmMode > 2) dmMode = 2;
2481 if (n == 1) {
2482 #ifdef CLIENT
2483 GCmdBuf << "listen 0\n";
2484 #endif
2485 DeathMatch = 0;
2486 NoMonsters = (cli_NoMonsters > 0 ? 1 : 0);
2487 } else {
2488 #ifdef CLIENT
2489 GCmdBuf << "listen 1\n";
2490 #endif
2491 DeathMatch = dmMode;
2492 if (dmMode) {
2493 NoMonsters = 1;
2494 } else {
2495 //NoMonsters = (dmMode ? 1 : 0);
2496 NoMonsters = (cli_NoMonsters > 0 ? 1 : 0);
2502 //==========================================================================
2504 // SV_ServerInterframeBK
2506 //==========================================================================
2507 void SV_ServerInterframeBK () {
2508 // if we're running a server (either dedicated, or combined, and we are in game), process server bookkeeping
2509 if (GGameInfo->NetMode == NM_DedicatedServer || GGameInfo->NetMode == NM_ListenServer) {
2510 SV_SendClientMessages(false); // not full
2515 //==========================================================================
2517 // SV_AllClientsNeedsWorldUpdate
2519 // force world update on the next tick
2521 //==========================================================================
2522 void SV_AllClientsNeedsWorldUpdate () {
2523 for (int i = 0; i < MAXPLAYERS; ++i) {
2524 VBasePlayer *Player = GGameInfo->Players[i];
2525 if (!Player || !Player->Net) continue;
2526 Player->Net->NeedsUpdate = true;
2531 //==========================================================================
2533 // NET_SendNetworkHeartbeat
2535 //==========================================================================
2536 void NET_SendNetworkHeartbeat (bool forced) {
2537 #ifdef CLIENT
2538 CL_NetworkHeartbeat(forced);
2539 #endif
2540 #ifdef SERVER
2541 SV_NetworkHeartbeat(forced);
2542 #endif
2546 //==========================================================================
2548 // SV_ServerFrame
2550 //==========================================================================
2551 void SV_ServerFrame () {
2552 const bool haveClients = SV_CheckForNewClients();
2554 // there is no need to tick if we have no active clients
2555 // no, really
2556 if (haveClients || sv_loading || sv.intermission) {
2557 // freestep mode
2558 SV_Ticker();
2561 if (mapteleport_issued) {
2562 SV_MapTeleport(GLevelInfo->NextMap, mapteleport_flags, mapteleport_skill);
2565 SV_SendClientMessages(); // full
2566 SV_UpdateMaster();
2570 //==========================================================================
2572 // SV_FindClassFromEditorId
2574 //==========================================================================
2575 VClass *SV_FindClassFromEditorId (int Id, int GameFilter) {
2576 mobjinfo_t *nfo = VClass::FindMObjId(Id, GameFilter);
2577 if (nfo) return nfo->Class;
2578 return nullptr;
2582 //==========================================================================
2584 // SV_FindClassFromScriptId
2586 //==========================================================================
2587 VClass *SV_FindClassFromScriptId (int Id, int GameFilter) {
2588 mobjinfo_t *nfo = VClass::FindScriptId(Id, GameFilter);
2589 if (nfo) return nfo->Class;
2590 return nullptr;
2594 //==========================================================================
2596 // COMMAND Say
2598 //==========================================================================
2599 COMMAND(Say) {
2600 CMD_FORWARD_TO_SERVER();
2601 if (Args.length() < 2) return;
2603 VStr Text;
2604 for (int i = 1; i < Args.length(); ++i) {
2605 VStr s = Args[i].xstrip();
2606 if (!s.isEmpty()) {
2607 if (!Text.isEmpty()) Text += " ";
2608 Text += s;
2611 Text = Text.xstrip();
2612 if (Text.isEmpty()) return;
2613 GLevelInfo->BroadcastChatPrint(Player->PlayerName, Text);
2614 GLevelInfo->StartSound(TVec(0, 0, 0), 0, GSoundManager->GetSoundID("misc/chat"), 0, 1.0f, 0.0f/*attenuation*/, false,
2615 1.0f); /* force pitch */
2616 #ifndef CLIENT
2617 Text = VStr("[")+Player->PlayerName.RemoveColors().xstrip()+"]:";
2618 for (int i = 1; i < Args.length(); ++i) {
2619 Text += " ";
2620 Text += Args[i].RemoveColors().xstrip();
2622 GCon->Logf(NAME_Chat, "%s", *Text);
2623 #endif
2627 //==========================================================================
2629 // COMMAND gc_toggle_stats
2631 //==========================================================================
2632 COMMAND(gc_toggle_stats) {
2633 VObject::GGCMessagesAllowed = !VObject::GGCMessagesAllowed;
2637 //==========================================================================
2639 // COMMAND gc_show_all_objects
2641 //==========================================================================
2642 COMMAND(gc_show_all_objects) {
2643 int total = VObject::GetObjectsCount();
2644 GCon->Log("===============");
2645 GCon->Logf("total array size: %d", total);
2646 for (int f = 0; f < total; ++f) {
2647 VObject *o = VObject::GetIndexObject(f);
2648 if (!o) continue;
2649 if (o->IsDestroyed()) GCon->Logf(" #%5d: %p: DESTROYED! (%s)", f, o, o->GetClass()->GetName());
2650 else if (o->IsDelayedDestroy()) GCon->Logf(" #%5d: %p: DELAYED! (%s)", f, o, o->GetClass()->GetName());
2651 else GCon->Logf(" #%5d: %p: `%s`", f, o, o->GetClass()->GetName());
2656 //==========================================================================
2658 // COMMAND NetChanInfo
2660 //==========================================================================
2661 COMMAND(NetChanInfo) {
2662 if (GGameInfo->NetMode <= NM_Standalone) {
2663 GCon->Logf(NAME_Error, "no netchan info available without a network game!");
2664 return;
2667 #ifdef CLIENT
2668 if (GGameInfo->NetMode == NM_Client) {
2669 if (!cl || !cl->Net) return;
2670 GCon->Logf(NAME_DevNet, "%s: client: %d open channels", *cl->Net->GetAddress(), cl->Net->OpenChannels.Length());
2671 return;
2673 #endif
2675 if (GGameInfo->NetMode == NM_DedicatedServer || GGameInfo->NetMode == NM_ListenServer) {
2676 for (int i = 0; i < MAXPLAYERS; ++i) {
2677 VBasePlayer *plr = GGameInfo->Players[i];
2678 if (!plr || !plr->Net) continue;
2679 GCon->Logf(NAME_DevNet, "%s: player #%d: %d open channels", *plr->Net->GetAddress(), i, plr->Net->OpenChannels.Length());