]> git.xonotic.org Git - xonotic/darkplaces.git/commitdiff
Implement CSQC_SIMPLE aka hud-only CSQC, improve compatibility
authorbones_was_here <bones_was_here@xonotic.au>
Tue, 2 Apr 2024 14:35:20 +0000 (00:35 +1000)
committerbones_was_here <bones_was_here@xonotic.au>
Thu, 4 Apr 2024 11:11:12 +0000 (21:11 +1000)
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 <bones_was_here@xonotic.au>
12 files changed:
cl_main.c
cl_screen.c
csprogs.c
dpdefs/csprogsdefs.qc
menu.c
progsvm.h
prvm_edict.c
prvm_offsets.h
render.h
sbar.c
sbar.h
sv_main.c

index 0195c8b767a3ab175d68dd7008788a3727716c12..b4d7760c328b40db27b44b224684aa11557fc521 100644 (file)
--- 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();
index 203322263ebdd0f09715a48a001cc3acbe0bbe19..04d25a01b946241eb92e92288b73f103390275bc 100644 (file)
@@ -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();
        }
index 3d8e83f8a25b3734db2c01bd6f1bf0fa4c5d3e41..51e324c031d3b331f5cba0be431d143f5d77f680 100644 (file)
--- 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;
index 39d7024668360edd435e7bdfa50e0e88600e38ae..b5c057462ca87037c2a069f79f849cad0d2d735a 100644 (file)
@@ -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 d4d32b811b1836dcfcf7ad0f932e837bb05eed8f..7b51ab4cf523f2069b30bfc1734903b04975ff7a 100644 (file)
--- 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
index da6c45dd118414f1c04ee48e963049ee7faa37ec..32632f30f9799e100fe30abb693f4da95e5b0db0 100644 (file)
--- 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);
index 01847ab9db3d2a981bda238bb230e04f77d41503..0304c3ca353848ca3e406a4871c809501b102e5c 100644 (file)
@@ -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);
index 97a2defbcc3d5a0a5fbcd3caef342d97545f9cbd..aea8da3e9f0720a1396d4cf1761888860e186eb2 100644 (file)
@@ -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)
index 8bfd9b77fac8f1344d941b2179b84d6efdb22962..fe21b6000a1e266cb987ae4dc3ddaf7c440664e2 100644 (file)
--- 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 1a0b4a905ac72a5770b3827af46f4869e932c14c..69707e448cbb54b87f5b11b13dcc34c8d509aec1 100644 (file)
--- 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 8f82d15b8dfcd8b34babab0adb2ee1776523fe7c..5de4c91d468368a4ea8b5c5304bec4bbdd2bc31b 100644 (file)
--- 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
 
index d2072e6139fa30eddc27b84e6c61881bed42f0a0..f35abf3fe2aba1764441059aae8d740ceb740436 100644 (file)
--- 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"