djcev.com

//

Git Repos / fte_dogmode / qc / sv_entry.qc

Last update to this file was on 2024-03-24 at 02:40.

Show sv_entry.qc

//==============================================================================
// server-side entrypoints; inspired by Nuclide's server/entry.qc -- CEV
//==============================================================================

//======================================================================
// globals
//======================================================================

#ifdef SSQC
float gameover; // set when a rule exits
float intermission;
float intermission_exittime;
entity used_exit;
string nextmap;

float skill; // latched, reset on StartFrame
float framecount;

nosave float clean_up_client_stuff;
nosave float gamestarted;
#endif

//======================================================================
// forward declarations
//======================================================================

#ifdef SSQC
// helper functions
void() NextLevel;
void() CheckRules;
void(string message) ShowIntermissionMessage;
void() StartMessageIntermission;
void() Episode1End;
void() Episode2End;
void() Episode3End;
void() Episode4End;
float() RunId1Intermissions;
entity() FindIntermission;
void() GotoNextMap;
float(float start) ShowIntermissionTextEntity;
void() ExitIntermission;
void() IntermissionThink;
void() RestartLoopSounds_think;
void() RestartLoopSounds;

// proper entrypoints
void() main;
void() ClientConnect;
void() ClientDisconnect;
void() ClientKill;
void() SetNewParms;
void() SetChangeParms;
void() PutClientInServer;
void() PlayerPreThink;
void() SV_RunClientCommand;
void() PlayerPostThink;
void() StartFrame;
// void() EndFrame;
// void(void() spawnfunc) CheckSpawn;
#endif

//------------------------------------------------------------------------------

//======================================================================
// Helper functions - map & game rules
//======================================================================

#ifdef SSQC
//----------------------------------------------------------------------
// NextLevel -- go to the next level for deathmatch
// only called if a time or frag limit has expired
//----------------------------------------------------------------------
void() NextLevel =
{
local entity t_level;

if (mapname == "start")
{
if (!cvar ("registered"))
{
mapname = "e1m1";
}
else if (!(serverflags & 1))
{
mapname = "e1m1";
serverflags = serverflags | 1;
}
else if (!(serverflags & 2))
{
mapname = "e2m1";
serverflags = serverflags | 2;
}
else if (!(serverflags & 4))
{
mapname = "e3m1";
serverflags = serverflags | 4;
}
else if (!(serverflags & 8))
{
mapname = "e4m1";
serverflags = serverflags - 7;
}

t_level = spawn_trigger_changelevel (self,
self.origin, mapname);
}
else
{
// find a trigger changelevel
t_level = findfloat (world, classtype, CT_TRIGGER_CHANGELEVEL);

// go back to start if no trigger_changelevel
if (!t_level)
t_level = spawn_trigger_changelevel (self,
self.origin, "start");
}

nextmap = t_level.map;
gameover = TRUE;

if (t_level.nextthink < time)
t_level.nextthink = time + 0.1;
};

//----------------------------------------------------------------------
// CheckRules -- Exit deathmatch games upon conditions; 'self' is client
//----------------------------------------------------------------------
void() CheckRules =
{
// someone else quit the game already
if (gameover)
return;

local float timelimit = cvar ("timelimit") * 60;
local float fraglimit = cvar ("fraglimit");

// 1998-07-27 Timelimit/Fraglimit fix by Maddes
if (deathmatch && timelimit && time >= timelimit)
{
NextLevel ();
return;
}

// 1998-07-27 Timelimit/Fraglimit fix by Maddes
if (deathmatch && fraglimit && self.frags >= fraglimit)
{
NextLevel ();
return;
}
};
#endif

//======================================================================
// Helper functions - intermission handling
//======================================================================

#ifdef SSQC
//----------------------------------------------------------------------
void(string message) ShowIntermissionMessage =
{
WriteByte (MSG_ALL, SVC_FINALE);
WriteString (MSG_ALL, message);
};

//----------------------------------------------------------------------
void() StartMessageIntermission =
{
WriteByte (MSG_ALL, SVC_CDTRACK);
WriteByte (MSG_ALL, 2);
WriteByte (MSG_ALL, 3);
};

