799 lines
19 KiB
Plaintext
799 lines
19 KiB
Plaintext
/*
|
|
* Copyright (c) 2016-2022 Vera Visions LLC.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
static int g_ent_spawned;
|
|
|
|
/** Called once every single tic on the server. */
|
|
void
|
|
StartFrame(void)
|
|
{
|
|
PMove_StartFrame();
|
|
|
|
/* For entity parenting to work, we need to go through and run on every
|
|
* this method on every NSEntity class */
|
|
for (entity a = world; (a = findfloat(a, ::identity, 1));) {
|
|
NSEntity ent = (NSEntity)a;
|
|
ent.ParentUpdate();
|
|
}
|
|
|
|
if (g_ents_initialized)
|
|
g_grMode.FrameStart();
|
|
|
|
Vote_Frame();
|
|
}
|
|
|
|
/** Called when the client-slot is being prepared for a player.
|
|
The client may not fully spawn into the world (yet), as they're still
|
|
loading or receiving packets.
|
|
|
|
The `self` global is the connecting client in question.
|
|
*/
|
|
void
|
|
ClientConnect(void)
|
|
{
|
|
int playercount = 0;
|
|
|
|
/* don't carry over team settings from a previous session */
|
|
forceinfokey(self, "*team", "0");
|
|
|
|
/* bot needs special init */
|
|
#ifdef BOT_INCLUDED
|
|
if (clienttype(self) == CLIENTTYPE_BOT) {
|
|
spawnfunc_bot();
|
|
} else
|
|
#endif
|
|
|
|
/* make sure you never change the classname. ever. */
|
|
if (self.classname != "player") {
|
|
spawnfunc_player();
|
|
}
|
|
|
|
if (g_ents_initialized)
|
|
g_grMode.PlayerConnect((NSClientPlayer)self);
|
|
|
|
for (entity a = world; (a = find(a, ::classname, "player"));)
|
|
playercount++;
|
|
|
|
/* Force node init */
|
|
if (playercount == 1) {
|
|
Nodes_Init();
|
|
}
|
|
}
|
|
|
|
/** Called when a player leaves the server. At the end of the function the
|
|
client slot referred to by the `self` global will be cleared.
|
|
This means the fields will still be accessible inside of this function.
|
|
*/
|
|
void
|
|
ClientDisconnect(void)
|
|
{
|
|
if (g_ents_initialized)
|
|
g_grMode.PlayerDisconnect((NSClientPlayer)self);
|
|
|
|
/* this will hide/remove the player from other clients */
|
|
player pl = (player)self;
|
|
pl.Disappear();
|
|
}
|
|
|
|
/** Called by the `kill` console command.
|
|
The `self` global is the client issuing the command.
|
|
*/
|
|
void
|
|
ClientKill(void)
|
|
{
|
|
if (g_ents_initialized)
|
|
g_grMode.PlayerKill((NSClientPlayer)self);
|
|
}
|
|
|
|
/** This is run every frame on every spectator.
|
|
The `self` global refers to one of any given amount of spectator.
|
|
*/
|
|
void
|
|
SpectatorThink(void)
|
|
{
|
|
if (self.classname == "spectator") {
|
|
NSClientSpectator spec = (NSClientSpectator)self;
|
|
spec.PreFrame();
|
|
spec.PostFrame();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** Called when a NSClientSpectator joins the server.
|
|
The `self` global is the connecting NSClientSpectator in question.
|
|
*/
|
|
void
|
|
SpectatorConnect(void)
|
|
{
|
|
spawnfunc_NSClientSpectator();
|
|
}
|
|
|
|
/** Called when a NSClientSpectator leaves the server.
|
|
The `self` global is the leaving NSClientSpectator in question.
|
|
Attributes cleared when this function is done executing.
|
|
*/
|
|
void
|
|
SpectatorDisconnect(void)
|
|
{
|
|
}
|
|
|
|
/** Called when a player enters the game, having fully connected and loaded into
|
|
the session.
|
|
The `self` global is the player in question.
|
|
The 'parmX' globals are also populated with any data carried over from
|
|
past levels for the player in question.
|
|
*/
|
|
void
|
|
PutClientInServer(void)
|
|
{
|
|
if (g_ents_initialized)
|
|
g_grMode.PlayerSpawn((NSClientPlayer)self);
|
|
|
|
Plugin_PlayerEntered((NSClientPlayer)self);
|
|
|
|
/* activate all game_playerspawn entities */
|
|
for (entity a = world; (a = find(a, ::targetname, "game_playerspawn"));) {
|
|
NSEntity t = (NSEntity)a;
|
|
|
|
if (t.Trigger)
|
|
t.Trigger(self, TRIG_TOGGLE);
|
|
}
|
|
|
|
/* the game and its triggers start when the player is ready to see it */
|
|
trigger_auto_trigger();
|
|
}
|
|
|
|
/** Run before game physics have taken place.
|
|
The `self` global refers to a single client, as this function is called
|
|
times the amount of players in a given game.
|
|
*/
|
|
void
|
|
PlayerPreThink(void)
|
|
{
|
|
if (self.classname == "spectator") {
|
|
//NSClientSpectator spec = (NSClientSpectator)self;
|
|
//spec.PreFrame();
|
|
return;
|
|
}
|
|
|
|
if (self.classname != "player") {
|
|
return;
|
|
}
|
|
|
|
#ifdef BOT_INCLUDED
|
|
if (clienttype(self) == CLIENTTYPE_BOT) {
|
|
((bot)self).PreFrame();
|
|
}
|
|
#endif
|
|
|
|
if (g_ents_initialized)
|
|
g_grMode.PlayerPreFrame((NSClientPlayer)self);
|
|
}
|
|
|
|
/** Run after game physics have taken place.
|
|
The `self` global refers to a single client, as this function is called
|
|
times the amount of players in a given game.
|
|
*/
|
|
void
|
|
PlayerPostThink(void)
|
|
{
|
|
if (self.classname == "spectator") {
|
|
SpectatorThink();
|
|
return;
|
|
}
|
|
|
|
if (self.classname != "player") {
|
|
return;
|
|
}
|
|
|
|
#ifdef BOT_INCLUDED
|
|
if (clienttype(self) == CLIENTTYPE_BOT) {
|
|
((bot)self).PostFrame();
|
|
}
|
|
#endif
|
|
|
|
if (g_ents_initialized) {
|
|
player pl = (player)self;
|
|
g_grMode.PlayerPostFrame((NSClientPlayer)self);
|
|
pl.EvaluateEntity();
|
|
}
|
|
}
|
|
|
|
/** Called when we spawn in a new map (both single and multiplayer) with no level
|
|
change ever having taken place.
|
|
The `self` global does not refer to anything.
|
|
*/
|
|
void
|
|
SetNewParms(void)
|
|
{
|
|
print("--------- Setting New Level Parameters ----------\n");
|
|
|
|
if (g_ents_initialized)
|
|
g_grMode.LevelNewParms();
|
|
}
|
|
|
|
/** Called whenever a single-player level change is about to happen, carrying
|
|
over data from one level to the next. This is not called with the 'map' command.
|
|
|
|
The `self` global refers to a client that's partaking in the level-change.
|
|
Make sure we're saving important fields/attributes in the 'parmX' globals
|
|
allocated for every client.
|
|
*/
|
|
void
|
|
SetChangeParms(void)
|
|
{
|
|
print("--------- Setting Level-Change Parameters ----------\n");
|
|
|
|
if (g_ents_initialized)
|
|
g_grMode.LevelChangeParms((NSClientPlayer)self);
|
|
}
|
|
|
|
/** Run whenever an input packet by a client has been received.
|
|
|
|
The `self` global is the entity having sent the input packet,
|
|
with the input_X globals being set to the appropriate data.
|
|
*/
|
|
void
|
|
SV_RunClientCommand(void)
|
|
{
|
|
NSClient cl = (NSClient)self;
|
|
|
|
if (self.classname != "player" && self.classname != "spectator")
|
|
return;
|
|
|
|
if (!Plugin_RunClientCommand()) {
|
|
/* TODO */
|
|
}
|
|
|
|
cl.SharedInputFrame();
|
|
cl.ServerInputFrame();
|
|
}
|
|
|
|
/** Any 'cmd' from the client get sent here and handled.
|
|
Unlike ConsoleCommmand() if you want to let the server engine
|
|
take over, you need to pass the string 'cmd' over via clientcommand().
|
|
|
|
Notable examples of client cmd's involve the chat system.
|
|
*/
|
|
void
|
|
SV_ParseClientCommand(string cmd)
|
|
{
|
|
string newcmd = Plugin_ParseClientCommand(cmd);
|
|
int argc;
|
|
|
|
/* give the game-mode a chance to override us */
|
|
if (g_ents_initialized)
|
|
if (g_grMode.ClientCommand((NSClient)self, cmd) == true)
|
|
return;
|
|
|
|
argc = tokenize(cmd);
|
|
|
|
switch (argv(0)) {
|
|
case "say":
|
|
if (argc == 2)
|
|
g_grMode.ChatMessageAll((NSClient)self, argv(1));
|
|
else
|
|
g_grMode.ChatMessageAll((NSClient)self, substring(cmd, 5, -2));
|
|
break;
|
|
case "say_team":
|
|
if (argc == 2)
|
|
g_grMode.ChatMessageTeam((NSClient)self, argv(1));
|
|
else
|
|
g_grMode.ChatMessageTeam((NSClient)self, substring(cmd, 10, -2));
|
|
break;
|
|
case "spectate":
|
|
if (self.classname != "player")
|
|
break;
|
|
ClientKill();
|
|
spawnfunc_NSClientSpectator();
|
|
break;
|
|
case "play":
|
|
if (self.classname != "spectator")
|
|
break;
|
|
spawnfunc_player();
|
|
PutClientInServer();
|
|
break;
|
|
case "setpos":
|
|
if (cvar("sv_cheats") == 1) {
|
|
setorigin(self, stov(argv(1)));
|
|
}
|
|
break;
|
|
case "timeleft":
|
|
string msg;
|
|
string timestring;
|
|
float timeleft;
|
|
timeleft = cvar("timelimit") - (time / 60);
|
|
timestring = Util_TimeToString(timeleft);
|
|
msg = sprintf("we have %s minutes remaining", timestring);
|
|
bprint(PRINT_CHAT, msg);
|
|
break;
|
|
default:
|
|
clientcommand(self, cmd);
|
|
}
|
|
}
|
|
|
|
/** Called when the QC module gets loaded.
|
|
No entities exist yet and we are unable to allocate any in here.
|
|
So avoid calling spawn() related functions here. */
|
|
void
|
|
init(float prevprogs)
|
|
{
|
|
print("--------- Initializing Server Game ----------\n");
|
|
print("Built: " __DATE__ " " __TIME__"\n");
|
|
print("QCC: " __QCCVER__ "\n");
|
|
|
|
Plugin_Init();
|
|
|
|
Sound_Init();
|
|
PropData_Init();
|
|
SurfData_Init();
|
|
DecalGroups_Init();
|
|
}
|
|
|
|
/** Called inside initents() to make sure the entities have their Respawn()
|
|
method called at the beginning of them having spawned.
|
|
*/
|
|
void
|
|
init_respawn(void)
|
|
{
|
|
int endspawn = 0;
|
|
|
|
if (g_ents_initialized)
|
|
g_grMode.InitPostEnts();
|
|
|
|
/* of all the map entities that we wanted to spawn, how many are left? */
|
|
for (entity a = world; (a = findfloat(a, ::_mapspawned, true));) {
|
|
endspawn++;
|
|
}
|
|
|
|
print(sprintf("...%i entities spawned (%i inhibited)\n", g_ent_spawned, g_ent_spawned - endspawn));
|
|
remove(self);
|
|
}
|
|
|
|
entity g_respawntimer;
|
|
.string skyname;
|
|
|
|
/** Called by the engine when we're ready to spawn entities.
|
|
Before this, we are not able to spawn, touch or allocate any entity slots.
|
|
*/
|
|
void
|
|
initents(void)
|
|
{
|
|
/* sound shader init */
|
|
Materials_Init();
|
|
MOTD_Init();
|
|
PMove_Init();
|
|
|
|
/* TODO: turn these effects into sound shaders */
|
|
precache_sound("weapons/explode3.wav");
|
|
precache_sound("weapons/explode4.wav");
|
|
precache_sound("weapons/explode5.wav");
|
|
precache_sound("debris/glass1.wav");
|
|
precache_sound("debris/glass2.wav");
|
|
precache_sound("debris/glass3.wav");
|
|
precache_sound("debris/wood1.wav");
|
|
precache_sound("debris/wood2.wav");
|
|
precache_sound("debris/wood3.wav");
|
|
precache_sound("debris/metal1.wav");
|
|
precache_sound("debris/metal2.wav");
|
|
precache_sound("debris/metal3.wav");
|
|
precache_sound("debris/flesh1.wav");
|
|
precache_sound("debris/flesh2.wav");
|
|
precache_sound("debris/flesh3.wav");
|
|
precache_sound("debris/flesh5.wav");
|
|
precache_sound("debris/flesh6.wav");
|
|
precache_sound("debris/flesh7.wav");
|
|
precache_sound("debris/concrete1.wav");
|
|
precache_sound("debris/concrete2.wav");
|
|
precache_sound("debris/concrete3.wav");
|
|
|
|
/** compat... */
|
|
precache_sound("misc/null.wav");
|
|
precache_sound("common/null.wav");
|
|
|
|
precache_sound("player/pl_fallpain3.wav");
|
|
precache_sound("items/9mmclip1.wav");
|
|
precache_sound("items/gunpickup2.wav");
|
|
precache_sound("common/wpn_select.wav");
|
|
precache_sound("common/wpn_denyselect.wav");
|
|
precache_sound("player/sprayer.wav");
|
|
precache_sound("items/flashlight1.wav");
|
|
|
|
Sound_Precache("player.gasplight");
|
|
Sound_Precache("player.gaspheavy");
|
|
Sound_Precache("player.waterenter");
|
|
Sound_Precache("player.waterexit");
|
|
Sound_Precache("damage_bullet.hit");
|
|
Sound_Precache("player.spraylogo");
|
|
Sound_Precache("step_wade.left");
|
|
Sound_Precache("step_wade.right");
|
|
Sound_Precache("step_ladder.left");
|
|
Sound_Precache("step_ladder.right");
|
|
Sound_Precache("step_swim.left");
|
|
Sound_Precache("step_swim.right");
|
|
|
|
if (!g_grMode)
|
|
Game_InitRules();
|
|
|
|
Game_Worldspawn();
|
|
Decals_Init();
|
|
Sentences_Init();
|
|
|
|
/* TODO: Make sure every entity calls Respawn inside the constructor, then
|
|
* remove this */
|
|
g_respawntimer = spawn();
|
|
g_respawntimer.think = init_respawn;
|
|
g_respawntimer.nextthink = time + 0.1f;
|
|
|
|
/* menu background lock */
|
|
if (cvar("sv_background") == 1) {
|
|
forceinfokey(world, "background", "1");
|
|
localcmd("sv_background 0\n");
|
|
} else {
|
|
forceinfokey(world, "background", "0");
|
|
}
|
|
|
|
/* the maxclients serverinfo key? yeah, that one lies to the client. so
|
|
* let's add our own that we can actually trust. */
|
|
forceinfokey(world, "sv_playerslots", cvar_string("sv_playerslots"));
|
|
|
|
Plugin_InitEnts();
|
|
Mapcycle_Init();
|
|
Vote_Init();
|
|
ChangeTarget_Activate();
|
|
|
|
g_ents_initialized = TRUE;
|
|
|
|
/* engine hacks for dedicated servers */
|
|
cvar_set("s_nominaldistance", "1000");
|
|
}
|
|
|
|
var int autocvar_sv_levelexec = 1;
|
|
|
|
/** The first entity spawn function. You want to make sure to put anything in here
|
|
that'll affect subsequent initialization of map entities.
|
|
|
|
Keep in mind that any find() or similar function will not find any entity but 'world',
|
|
as they do not exist yet.
|
|
*/
|
|
void
|
|
worldspawn(void)
|
|
{
|
|
print("--------- Map Initialization ---------
|
|
\n");
|
|
print(sprintf("Map: %s
|
|
\n", mapname));
|
|
print("----------- Game Map Init ------------
|
|
\n");
|
|
|
|
lightstyle(0, "m");
|
|
lightstyle(1, "mmnmmommommnonmmonqnmmo");
|
|
lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
|
|
lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
|
|
lightstyle(4, "mamamamamama");
|
|
lightstyle(5, "jklmnopqrstuvwxyzyxwvutsrqponmlkj");
|
|
lightstyle(6, "nmonqnmomnmomomno");
|
|
lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm");
|
|
lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
|
|
lightstyle(9, "aaaaaaaazzzzzzzz");
|
|
lightstyle(10, "mmamammmmammamamaaamammma");
|
|
lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
|
|
lightstyle(12, "mmnnmmnnnmmnn");
|
|
lightstyle(63, "a");
|
|
Skill_Init();
|
|
|
|
EntityDef_Init();
|
|
MapTweaks_Init();
|
|
|
|
precache_model("models/error.vvm");
|
|
|
|
if (autocvar_sv_levelexec)
|
|
readcmd(sprintf("exec maps/%s.cfg\n", mapname));
|
|
|
|
/* we need to flush this, so that any leftover serverinfo
|
|
* in the server-config gets overwritten */
|
|
forceinfokey(world, "skyname", "");
|
|
|
|
/* Set the default sky */
|
|
if (serverkeyfloat("*bspversion") == BSPVER_HL) {
|
|
if (!self.skyname) {
|
|
self.skyname = "desert";
|
|
}
|
|
forceinfokey(world, "skyname", self.skyname);
|
|
}
|
|
|
|
print("Spawning entities\n");
|
|
}
|
|
|
|
/** Any command executed on the server (either tty, rcon or `sv`) gets
|
|
sent here first.
|
|
|
|
When returning FALSE the server will interpret the command.
|
|
Returning TRUE will mark the command as 'resolved' and the engine
|
|
will not attempt handling it.
|
|
|
|
The client-side equivalent is `CSQC_ConsoleCommand` (src/client/entry.qc)
|
|
*/
|
|
float
|
|
ConsoleCmd(string cmd)
|
|
{
|
|
player pl;
|
|
|
|
/* some sv commands can only be executed by a player in-world */
|
|
if ( !self ) {
|
|
for ( other = world; ( other = find( other, classname, "player" ) ); ) {
|
|
if ( clienttype( other ) == CLIENTTYPE_REAL ) {
|
|
self = other;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!self) {
|
|
for ( other = world; ( other = find( other, classname, "spectator" ) ); ) {
|
|
if ( clienttype( other ) == CLIENTTYPE_REAL ) {
|
|
self = other;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pl = (player)self;
|
|
|
|
/* give the game-mode a chance to override us */
|
|
if (g_ents_initialized)
|
|
if (g_grMode.ConsoleCommand(pl, cmd) == TRUE)
|
|
return (1);
|
|
|
|
/* time to handle commands that apply to all games */
|
|
tokenize(cmd);
|
|
switch (argv(0)) {
|
|
case "trigger_ent":
|
|
string targ;
|
|
targ = argv(1);
|
|
|
|
if (targ)
|
|
for (entity a = world; (a = find(a, ::targetname, argv(1)));) {
|
|
NSEntity t = (NSEntity)a;
|
|
|
|
if (t.Trigger)
|
|
t.Trigger(self, TRIG_TOGGLE);
|
|
}
|
|
break;
|
|
case "goto_ent":
|
|
static entity finder;
|
|
finder = find(finder, ::classname, argv(1));
|
|
|
|
if (finder)
|
|
setorigin(pl, finder.origin);
|
|
break;
|
|
case "respawn_ents":
|
|
for (entity a = world; (a = findfloat(a, ::identity, 1));) {
|
|
NSEntity ent = (NSEntity)a;
|
|
ent.Respawn();
|
|
}
|
|
break;
|
|
case "spawn":
|
|
entity unit = spawn();
|
|
unit.classname = strcat("spawnfunc_", argv(1));
|
|
self = unit;
|
|
callfunction(self.classname);
|
|
self = pl;
|
|
|
|
makevectors(pl.v_angle);
|
|
if (unit.identity == 1) {
|
|
NSEntity ent = (NSEntity)unit;
|
|
}
|
|
traceline(pl.origin, pl.origin + (v_forward * 1024), MOVE_NORMAL, pl);
|
|
setorigin(unit, trace_endpos);
|
|
break;
|
|
#ifdef BOT_INCLUDED
|
|
case "way":
|
|
Way_Cmd();
|
|
break;
|
|
#endif
|
|
default:
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/** Returns TRUE if the server can pause the server when the 'pause' command
|
|
is being executed.
|
|
*/
|
|
float
|
|
SV_ShouldPause(float newstatus)
|
|
{
|
|
if (serverkeyfloat("background") == 1)
|
|
return (0);
|
|
|
|
if (cvar("pausable") == 1)
|
|
return (1);
|
|
|
|
if (cvar("sv_playerslots") > 1)
|
|
return (0);
|
|
|
|
return newstatus;
|
|
}
|
|
|
|
//#define REEDICT 1
|
|
|
|
/** Called by the engine when we're loading a savegame file.
|
|
|
|
This deals with the de and re-allocation of all map entities from
|
|
the passed file handle.
|
|
*/
|
|
void
|
|
SV_PerformLoad(float fh, float entcount, float playerslots)
|
|
{
|
|
entity e = world;
|
|
entity eold;
|
|
string l;
|
|
float n = 0;
|
|
NSEntity loadent = __NULL__;
|
|
int num_loaded = 0i;
|
|
|
|
print("--------- Loading Existing Save ----------\n");
|
|
g_isloading = true;
|
|
|
|
#if 0
|
|
/* mark anything else for deletion */
|
|
while ((e=nextent(e))) {
|
|
if (edict_num(1) != e) {
|
|
if (e.identity != 0) {
|
|
e.think = Util_Destroy;
|
|
e.nextthink = time + 0.05f;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef REEDICT
|
|
while ((e=nextent(e))) {
|
|
if (edict_num(1) != e)
|
|
remove(e);
|
|
}
|
|
#else
|
|
e = world;
|
|
#endif
|
|
|
|
/* read line per line of our file handle */
|
|
while ((l=fgets(fh))) {
|
|
float args = tokenize_console(l);
|
|
|
|
if (!args)
|
|
break;
|
|
|
|
if (argv(0) == "ENTITY") {
|
|
string cname;
|
|
cname = argv(2);
|
|
#ifndef REEDICT
|
|
n = stof(argv(1));
|
|
e = edict_num(n);
|
|
print(sprintf("Creating %s (edict %d)\n", cname, n));
|
|
#else
|
|
entity e = spawn();
|
|
#endif
|
|
loadent = __NULL__;
|
|
|
|
__fullspawndata = fgets(fh);
|
|
|
|
/* call the constructor if one is present, init the default fields */
|
|
if (isfunction(strcat("spawnfunc_", cname))) {
|
|
e.classname = cname;
|
|
|
|
eold = self;
|
|
self = e;
|
|
callfunction(strcat("spawnfunc_", cname));
|
|
e.classname = cname;
|
|
loadent = (NSEntity)e;
|
|
self = eold;
|
|
} else {
|
|
print(sprintf("Could not spawn %s\n", cname));
|
|
remove(e);
|
|
continue;
|
|
}
|
|
} else if (argv(0) == "{") {
|
|
} else if (argv(0) == "}") {
|
|
if (loadent) {
|
|
loadent.RestoreComplete();
|
|
num_loaded++;
|
|
print(sprintf("completed %s (edict %d)\n\n", loadent.classname, n));
|
|
loadent = __NULL__;
|
|
}
|
|
} else {
|
|
if (loadent) {
|
|
int c = tokenize(l);
|
|
|
|
if (c == 2) {
|
|
loadent.Restore(argv(0), argv(1));
|
|
//print(sprintf("%s %s\n", argv(0), argv(1)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
print(sprintf("loaded %i entities\n", num_loaded));
|
|
g_isloading = false;
|
|
}
|
|
|
|
/** Called when we are saving our game. We only get passed a file handle
|
|
to work with and dump entity data as well as some global info directly
|
|
into it.
|
|
*/
|
|
void
|
|
SV_PerformSave(float fh, float entcount, float playerslots)
|
|
{
|
|
float i = 0;
|
|
int num_saved = 0i;
|
|
entity e;
|
|
|
|
print("--------- Performing Save ----------\n");
|
|
|
|
for (i = 0; i < entcount; i++) {
|
|
NSEntity willsave;
|
|
e = edict_num(i);
|
|
|
|
if (e==world && i)
|
|
continue;
|
|
|
|
if (wasfreed(e))
|
|
continue;
|
|
|
|
if (e.identity == 0)
|
|
continue;
|
|
|
|
willsave = (NSEntity)e;
|
|
fputs(fh, sprintf("ENTITY \"%d\" %S\n", i, willsave.classname));
|
|
fputs(fh, "{\n");
|
|
willsave.Save(fh);
|
|
fputs(fh, "}\n");
|
|
num_saved++;
|
|
}
|
|
fclose(fh);
|
|
print(sprintf("saved %i entities\n", num_saved));
|
|
}
|
|
|
|
/** Called by the engine to check with us if a spawn function exists.
|
|
|
|
The `self` global refers to an already allocated entity, which we have
|
|
to remove in case we won't initialize it.
|
|
*/
|
|
void
|
|
CheckSpawn(void() spawnfunc)
|
|
{
|
|
if (EntityDef_SpawnClassname(self.classname))
|
|
return;
|
|
if (MapTweak_EntitySpawn(self))
|
|
return;
|
|
|
|
if (spawnfunc) {
|
|
spawnfunc();
|
|
self._mapspawned = true;
|
|
g_ent_spawned++;
|
|
} else {
|
|
print(sprintf("^1Cannot find entity class ^7%s\n", self.classname));
|
|
remove(self);
|
|
}
|
|
|
|
/* check if this entity was meant to spawn on the client-side only */
|
|
if (self.identity) {
|
|
NSEntity ent = (NSEntity)self;
|
|
|
|
if (ent.CanSpawn(false) == false) {
|
|
ent.Destroy();
|
|
g_ent_spawned--;
|
|
}
|
|
}
|
|
}
|