1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
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.
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.
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/>.
25 //**************************************************************************
26 #include "../gamedefs.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"
41 # include "../screen.h"
42 # include "../drawer.h"
44 # include "../client/client.h"
45 # include "../client/cl_local.h"
47 # include "../render/r_public.h"
49 #include "../psim/p_decal.h"
50 #include "../decorate/vc_decorate.h"
51 #include "../dehacked/vc_dehacked.h"
54 // this is for BDW mod, to have tracers
55 VCvarB
r_models("r_models", true, "Allow 3d models?", CVAR_NoShadow
/*|CVAR_Archive*/);
60 extern VCvarB r_wipe_enabled
;
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
81 static constexpr double FrameTime
= 0x1.d41d41d41d41ep
-6; // see above
82 double SV_GetFrameTimeConstant () noexcept
{ return FrameTime
; }
84 static constexpr float FrameTime
= 0x1.d41d44p
-6; // see above
85 float SV_GetFrameTimeConstant () noexcept
{ return FrameTime
; }
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
);
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;
208 VStream
*(*dgOpenFile
) (VStr filename
, void *userdata
);
210 VCVFSSaver () : userdata(VMemberBase::userdata
), dgOpenFile(VMemberBase::dgOpenFile
) {}
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);
236 //==========================================================================
238 // VServerNetContext::GetLevel
240 //==========================================================================
241 VLevel
*VServerNetContext::GetLevel () {
246 //==========================================================================
248 // ProcessServerModOption
250 //==========================================================================
251 static void ProcessServerModOption (VScriptParser
*sc
, bool single
) {
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;
261 sc
->Error(va("unknown server mod option '%s'", *sc
->String
));
266 //==========================================================================
270 //==========================================================================
271 static void ParseModListLine (VScriptParser
*sc
, int mode
, const char *modtypestr
) {
273 if (mode
!= VCMODS_CLIENT
) {
275 if (sc
->Check("option")) {
276 ProcessServerModOption(sc
, true);
278 } else if (sc
->Check("options")) {
280 while (!sc
->Check("}")) {
281 if (sc
->AtEnd()) sc
->Error("unclosed options block");
282 ProcessServerModOption(sc
, false);
287 GCon
->Logf(NAME_Debug
, " <%s>\n", *sc
->String
.quote());
289 bool doSection
= false;
290 if (sc
->Check("pre")) {
291 doLoad
= (mode
== VCMODS_SERVER_PRE
);
293 } else if (sc
->Check("post")) {
294 doLoad
= (mode
== VCMODS_SERVER_POST
);
297 doLoad
= (mode
== VCMODS_SERVER_PRE
);
300 GCon
->Logf(NAME_Debug
, " :<%s> (doSection=%d; doLoad=%d)\n", *sc
->String
.quote(),
301 (int)doSection
, (int)doLoad
);
308 while (!sc
->Check("}")) {
309 if (sc
->AtEnd()) sc
->Error("unclosed mods block");
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());
317 sc
->SkipBracketed(true);
327 if (sc
->String
== "{" || sc
->String
== "}") sc
->Error("wtf?!");
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 //==========================================================================
340 //==========================================================================
341 static void LoadModList (const int ScLump
, int mode
, bool newStyle
) {
342 const char *modtypestr
;
344 case VCMODS_SERVER_PRE
:
345 modtypestr
= "server (pre)";
347 case VCMODS_SERVER_POST
:
348 modtypestr
= "server (post)";
351 modtypestr
= "client";
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
);
361 sc
->SetEscape(false);
362 sc
->SetSemicolonComments(true);
363 sc
->SetHashComments(true);
366 GCon
->Logf(NAME_Init
, "parsing Vavoom C mod list from '%s'", *W_FullLumpName(ScLump
));
368 GCon
->Logf(NAME_Init
, "parsing old-style Vavoom C mod list from '%s'", *W_FullLumpName(ScLump
));
372 while (!sc
->AtEnd()) {
373 if (sc
->Check("client")) {
374 if (mode
!= VCMODS_CLIENT
) {
375 sc
->SkipBracketed(false);
378 } else if (sc
->Check("server")) {
379 if (mode
== VCMODS_CLIENT
) {
380 sc
->SkipBracketed(false);
384 sc
->Error(va("invalid modlist section '%s'", *sc
->String
));
387 while (!sc
->Check("}")) {
388 if (sc
->AtEnd()) sc
->Error("unfinished section");
389 ParseModListLine(sc
, mode
, modtypestr
);
393 while (!sc
->AtEnd()) {
394 ParseModListLine(sc
, mode
, modtypestr
);
395 if (!sc
->GetString()) break;
410 //==========================================================================
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 //==========================================================================
431 // loading mods, take list from modlistfile
432 // load user-specified Vavoom C script files
434 //==========================================================================
435 void G_LoadVCMods (int mode
) {
438 case VCMODS_SERVER_PRE
:
439 modlistfile
= "loadvcs";
441 case VCMODS_SERVER_POST
:
442 modlistfile
= "loadvcs";
445 modlistfile
= "loadvcc";
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")) {
456 while (f
< nslist
.length() && W_LumpFile(nslist
[f
].lump
) != W_LumpFile(it
.lump
)) {
459 if (f
== nslist
.length()) {
460 ModListInfo
&nfo
= nslist
.Alloc();
464 nslist
[f
].lump
= it
.lump
;
468 // collect old-style lumps
469 for (auto &&it
: WadNSNameIterator(modlistfile
, WADNS_Global
)) {
471 while (f
< nslist
.length() && W_LumpFile(nslist
[f
].lump
) != W_LumpFile(it
.lump
)) {
474 if (f
== nslist
.length()) {
475 ModListInfo
&nfo
= nslist
.Alloc();
477 nfo
.newStyle
= false;
478 } else if (!nslist
[f
].newStyle
) {
479 nslist
[f
].lump
= it
.lump
;
484 if (nslist
.length()) {
485 smsort_r(nslist
.ptr(), (size_t)nslist
.length(), sizeof(nslist
[0]), nslist_cmp
, nullptr);
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();
514 // this can be called from mapinfo parser, so don't clear it here
515 //CustomDamageFactors.clear(); // we don't need them anymore
520 //==========================================================================
524 //==========================================================================
525 vuint64
SV_GetModListHash (vuint32
*old
) {
527 // get list of loaded modules
528 auto wadlist
= FL_GetWadPk3ListSmall();
529 for (auto &&wadname
: wadlist
) {
533 //GCon->Logf(NAME_Debug, "modlist:\n%s", *modlist);
535 *old
= XXH32(*modlist
, (vint32
)modlist
.length(), (vuint32
)wadlist
.length());
538 uint8_t hash
[RIPEMD160_BYTES
];
539 ripemd160_init(&ctx
);
540 ripemd160_put(&ctx
, *modlist
, (unsigned)modlist
.length());
541 ripemd160_finish(&ctx
, hash
);
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);
555 //==========================================================================
557 // SV_GetModListHashOld
559 //==========================================================================
560 vuint64
SV_GetModListHashOld (vuint32
*old
) {
562 // get list of loaded modules
563 auto wadlist
= FL_GetWadPk3List();
564 for (auto &&wadname
: wadlist
) {
568 //GCon->Logf(NAME_Debug, "modlist:\n%s", *modlist);
570 *old
= XXH32(*modlist
, (vint32
)modlist
.length(), (vuint32
)wadlist
.length());
573 uint8_t hash
[RIPEMD160_BYTES
];
574 ripemd160_init(&ctx
);
575 ripemd160_put(&ctx
, *modlist
, (unsigned)modlist
.length());
576 ripemd160_finish(&ctx
, hash
);
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);
590 //==========================================================================
594 //==========================================================================
595 void SV_LoadMods () {
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();
613 ProcessDehackedFiles();
615 G_LoadVCMods(VCMODS_SERVER_POST
);
619 //==========================================================================
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();
634 if (VMethod::ReportUnusedBuiltins()) Sys_Error("found some unused builtins (internal engine error)");
635 VPackage::DumpCodeSizeStats();
637 VMemberBase::StaticCompilerShutdown();
638 CompilerReportMemory();
642 //==========================================================================
646 //==========================================================================
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();
666 P_InitTerrainTypes();
671 //==========================================================================
675 // call after texture manager updated a flat
677 //==========================================================================
678 void SV_UpdateSkyFlat () {
679 if (GGameInfo
) GGameInfo
->skyflatnum
= skyflatnum
;
683 //==========================================================================
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 //==========================================================================
758 //==========================================================================
759 void P_InitThinkers () {
763 //==========================================================================
767 //==========================================================================
768 void SV_Shutdown () {
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();
783 svs
.serverinfo
.Clean();
785 delete ServerNetContext
;
786 ServerNetContext
= nullptr;
790 //==========================================================================
794 //==========================================================================
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();
812 //Host_CollectGarbage(true); // later
814 memset(&sv
, 0, sizeof(sv
));
816 // clear drawer level
817 if (Drawer
) Drawer
->RendLev
= nullptr;
818 // make sure all sounds are stopped
819 GAudio
->StopAllSound();
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
834 if (cls
.demorecording
|| cls
.demoplayback
) return;
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) {
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 () {
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
];
914 if (!(player
->PlayerFlags
&VBasePlayer::PF_IsBot
)) {
915 if (GGameInfo
->NetMode
== NM_DedicatedServer
|| GGameInfo
->NetMode
== NM_ListenServer
) {
919 if (!(GGameInfo
->NetMode
== NM_ListenServer
&& player
== cl
)) continue;
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);
933 player
->PlayerFlags
|= VBasePlayer::PF_AttackDown
;
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);
943 player
->PlayerFlags
|= VBasePlayer::PF_UseDown
;
945 player
->PlayerFlags
&= ~VBasePlayer::PF_UseDown
;
950 if (svs
.deathmatch
&& sv
.intertime
< 4) {
951 // wait for 4 seconds before allowing a skip
953 //GCon->Logf(NAME_Debug, " delayed intermission skip");
954 triedToSkipIntermission
= true;
957 } else if (triedToSkipIntermission
) {
959 triedToSkipIntermission
= false;
962 // no alive players, and network game? skip intermission
963 if (!hasAlivePlayer
&& (GGameInfo
->NetMode
== NM_DedicatedServer
/*|| GGameInfo->NetMode == NM_ListenServer*/)) {
965 GCon
->Log(NAME_Debug
, "forced intermisstion 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
;
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 //==========================================================================
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;
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
;
1040 Player
->AcsMouseX
= Player
->AcsPrevMouseX
;
1041 Player
->AcsMouseY
= Player
->AcsPrevMouseY
;
1042 Player
->AcsPrevMouseX
= 0;
1043 Player
->AcsPrevMouseY
= 0;
1048 //==========================================================================
1052 //==========================================================================
1053 static void SV_RunClients (bool skipFrame
=false) {
1054 int currMaxFrags
= 0;
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);
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
);
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
;
1104 triedToSkipIntermission
= false;
1109 //==========================================================================
1113 //==========================================================================
1114 static void SV_UpdateMaster () {
1115 if (GGameInfo
->NetMode
!= NM_DedicatedServer
&& GGameInfo
->NetMode
!= NM_ListenServer
) {
1116 LastMasterUpdate
= 0;
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 //==========================================================================
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;
1144 //==========================================================================
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;
1156 if (sv_loading
|| sv
.intermission
) {
1157 GGameInfo
->frametime
= host_frametime
;
1158 // do not run intermission while wiping
1161 const float saved_frametime
= host_frametime
;
1162 bool frameSkipped
= false;
1163 bool timeLimitReached
= false;
1164 bool runClientsCalled
= false;
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(); }
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);
1194 if (TimerGame
> 0) {
1195 if (GLevel
->TicTime
> TimerGame
) {
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);
1215 if (!completed
&& FragGame
> 0) {
1216 //static int lastMaxFrags = -1;
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
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 //==========================================================================
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
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 //==========================================================================
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
;
1310 //==========================================================================
1314 //==========================================================================
1315 static void G_DoCompleted (bool ignoreNoExit
) {
1317 if (sv
.intermission
) return;
1319 if (!ignoreNoExit
&& NoExit
/*&& svs.deathmatch*/ && (GGameInfo
->NetMode
== NM_DedicatedServer
|| GGameInfo
->NetMode
== NM_ListenServer
)) {
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
1330 for (int i
= 0; i
< MAXPLAYERS
; ++i
) {
1331 if (GGameInfo
->Players
[i
]) GGameInfo
->Players
[i
]->eventPlayerBeforeExitMap();
1335 SV_AutoSaveOnLevelExit();
1336 SCR_SignalWipeStart();
1339 sv
.intermission
= server_t::IM_EndLevel
;
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();
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
[] = {
1384 "EndGameUnderwater",
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
)) {
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
) {
1430 VStr prefix
= (aidx
< args
.length() ? args
[aidx
] : VStr());
1432 for (const char **fin
= knownFinalesList
; *fin
; ++fin
) list
.append(VStr(*fin
));
1433 return AutoCompleteFromListCmd(prefix
, list
);
1435 return VStr::EmptyString
;
1440 //==========================================================================
1442 // COMMAND TeleportNewMap
1444 // TeleportNewMap [mapname leaveposition] | [**forced**]
1446 //==========================================================================
1447 COMMAND_WITH_AC(TeleportNewMap
) {
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();
1472 Draw_TeleportIcon();
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
{
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");
1523 // dumb clients must forward this
1524 if (GGameInfo
->NetMode
== NM_None
/*|| GGameInfo->NetMode == NM_Client*/) return;
1526 CMD_FORWARD_TO_SERVER();
1529 if (!Args
[2].convertInt(&posidx
)) {
1530 GCon
->Logf(NAME_Warning
, "ACS_TeleportNewMap: invalid position index '%s'", *Args
[2]);
1535 if (!Args
[3].convertInt(&flags
)) {
1536 GCon
->Logf(NAME_Warning
, "ACS_TeleportNewMap: invalid flags value '%s'", *Args
[3]);
1539 flags
|= CHANGELEVEL_REMOVEKEYS
;
1542 if (!Args
[4].convertInt(&flags
)) {
1543 GCon
->Logf(NAME_Warning
, "ACS_TeleportNewMap: invalid skill value '%s'", *Args
[4]);
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]);
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();
1566 Draw_TeleportIcon();
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]]]");
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!");
1599 CMD_FORWARD_TO_SERVER();
1602 if (Args
.length() > 2) {
1603 if (!Args
[2].convertInt(&posidx
)) {
1604 GCon
->Logf(NAME_Warning
, "TeleportNewMapEx: invalid position index '%s'", *Args
[2]);
1609 int flags
= CHANGELEVEL_REMOVEKEYS
, skill
= -1;
1610 for (int f
= 3; f
< Args
.length(); ++f
) {
1612 for (const TeleportMapExFlag
*tff
= TMEFlags
; tff
->name
; ++tff
) {
1613 if (Args
[f
].strEquCI(tff
->name
)) {
1615 if (tff
->reset
) flags
&= ~tff
->value
; else flags
|= tff
->value
;
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
]);
1625 flags
|= CHANGELEVEL_CHANGESKILL
;
1628 GCon
->Logf(NAME_Warning
, "TeleportNewMapEx: invalid flag '%s'", *Args
[f
]);
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] == "+") {
1638 C_Stop(true); // immediate
1640 G_DoCompleted(false);
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]);
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
;
1666 Draw_TeleportIcon();
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());
1686 int mapcount
= P_GetNumMaps();
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) {
1697 for (const TeleportMapExFlag
*tff
= TMEFlags
; tff
->name
; ++tff
) {
1698 // check for duplicate
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 //==========================================================================
1713 //==========================================================================
1714 static void G_DoReborn (int playernum
, bool cheatReborn
) {
1715 if (!GGameInfo
->Players
[playernum
] ||
1716 !(GGameInfo
->Players
[playernum
]->PlayerFlags
&VBasePlayer::PF_Spawned
))
1720 if (GGameInfo
->NetMode
== NM_Standalone
&& !cheatReborn
) {
1721 GCmdBuf
<< "Restart\n";
1722 GGameInfo
->Players
[playernum
]->PlayerState
= PST_LIVE
;
1724 GGameInfo
->Players
[playernum
]->eventNetGameReborn();
1729 //==========================================================================
1733 //==========================================================================
1734 int NET_SendToAll (int blocktime
) {
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()) {
1760 for (int i
= 0; i
< svs
.max_clients
; ++i
) {
1761 VBasePlayer
*Player
= GGameInfo
->Players
[i
];
1762 if (!Player
) continue;
1766 //Player->Net->ForceAllowSendForServer();
1767 Player
->Net
->GetGeneralChannel()->Close();
1773 if (Player
->Net
->IsClosed()) {
1776 //Player->Net->ForceAllowSendForServer();
1777 Player
->Net
->GetMessages();
1778 Player
->Net
->Tick();
1784 if ((Sys_Time()-start
) > blocktime
) break;
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
;
1802 Player
->Net
->LoadedNewLevel();
1803 Player
->Net
->SendServerInfo();
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 //==========================================================================
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");
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
;
1859 svs
.deathmatch
= DeathMatch
;
1860 GGameInfo
->deathmatch
= svs
.deathmatch
;
1865 GGameInfo
->NetMode
= (titlemap
? NM_TitleMap
: svs
.max_clients
== 1 ? NM_Standalone
: NM_ListenServer
);
1867 GGameInfo
->NetMode
= NM_DedicatedServer
;
1870 GGameInfo
->WorldInfo
= GGameInfo
->eventCreateWorldInfo();
1872 GGameInfo
->WorldInfo
->SetSkill(Skill
);
1873 GGameInfo
->eventInitNewGame(GGameInfo
->WorldInfo
->GameSkill
);
1877 //GCon->Log("*** UNLATCH ***");
1881 SV_LoadLevel(VName(mapname
, VName::AddLower8
));
1882 GLevel
->NetContext
= ServerNetContext
;
1883 GLevel
->WorldInfo
= GGameInfo
->WorldInfo
;
1886 if (GGameInfo
->NetMode
<= NM_Standalone
) SCR_SignalWipeStart();
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
);
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
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
;
1926 if (AcsHasScripts(GLevel
->Acs
)) GCon
->Log("Found some ACS scripts");
1927 if (GLevel
->scriptThinkers
.length()) GCon
->Logf("ACS thinkers: %d", GLevel
->scriptThinkers
.length());
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
;
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 //==========================================================================
1967 //==========================================================================
1969 if (Source
== SRC_Command
) {
1970 GCon
->Log("PreSpawn is not valid from console");
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
);
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
);
1985 GCon
->Logf(NAME_DevNet
, "%s: have existing channel for `GLevelInfo` (why?!)", *Chan
->GetDebugName());
1991 //==========================================================================
1995 //==========================================================================
1996 COMMAND(Client_Spawn
) {
1997 if (Source
== SRC_Command
) {
1998 GCon
->Log("Client_Spawn is not valid from console");
2001 Player
->SpawnClient();
2005 //==========================================================================
2009 //==========================================================================
2010 void SV_DropClient (VBasePlayer
*Player
, bool 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();
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();
2032 GCon
->Log(NAME_DevNet
, "deleting player connection");
2034 Player
->Net
= nullptr;
2037 --svs
.num_connected
;
2038 Player
->UserInfo
= VStr();
2042 //==========================================================================
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 () {
2052 // so we could minimize uniqueid
2053 MN_DeactivateMenu();
2056 if (GGameInfo
->NetMode
== NM_None
) {
2057 Host_CollectGarbage(true, true); // force-collect garbage, and set new unique id
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;
2076 if (GGameInfo
->NetMode
== NM_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");
2084 if (cl
->Net
->GetGeneralChannel()) cl
->Net
->GetGeneralChannel()->Close();
2091 cl
->ConditionalDestroy();
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);
2115 if (GGameInfo
->WorldInfo
) {
2116 delete GGameInfo
->WorldInfo
;
2117 GGameInfo
->WorldInfo
= nullptr;
2119 for (int i
= 0; i
< MAXPLAYERS
; ++i
) {
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
));
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
) {
2135 LastMasterUpdate
= 0;
2142 cls
.clearForDisconnect(); // this resets demo playback flag too
2144 if (GGameInfo
->NetMode
!= NM_DedicatedServer
) GClGame
->eventDisconnected();
2149 GGameInfo
->NetMode
= NM_None
;
2151 Host_CollectGarbage(true, true); // force-collect garbage, and set new unique id
2156 //==========================================================================
2160 //==========================================================================
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();
2174 //==========================================================================
2178 //==========================================================================
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.");
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 //==========================================================================
2204 //==========================================================================
2206 CMD_FORWARD_TO_SERVER();
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 //==========================================================================
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
) {
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
;
2238 //GCon->Logf(NAME_Debug, "player #%d: reborn!", SV_GetPlayerNum(player));
2239 player
->MO
= nullptr;
2240 player
->PlayerState
= PST_REBORN
;
2241 player
->eventPutClientIntoServer();
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 () {
2263 VSocketPublic
*sock
= GNet
->CheckNewConnections(false); // not only rcon
2266 //GCon->Logf(NAME_DevNet, "SV_CheckForNewClients: got client at %s", *sock->Address);
2268 // init a new client structure
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
;
2283 return true; // always
2285 return (svs
.num_connected
> 0);
2290 //==========================================================================
2294 //==========================================================================
2295 void SV_ConnectBot (const char *name
) {
2298 if (GGameInfo
->NetMode
== NM_None
|| GGameInfo
->NetMode
== NM_Client
) {
2299 GCon
->Log("Game is not running");
2303 if (svs
.num_connected
>= svs
.max_clients
) {
2304 GCon
->Log("Server is full");
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 //==========================================================================
2326 //==========================================================================
2328 SV_ConnectBot(Args
.length() > 1 ? *Args
[1] : "");
2332 //==========================================================================
2336 //==========================================================================
2337 COMMAND_WITH_AC(Map
) {
2340 if (Args
.length() != 2) {
2341 GCon
->Log("map <mapname> : change level");
2348 // default the player start spot group to 0
2350 GGameInfo
->RebornPosition
= RebornPosition
;
2352 if ((int)Skill
< 0) Skill
= 0;
2353 else if ((int)Skill
>= P_GetNumSkills()) Skill
= P_GetNumSkills()-1;
2356 SV_SpawnServer(*mapname
, true/*spawn thinkers*/);
2359 if (GGameInfo
->NetMode
!= NM_DedicatedServer
) CL_SetupLocalPlayer();
2363 //==========================================================================
2367 //==========================================================================
2369 VStr prefix
= (aidx
< args
.length() ? args
[aidx
] : VStr());
2373 if (fsys_PWadMaps
.length()) {
2374 list
.resize(fsys_PWadMaps
.length());
2375 for (auto &&lmp
: fsys_PWadMaps
) list
.append(lmp
.mapname
);
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.");
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
2439 GGameInfo
->RebornPosition
= RebornPosition
;
2442 SV_SpawnServer("titlemap", true/*spawn thinkers*/, true/*titlemap*/);
2444 CL_SetupLocalPlayer();
2446 loadingTitlemap
= false;
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
);
2462 if (GGameInfo
->NetMode
!= NM_None
&& GGameInfo
->NetMode
!= NM_Client
) {
2463 GCon
->Log("maxplayers can not be changed while a server is running.");
2467 int n
= VStr::atoi(*Args
[1]);
2469 if (n
> MAXPLAYERS
) {
2471 GCon
->Logf("maxplayers set to %d", n
);
2473 svs
.max_clients
= n
;
2476 if (Args
.length() > 2) {
2477 dmMode
= VStr::atoi(*Args
[2]);
2478 if (dmMode
< 0 || dmMode
> 2) dmMode
= 2;
2483 GCmdBuf
<< "listen 0\n";
2486 NoMonsters
= (cli_NoMonsters
> 0 ? 1 : 0);
2489 GCmdBuf
<< "listen 1\n";
2491 DeathMatch
= dmMode
;
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
) {
2538 CL_NetworkHeartbeat(forced
);
2541 SV_NetworkHeartbeat(forced
);
2546 //==========================================================================
2550 //==========================================================================
2551 void SV_ServerFrame () {
2552 const bool haveClients
= SV_CheckForNewClients();
2554 // there is no need to tick if we have no active clients
2556 if (haveClients
|| sv_loading
|| sv
.intermission
) {
2561 if (mapteleport_issued
) {
2562 SV_MapTeleport(GLevelInfo
->NextMap
, mapteleport_flags
, mapteleport_skill
);
2565 SV_SendClientMessages(); // full
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
;
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
;
2594 //==========================================================================
2598 //==========================================================================
2600 CMD_FORWARD_TO_SERVER();
2601 if (Args
.length() < 2) return;
2604 for (int i
= 1; i
< Args
.length(); ++i
) {
2605 VStr s
= Args
[i
].xstrip();
2607 if (!Text
.isEmpty()) Text
+= " ";
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 */
2617 Text
= VStr("[")+Player
->PlayerName
.RemoveColors().xstrip()+"]:";
2618 for (int i
= 1; i
< Args
.length(); ++i
) {
2620 Text
+= Args
[i
].RemoveColors().xstrip();
2622 GCon
->Logf(NAME_Chat
, "%s", *Text
);
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
);
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!");
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());
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());