//----------------------------------------------------------------------
void() Episode1End =
{
StartMessageIntermission ();

if (!cvar("registered"))
{
ShowIntermissionMessage ("As the corpse of the monstrous "
"entity\nChthon sinks back into the lava whence\n"
"it rose, you grip the Rune of Earth\n"
"Magic tightly. Now that you have\n"
"conquered the Dimension of the Doomed,\n"
"realm of Earth Magic, you are ready to\n"
"complete your task in the other three\n"
"haunted lands of Quake. Or are you? If\n"
"you don't register Quake, you'll never\n"
"know what awaits you in the Realm of\n"
"Black Magic, the Netherworld, and the\nElder World!");
}
else
{
ShowIntermissionMessage ("As the corpse of the monstrous"
"entity\nChthon sinks back into the lava whence\n"
"it rose, you grip the Rune of Earth\n"
"Magic tightly. Now that you have\n"
"conquered the Dimension of the Doomed,\n"
"realm of Earth Magic, you are ready to\n"
"complete your task. A Rune of magic\n"
"power lies at the end of each haunted\n"
"land of Quake. Go forth, seek the\n"
"totality of the four Runes!");
}
};

//----------------------------------------------------------------------
void() Episode2End =
{
StartMessageIntermission ();

ShowIntermissionMessage ("The Rune of Black Magic throbs evilly in\n"
"your hand and whispers dark thoughts\n"
"into your brain. You learn the inmost\n"
"lore of the Hell-Mother; Shub-Niggurath!\n"
"You now know that she is behind all the\n"
"terrible plotting which has led to so\n"
"much death and horror. But she is not\n"
"inviolate! Armed with this Rune, you\n"
"realize that once all four Runes are\n"
"combined, the gate to Shub-Niggurath's\n"
"Pit will open, and you can face the\n"
"Witch-Goddess herself in her frightful\n"
"otherworld cathedral.");
};

//----------------------------------------------------------------------
void() Episode3End =
{
StartMessageIntermission ();

ShowIntermissionMessage ("The charred viscera of diabolic horrors\n"
"bubble viscously as you seize the Rune\n"
"of Hell Magic. Its heat scorches your\n"
"hand, and its terrible secrets blight\n"
"your mind. Gathering the shreds of your\n"
"courage, you shake the devil's shackles\n"
"from your soul, and become ever more\n"
"hard and determined to destroy the\n"
"hideous creatures whose mere existence\n"
"threatens the souls and psyches of all\n"
"the population of Earth.");

};

//----------------------------------------------------------------------
void() Episode4End =
{
StartMessageIntermission ();

ShowIntermissionMessage ("Despite the awful might of the Elder\n"
"World, you have achieved the Rune of\n"
"Elder Magic, capstone of all types of\n"
"arcane wisdom. Beyond good and evil,\n"
"beyond life and death, the Rune\n"
"pulsates, heavy with import. Patient and\n"
"potent, the Elder Being Shub-Niggurath\n"
"weaves her dire plans to clear off all\n"
"life from the Earth, and bring her own\n"
"foul offspring to our world! For all the\n"
"dwellers in these nightmare dimensions\n"
"are her descendants! Once all Runes of\n"
"magic power are united, the energy\n"
"behind them will blast open the Gateway\n"
"to Shub-Niggurath, and you can travel\n"
"there to foil the Hell-Mother's plots\nin person.");
};

//----------------------------------------------------------------------
float() RunId1Intermissions =
{
if (world.skip_id1_overrides == 0)
{
if (world.model == "maps/e1m7.bsp")
{
Episode1End ();
return 1;
}
else if (world.model == "maps/e2m6.bsp")
{
Episode2End ();
return 1;
}
else if (world.model == "maps/e3m6.bsp")
{
Episode3End ();
return 1;
}
else if (world.model == "maps/e4m7.bsp")
{
Episode4End ();
return 1;
}
}
return 0;
};

