From 3f9fdd41aec12c0832ed07f2ba0239383a3801d4 Mon Sep 17 00:00:00 2001 From: bones_was_here Date: Wed, 3 Apr 2024 00:35:20 +1000 Subject: [PATCH] Implement CSQC_SIMPLE aka hud-only CSQC, improve compatibility This should match the behaviours of engines with minimal CSQC support, allowing DP to run any csprogs made for them. Fixes https://github.com/DarkPlacesEngine/darkplaces/issues/108 Updates dpdefs to include compatible parameter names. Adds VM-specific code for checking required functions during QC program loading. Signed-off-by: bones_was_here --- cl_main.c | 2 +- cl_screen.c | 6 +++- csprogs.c | 68 +++++++++++++++++++++++++++++++++++-------- dpdefs/csprogsdefs.qc | 16 ++++++---- menu.c | 25 ++++++++++------ progsvm.h | 8 +++-- prvm_edict.c | 11 +++---- prvm_offsets.h | 4 +++ render.h | 1 + sbar.c | 3 +- sbar.h | 3 ++ sv_main.c | 11 ++++--- 12 files changed, 112 insertions(+), 46 deletions(-) diff --git a/cl_main.c b/cl_main.c index 0195c8b7..b4d7760c 100644 --- a/cl_main.c +++ b/cl_main.c @@ -2091,7 +2091,7 @@ void CL_UpdateWorld(void) CL_UpdateViewModel(); // when csqc is loaded, it will call this in CSQC_UpdateView - if (!CLVM_prog->loaded) + if (!CLVM_prog->loaded || CLVM_prog->flag & PRVM_CSQC_SIMPLE) { // clear the CL_Mesh_Scene() used for some engine effects CL_MeshEntities_Scene_Clear(); diff --git a/cl_screen.c b/cl_screen.c index 20332226..04d25a01 100644 --- a/cl_screen.c +++ b/cl_screen.c @@ -1682,7 +1682,7 @@ static void SCR_DrawScreen (void) // if CSQC is loaded, it is required to provide the CSQC_UpdateView function, // and won't render a view if it does not call that. - if (CLVM_prog->loaded) + if (CLVM_prog->loaded && !(CLVM_prog->flag & PRVM_CSQC_SIMPLE)) CL_VM_UpdateView(r_stereo_side ? 0.0 : max(0.0, cl.time - cl.oldtime)); else { @@ -1757,7 +1757,11 @@ static void SCR_DrawScreen (void) SCR_DrawTurtle (); SCR_DrawPause (); if (!r_letterbox.value) + { Sbar_Draw(); + if (CLVM_prog->loaded && CLVM_prog->flag & PRVM_CSQC_SIMPLE) + CL_VM_DrawHud(r_stereo_side ? 0.0 : max(0.0, cl.time - cl.oldtime)); + } SHOWLMP_drawall(); SCR_CheckDrawCenterString(); } diff --git a/csprogs.c b/csprogs.c index 3d8e83f8..51e324c0 100644 --- a/csprogs.c +++ b/csprogs.c @@ -44,16 +44,22 @@ void CL_VM_PreventInformationLeaks(void) PRVM_clientglobalfloat(trace_networkentity) = 0; } -//[515]: these are required funcs -static const char *cl_required_func[] = -{ - "CSQC_Init", - "CSQC_InputEvent", - "CSQC_UpdateView", - "CSQC_ConsoleCommand", -}; -static int cl_numrequiredfunc = sizeof(cl_required_func) / sizeof(char*); +/** Previous DP versions declined to load csprogs if it lacked any of: + * CSQC_Init, CSQC_InputEvent, CSQC_UpdateView, CSQC_ConsoleCommand + * whereas in FTE and QSS-based engines the minimum is either CSQC_UpdateView + * or CSQC_DrawHud (only called in CSQC_SIMPLE aka hud-only mode) + * and the other funcs are optional, so we now behave the same here. + */ +static void CL_CheckRequiredFuncs(prvm_prog_t *prog, const char *filename) +{ + if (PRVM_ED_FindFunction(prog, "CSQC_UpdateView")) + return; + else if (PRVM_ED_FindFunction(prog, "CSQC_DrawHud")) + prog->flag |= PRVM_CSQC_SIMPLE; + else + prog->error_cmd("%s: no CSQC_UpdateView (EXT_CSQC) or CSQC_DrawHud (CSQC_SIMPLE) function found in %s", prog->name, filename); +} #define CL_REQFIELDS (sizeof(cl_reqfields) / sizeof(prvm_required_field_t)) @@ -524,6 +530,35 @@ qbool CL_VM_UpdateView (double frametime) return true; } +void CL_VM_DrawHud(double frametime) +{ + prvm_prog_t *prog = CLVM_prog; + + R_TimeReport("pre-DrawHud"); + + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + CSQC_SetGlobals(frametime); + + VectorSet(PRVM_G_VECTOR(OFS_PARM0), vid_conwidth.integer, vid_conheight.integer, 0); + PRVM_G_FLOAT(OFS_PARM1) = sb_showscores; + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_DrawHud), "QC function CSQC_DrawHud is missing"); + + if (PRVM_clientfunction(CSQC_DrawScores)) + { + VectorSet(PRVM_G_VECTOR(OFS_PARM0), vid_conwidth.integer, vid_conheight.integer, 0); + PRVM_G_FLOAT(OFS_PARM1) = sb_showscores; + if (key_dest != key_menu) + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_DrawScores), "QC function CSQC_DrawScores is missing"); + } + else if (sb_showscores || (cl.stats[STAT_HEALTH] <= 0 && cl_deathscoreboard.integer)) + if (!cl.islocalgame) // LadyHavoc: changed to draw the deathmatch overlays in any multiplayer mode + Sbar_DeathmatchOverlay (); + + R_TimeReport("DrawHud"); +} + + qbool CL_VM_ConsoleCommand(const char *text, size_t textlen) { prvm_prog_t *prog = CLVM_prog; @@ -1031,7 +1066,7 @@ void CL_VM_Init (void) prog->error_cmd = Host_Error; prog->ExecuteProgram = CLVM_ExecuteProgram; - PRVM_Prog_Load(prog, csprogsfn, csprogsdata, csprogsdatasize, cl_numrequiredfunc, cl_required_func, CL_REQFIELDS, cl_reqfields, CL_REQGLOBALS, cl_reqglobals); + PRVM_Prog_Load(prog, csprogsfn, csprogsdata, csprogsdatasize, CL_CheckRequiredFuncs, CL_REQFIELDS, cl_reqfields, CL_REQGLOBALS, cl_reqglobals); if (!prog->loaded) { @@ -1081,9 +1116,18 @@ void CL_VM_Init (void) VectorCopy(cl.world.maxs, PRVM_clientedictvector(prog->edicts, maxs)); VectorCopy(cl.world.mins, PRVM_clientedictvector(prog->edicts, absmin)); VectorCopy(cl.world.maxs, PRVM_clientedictvector(prog->edicts, absmax)); + PRVM_clientedictfloat(prog->edicts, solid) = SOLID_BSP; + PRVM_clientedictfloat(prog->edicts, modelindex) = 1; + PRVM_clientedictfloat(prog->edicts, model) = PRVM_SetEngineString(prog, cl.worldmodel->name); - // call the prog init - prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Init), "QC function CSQC_Init is missing"); + // call the prog init if it exists + if (PRVM_clientfunction(CSQC_Init)) + { + PRVM_G_FLOAT(OFS_PARM0) = 1.0f; // CSQC_SIMPLE engines always pass 0, FTE always passes 1 + PRVM_G_INT(OFS_PARM1) = PRVM_SetEngineString(prog, gamename); + PRVM_G_FLOAT(OFS_PARM2) = 1.0f; // TODO DP versions... + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Init), "QC function CSQC_Init is missing"); + } // Once CSQC_Init was called, we consider csqc code fully initialized. prog->inittime = host.realtime; diff --git a/dpdefs/csprogsdefs.qc b/dpdefs/csprogsdefs.qc index 39d70246..b5c05746 100644 --- a/dpdefs/csprogsdefs.qc +++ b/dpdefs/csprogsdefs.qc @@ -42,13 +42,17 @@ float trace_inopen; float trace_inwater; // -// required prog functions +// prog functions called by engine // -void() CSQC_Init; -void() CSQC_Shutdown; -float(float f, float t, float n) CSQC_InputEvent; -void(float w, float h) CSQC_UpdateView; -float(string s) CSQC_ConsoleCommand; +void CSQC_Init(float apilevel, string enginename, float engineversion); +void CSQC_Shutdown(); +float CSQC_InputEvent(float evtype, float scanx, float chary); +void CSQC_UpdateView(float vid_width, float vid_height, float notmenu); // required for EXT_CSQC (preferred) +float CSQC_ConsoleCommand(string cmdstr); +#ifdef CSQC_SIMPLE // hud-only CSQC + void CSQC_DrawHud(vector virtsize, float showscores); // required for CSQC_SIMPLE (fallback) + void CSQC_DrawScores(vector virtsize, float showscores); +#endif //these fields are read and set by the default player physics vector pmove_org; diff --git a/menu.c b/menu.c index d4d32b81..7b51ab4c 100644 --- a/menu.c +++ b/menu.c @@ -5038,15 +5038,22 @@ void M_Shutdown(void) //============================================================================ // Menu prog handling -static const char *m_required_func[] = { -"m_init", -"m_keydown", -"m_draw", -"m_toggle", -"m_shutdown", -}; +static void MP_CheckRequiredFuncs(prvm_prog_t *prog, const char *filename) +{ + int i; + const char *m_required_func[] = { + "m_init", + "m_keydown", + "m_draw", + "m_toggle", + "m_shutdown", + }; + int m_numrequiredfunc = sizeof(m_required_func) / sizeof(char*); -static int m_numrequiredfunc = sizeof(m_required_func) / sizeof(char*); + for(i = 0; i < m_numrequiredfunc; ++i) + if(PRVM_ED_FindFunction(prog, m_required_func[i]) == 0) + prog->error_cmd("%s: %s not found in %s",prog->name, m_required_func[i], filename); +} static prvm_required_field_t m_required_fields[] = { @@ -5416,7 +5423,7 @@ static void MP_Init (void) // allocate the mempools prog->progs_mempool = Mem_AllocPool(menu_progs.string, 0, NULL); - PRVM_Prog_Load(prog, menu_progs.string, NULL, 0, m_numrequiredfunc, m_required_func, m_numrequiredfields, m_required_fields, m_numrequiredglobals, m_required_globals); + PRVM_Prog_Load(prog, menu_progs.string, NULL, 0, MP_CheckRequiredFuncs, m_numrequiredfields, m_required_fields, m_numrequiredglobals, m_required_globals); // note: OP_STATE is not supported by menu qc, we don't even try to detect // it here diff --git a/progsvm.h b/progsvm.h index da6c45dd..32632f30 100644 --- a/progsvm.h +++ b/progsvm.h @@ -233,7 +233,9 @@ extern prvm_eval_t prvm_badvalue; #endif //============================================================================ -#define PRVM_OP_STATE 1 +// prog->flag +#define PRVM_OP_STATE 1 +#define PRVM_CSQC_SIMPLE 2 #ifdef DP_SMALLMEMORY #define PRVM_MAX_STACK_DEPTH 128 @@ -697,7 +699,7 @@ typedef struct prvm_prog_s const char *name; // [INIT] // flag - used to store general flags like PRVM_GE_SELF, etc. - int flag; + unsigned flag; const char **extensionstring; // [INIT] @@ -922,7 +924,7 @@ Load a program with LoadProgs */ // Load expects to be called right after Reset void PRVM_Prog_Init(prvm_prog_t *prog, struct cmd_state_s *cmd); -void PRVM_Prog_Load(prvm_prog_t *prog, const char *filename, unsigned char *data, int64_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global); +void PRVM_Prog_Load(prvm_prog_t *prog, const char *filename, unsigned char *data, fs_offset_t size, void CheckRequiredFuncs(prvm_prog_t *prog, const char *filename), int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global); void PRVM_Prog_Reset(prvm_prog_t *prog); void PRVM_StackTrace(prvm_prog_t *prog); diff --git a/prvm_edict.c b/prvm_edict.c index 01847ab9..0304c3ca 100644 --- a/prvm_edict.c +++ b/prvm_edict.c @@ -1999,7 +1999,7 @@ PRVM_Prog_Load =============== */ static void PRVM_UpdateBreakpoints(prvm_prog_t *prog); -void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global) +void PRVM_Prog_Load(prvm_prog_t *prog, const char *filename, unsigned char *data, fs_offset_t size, void CheckRequiredFuncs(prvm_prog_t *prog, const char *filename), int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global) { int i; dprograms_t *dprograms; @@ -2496,10 +2496,9 @@ void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * da Mem_Free(dprograms); dprograms = NULL; - // check required functions - for(i=0 ; i < numrequiredfunc ; i++) - if(PRVM_ED_FindFunction(prog, required_func[i]) == 0) - prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename); + prog->flag = 0; + // expected to not return (call prog->error_cmd) if checks fail + CheckRequiredFuncs(prog, filename); PRVM_LoadLNO(prog, filename); @@ -2703,8 +2702,6 @@ fail: // set flags & mdef_ts in prog - prog->flag = 0; - PRVM_FindOffsets(prog); prog->init_cmd(prog); diff --git a/prvm_offsets.h b/prvm_offsets.h index 97a2defb..aea8da3e 100644 --- a/prvm_offsets.h +++ b/prvm_offsets.h @@ -94,6 +94,8 @@ PRVM_DECLARE_clientfunction(CSQC_Parse_StuffCmd) PRVM_DECLARE_clientfunction(CSQC_Parse_TempEntity) PRVM_DECLARE_clientfunction(CSQC_Shutdown) PRVM_DECLARE_clientfunction(CSQC_UpdateView) +PRVM_DECLARE_clientfunction(CSQC_DrawHud) +PRVM_DECLARE_clientfunction(CSQC_DrawScores) PRVM_DECLARE_clientfunction(GameCommand) PRVM_DECLARE_clientfunction(URI_Get_Callback) PRVM_DECLARE_clientglobaledict(other) @@ -416,6 +418,8 @@ PRVM_DECLARE_function(CSQC_Parse_StuffCmd) PRVM_DECLARE_function(CSQC_Parse_TempEntity) PRVM_DECLARE_function(CSQC_Shutdown) PRVM_DECLARE_function(CSQC_UpdateView) +PRVM_DECLARE_function(CSQC_DrawHud) +PRVM_DECLARE_function(CSQC_DrawScores) PRVM_DECLARE_function(ClientConnect) PRVM_DECLARE_function(ClientDisconnect) PRVM_DECLARE_function(ClientKill) diff --git a/render.h b/render.h index 8bfd9b77..fe21b600 100644 --- a/render.h +++ b/render.h @@ -975,6 +975,7 @@ void R_Model_Sprite_Draw(entity_render_t *ent); struct prvm_prog_s; void R_UpdateFog(void); qbool CL_VM_UpdateView(double frametime); +void CL_VM_DrawHud(double frametime); void SCR_DrawConsole(void); void R_Shadow_EditLights_DrawSelectedLightProperties(void); void R_DecalSystem_Reset(decalsystem_t *decalsystem); diff --git a/sbar.c b/sbar.c index 1a0b4a90..69707e44 100644 --- a/sbar.c +++ b/sbar.c @@ -116,7 +116,6 @@ cvar_t crosshair_color_alpha = {CF_CLIENT | CF_ARCHIVE, "crosshair_color_alpha", cvar_t crosshair_size = {CF_CLIENT | CF_ARCHIVE, "crosshair_size", "1", "adjusts size of the crosshair on the screen"}; static void Sbar_MiniDeathmatchOverlay (int x, int y); -static void Sbar_DeathmatchOverlay (void); static void Sbar_IntermissionOverlay (void); static void Sbar_FinaleOverlay (void); @@ -392,6 +391,8 @@ void Sbar_Init (void) Cvar_RegisterVariable(&sbar_miniscoreboard_size); Cvar_RegisterVariable(&sbar_info_pos); Cvar_RegisterVariable(&cl_deathscoreboard); + // This name is used by QuakeSpasm-based engines and is read by the Alkaline 1.2 CSQC + Cvar_RegisterVirtual(&sbar_alpha_bg, "scr_sbaralpha"); Cvar_RegisterVariable(&crosshair_color_red); Cvar_RegisterVariable(&crosshair_color_green); diff --git a/sbar.h b/sbar.h index 8f82d15b..5de4c91d 100644 --- a/sbar.h +++ b/sbar.h @@ -38,5 +38,8 @@ void Sbar_ShowFPS_Update(void); int Sbar_GetSortedPlayerIndex (int index); void Sbar_SortFrags (void); +extern cvar_t cl_deathscoreboard; +void Sbar_DeathmatchOverlay (void); + #endif diff --git a/sv_main.c b/sv_main.c index d2072e61..f35abf3f 100644 --- a/sv_main.c +++ b/sv_main.c @@ -265,12 +265,11 @@ static const char *standardeffectnames[EFFECT_TOTAL] = "SVC_PARTICLE" }; -#define SV_REQFUNCS 0 -#define sv_reqfuncs NULL -//#define SV_REQFUNCS (sizeof(sv_reqfuncs) / sizeof(const char *)) -//static const char *sv_reqfuncs[] = { -//}; +static void SV_CheckRequiredFuncs(prvm_prog_t *prog, const char *filename) +{ + // no required funcs?! +} #define SV_REQFIELDS (sizeof(sv_reqfields) / sizeof(prvm_required_field_t)) @@ -2346,7 +2345,7 @@ static void SV_VM_Setup(void) prog->error_cmd = Host_Error; prog->ExecuteProgram = SVVM_ExecuteProgram; - PRVM_Prog_Load(prog, sv_progs.string, NULL, 0, SV_REQFUNCS, sv_reqfuncs, SV_REQFIELDS, sv_reqfields, SV_REQGLOBALS, sv_reqglobals); + PRVM_Prog_Load(prog, sv_progs.string, NULL, 0, SV_CheckRequiredFuncs, SV_REQFIELDS, sv_reqfields, SV_REQGLOBALS, sv_reqglobals); // some mods compiled with scrambling compilers lack certain critical // global names and field names such as "self" and "time" and "nextthink" -- 2.39.2