nuclide/src/client/entry.qc

629 lines
14 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.
*/
/*
=================
CSQC_UpdateSeat
Updates our seat pointers, call this when you need to verify we're
getting the current player's info and not someone elses on the same
machine (splitscreen)
=================
*/
void
CSQC_UpdateSeat(void)
{
int s = (float)getproperty(VF_ACTIVESEAT);
pSeat = &g_seats[s];
pSeatLocal = &g_seatslocal[s];
g_view = g_viewSeats[s];
}
/*
=================
CSQC_Init
First thing inited in the progs. Before any world is initialized.
=================
*/
void
CSQC_Init(float apilevel, string enginename, float engineversion)
{
print("--------- Initializing Client Game ----------\n");
for (int i = 0; i < 4; i++) {
g_viewSeats[i] = spawn(NSView);
g_viewSeats[i].SetSeatID(i);
}
pSeat = &g_seats[0];
pSeatLocal = &g_seatslocal[0];
g_view = g_viewSeats[0];
g_numplayerslots = (int)serverkeyfloat("sv_playerslots");
Cmd_Init();
/* Sound shaders */
Sound_Init();
PropData_Init();
precache_sound("common/wpn_hudon.wav");
precache_sound("common/wpn_hudoff.wav");
precache_sound("common/wpn_moveselect.wav");
precache_sound("common/wpn_select.wav");
/* VGUI */
VGUI_Init();
/* Game specific inits */
ClientGame_Init(apilevel, enginename, engineversion);
EFX_Init();
Titles_Init();
Sentences_Init();
Decals_Init();
Way_Init();
Materials_Init();
/* let the menu know we're a multi or a singleplayer game */
if (serverkeyfloat("sv_playerslots") == 1)
cvar_set("_menu_singleplayer", "1");
else
cvar_set("_menu_singleplayer", "0");
/* end msg */
print("Client game initialized.\n");
/* because the engine will do really bad hacks to our models otherwise. e.g. R6284 */
cvar_set("r_fullbrightSkins", "0");
}
/*
=================
CSQC_RendererRestarted
Called by vid_reload callbacks
=================
*/
void
CSQC_RendererRestarted(string rstr)
{
print("--------- Reloading Graphical Resources ----------\n");
/* Fonts */
Font_Load("fonts/font16.font", FONT_16);
Font_Load("fonts/font20.font", FONT_20);
Font_Load("fonts/fontcon.font", FONT_CON);
/* Particles */
PART_DUSTMOTE = particleeffectnum("volume.dustmote");
PART_BURNING = particleeffectnum("burn.burning");
/* 2D Pics */
precache_pic("gfx/vgui/icntlk_sv");
precache_pic("gfx/vgui/icntlk_pl");
/* View */
Chat_Init();
#ifndef NEW_INVENTORY
Weapons_Init();
#endif
Scores_Init();
View_Init();
ClientGame_RendererRestart(rstr);
HUD_Init();
/* GS-Entbase */
Fade_Init();
Decal_Reload();
Sky_Update(TRUE);
Entities_RendererRestarted();
DetailTex_Init();
//g_shellchrome = spriteframe("sprites/shellchrome.spr", 0, 0.0f);
g_shellchromeshader = shaderforname("shellchrome", sprintf("{\ndeformVertexes bulge 1.25 1.25 0\n{\nmap %s\ntcMod scroll -0.1 0.1\ntcGen environment\nrgbGen entity\n}\n}", "textures/sfx/reflection.tga"));
g_shellchromeshader_cull = shaderforname("shellchrome2", sprintf("{\ncull back\ndeformVertexes bulge 1.5 1.5 0\n{\nmap %s\ntcMod scroll -0.1 0.1\ntcGen environment\nrgbGen entity\n}\n}", "textures/sfx/reflection.tga"));
/* end msg */
print("Graphical resources reloaded\n");
}
/* this is so that profile_csqc reports more accurate statistics as to
what causes computation time */
void
CSQC_RenderScene(void)
{
renderscene();
}
void
CSQC_Update2D(float w, float h, bool focus)
{
NSClientPlayer cl = (NSClientPlayer)pSeat->m_ePlayer;
self = cl;
if (Util_GetMaxPlayers() > 1 && !VGUI_Active() && (Client_InIntermission() || (!cl.IsFakeSpectator() && cl.IsDead()))) {
Scores_Draw();
Chat_Draw();
Print_Draw();
} else if (Util_IsFocused() == true) {
GameText_Draw();
PointMessage_Draw();
if (Client_IsSpectator(cl) == false) {
HUD_Draw();
} else {
HUD_DrawSpectator();
}
Voice_DrawHUD();
Chat_Draw();
Print_Draw();
/* no prints overlapping scoreboards */
if (pSeat->m_iScoresVisible == TRUE) {
Scores_Draw();
} else {
VGUI_Draw();
Print_DrawCenterprint();
}
}
}
/*
=================
CSQC_UpdateView
Run every single frame we're connected to a session.
=================
*/
void
CSQC_UpdateView(float w, float h, float focus)
{
NSClient cl = __NULL__;
int s;
g_focus = (bool)focus;
if (w == 0 || h == 0) {
return;
} else {
/* First time we can effectively call VGUI
* because until now we don't know the video res.
*/
if (!video_res[0] && !video_res[1]) {
video_res[0] = w;
video_res[1] = h;
ClientGame_InitDone();
}
}
/* While the init above may have already happened,
people are able to resize windows dynamically too. */
if (w != video_res[0] || h != video_res[1]) {
video_res[0] = w;
video_res[1] = h;
VGUI_Reposition();
}
/* these have to be checked every frame */
Fog_Update();
Sky_Update(FALSE);
cvar_set("_background", serverkey("background"));
/* ensure background maps do not get paused */
if (serverkeyfloat("background") == 1)
setpause(FALSE);
/* bounds sanity check */
if (numclientseats > g_seats.length) {
numclientseats = g_seats.length;
}
/* null our globals */
for (s = g_seats.length; s-- > numclientseats;) {
pSeat = &g_seats[s];
pSeatLocal = &g_seatslocal[s];
g_view = g_viewSeats[0];
pSeat->m_ePlayer = world;
}
/* this is running whenever we're doing 'buildcubemaps' */
if (g_iCubeProcess == TRUE) {
setproperty(VF_DRAWENGINESBAR, (float)0);
setproperty(VF_DRAWCROSSHAIR, (float)0);
setproperty(VF_DRAWWORLD, (float)1);
setproperty(VF_MIN, [0,0]);
setproperty(VF_VIEWENTITY, player_localentnum);
setproperty(VF_ORIGIN, g_vecCubePos);
setproperty(VF_ANGLES, [0,0,0]);
setproperty(VF_SIZE_X, g_dCubeSize);
setproperty(VF_SIZE_Y, g_dCubeSize);
setproperty(VF_AFOV, 90.0f);
CSQC_RenderScene();
} else {
/* now render each player seat */
for (s = numclientseats; s-- > 0;) {
pSeat = &g_seats[s];
pSeatLocal = &g_seatslocal[s];
g_view = g_viewSeats[s];
setproperty(VF_ACTIVESEAT, (float)s);
pSeat->m_ePlayer = findfloat(world, ::entnum, player_localentnum);
if (pSeat->m_ePlayer == world)
continue;
cl = (NSClient)pSeat->m_ePlayer;
/* set up our single/split viewport */
View_CalcViewport(s, w, h);
/* our view target ourselves, if we're alive... */
g_view.SetViewTarget((NSEntity)pSeat->m_ePlayer);
if (Client_IsSpectator(cl))
g_view.SetViewMode(VIEWMODE_SPECTATING);
else
g_view.SetViewMode(VIEWMODE_FPS);
g_view.UpdateView();
/* 2D calls happen here, after rendering is done */
CSQC_Update2D(w, h, focus);
}
}
/* this sucks and doesn't take seats into account */
EFX_UpdateListener();
DSP_UpdateSoundscape();
/* draw AL debug info (no regard for seating */
if (autocvar_s_al_debug)
EFX_DebugInfo();
/* make sure we're not running these on invalid seats post frame */
pSeat = __NULL__;
pSeatLocal = __NULL__;
}
/*
=================
CSQC_InputEvent
Updates all our input related globals for use in other functions
=================
*/
float
CSQC_InputEvent(float fEventType, float fKey, float fCharacter, float fDeviceID)
{
CSQC_UpdateSeat();
switch (fEventType) {
case IE_KEYDOWN:
break;
case IE_KEYUP:
break;
case IE_MOUSEABS:
mouse_pos[0] = fKey;
mouse_pos[1] = fCharacter;
break;
case IE_MOUSEDELTA:
mouse_pos[0] += fKey;
mouse_pos[1] += fCharacter;
if (mouse_pos[0] < 0) {
mouse_pos[0] = 0;
} else if (mouse_pos[0] > video_res[0]) {
mouse_pos[0] = video_res[0];
}
if (mouse_pos[1] < 0) {
mouse_pos[1] = 0;
} else if (mouse_pos[1] > video_res[1]) {
mouse_pos[1] = video_res[1];
}
break;
default:
return (1);
}
pSeat->m_bInterfaceFocused = false;
for (entity e = world; (e = find(e, ::classname, "NSInteractiveSurface"));) {
vector vecPos = pSeat->m_ePlayer.origin + pSeat->m_ePlayer.view_ofs;
NSInteractiveSurface surf = (NSInteractiveSurface) e;
if (surf.FocusCheck(vecPos, view_angles)) {
pSeat->m_bInterfaceFocused = true;
surf.Input(fEventType, fKey, fCharacter, fDeviceID);
break;
}
}
g_vecMousePos = getmousepos();
bool vgui_pressed = VGUI_Input(fEventType, fKey, fCharacter, fDeviceID);
if (g_vguiWidgetCount) {
setcursormode(TRUE, "gfx/cursor", [0,0,0], 1.0f);
} else {
setcursormode(FALSE, "gfx/cursor", [0,0,0], 1.0f);
}
return (vgui_pressed);
}
/*
=================
CSQC_Input_Frame
Hijacks and controls what input globals are being sent to the server
=================
*/
void
CSQC_Input_Frame(void)
{
entity me;
if (Util_IsPaused())
return;
CSQC_UpdateSeat();
me = pSeat->m_ePlayer;
if (me.classname == "player" || me.classname == "spectator") {
NSClient pl = (NSClient)me;
pl.ClientInputFrame();
}
}
/*
=================
CSQC_Parse_Event
Whenever we call a SVC_CGAMEPACKET on the SSQC, this is being run
=================
*/
void
CSQC_Parse_Event(void)
{
/* always 0, unless it was sent with a MULTICAST_ONE or MULTICAST_ONE_R to p2+ */
CSQC_UpdateSeat();
float fHeader = readbyte();
int ret = ClientGame_EventParse(fHeader);
if (ret == 1) {
return;
}
Event_Parse(fHeader);
}
/*
=================
CSQC_ConsoleCommand
Commands not protected by the engine get passed here.
If we return 0 this means the engine needs to either handle
the command or throw a 'unrecognized command' message.
=================
*/
float
CSQC_ConsoleCommand(string sCMD)
{
/* the engine will hide the p1 etc commands... which is fun... */
CSQC_UpdateSeat();
tokenize(sCMD);
/* give us a chance to override commands */
int ret = ClientGame_ConsoleCommand();
/* successful override */
if (ret == (1))
return (1);
return Cmd_Parse(sCMD);
}
/*
=================
CSQC_ConsoleCommand
Engine or server game will occasionally pass messages through here.
There are 4 different types currently:
PRINT_LOW = low on the screen.
PRINT_MEDIUM = medium level on the screen.
PRINT_HIGH = top level on the screen
PRINT_CHAT = chat message
Currently, everything but chat gets piped into a single printbuffer,
similar to NetQuake.
FIXME: We'd like to expose this further to modification.
=================
*/
void
CSQC_Parse_Print(string sMessage, float fLevel)
{
CSQC_UpdateSeat();
/* chat goes through here */
if (fLevel == PRINT_CHAT) {
Chat_Parse(sMessage);
return;
}
/* the rest goes into our print buffer */
if (pSeat->m_iPrintLines < 4) {
pSeat->m_strPrintBuffer[pSeat->m_iPrintLines + 1] = sMessage;
pSeat->m_iPrintLines++;
} else {
for (int i = 0; i < 4; i++) {
pSeat->m_strPrintBuffer[i] = pSeat->m_strPrintBuffer[i + 1];
}
pSeat->m_strPrintBuffer[4] = sMessage;
}
pSeat->m_flPrintTime = time + CHAT_TIME;
/* log to console */
localcmd(sprintf("echo \"%s\"\n", sMessage));
}
/*
=================
CSQC_Parse_CenterPrint
Catches every centerprint call and allows us to tinker with it.
That's how we are able to add color, alpha and whatnot.
Keep in mind that newlines need to be tokenized
=================
*/
float
CSQC_Parse_CenterPrint(string sMessage)
{
CSQC_UpdateSeat();
pSeat->m_iCenterprintLines = tokenizebyseparator(sMessage, "\n");
for (int i = 0; i < (pSeat->m_iCenterprintLines); i++) {
pSeat->m_strCenterprintBuffer[i] = sprintf("^xF80%s", argv(i));
}
pSeat->m_flCenterprintAlpha = 1;
pSeat->m_flCenterprintTime = time + 3;
return (1);
}
/*
=================
CSQC_Ent_Update
Called when an entity is being networked from the server game.
ClientGame_EntityUpdate allows the project to do game specific
overrides. If that returns 0 Nuclide will attempt to handle it.
If neither handles it we'll get a protocol error.
=================
*/
void
CSQC_Ent_Update(float new)
{
float t;
t = readbyte();
/* client didn't override anything */
if (ClientGame_EntityUpdate(t, new)) {
return;
}
Entity_EntityUpdate(t, new);
}
/*
=================
CSQC_WorldLoaded
Whenever the world is fully initialized...
=================
*/
void
CSQC_WorldLoaded(void)
{
print("--------- Initializing Client World ----------\n");
//DetailTex_Init();
/* Primarily for the flashlight */
if (serverkeyfloat("*bspversion") != BSPVER_HL) {
localcmd("r_shadow_realtime_dlight 1\n");
} else {
localcmd("r_shadow_realtime_dlight 0\n");
}
string strTokenized;
getentitytoken(0);
while (1) {
strTokenized = getentitytoken();
if (strTokenized == "") {
break;
}
if (strTokenized != "{") {
print("^1[WARNING] ^7Bad entity data\n");
break;
}
if (!Entities_ParseLump()) {
print("^1[WARNING] ^7Bad entity data\n");
break;
}
}
for (entity a = world; (a = findfloat(a, ::isCSQC, TRUE));) {
NSEntity ent = (NSEntity)a;
ent.Respawn();
}
print("Client world initialized.\n");
}
/*
=================
CSQC_Ent_Remove
Whenever an entity gets removed from the server and will no longer
receive entity updates.
=================
*/
void
CSQC_Ent_Remove(void)
{
if (self.isCSQC) {
NSEntity me = (NSEntity)self;
me.OnRemoveEntity();
if (me.skeletonindex)
skel_delete(me.skeletonindex);
remove(self);
} else {
remove(self);
}
}
/*
=================
CSQC_Shutdown
Incase you need to free something
=================
*/
void
CSQC_Shutdown(void)
{
print("--------- Shutting Client Game ----------\n");
Decal_Shutdown();
Sentences_Shutdown();
Titles_Shutdown();
Sound_Shutdown();
PropData_Shutdown();
EFX_Shutdown();
print("Client game shutdown.\n");
}