//----------------------------------------------------------------------
// FindIntermission -- Returns the entity to view from
//----------------------------------------------------------------------
entity() FindIntermission =
{
local entity spot;
local float cyc;

// look for info_intermission first
spot = findfloat (world, classtype, CT_INFO_INTERMISSION);
if (spot)
{ // pick a random one
cyc = random() * 4;
while (cyc > 1)
{
spot = findfloat (spot, classtype,
CT_INFO_INTERMISSION);
if (!spot)
spot = findfloat (spot, classtype,
CT_INFO_INTERMISSION);
cyc = cyc - 1;
}
return spot;
}

// then look for the start position
spot = findfloat (world, classtype, CT_INFO_PLAYER_START);
if (spot)
return spot;

// testinfo_player_start is only found in regioned levels
spot = findfloat (world, classtype, CT_INFO_TESTPLAYERSTART);
if (spot)
return spot;

objerror ("FindIntermission: no spot");

// just to suppress the compiler warning
return world;
};

//----------------------------------------------------------------------
void() GotoNextMap =
{
if (cvar("samelevel"))
// if samelevel is set, stay on same level
changelevel (mapname);
else
changelevel (nextmap);
};

//----------------------------------------------------------------------
float(float start) ShowIntermissionTextEntity =
{
if (!(used_exit.spawnflags & 2))
{
local entity end_message;
for (end_message = world; (end_message = findfloat
(end_message, classtype, CT_INFO_INTERMISSIONTEXT)); )
{
if (end_message.cnt == (intermission - 1))
{
if (start == 1)
StartMessageIntermission ();
ShowIntermissionMessage (end_message.message);
return 1;
}
}
}
return 0;
};

//----------------------------------------------------------------------
void() ExitIntermission =
{
// skip any text in deathmatch
if (deathmatch)
{
GotoNextMap ();
return;
}

intermission_exittime = time + 1;
intermission = intermission + 1;

// run some text if at the end of an episode
if (intermission == 2)
{
if (RunId1Intermissions() == 1)
{
return;
}
else
{
if (used_exit.message != "")
{
// favor message on changelevel
StartMessageIntermission ();
ShowIntermissionMessage (used_exit.message);
return;
}
if (ShowIntermissionTextEntity(1))
{
return;
}
}

GotoNextMap ();
}

if (intermission == 3)
{
if (!cvar("registered"))
{
// shareware episode has been completed,
// go to sell screen
WriteByte (MSG_ALL, SVC_SELLSCREEN);
return;
}
if (ShowIntermissionTextEntity(0))
{
return;
}
if ((serverflags & 15) == 15)
{
ShowIntermissionMessage ("Now, you have all four Runes."
" You sense\ntremendous invisible forces moving"
" to\nunseal ancient barriers."
" Shub-Niggurath\nhad hoped to use the Runes"
" Herself to\nclear off the Earth, but now"
" instead,\nyou will use them to enter her"
" home and\nconfront her as an avatar of"
" avenging\nEarth-life. If you defeat her,"
" you will\nbe remembered forever as the"
" savior of\nthe planet. If she conquers,"
" it will be\nas if you had never been born.");
return;
}
}

if (intermission > 3)
{
if (ShowIntermissionTextEntity(0))
{
return;
}
}

GotoNextMap ();
};

//----------------------------------------------------------------------
// IntermissionThink
// When the player presses attack or jump, change to the next level
// 'self' in this context is the client/player
//----------------------------------------------------------------------
void() IntermissionThink =
{
// full send -- CEV
// self.fixangle = TRUE;
self.velocity = '0 0 0';
self.SendFlags = 0xffffff;

if (time < intermission_exittime)
return;

if (!self.button0 && !self.button1 && !self.button2)
return;

ExitIntermission ();
};

//----------------------------------------------------------------------
void() RestartLoopSounds_think =
{
sound (self, self.impulse, self.noise, self.volume, self.speed);
};

//----------------------------------------------------------------------
void() RestartLoopSounds =
{
entity e;
e = find (world, classname, "play_sound_triggered");
while (e) {
if (e.spawnflags & 3 == 3) {
// both "toggle" and "looped" need to be set
if (e.state == 1) {
e.nextthink = time + 0.1;
e.think = RestartLoopSounds_think;
}
}
e = find (e, classname, "play_sound_triggered");
}
};
#endif

//======================================================================
// REAL entrypoints (the following functions are called by the engine)
//======================================================================

#ifdef SSQC
//----------------------------------------------------------------------
void() main =
{
dprint ("main function\n");

// these are just commands the the prog compiler to copy these files
precache_file ("progs.dat");
precache_file ("gfx.wad");
precache_file ("quake.rc");
precache_file ("default.cfg");

precache_file ("end1.bin");
precache_file2 ("end2.bin");

precache_file ("demo1.dem");
precache_file ("demo2.dem");
precache_file ("demo3.dem");

// these are all of the lumps from the cached.ls files
precache_file ("gfx/palette.lmp");
precache_file ("gfx/colormap.lmp");

precache_file2 ("gfx/pop.lmp");

precache_file ("gfx/complete.lmp");
precache_file ("gfx/inter.lmp");

precache_file ("gfx/ranking.lmp");
precache_file ("gfx/vidmodes.lmp");
precache_file ("gfx/finale.lmp");
precache_file ("gfx/conback.lmp");
precache_file ("gfx/qplaque.lmp");

precache_file ("gfx/menudot1.lmp");
precache_file ("gfx/menudot2.lmp");
precache_file ("gfx/menudot3.lmp");
precache_file ("gfx/menudot4.lmp");
precache_file ("gfx/menudot5.lmp");
precache_file ("gfx/menudot6.lmp");

precache_file ("gfx/menuplyr.lmp");
precache_file ("gfx/bigbox.lmp");
precache_file ("gfx/dim_modm.lmp");
precache_file ("gfx/dim_drct.lmp");
precache_file ("gfx/dim_ipx.lmp");
precache_file ("gfx/dim_tcp.lmp");
precache_file ("gfx/dim_mult.lmp");
precache_file ("gfx/mainmenu.lmp");

precache_file ("gfx/box_tl.lmp");
precache_file ("gfx/box_tm.lmp");
precache_file ("gfx/box_tr.lmp");

precache_file ("gfx/box_ml.lmp");
precache_file ("gfx/box_mm.lmp");
precache_file ("gfx/box_mm2.lmp");
precache_file ("gfx/box_mr.lmp");

precache_file ("gfx/box_bl.lmp");
precache_file ("gfx/box_bm.lmp");
precache_file ("gfx/box_br.lmp");

precache_file ("gfx/sp_menu.lmp");
precache_file ("gfx/ttl_sgl.lmp");
precache_file ("gfx/ttl_main.lmp");
precache_file ("gfx/ttl_cstm.lmp");

precache_file ("gfx/mp_menu.lmp");

precache_file ("gfx/netmen1.lmp");
precache_file ("gfx/netmen2.lmp");
precache_file ("gfx/netmen3.lmp");
precache_file ("gfx/netmen4.lmp");
precache_file ("gfx/netmen5.lmp");

precache_file ("gfx/sell.lmp");

precache_file ("gfx/help0.lmp");
precache_file ("gfx/help1.lmp");
precache_file ("gfx/help2.lmp");
precache_file ("gfx/help3.lmp");
precache_file ("gfx/help4.lmp");
precache_file ("gfx/help5.lmp");

precache_file ("gfx/pause.lmp");
precache_file ("gfx/loading.lmp");

precache_file ("gfx/p_option.lmp");
precache_file ("gfx/p_load.lmp");
precache_file ("gfx/p_save.lmp");
precache_file ("gfx/p_multi.lmp");

// sounds loaded by C code
precache_sound ("misc/menu1.wav");
precache_sound ("misc/menu2.wav");
precache_sound ("misc/menu3.wav");

precache_sound ("ambience/water1.wav");
precache_sound ("ambience/wind2.wav");

// shareware
precache_file ("maps/start.bsp");

precache_file ("maps/e1m1.bsp");
precache_file ("maps/e1m2.bsp");
precache_file ("maps/e1m3.bsp");
precache_file ("maps/e1m4.bsp");
precache_file ("maps/e1m5.bsp");
precache_file ("maps/e1m6.bsp");
precache_file ("maps/e1m7.bsp");
precache_file ("maps/e1m8.bsp");

// registered
precache_file2 ("gfx/pop.lmp");

precache_file2 ("maps/e2m1.bsp");
precache_file2 ("maps/e2m2.bsp");
precache_file2 ("maps/e2m3.bsp");
precache_file2 ("maps/e2m4.bsp");
precache_file2 ("maps/e2m5.bsp");
precache_file2 ("maps/e2m6.bsp");
precache_file2 ("maps/e2m7.bsp");

precache_file2 ("maps/e3m1.bsp");
precache_file2 ("maps/e3m2.bsp");
precache_file2 ("maps/e3m3.bsp");
precache_file2 ("maps/e3m4.bsp");
precache_file2 ("maps/e3m5.bsp");
precache_file2 ("maps/e3m6.bsp");
precache_file2 ("maps/e3m7.bsp");

precache_file2 ("maps/e4m1.bsp");
precache_file2 ("maps/e4m2.bsp");
precache_file2 ("maps/e4m3.bsp");
precache_file2 ("maps/e4m4.bsp");
precache_file2 ("maps/e4m5.bsp");
precache_file2 ("maps/e4m6.bsp");
precache_file2 ("maps/e4m7.bsp");
precache_file2 ("maps/e4m8.bsp");

precache_file2 ("maps/end.bsp");

precache_file2 ("maps/dm1.bsp");
precache_file2 ("maps/dm2.bsp");
precache_file2 ("maps/dm3.bsp");
precache_file2 ("maps/dm4.bsp");
precache_file2 ("maps/dm5.bsp");
precache_file2 ("maps/dm6.bsp");
};

//----------------------------------------------------------------------
// ClientConnect
// called when a player connects to a server; the client may not have
// fully spawned yet (according to Nuclide's comments). self in this
// context refers to the connecting client. -- CEV
//----------------------------------------------------------------------
void() ClientConnect =
{
// a client connecting during an intermission can cause problems
if (intermission)
ExitIntermission ();

if (self.classname != "player")
// run the player spawn function, initializing 'self'
// as a player object -- CEV
player_init (self);

bprint (self.netname);
bprint (" entered the game\n");
};

//----------------------------------------------------------------------
// ClientDisconnect
// called when a player disconnects from a server; the client will be
// removed after this function, so 'self' in this context is the player
// and any fields of self can be accessed. -- CEV
//----------------------------------------------------------------------
void() ClientDisconnect =
{
// if the level end trigger has been activated, just return
// since they aren't *really* leaving
if (gameover)
return;

// let everyone else know
bprint (self.netname);
bprint (" left the game with ");
bprint (ftos(self.frags));
bprint (" frags\n");
sound (self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE);

if (self.classtype == CT_PLAYER)
player_set_suicide_frame ();
else
dprint ("ClientDisconnect: ERROR! Encountered a non-player!\n");
};

//----------------------------------------------------------------------
// ClientKill -- Player entered the suicide command; 'self' is the client
//----------------------------------------------------------------------
void() ClientKill =
{
// 1998-07-27 Suicide during intermission fix by Zhenga start
// not allowed during intermission
if ((intermission) && ((coop) || (deathmatch)))
return;
// 1998-07-27 Suicide during intermission fix by Zhenga end

bprint (self.netname);
bprint (" suicides\n");

if (self.classtype == CT_PLAYER)
{
player_set_suicide_frame ();
self.modelindex = modelindex_player;
// extra penalty
self.frags = self.frags - 2;
player_respawn ();
}
else
{
dprint ("ClientKill: ERROR! suicide from a non-player!\n");
}
};

//----------------------------------------------------------------------
void() SetNewParms =
{
parm2 = 100; // self.health
parm3 = 0; // self.armorvalue
parm5 = 0; // self.ammo_nails
parm6 = 0; // self.ammo_rockets
parm7 = 0; // self.ammo_cells
parm9 = 0; // self.armortype * 100

// "reset_items 2" makes the player start with only the axe -- iw
if (world.reset_items == 2)
{
parm1 = IT_AXE; // self.items
parm4 = 0; // self.ammo_shells
parm8 = IT_AXE; // self.weapon
}
else
{
parm1 = IT_AXE | IT_SHOTGUN; // self.items
parm4 = 25; // self.ammo_shells
parm8 = IT_SHOTGUN; // self.weapon
}
};

//----------------------------------------------------------------------
void() SetChangeParms =
{
if (self.health <= 0)
{
SetNewParms ();
return;
}

// remove items
self.items = self.items - (self.items &
(IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY
| IT_SUIT | IT_QUAD)
);

// cap super health
if (self.health > 100)
self.health = 100;
if (self.health < 50)
self.health = 50;
parm1 = self.items;
parm2 = self.health;
parm3 = self.armorvalue;
if (self.ammo_shells < 25)
parm4 = 25;
else
parm4 = self.ammo_shells;
parm5 = self.ammo_nails;
parm6 = self.ammo_rockets;
parm7 = self.ammo_cells;
parm8 = self.weapon;
parm9 = self.armortype * 100;
};

//----------------------------------------------------------------------
// PutClientInServer -- called each time a player is spawned
// according to Nuclide: player has fully connected and loaded in.
// 'self' is the player/client, parmX are populated with data carried
// over from previous levels for this specific client. -- CEV
//----------------------------------------------------------------------
void() PutClientInServer =
{
if (self.classname == "player")
dprint ("PutClientInServer: classname already player!\n");
};

//----------------------------------------------------------------------
// PlayerPreThink -- Called every frame before physics are run
//----------------------------------------------------------------------
void() PlayerPreThink =
{
if (intermission)
{
// otherwise a button could be missed between the think tics
IntermissionThink ();
return;
}

if (self.view_ofs == '0 0 0')
{
// Check for cutscene stuff.
// TODO CEV Cutscene_Think ();
// intermission or finale
return;
}

// Check map / gametype rules, change to next level if requested -- CEV
CheckRules ();

// call prethink on the player after double checking classtype -- CEV
if (self.classtype == CT_PLAYER)
player_prethink ();
};

//----------------------------------------------------------------------
// SV_RunClientCommand -- Called every frame to run physics
//----------------------------------------------------------------------
void() SV_RunClientCommand =
{
// should match the one used by csqc.
if (world_standardphysics)
// for testing -- CEV
runstandardplayerphysics (self);
else
// for real. See pmove.qc -- CEV
PM_Move (self);
};

//----------------------------------------------------------------------
// PlayerPostThink -- Called every frame after physics are run
//----------------------------------------------------------------------
void() PlayerPostThink =
{
if (intermission)
return;

// call postthink on the player after double checking classtype -- CEV
if (self.classtype == CT_PLAYER)
player_postthink ();
};

//----------------------------------------------------------------------
void() StartFrame =
{
// new spawnflags for all entities -- iw
if (!done_inhibition_summary)
PrintInhibitionSummary ();

// per-frame latched variables -- CEV
teamplay = cvar ("teamplay");
skill = cvar ("skill");

framecount = framecount + 1;

if (clean_up_client_stuff)
{
clean_up_client_stuff -= 1;
RestartLoopSounds ();
}
else if (!gamestarted && framecount > 2)
{
if (framecount != 3)
clean_up_client_stuff += 2;

gamestarted = TRUE;
}
};

/*
// placeholder in case it's necessary -- CEV
void() EndFrame =
{
dprint ("EndFrame: entering...\n");
};
*/

/*
// placeholder in case it's necessary; based on code from Nuclide SDK -- CEV
void(void() spawnfunc) CheckSpawn =
{
if (spawnfunc)
{
// dprint (sprintf("CheckSpawn: creating %s\n",
// self.classname));
spawnfunc ();
}
else
{
// print (sprintf("CheckSpawn: cannot find entity class %s\n",
// self.classname));
remove (self);
}
};
*/
#endif

Return to the top of this page or return to the overview of this repo.

Log sv_entry.qc

Date Commit Message Author + -
2024-03-24 2nd pass refactor, rework QC class structure cev +903  

Return to the top of this page or return to the overview of this repo.