diff --git a/CMakeLists.txt b/CMakeLists.txt index e39893598..24b81f5ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,6 +324,7 @@ ADD_EXECUTABLE(fteqw WIN32 engine/client/m_multi.c engine/client/m_options.c engine/client/m_script.c + engine/client/m_native.c engine/client/m_single.c engine/client/menu.c engine/client/p_classic.c diff --git a/engine/Makefile b/engine/Makefile index 420afff57..77f235287 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -599,6 +599,7 @@ CLIENT_OBJS = \ m_options.o \ m_single.o \ m_script.o \ + m_native.o \ m_mp3.o \ roq_read.o \ clq2_cin.o \ diff --git a/engine/client/api_menu.h b/engine/client/api_menu.h new file mode 100644 index 000000000..46b0c2b20 --- /dev/null +++ b/engine/client/api_menu.h @@ -0,0 +1,157 @@ + /* + * Copyright (c) 2015-2018 + * Marco Hladik All rights reserved. + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this. If not, see . + */ + +#define NATIVEMENU_API_VERSION_MIN 0 //will be updated any time a symbol is renamed. +#define NATIVEMENU_API_VERSION_MAX 0 //bumped for any change. +#ifndef NATIVEMENU_API_VERSION //so you can hold back the reported version in order to work with older engines. +#define NATIVEMENU_API_VERSION NATIVEMENU_API_VERSION_MAX //version reported to the other side. +#endif + +struct vfsfile_s; +struct serverinfo_s; +struct searchpathfuncs_s; +enum slist_test_e; +enum hostcachekey_e; //obtained via calls to gethostcacheindexforkey +enum fs_relative; +#ifndef __QUAKEDEF_H__ + #ifdef __cplusplus + typedef enum {qfalse, qtrue} qboolean;//false and true are forcivly defined. + #else + typedef enum {false, true} qboolean; + #endif + typedef float vec_t; + typedef vec_t vec2_t[2]; + typedef vec_t vec3_t[3]; + typedef vec_t vec4_t[4]; + #ifdef _MSC_VER + #define QDECL __cdecl + #else + #define QDECL + #endif + + #include + typedef uint64_t qofs_t; +#endif + +struct menu_inputevent_args_s +{ + enum { + MIE_KEYDOWN = 0, + MIE_KEYUP = 1, + MIE_MOUSEDELTA = 2, + MIE_MOUSEABS = 3, + } eventtype; + unsigned int devid; + union + { + struct + { + unsigned int scancode; + unsigned int charcode; + } key; + struct + { + float delta[2]; + float screen[2]; //virtual coords + } mouse; + }; +}; + +typedef struct { + int api_version; //this may be higher than you expect. + + int (*checkextension) (const char *ext); + void (*error) (const char *err); + void (*printf) (const char *text, ...); + void (*dprintf) (const char *text, ...); + void (*localcmd) (const char *cmd); + float (*cvar_float) (const char *name); + const char *(*cvar_string) (const char *name); //return value lasts until cvar_set is called, etc, so don't cache. + void (*cvar_set) (const char *name, const char *value); + void (*registercvar) (const char *name, const char *defaultvalue, unsigned int flags, const char *description); + + int (*isserver) (void); + int (*getclientstate) (void); + void (*localsound) (const char *sample, int channel, float volume); + + // file input / search crap + struct vfsfile_s *(*fopen) (const char *filename, char *modestring, enum fs_relative fsroot); //modestring should be one of rb,r+b,wb,w+b,ab,wbp. Mostly use a root of FS_GAMEONLY for writes, otherwise FS_GAME for reads. + void (*fclose) (struct vfsfile_s *fhandle); + char *(*fgets) (struct vfsfile_s *fhandle, char *out, size_t outsize); //returns output buffer, or NULL + void (*fprintf) (struct vfsfile_s *fhandle, const char *s, ...); + void (*EnumerateFiles) (const char *match, int (QDECL *callback)(const char *fname, qofs_t fsize, time_t mtime, void *ctx, struct searchpathfuncs_s *package), void *ctx); + + // Drawing stuff +// int (*iscachedpic) (const char *name); + void *(*precache_pic) (const char *name); + int (*drawgetimagesize) (void *pic, int *x, int *y); + void (*drawquad) (vec2_t position[4], vec2_t texcoords[4], void *pic, vec4_t rgba, unsigned int be_flags); +// void (*drawsubpic) (vec2_t pos, vec2_t sz, const char *pic, vec2_t srcpos, vec2_t srcsz, vec4_t rgba, unsigned int be_flags); +// void (*drawfill) (vec2_t position, vec2_t size, vec4_t rgba, unsigned int be_flags); +// float (*drawcharacter) (vec2_t position, int character, vec2_t scale, vec4_t rgba, unsigned int be_flags); +// float (*drawrawstring) (vec3_t position, char *text, vec3_t scale, vec4_t rgba, unsigned int be_flags); + float (*drawstring) (vec2_t position, const char *text, float height, vec4_t rgba, unsigned int be_flags); + float (*stringwidth) (const char *text, float height); + void (*drawsetcliparea) (float x, float y, float width, float height); + void (*drawresetcliparea) (void); + + // Menu specific stuff + qboolean (*setkeydest) (qboolean focused); //returns whether it changed. + int (*getkeydest) (void); //returns 0 if unfocused, -1 if active-but-unfocused, 1 if focused-and-active. + int (*setmousetarget) (const char *cursorname, float hot_x, float hot_y, float scale); //forces absolute mouse coords whenever cursorname isn't NULL + const char *(*keynumtostring) (int keynum, int modifier); + int (*stringtokeynum) (const char *key, int *modifier); + int (*findkeysforcommand) (int bindmap, const char *command, int *out_scancodes, int *out_modifiers, int keycount); + + // Server browser stuff + int (*gethostcachevalue) (int type); + char *(*gethostcachestring) (struct serverinfo_s *host, enum hostcachekey_e fld); + float (*gethostcachenumber) (struct serverinfo_s *host, enum hostcachekey_e fld); + void (*resethostcachemasks) (void); + void (*sethostcachemaskstring) (qboolean or, enum hostcachekey_e fld, char *str, enum slist_test_e op); + void (*sethostcachemasknumber) (qboolean or, enum hostcachekey_e fld, int num, enum slist_test_e op); + void (*sethostcachesort) (enum hostcachekey_e fld, qboolean descending); + void (*resorthostcache) (void); + struct serverinfo_s *(*getsortedhost) (int idx); + void (*refreshhostcache) (qboolean fullreset); + enum hostcachekey_e (*gethostcacheindexforkey) (const char *key); +} menu_import_t; + +typedef struct { + int api_version; + + void (*Init) (void); + void (*Shutdown) (void); + void (*Draw) (int width, int height, float frametime); + void (*DrawLoading) (int width, int height, float frametime); + void (*Toggle) (int wantmode); + int (*InputEvent) (struct menu_inputevent_args_s ev); + void (*ConsoleCommand) (const char *cmd); +} menu_export_t; + +#ifndef NATIVEEXPORT + #ifdef _WIN32 + #define NATIVEEXPORTPROTO QDECL + #define NATIVEEXPORT __declspec(dllexport) NATIVEEXPORTPROTO + #else + #define NATIVEEXPORTPROTO + #define NATIVEEXPORT __attribute__((visibility("default"))) + #endif +#endif + +menu_export_t *NATIVEEXPORTPROTO GetMenuAPI (menu_import_t *import); diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 1e287d55a..510a1b41c 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -4475,7 +4475,7 @@ Host_EndGame Call this to drop to a console without exiting the qwcl ================ */ -NORETURN void VARGS Host_EndGame (char *message, ...) +NORETURN void VARGS Host_EndGame (const char *message, ...) { va_list argptr; char string[1024]; @@ -4510,7 +4510,7 @@ Host_Error This shuts down the client and exits qwcl ================ */ -void VARGS Host_Error (char *error, ...) +void VARGS Host_Error (const char *error, ...) { va_list argptr; char string[1024]; @@ -5581,6 +5581,7 @@ double Host_Frame (double time) #endif cls.framecount++; + cl.lasttime = cl.time; RSpeedRemark(); @@ -5799,9 +5800,7 @@ void CL_StartCinematicOrMenu(void) UI_Start(); #endif -#ifdef MENU_DAT Cbuf_AddText("menu_restart\n", RESTRICT_LOCAL); -#endif Con_TPrintf ("^Ue080^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081 %s %sInitialized ^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue082\n", *fs_gamename.string?fs_gamename.string:"Nothing", com_installer?"Installer ":""); diff --git a/engine/client/cl_master.h b/engine/client/cl_master.h index 7b03cc03a..e1ea86a8a 100644 --- a/engine/client/cl_master.h +++ b/engine/client/cl_master.h @@ -47,7 +47,7 @@ enum mastertype_e }; -typedef enum +typedef enum hostcachekey_e { SLKEY_PING, SLKEY_MAP, @@ -81,7 +81,7 @@ typedef enum SLKEY_CUSTOM = SLKEY_PLAYER0+MAX_CLIENTS } hostcachekey_t; -typedef enum +typedef enum slist_test_e { SLIST_TEST_CONTAINS, SLIST_TEST_NOTCONTAIN, @@ -226,11 +226,11 @@ void MasterInfo_Refresh(qboolean doreset); void Master_QueryServer(serverinfo_t *server); void MasterInfo_WriteServers(void); -int Master_KeyForName(const char *keyname); -float Master_ReadKeyFloat(serverinfo_t *server, int keynum); -char *Master_ReadKeyString(serverinfo_t *server, int keynum); +hostcachekey_t Master_KeyForName(const char *keyname); +float Master_ReadKeyFloat(serverinfo_t *server, hostcachekey_t keynum); +char *Master_ReadKeyString(serverinfo_t *server, hostcachekey_t keynum); -void Master_SortServers(void); +int Master_SortServers(void); void Master_SetSortField(hostcachekey_t field, qboolean descending); hostcachekey_t Master_GetSortField(void); qboolean Master_GetSortDescending(void); diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index fe41ed795..fe2b1c322 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -955,6 +955,10 @@ void SCR_DrawCursor(void) cmod = kc_console; else if ((key_dest_mask & key_dest_absolutemouse & kdm_gmenu)) cmod = kc_menu; +#ifdef MENU_NATIVECODE + else if ((key_dest_mask & key_dest_absolutemouse & kdm_nmenu)) + cmod = kc_nmenu; +#endif else// if (key_dest_mask & key_dest_absolutemouse) cmod = prydoncursornum?kc_console:kc_game; @@ -1892,6 +1896,13 @@ void SCR_DrawLoading (qboolean opaque) return; //menuqc should have just drawn whatever overlays it wanted. } #endif +#ifdef MENU_NATIVECODE + if (mn_entry && mn_entry->DrawLoading) + { + mn_entry->DrawLoading(vid.width, vid.height, host_frametime); + return; + } +#endif //int mtype = M_GameType(); //unused variable y = vid.height/2; @@ -3383,6 +3394,10 @@ void SCR_DrawTwoDimensional(int uimenu, qboolean nohud) #ifdef MENU_DAT MP_Draw(); +#endif +#ifdef MENU_NATIVECODE + if (mn_entry) + mn_entry->Draw(vid.width, vid.height, host_frametime); #endif M_Draw (uimenu); diff --git a/engine/client/client.h b/engine/client/client.h index da05aab06..4d5b294a1 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -808,6 +808,7 @@ typedef struct // the client simulates or interpolates movement to get these values double time; // this is the time value that the client // is rendering at. always <= realtime + double lasttime; //cl.time from last frame. float servertime; //current server time, bound between gametime and gametimemark float mtime; //server time as on the server when we last received a packet. not allowed to decrease. diff --git a/engine/client/in_generic.c b/engine/client/in_generic.c index 85ee06754..41e4dc0df 100644 --- a/engine/client/in_generic.c +++ b/engine/client/in_generic.c @@ -704,24 +704,57 @@ void IN_MoveMouse(struct mouse_s *mouse, float *movements, int pnum, float frame { //many mods assume only a single mouse device. //when we have multiple active abs devices, we need to avoid sending all of them, because that just confuses everyone. such mods will see only devices that are actually moving, so uni-cursor mods will see only the one that moved most recently. mouse->updated = false; -#ifdef MENU_DAT - if (!runningindepphys && MP_MousePosition(mouse->oldpos[0], mouse->oldpos[1], mouse->qdeviceid)) + if (!runningindepphys) { - mx = 0; - my = 0; - } +#ifdef MENU_NATIVECODE + if (mn_entry) + { + struct menu_inputevent_args_s ev = {MIE_MOUSEABS, mouse->qdeviceid}; + ev.mouse.delta[0] = mx; + ev.mouse.delta[1] = my; + ev.mouse.screen[0] = (mouse->oldpos[0] * vid.width) / vid.pixelwidth; + ev.mouse.screen[1] = (mouse->oldpos[1] * vid.width) / vid.pixelwidth; + if (mn_entry->InputEvent(ev)) + { + mx = 0; + my = 0; + } + } +#endif +#ifdef MENU_DAT + if (MP_MousePosition(mouse->oldpos[0], mouse->oldpos[1], mouse->qdeviceid)) + { + mx = 0; + my = 0; + } #endif #ifdef CSQC_DAT - if (!runningindepphys && CSQC_MousePosition(mouse->oldpos[0], mouse->oldpos[1], mouse->qdeviceid)) - { - mx = 0; - my = 0; - } + if (!runningindepphys && CSQC_MousePosition(mouse->oldpos[0], mouse->oldpos[1], mouse->qdeviceid)) + { + mx = 0; + my = 0; + } #endif + } } } else { +#ifdef MENU_NATIVECODE + if (mn_entry && Key_Dest_Has(kdm_nmenu) && (mx || my)) + { + struct menu_inputevent_args_s ev = {MIE_MOUSEABS, mouse->qdeviceid}; + ev.mouse.delta[0] = mx; + ev.mouse.delta[1] = my; + ev.mouse.screen[0] = (mouse->oldpos[0] * vid.width) / vid.pixelwidth; + ev.mouse.screen[1] = (mouse->oldpos[1] * vid.width) / vid.pixelwidth; + if (mn_entry->InputEvent(ev)) + { + mx = 0; + my = 0; + } + } +#endif #ifdef MENU_DAT if (Key_Dest_Has(kdm_gmenu)) if (mx || my) diff --git a/engine/client/keys.c b/engine/client/keys.c index 822445871..1f7f1a42a 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -2636,6 +2636,15 @@ void Key_Event (unsigned int devid, int key, unsigned int unicode, qboolean down #ifdef MENU_DAT if (Key_Dest_Has(kdm_gmenu) && !Key_Dest_Has(kdm_editor|kdm_console|kdm_cwindows)) MP_Keyup (key, unicode, devid); +#endif +#ifdef MENU_NATIVECODE + if (mn_entry) + { + struct menu_inputevent_args_s ev = {MIE_KEYUP, devid}; + ev.key.scancode = key; + ev.key.charcode = unicode; + mn_entry->InputEvent(ev); + } #endif return; } @@ -2659,6 +2668,18 @@ void Key_Event (unsigned int devid, int key, unsigned int unicode, qboolean down #endif else if (Key_Dest_Has(kdm_emenu)) M_Keydown (key, unicode); +#ifdef MENU_NATIVECODE + else if (Key_Dest_Has(kdm_nmenu)) + { + if (mn_entry) + { + struct menu_inputevent_args_s ev = {MIE_KEYDOWN, devid}; + ev.key.scancode = key; + ev.key.charcode = unicode; + mn_entry->InputEvent(ev); + } + } +#endif #ifdef MENU_DAT else if (Key_Dest_Has(kdm_gmenu)) MP_Keydown (key, unicode, devid); @@ -2709,6 +2730,15 @@ void Key_Event (unsigned int devid, int key, unsigned int unicode, qboolean down } if (Key_Dest_Has(kdm_emenu)) M_Keyup (key, unicode); +#ifdef MENU_NATIVECODE + if (Key_Dest_Has(kdm_nmenu) && mn_entry) + { + struct menu_inputevent_args_s ev = {MIE_KEYUP, devid}; + ev.key.scancode = key; + ev.key.charcode = unicode; + mn_entry->InputEvent(ev); + } +#endif #ifdef MENU_DAT if (Key_Dest_Has(kdm_gmenu)) MP_Keyup (key, unicode, devid); @@ -2803,6 +2833,19 @@ void Key_Event (unsigned int devid, int key, unsigned int unicode, qboolean down return; } } +#ifdef MENU_NATIVECODE + if (Key_Dest_Has(kdm_nmenu)) + { + if (mn_entry) + { + struct menu_inputevent_args_s ev = {MIE_KEYDOWN, devid}; + ev.key.scancode = key; + ev.key.charcode = unicode; + if (mn_entry->InputEvent(ev)) + return; + } + } +#endif #ifdef MENU_DAT if (Key_Dest_Has(kdm_gmenu)) { diff --git a/engine/client/keys.h b/engine/client/keys.h index c3866bf36..2233d4ff7 100644 --- a/engine/client/keys.h +++ b/engine/client/keys.h @@ -250,16 +250,22 @@ typedef enum //highest has priority kdm_centerprint = 1u<<1, //enabled when there's a centerprint menu with clickable things. kdm_message = 1u<<2, kdm_gmenu = 1u<<3, //menu.dat - kdm_emenu = 1u<<4, //engine's menus - kdm_editor = 1u<<5, - kdm_console = 1u<<6, - kdm_cwindows = 1u<<7, +#ifdef MENU_NATIVECODE + kdm_nmenu = 1u<<4, +#else + kdm_nmenu = 0, +#endif + kdm_emenu = 1u<<5, //engine's menus + kdm_editor = 1u<<6, + kdm_console = 1u<<7, + kdm_cwindows = 1u<<8, } keydestmask_t; //unsigned int Key_Dest_Get(void); //returns highest priority destination #define Key_Dest_Add(kdm) (key_dest_mask |= (kdm)) #define Key_Dest_Remove(kdm) (key_dest_mask &= ~(kdm)) #define Key_Dest_Has(kdm) (key_dest_mask & (kdm)) +#define Key_Dest_Has_Higher(kdm) (key_dest_mask & (~0&~((kdm)|((kdm)-1)))) //must be a single bit #define Key_Dest_Toggle(kdm) do {if (key_dest_mask & kdm) Key_Dest_Remove(kdm); else Key_Dest_Add(kdm);}while(0) extern unsigned int key_dest_absolutemouse; //if the active key dest bit is set, the mouse is absolute. @@ -271,9 +277,12 @@ extern int key_lastpress; enum { - kc_game, - kc_menu, - kc_console, + kc_game, //csprogs.dat + kc_menu, //menu.dat +#ifdef MENU_NATIVECODE + kc_nmenu, +#endif + kc_console, //generic engine-defined cursor kc_max }; extern struct key_cursor_s diff --git a/engine/client/m_native.c b/engine/client/m_native.c new file mode 100644 index 000000000..8f4f5b3c9 --- /dev/null +++ b/engine/client/m_native.c @@ -0,0 +1,286 @@ +#include "quakedef.h" +#ifdef MENU_NATIVECODE +static dllhandle_t *libmenu; +menu_export_t *mn_entry; + +extern unsigned int r2d_be_flags; +#include "pr_common.h" +#include "shader.h" +#include "cl_master.h" + +static int MN_checkextension(const char *extname) +{ + unsigned int i; + for (i = 0; i < QSG_Extensions_count; i++) + { + if (!strcmp(QSG_Extensions[i].name, extname)) + return true; + } + return false; +} +static void MN_localcmd(const char *text) +{ + Cbuf_AddText(text, RESTRICT_LOCAL); //menus are implicitly trusted. latching and other stuff would be a nightmare otherwise. +} +static void MN_registercvar(const char *cvarname, const char *defaulttext, unsigned int flags, const char *description) +{ + Cvar_Get2(cvarname, defaulttext, flags, description, NULL); +} +static int MN_getserverstate(void) +{ + if (!sv.active) + return 0; + if (svs.allocated_client_slots <= 1) + return 1; + return 2; +} +static int MN_getclientstate(void) +{ + if (cls.state >= ca_active) + return 2; + if (cls.state != ca_disconnected) + return 1; + return 0; +} +static void MN_fclose(vfsfile_t *f) +{ + VFS_CLOSE(f); +} +static void *MN_precache_pic(const char *picname) +{ + return R2D_SafeCachePic(picname); +} +static int MN_drawgetimagesize(void *pic, int *w, int *h) +{ + return R_GetShaderSizes(pic, w, h, true); +} +static void MN_drawquad(vec2_t position[4], vec2_t texcoords[4], void *pic, vec4_t rgba, unsigned int be_flags) +{ + r2d_be_flags = be_flags; + R2D_ImageColours(rgba[0], rgba[1], rgba[2], rgba[3]); + R2D_Image2dQuad(position, texcoords, pic); + r2d_be_flags = 0; +} +static float MN_drawstring(vec2_t position, const char *text, float height, vec4_t rgba, unsigned int be_flags) +{ + float px, py, ix; + unsigned int codeflags, codepoint; + conchar_t buffer[2048], *str = buffer; + COM_ParseFunString(CON_WHITEMASK, text, buffer, sizeof(buffer), false); + + Font_BeginScaledString(font_default, position[0], position[1], height, height, &px, &py); + ix=px; + while(*str) + { + str = Font_Decode(str, &codeflags, &codepoint); + px = Font_DrawScaleChar(px, py, codeflags, codepoint); + } + Font_EndString(font_default); + return ((px-ix)*(float)vid.width)/(float)vid.rotpixelwidth; +} +static float MN_stringwidth(const char *text, float height) +{ + float px, py; + conchar_t buffer[2048], *end; + end = COM_ParseFunString(CON_WHITEMASK, text, buffer, sizeof(buffer), false); + + Font_BeginScaledString(font_default, 0, 0, height, height, &px, &py); + px = Font_LineScaleWidth(buffer, end); + Font_EndString(font_default); + return (px * (float)vid.width) / (float)vid.rotpixelwidth; +} +static void MN_drawsetcliparea(float x, float y, float width, float height) +{ + srect_t srect; + if (R2D_Flush) + R2D_Flush(); + + srect.x = x / (float)vid.fbvwidth; + srect.y = y / (float)vid.fbvheight; + srect.width = width / (float)vid.fbvwidth; + srect.height = height / (float)vid.fbvheight; + srect.dmin = -99999; + srect.dmax = 99999; + srect.y = (1-srect.y) - srect.height; + BE_Scissor(&srect); +} +static void MN_drawresetcliparea(void) +{ + if (R2D_Flush) + R2D_Flush(); + BE_Scissor(NULL); +} +static qboolean MN_setkeydest(qboolean focused) +{ + qboolean ret = Key_Dest_Has(kdm_nmenu); + if (ret == focused) + return false; + if (focused) + { + if (key_dest_absolutemouse & kdm_nmenu) + { //we're activating the mouse cursor now... make sure the position is actually current. + struct menu_inputevent_args_s ev = {MIE_MOUSEABS, -1}; + ev.mouse.screen[0] = mousecursor_x; + ev.mouse.screen[1] = mousecursor_y; + mn_entry->InputEvent(ev); + } + Key_Dest_Add(kdm_nmenu); + } + else + Key_Dest_Remove(kdm_nmenu); + return true; +} +static int MN_getkeydest(void) +{ + if (Key_Dest_Has(kdm_nmenu)) + { + if (Key_Dest_Has_Higher(kdm_nmenu)) + return -1; + return 1; + } + return 0; +} +static int MN_setmousetarget(const char *cursorname, float hot_x, float hot_y, float scale) +{ + if (cursorname) + { + struct key_cursor_s *m = &key_customcursor[kc_nmenu]; + if (scale <= 0) + scale = 1; + if (!strcmp(m->name, cursorname) || m->hotspot[0] != hot_x || m->hotspot[1] != hot_y || m->scale != scale) + { + Q_strncpyz(m->name, cursorname, sizeof(m->name)); + m->hotspot[0] = hot_x; + m->hotspot[1] = hot_y; + m->scale = scale; + m->dirty = true; + } + key_dest_absolutemouse |= kdm_nmenu; + } + else + key_dest_absolutemouse &= ~kdm_nmenu; + return true; +} + +void MN_Shutdown(void) +{ + Key_Dest_Remove(kdm_nmenu); + if (mn_entry) + { + mn_entry->Shutdown(); + mn_entry = NULL; + } + if (libmenu) + { + Sys_CloseLibrary(libmenu); + libmenu = NULL; + } +} +qboolean MN_Init(void) +{ + menu_export_t *(QDECL *pGetMenuAPI) ( menu_import_t *import ); + static menu_import_t imports = + { + NATIVEMENU_API_VERSION_MAX, + + MN_checkextension, + Host_Error, + Con_Printf, + Con_DPrintf, + MN_localcmd, + Cvar_VariableValue, + Cvar_VariableString, + Cvar_SetNamed, + MN_registercvar, + + MN_getserverstate, + MN_getclientstate, + S_LocalSound2, + + // file input / search crap + FS_OpenVFS, + MN_fclose, + VFS_GETS, + VFS_PRINTF, + COM_EnumerateFiles, + + // Drawing stuff + MN_precache_pic, + MN_drawgetimagesize, + MN_drawquad, + MN_drawstring, + MN_stringwidth, + MN_drawsetcliparea, + MN_drawresetcliparea, + + // Menu specific stuff + MN_setkeydest, + MN_getkeydest, + MN_setmousetarget, + Key_KeynumToString, + Key_StringToKeynum, + M_FindKeysForBind, + + // Server browser stuff + NULL,//MN_gethostcachevalue, + Master_ReadKeyString, + Master_ReadKeyFloat, + + Master_ClearMasks, + Master_SetMaskString, + Master_SetMaskInteger, + Master_SetSortField, + Master_SortServers, + Master_SortedServer, + MasterInfo_Refresh, + + Master_KeyForName, + }; + dllfunction_t funcs[] = + { + {(void*)&pGetMenuAPI, "GetMenuAPI"}, + {NULL} + }; + void *iterator = NULL; + char syspath[MAX_OSPATH]; + char gamepath[MAX_QPATH]; + + while(COM_IteratePaths(&iterator, syspath, sizeof(syspath), gamepath, sizeof(gamepath))) + { + if (!com_nogamedirnativecode.ival) + libmenu = Sys_LoadLibrary(va("%smenu_"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, syspath), funcs); + if (libmenu) + break; + + if (host_parms.binarydir && !strchr(gamepath, '/') && !strchr(gamepath, '\\')) + libmenu = Sys_LoadLibrary(va("%smenu_%s_"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, host_parms.binarydir, gamepath), funcs); + if (libmenu) + break; + + //some build systems don't really know the cpu type. + if (host_parms.binarydir && !strchr(gamepath, '/') && !strchr(gamepath, '\\')) + libmenu = Sys_LoadLibrary(va("%smenu_%s" ARCH_DL_POSTFIX, host_parms.binarydir, gamepath), funcs); + if (libmenu) + break; + } + + if (libmenu) + { + key_dest_absolutemouse |= kdm_nmenu; + + mn_entry = pGetMenuAPI (&imports); + if (mn_entry && mn_entry->api_version >= NATIVEMENU_API_VERSION_MIN && mn_entry->api_version <= NATIVEMENU_API_VERSION_MAX) + { + mn_entry->Init(); + return true; + } + else + mn_entry = NULL; + MN_Shutdown(); + Sys_CloseLibrary(libmenu); + libmenu = NULL; + } + + return false; +} +#endif \ No newline at end of file diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 0f06d7296..3bd4c0e6e 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -3638,8 +3638,12 @@ void M_Menu_ModelViewer_f(void) typedef struct { - ftemanifest_t **manifests; - size_t nummanifests; + struct + { + ftemanifest_t *manifest; + char *gamedir; + } *mod; + size_t nummods; int y; } modmenu_t; @@ -3652,7 +3656,7 @@ static void Mods_Draw(int x, int y, struct menucustom_s *c, struct menu_s *m) mods->y = y; - if (!mods->nummanifests) + if (!mods->nummods) { Draw_FunString(x, y+0, "No games or mods known"); #if defined(FTE_TARGET_WEB) || defined(NACL) @@ -3665,12 +3669,22 @@ static void Mods_Draw(int x, int y, struct menucustom_s *c, struct menu_s *m) return; } - for (i = 0; y+8 <= ym && i < mods->nummanifests; y+=8, i++) + for (i = 0; y+8 <= ym && i < mods->nummods; y+=8, i++) { - if (mousecursor_y >= y && mousecursor_y < y+8) - Draw_AltFunString(x, y, mods->manifests[i]->formalname); + if (mods->mod[i].manifest) + { + if (mousecursor_y >= y && mousecursor_y < y+8) + Draw_AltFunString(x, y, mods->mod[i].manifest->formalname); + else + Draw_FunString(x, y, mods->mod[i].manifest->formalname); + } else - Draw_FunString(x, y, mods->manifests[i]->formalname); + { + if (mousecursor_y >= y && mousecursor_y < y+8) + Draw_AltFunString(x, y, mods->mod[i].gamedir); + else + Draw_FunString(x, y, mods->mod[i].gamedir); + } } } static qboolean Mods_Key(struct menucustom_s *c, struct menu_s *m, int key, unsigned int unicode) @@ -3678,14 +3692,14 @@ static qboolean Mods_Key(struct menucustom_s *c, struct menu_s *m, int key, unsi modmenu_t *mods = c->dptr; int i; ftemanifest_t *man; - if (key == K_MOUSE1) + if (key == K_MOUSE1 || key == K_ENTER || key == K_GP_A) { qboolean wasgameless = !*FS_GetGamedir(false); i = (mousecursor_y - mods->y)/8; - if (i < 0 || i > mods->nummanifests) + if (i < 0 || i > mods->nummods) return false; - man = mods->manifests[i]; - mods->manifests[i] = NULL; //make sure the manifest survives the menu being closed. + man = mods->mod[i].manifest; + mods->mod[i].manifest = NULL; //make sure the manifest survives the menu being closed. M_RemoveMenu(m); FS_ChangeGame(man, true, true); @@ -3711,22 +3725,60 @@ static void Mods_Remove (struct menu_s *m) modmenu_t *mods = m->data; int i; - for (i = 0; i < mods->nummanifests; i++) + for (i = 0; i < mods->nummods; i++) { - if (mods->manifests[i]) - FS_Manifest_Free(mods->manifests[i]); + if (mods->mod[i].manifest) + FS_Manifest_Free(mods->mod[i].manifest); + Z_Free(mods->mod[i].gamedir); } - Z_Free(mods->manifests); - mods->manifests = NULL; + Z_Free(mods->mod); + mods->mod = NULL; } -static qboolean Mods_AddMod(void *usr, ftemanifest_t *man) +static qboolean Mods_AddManifest(void *usr, ftemanifest_t *man) { modmenu_t *mods = usr; - int i = mods->nummanifests; - mods->manifests = BZ_Realloc(mods->manifests, (i+1) * sizeof(*mods->manifests)); - mods->manifests[i] = man; - mods->nummanifests = i+1; + int i = mods->nummods; + mods->mod = BZ_Realloc(mods->mod, (i+1) * sizeof(*mods->mod)); + mods->mod[i].manifest = man; + mods->mod[i].gamedir = NULL; + mods->nummods = i+1; + return true; +} +static int QDECL Mods_AddGamedir(const char *fname, qofs_t fsize, time_t mtime, void *usr, searchpathfuncs_t *spath) +{ + modmenu_t *mods = usr; + size_t l = strlen(fname); + int i, p; + char gamedir[MAX_QPATH]; + if (l && fname[l-1] == '/' && l < countof(gamedir)) + { + l--; + memcpy(gamedir, fname, l); + gamedir[l] = 0; + for (i = 0; i < mods->nummods; i++) + { + //don't add dupes (can happen from gamedir+homedir) + //if the gamedir was already included in one of the manifests, don't bother including it again. + //this generally removes id1. + if (mods->mod[i].manifest) + { + for (p = 0; p < countof(fs_manifest->gamepath); p++) + if (mods->mod[i].manifest->gamepath[p].path) + if (!Q_strcasecmp(mods->mod[i].manifest->gamepath[p].path, gamedir)) + return true; + } + else if (mods->mod[i].gamedir) + { + if (!Q_strcasecmp(mods->mod[i].gamedir, gamedir)) + return true; + } + } + mods->mod = BZ_Realloc(mods->mod, (i+1) * sizeof(*mods->mod)); + mods->mod[i].manifest = NULL; + mods->mod[i].gamedir = Z_StrDup(gamedir); + mods->nummods = i+1; + } return true; } @@ -3737,9 +3789,16 @@ void M_Menu_Mods_f (void) modmenu_t mods; menucustom_t *c; menu_t *menu; + extern qboolean com_homepathenabled; memset(&mods, 0, sizeof(mods)); - FS_EnumerateKnownGames(Mods_AddMod, &mods); + FS_EnumerateKnownGames(Mods_AddManifest, &mods); + + if (com_homepathenabled) + Sys_EnumerateFiles(com_homepath, "*", Mods_AddGamedir, &mods, NULL); + Sys_EnumerateFiles(com_gamepath, "*", Mods_AddGamedir, &mods, NULL); + + //FIXME: sort by mtime? Key_Dest_Add(kdm_emenu); diff --git a/engine/client/menu.c b/engine/client/menu.c index ad20ec30b..07e327e66 100644 --- a/engine/client/menu.c +++ b/engine/client/menu.c @@ -196,6 +196,74 @@ int M_FindKeysForCommand (int bindmap, int pnum, const char *command, int *keyli return M_FindKeysForBind(bindmap, va("%s%s", prefix, command), keylist, keymods, keycount); } +/* +================ +M_ToggleMenu_f +================ +*/ +void M_ToggleMenu_f (void) +{ +#ifndef NOBUILTINMENUS + if (topmenu) + { + Key_Dest_Add(kdm_emenu); + return; + } +#endif + +#ifdef CSQC_DAT + if (CSQC_ConsoleCommand(-1, "togglemenu")) + { + Key_Dest_Remove(kdm_console|kdm_cwindows); + return; + } +#endif +#ifdef MENU_DAT + if (MP_Toggle(1)) + { + Key_Dest_Remove(kdm_console|kdm_cwindows); + return; + } +#endif +#ifdef MENU_NATIVECODE + if (mn_entry) + { + mn_entry->Toggle(1); + Key_Dest_Remove(kdm_console|kdm_cwindows); + return; + } +#endif +#ifdef VM_UI + if (UI_OpenMenu()) + return; +#endif + +#ifndef NOBUILTINMENUS + M_Menu_Main_f (); + Key_Dest_Remove(kdm_console|kdm_cwindows); +#endif +} + +/* +================ +M_Restart_f +================ +*/ +void M_Init_Internal (void); +void M_Restart_f(void) +{ + M_Shutdown(false); + + if (!strcmp(Cmd_Argv(1), "off")) + { //explicitly restart the engine's menu. not the menuqc crap + //don't even start csqc menus. + M_Init_Internal(); + } + else + M_Reinit(); +} + + #ifndef NOBUILTINMENUS void M_Menu_Audio_f (void); @@ -318,61 +386,6 @@ void M_CloseMenu_f (void) M_RemoveAllMenus(false); Key_Dest_Remove(kdm_emenu); } -/* -================ -M_ToggleMenu_f -================ -*/ -void M_ToggleMenu_f (void) -{ - if (topmenu) - { - Key_Dest_Add(kdm_emenu); - return; - } - -#ifdef CSQC_DAT - if (CSQC_ConsoleCommand(-1, "togglemenu")) - { - Key_Dest_Remove(kdm_console|kdm_cwindows); - return; - } -#endif -#ifdef MENU_DAT - if (MP_Toggle(1)) - { - Key_Dest_Remove(kdm_console|kdm_cwindows); - return; - } -#endif -#ifdef VM_UI - if (UI_OpenMenu()) - return; -#endif - - //it IS a toggle, so close the menu if its already active - if (Key_Dest_Has(kdm_emenu)) - { - Key_Dest_Remove(kdm_emenu); - return; - } - if (Key_Dest_Has(kdm_console|kdm_cwindows)) - Key_Dest_Remove(kdm_console|kdm_cwindows); -/* - { - if (cls.state != ca_active) - { - Key_Dest_Remove(kdm_console); - M_Menu_Main_f(); - } - else - Con_ToggleConsole_Force (); - } - else*/ - { - M_Menu_Main_f (); - } -} //============================================================================= /* KEYS MENU */ @@ -1316,6 +1329,9 @@ void M_DeInit_Internal (void) void M_Shutdown(qboolean total) { +#ifdef MENU_NATIVECODE + MN_Shutdown(); +#endif #ifdef MENU_DAT MP_Shutdown(); #endif @@ -1325,6 +1341,9 @@ void M_Shutdown(qboolean total) void M_Reinit(void) { +#ifdef MENU_NATIVECODE + if (!MN_Init()) +#endif #ifdef MENU_DAT if (!MP_Init()) #endif @@ -1341,7 +1360,7 @@ void M_MenuPop_f(void); //menu.dat is loaded later... after the video and everything is up. void M_Init (void) { - + Cmd_AddCommand("menu_restart", M_Restart_f); Cmd_AddCommand("togglemenu", M_ToggleMenu_f); Cmd_AddCommand("closemenu", M_CloseMenu_f); Cmd_AddCommand("fps_preset", FPS_Preset_f); @@ -1380,12 +1399,18 @@ void M_Init_Internal (void){} void M_DeInit_Internal (void){} void M_Shutdown(qboolean total) { +#ifdef MENU_NATIVECODE + MN_Shutdown(); +#endif #ifdef MENU_DAT MP_Shutdown(); #endif } void M_Reinit(void) { +#ifdef MENU_NATIVECODE + if (!MN_Init()) +#endif #ifdef MENU_DAT if (!MP_Init()) #endif @@ -1395,6 +1420,9 @@ void M_Reinit(void) } void M_Init (void) { + Cmd_AddCommand("menu_restart", M_Restart_f); + Cmd_AddCommand("togglemenu", M_ToggleMenu_f); + Media_Init(); M_Reinit(); } diff --git a/engine/client/menu.h b/engine/client/menu.h index 2b3834090..6e6f11fb2 100644 --- a/engine/client/menu.h +++ b/engine/client/menu.h @@ -110,7 +110,6 @@ void M_Shutdown(qboolean total); void M_Keydown (int key, int unicode); void M_Keyup (int key, int unicode); void M_Draw (int uimenu); -void M_ToggleMenu_f (void); void M_Menu_Mods_f (void); //used at startup if the current gamedirs look dodgy. void M_Menu_Installer (void); //given an embedded manifest, this displays an install menu for said game. mpic_t *M_CachePic (char *path); @@ -454,7 +453,6 @@ void M_UnbindCommand (const char *command); //no builtin menu code. //stubs #define M_Menu_Prompt(cb,ctx,messages,optionyes,optionno,optioncancel) (cb)(ctx,-1) -#define M_ToggleMenu_f() Cbuf_AddText("togglemenu\n",RESTRICT_LOCAL) //#define M_Shutdown(t) MP_Shutdown() void M_Init (void); @@ -466,6 +464,7 @@ void M_Draw (int uimenu); #endif int M_FindKeysForCommand (int bindmap, int pnum, const char *command, int *keylist, int *keymods, int keycount); int M_FindKeysForBind (int bindmap, const char *command, int *keylist, int *keymods, int keycount); +void M_ToggleMenu_f (void); #ifdef MENU_DAT void MP_CvarChanged(cvar_t *var); @@ -484,6 +483,13 @@ int MP_BuiltinValid(const char *name, int num); qboolean MP_ConsoleCommand(const char *cmdtext); #endif +#ifdef MENU_NATIVECODE +#include "../native/api_menu.h" +extern menu_export_t *mn_entry; +void MN_Shutdown(void); +qboolean MN_Init(void); +#endif + #define MGT_BAD ~0 #define MGT_QUAKE1 0 #define MGT_HEXEN2 1 diff --git a/engine/client/net_master.c b/engine/client/net_master.c index 0b49a2785..1d78e4327 100644 --- a/engine/client/net_master.c +++ b/engine/client/net_master.c @@ -1067,7 +1067,7 @@ void Master_ResortServer(serverinfo_t *server) } } -void Master_SortServers(void) +int Master_SortServers(void) { serverinfo_t *server; @@ -1088,6 +1088,8 @@ void Master_SortServers(void) { Master_ResortServer(server); } + + return numvisibleservers; } serverinfo_t *Master_SortedServer(int idx) @@ -1104,7 +1106,7 @@ int Master_NumSorted(void) } -float Master_ReadKeyFloat(serverinfo_t *server, int keynum) +float Master_ReadKeyFloat(serverinfo_t *server, hostcachekey_t keynum) { if (!server) return -1; @@ -1164,7 +1166,7 @@ void Master_DecodeColour(vec3_t ret, int col) VectorSet(ret, ((col&0xff0000)>>16)/255.0, ((col&0x00ff00)>>8)/255.0, ((col&0x0000ff)>>0)/255.0); } -char *Master_ReadKeyString(serverinfo_t *server, int keynum) +char *Master_ReadKeyString(serverinfo_t *server, hostcachekey_t keynum) { static char adr[MAX_ADR_SIZE]; @@ -1237,7 +1239,7 @@ char *Master_ReadKeyString(serverinfo_t *server, int keynum) return ""; } -int Master_KeyForName(const char *keyname) +hostcachekey_t Master_KeyForName(const char *keyname) { int i; if (!strcmp(keyname, "map")) diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index 59b4ce164..2349a9826 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -7804,14 +7804,12 @@ qboolean CSQC_DrawView(void) if (csqcg.frametime) { - if (csqc_isdarkplaces) + if (1)//csqc_isdarkplaces) { - static double oldtime; if (cl.paused) *csqcg.frametime = 0; //apparently people can't cope with microstutter when they're using this as a test to see if the game is paused. else - *csqcg.frametime = bound(0, cl.time - oldtime, 0.1); - oldtime = cl.time; + *csqcg.frametime = bound(0, cl.time - cl.lasttime, 0.1); } else *csqcg.frametime = host_frametime; diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index d9b5f1991..7b478288b 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -2700,19 +2700,6 @@ void MP_CoreDump_f(void) } } -void MP_Reload_f(void) -{ - M_Shutdown(false); - - if (!strcmp(Cmd_Argv(1), "off")) - { //explicitly restart the engine's menu. not the menuqc crap - //don't even start csqc menus. - M_Init_Internal(); - } - else - M_Reinit(); -} - static void MP_Poke_f(void) { /*if (!SV_MayCheat()) @@ -2751,7 +2738,6 @@ void MP_Breakpoint_f(void) void MP_RegisterCvarsAndCmds(void) { Cmd_AddCommand("coredump_menuqc", MP_CoreDump_f); - Cmd_AddCommand("menu_restart", MP_Reload_f); Cmd_AddCommand("menu_cmd", MP_GameCommand_f); Cmd_AddCommand("breakpoint_menu", MP_Breakpoint_f); Cmd_AddCommand("loadfont", CL_LoadFont_f); diff --git a/engine/client/quakedef.h b/engine/client/quakedef.h index f9f08e7fa..fe6dbad83 100644 --- a/engine/client/quakedef.h +++ b/engine/client/quakedef.h @@ -321,8 +321,8 @@ void Host_InitCommands (void); void Host_Init (quakeparms_t *parms); void Host_FinishInit(void); void Host_Shutdown(void); -NORETURN void VARGS Host_Error (char *error, ...) LIKEPRINTF(1); -NORETURN void VARGS Host_EndGame (char *message, ...) LIKEPRINTF(1); +NORETURN void VARGS Host_Error (const char *error, ...) LIKEPRINTF(1); +NORETURN void VARGS Host_EndGame (const char *message, ...) LIKEPRINTF(1); qboolean Host_SimulationTime(float time); double Host_Frame (double time); qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file); diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c index ca6c2557f..aa6e2bba7 100644 --- a/engine/client/r_surf.c +++ b/engine/client/r_surf.c @@ -1734,7 +1734,12 @@ static void Surf_BuildLightMap_Worker (model_t *wmodel, msurface_t *surf, qbyte else { // set to full bright if no light data - if (!surf->samples || !wmodel->lightdata) + if (r_fullbright.ival) + { + for (i=0 ; ilightdata) { for (i=0 ; icached_light[0] = d_lightstylevalue[0]; surf->cached_colour[0] = cl_lightstyle[0].colourkey; } - else if (r_fullbright.ival) + else if (!surf->samples) { for (i=0 ; inext = webostates; + webostates = webostate; webogenerating = NULL; webogeneratingstate = 0; mod = webostate->wmodel; @@ -3097,7 +3107,9 @@ void R_GeneratedWorldEBO(void *ctx, void *data, size_t a_, size_t b_) memset(m, 0, sizeof(*m)); if (b->shader->flags & SHADER_NEEDSARRAYS) - { + { //this ebo cache stuff tracks only indexes, we don't know the actual surfs any more. + //if NEEDSARRAYS is flagged then the cpu will need access to the mesh data - which it doesn't have. + //while we could figure out this info, there would be a lot of vertexes that are not referenced, which would be horrendously slow. if (b->shader->flags & SHADER_SKY) continue; b->shader = R_RegisterShader_Vertex("unsupported"); @@ -3286,10 +3298,29 @@ void Surf_DrawWorld (void) #ifdef THREADEDWORLD if ((r_dynamic.ival < 0 || currentmodel->numbatches) && !r_refdef.recurse && currentmodel->type == mod_brush) { - if (webostate && webostate->wmodel != currentmodel) + struct webostate_s *webostate, *best = NULL, *generating = NULL; + vec_t bestdist = FLT_MAX; + for (webostate = webostates; webostate; webostate = webostate->next) { - R_DestroyWorldEBO(webostate); - webostate = NULL; + if (webostate->wmodel != currentmodel) + continue; + if (webostate->cluster[0] == r_viewcluster && webostate->cluster[1] == r_viewcluster2) + { + best = webostate; + break; + } + else + { + vec3_t m; + float d; + VectorSubtract(webostate->lastpos, r_refdef.vieworg, m); + d = DotProduct(m,m); + if (bestdist > d) + { + bestdist = d; + best = webostate; + } + } } if (qrenderer != QR_OPENGL && qrenderer != QR_VULKAN) @@ -3304,7 +3335,7 @@ void Surf_DrawWorld (void) if (webostate->lightstylevalues[i] != d_lightstylevalue[i]) break; } - if (webostate && webostate->cluster[0] == r_viewcluster && webostate->cluster[1] == r_viewcluster2 && i == MAX_LIGHTSTYLES) + if (webostate && i == MAX_LIGHTSTYLES) { } else @@ -3542,8 +3573,12 @@ void Surf_DeInit(void) #ifdef THREADEDWORLD while(webogenerating) COM_WorkerPartialSync(webogenerating, &webogeneratingstate, true); - R_DestroyWorldEBO(webostate); - webostate = NULL; + while (webostates) + { + void *webostate = webostates; + webostates = webostates->next; + R_DestroyWorldEBO(webostate); + } #endif for (i = 0; i < numlightmaps; i++) @@ -3578,13 +3613,19 @@ void Surf_Clear(model_t *mod) // return;/*they're on the hunk*/ #ifdef THREADEDWORLD + struct webostate_s **link, *t; while(webogenerating) COM_WorkerPartialSync(webogenerating, &webogeneratingstate, true); - if (webostate && webostate->wmodel == mod) + for (link = &webostates; (t=*link); ) { - R_DestroyWorldEBO(webostate); - webostate = NULL; + if (t->wmodel == mod) + { + *link = t->next; + R_DestroyWorldEBO(t); + } + else + link = &(*link)->next; } #endif @@ -4076,8 +4117,12 @@ void Surf_ClearLightmaps(void) #ifdef THREADEDWORLD while(webogenerating) COM_WorkerPartialSync(webogenerating, &webogeneratingstate, true); - R_DestroyWorldEBO(webostate); - webostate = NULL; + while (webostates) + { + void *webostate = webostates; + webostates = webostates->next; + R_DestroyWorldEBO(webostate); + } #endif } @@ -4215,6 +4260,10 @@ TRACE(("dbg: Surf_NewMap: tp\n")); { vec3_t mins, maxs; //fixme: no rotation + if (!cl_static_entities[i].ent.model && cl_static_entities[i].mdlidx > 0 && cl_static_entities[i].mdlidx < countof(cl.model_precache)) + cl_static_entities[i].ent.model = cl.model_precache[cl_static_entities[i].mdlidx]; + else if (!cl_static_entities[i].ent.model && cl_static_entities[i].mdlidx < 0 && (-cl_static_entities[i].mdlidx) < countof(cl.model_csqcprecache)) + cl_static_entities[i].ent.model = cl.model_csqcprecache[-cl_static_entities[i].mdlidx]; if (cl_static_entities[i].ent.model) { //unfortunately, we need to know the actual size so that we can get this right. bum. diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 9f993570b..45f32147b 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -227,7 +227,7 @@ cvar_t scr_conalpha = CVARC ("scr_conalpha", "0.7", Cvar_Limiter_ZeroToOne_Callback); cvar_t scr_consize = CVAR ("scr_consize", "0.5"); cvar_t scr_conspeed = CVAR ("scr_conspeed", "2000"); -// 10 - 170 +cvar_t scr_fov_mode = CVARFD ("scr_fov_mode", "0", CVAR_ARCHIVE, "Controls what the fov cvar actually controls:\n0: largest axis (ultra-wide monitors means less height will be visible).\n1: smallest axis (ultra-wide monitors will distort at the edges).\n2: horizontal axis.\n3: vertical axis."); cvar_t scr_fov = CVARFCD("fov", "90", CVAR_ARCHIVE, SCR_Fov_Callback, "field of vision, 1-170 degrees, standard fov is 90, nquake defaults to 108."); cvar_t scr_fov_viewmodel = CVARFD("r_viewmodel_fov", "", CVAR_ARCHIVE, @@ -904,6 +904,7 @@ void Renderer_Init(void) Cvar_Register(&scr_viewsize, SCREENOPTIONS); Cvar_Register(&scr_fov, SCREENOPTIONS); Cvar_Register(&scr_fov_viewmodel, SCREENOPTIONS); + Cvar_Register(&scr_fov_mode, SCREENOPTIONS); Cvar_Register (&scr_sshot_type, SCREENOPTIONS); Cvar_Register (&scr_sshot_compression, SCREENOPTIONS); diff --git a/engine/client/view.c b/engine/client/view.c index 167e65c1c..4d9f6288d 100644 --- a/engine/client/view.c +++ b/engine/client/view.c @@ -1166,15 +1166,67 @@ float CalcFov (float fov_x, float width, float height) return a; } +static void V_CalcAFov(float afov, float *x, float *y, float w, float h) +{ + extern cvar_t scr_fov_mode; + extern cvar_t r_stereo_separation; + int mode = scr_fov_mode.ival; +#ifdef FTE_TARGET_WEB + if (r_refdef.stereomethod == STEREO_WEBVR) + w *= 0.5; +#endif + if (r_refdef.stereomethod == STEREO_CROSSEYED && r_stereo_separation.value) + w *= 0.5; + + afov = bound(0.001, afov, 170); + +restart: + switch(mode) + { + default: + case 0: //maj- + if (h > w) + { //fov specified is interpreted as the horizontal fov at quake's original res: 640*432 (ie: with the sbar removed)... + // afov = CalcFov(afov, 640, 432); + mode = 3; //vertical + } + else + mode = 2; //horizontal + goto restart; + case 1: //min+ + if (h < w) + mode = 3; //vertical + else + mode = 2; //horizontal + goto restart; + case 2: //horizontal + *x = afov; + *y = CalcFov(afov, w, h); + + if (*y > 170) + { + *y = 170; + *x = CalcFov(170, h, w); + } + break; + case 3: //vertical + *y = afov; + *x = CalcFov(afov, h, w); + + if (*x > 170) + { //don't allow screwed fovs. + *x = 170; + *y = CalcFov(afov, w, h); + } + break; + } +} void V_ApplyAFov(playerview_t *pv) { //explicit fov overrides aproximate fov. //aproximate fov is our regular fov value. explicit is settable by gamecode for weird aspect ratios if (!r_refdef.fov_x || !r_refdef.fov_y) { - extern cvar_t r_stereo_separation; - float ws; - float afov = r_refdef.afov; if (!afov) //make sure its sensible. { @@ -1184,59 +1236,15 @@ void V_ApplyAFov(playerview_t *pv) afov *= pv->statsf[STAT_VIEWZOOM]/STAT_VIEWZOOM_SCALE; #endif } - afov = bound(0.001, afov, 170); - ws = 1; -#ifdef FTE_TARGET_WEB - if (r_refdef.stereomethod == STEREO_WEBVR) - ws = 0.5; -#endif - if (r_refdef.stereomethod == STEREO_CROSSEYED && r_stereo_separation.value) - ws = 0.5; - - //attempt to retain a classic fov - if (ws*r_refdef.vrect.width < (r_refdef.vrect.height*640)/432) - { - r_refdef.fov_y = CalcFov(afov, (ws*r_refdef.vrect.width*r_refdef.pxrect.width)/vid.fbvwidth, (r_refdef.vrect.height*r_refdef.pxrect.height)/vid.fbvheight); - r_refdef.fov_x = afov;//CalcFov(r_refdef.fov_y, 432, 640); - } - else - { - r_refdef.fov_y = CalcFov(afov, 640, 432); - r_refdef.fov_x = CalcFov(r_refdef.fov_y, r_refdef.vrect.height, r_refdef.vrect.width*ws); - } + V_CalcAFov(afov, &r_refdef.fov_x, &r_refdef.fov_y, (r_refdef.vrect.width*r_refdef.pxrect.width)/vid.fbvwidth, (r_refdef.vrect.height*r_refdef.pxrect.height)/vid.fbvheight); } if (!r_refdef.fovv_x || !r_refdef.fovv_y) { - extern cvar_t r_stereo_separation; - float ws; - float afov = scr_fov_viewmodel.value; if (afov) - { - afov = bound(0.001, afov, 170); - - ws = 1; -#ifdef FTE_TARGET_WEB - if (r_refdef.stereomethod == STEREO_WEBVR) - ws = 0.5; -#endif - if (r_refdef.stereomethod == STEREO_CROSSEYED && r_stereo_separation.value) - ws = 0.5; - - //attempt to retain a classic fov - if (ws*r_refdef.vrect.width < (r_refdef.vrect.height*640)/432) - { - r_refdef.fovv_y = CalcFov(afov, (ws*r_refdef.vrect.width*r_refdef.pxrect.width)/vid.fbvwidth, (r_refdef.vrect.height*r_refdef.pxrect.height)/vid.fbvheight); - r_refdef.fovv_x = afov;//CalcFov(r_refdef.fov_y, 432, 640); - } - else - { - r_refdef.fovv_y = CalcFov(afov, 640, 432); - r_refdef.fovv_x = CalcFov(r_refdef.fovv_y, r_refdef.vrect.height, r_refdef.vrect.width*ws); - } - } + V_CalcAFov(afov, &r_refdef.fovv_x, &r_refdef.fovv_y, (r_refdef.vrect.width*r_refdef.pxrect.width)/vid.fbvwidth, (r_refdef.vrect.height*r_refdef.pxrect.height)/vid.fbvheight); else { r_refdef.fovv_x = r_refdef.fov_x; diff --git a/engine/common/common.h b/engine/common/common.h index 20931eb52..3e405abf2 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -558,8 +558,8 @@ typedef struct vfsfile_s #define VFS_WRITE(vf,buffer,buflen) ((vf)->WriteBytes(vf,buffer,buflen)) #define VFS_FLUSH(vf) do{if((vf)->Flush)(vf)->Flush(vf);}while(0) #define VFS_PUTS(vf,s) do{const char *t=s;(vf)->WriteBytes(vf,t,strlen(t));}while(0) -char *VFS_GETS(vfsfile_t *vf, char *buffer, int buflen); -void VARGS VFS_PRINTF(vfsfile_t *vf, char *fmt, ...) LIKEPRINTF(2); +char *VFS_GETS(vfsfile_t *vf, char *buffer, size_t buflen); +void VARGS VFS_PRINTF(vfsfile_t *vf, const char *fmt, ...) LIKEPRINTF(2); enum fs_relative{ FS_BINARYPATH, //for dlls and stuff diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 8f3e95c92..65a1f7142 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -650,6 +650,15 @@ char *Cvar_VariableString (const char *var_name) return var->string; } +void Cvar_SetNamed (const char *var_name, const char *newvalue) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return; + Cvar_Set(var, newvalue); +} /* ============ diff --git a/engine/common/cvar.h b/engine/common/cvar.h index b31c71b55..9e344f434 100644 --- a/engine/common/cvar.h +++ b/engine/common/cvar.h @@ -202,6 +202,8 @@ float Cvar_VariableValue (const char *var_name); char *Cvar_VariableString (const char *var_name); // returns an empty string if not defined +void Cvar_SetNamed (const char *var_name, const char *newvalue); + char *Cvar_CompleteVariable (const char *partial); // attempts to match a partial variable name for command line completion // returns NULL if nothing fits diff --git a/engine/common/fs.c b/engine/common/fs.c index 1186010e1..5b960a88e 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -114,14 +114,14 @@ void FS_UnRegisterFileSystemModule(void *module) } } -char *VFS_GETS(vfsfile_t *vf, char *buffer, int buflen) +char *VFS_GETS(vfsfile_t *vf, char *buffer, size_t buflen) { char in; char *out = buffer; - int len; - len = buflen-1; - if (len == 0) + size_t len; + if (buflen <= 1) return NULL; + len = buflen-1; while (len > 0) { if (VFS_READ(vf, &in, 1) != 1) @@ -146,7 +146,7 @@ char *VFS_GETS(vfsfile_t *vf, char *buffer, int buflen) return buffer; } -void VARGS VFS_PRINTF(vfsfile_t *vf, char *format, ...) +void VARGS VFS_PRINTF(vfsfile_t *vf, const char *format, ...) { va_list argptr; char string[1024]; @@ -4462,8 +4462,8 @@ static int FS_IdentifyDefaultGameFromDir(const char *basedir) //attempt to work out which game we're meant to be trying to run based upon a few things //1: fs_changegame console command override. fixme: needs to cope with manifests too. -//2: -quake3 argument implies that the user wants to run quake3. -//3: if we are ftequake3.exe then we always try to run quake3. +//2: -quake3 (etc) argument implies that the user wants to run quake3. +//3: otherwise if we are ftequake3.exe then we try to run quake3. //4: identify characteristic files within the working directory (like id1/pak0.pak implies we're running quake) //5: check where the exe actually is instead of simply where we're being run from. //6: try the homedir, just in case. diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index fcf5ebdbf..67a16e938 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -3932,12 +3932,32 @@ struct strbuf { char **strings; size_t used; size_t allocated; + int flags; }; +#define BUFFLAG_SAVED 1 #define BUFSTRBASE 1 struct strbuf *strbuflist; size_t strbufmax; +static void PR_buf_savegame(vfsfile_t *f, pubprogfuncs_t *prinst, qboolean binary) +{ + size_t i, bufno; + + for (bufno = 0; bufno < strbufmax; bufno++) + { + if (strbuflist[bufno].prinst == prinst && (strbuflist[bufno].flags & BUFFLAG_SAVED)) + { + VFS_PRINTF (f, "buffer %i %i %i %i\n", bufno, 1, ev_string, strbuflist[bufno].used); + VFS_PRINTF (f, "{\n"); + for (i = 0; i < strbuflist[bufno].used; i++) + if (strbuflist[bufno].strings[i]) + VFS_PRINTF (f, "%i %s\n", i, strbuflist[bufno].strings[i]); + VFS_PRINTF (f, "}\n"); + } + } +} + void PF_buf_shutdown(pubprogfuncs_t *prinst) { size_t i, bufno; @@ -3972,7 +3992,8 @@ void QCBUILTIN PF_buf_create (pubprogfuncs_t *prinst, struct globalvars_s *pr_g size_t i; const char *type = ((prinst->callargc>0)?PR_GetStringOfs(prinst, OFS_PARM0):"string"); -// unsigned int flags = ((prinst->callargc>1)?G_FLOAT(OFS_PARM1):1); + unsigned int flags = ((prinst->callargc>1)?G_FLOAT(OFS_PARM1):BUFFLAG_SAVED); + flags &= BUFFLAG_SAVED; if (!Q_strcasecmp(type, "string")) ; @@ -4001,6 +4022,7 @@ void QCBUILTIN PF_buf_create (pubprogfuncs_t *prinst, struct globalvars_s *pr_g strbuflist[i].used = 0; strbuflist[i].allocated = 0; strbuflist[i].strings = NULL; + strbuflist[i].flags = flags; G_FLOAT(OFS_RETURN) = i+BUFSTRBASE; } // #441 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) @@ -6239,6 +6261,10 @@ void PR_Common_Shutdown(pubprogfuncs_t *progs, qboolean errored) #endif tokenize_flush(); } +void PR_Common_SaveGame(vfsfile_t *f, pubprogfuncs_t *prinst, qboolean binary) +{ + PR_buf_savegame(f, prinst, binary); +} #define DEF_SAVEGLOBAL (1u<<15) diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index 2026001e0..55c9eb42e 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -499,6 +499,7 @@ void QCBUILTIN PF_whichpack (pubprogfuncs_t *prinst, struct globalvars_s *pr_glo int QDECL QCEditor (pubprogfuncs_t *prinst, const char *filename, int *line, int *statement, char *reason, pbool fatal); void PR_Common_Shutdown(pubprogfuncs_t *progs, qboolean errored); +void PR_Common_SaveGame(vfsfile_t *f, pubprogfuncs_t *prinst, qboolean binary); //FIXME pbool PR_RunWarning (pubprogfuncs_t *ppf, char *error, ...); diff --git a/engine/gl/gl_shader.c b/engine/gl/gl_shader.c index a8fa4b26e..40be9d55f 100644 --- a/engine/gl/gl_shader.c +++ b/engine/gl/gl_shader.c @@ -5425,6 +5425,8 @@ done:; for (i = 0; i < s->numpasses; i++) { pass = &s->passes[i]; + if (pass->prog) + continue; if (pass->numtcmods || (s->passes[i].tcgen != TC_GEN_BASE && s->passes[i].tcgen != TC_GEN_LIGHTMAP) || !(s->passes[i].flags & SHADER_PASS_NOCOLORARRAY)) { s->flags |= SHADER_NEEDSARRAYS; diff --git a/engine/gl/gl_vidlinuxglx.c b/engine/gl/gl_vidlinuxglx.c index 5c36ee9c3..96129a645 100644 --- a/engine/gl/gl_vidlinuxglx.c +++ b/engine/gl/gl_vidlinuxglx.c @@ -60,9 +60,13 @@ none of these issues will be fixed by a compositing window manager, because ther #ifdef VKQUAKE #include "vk/vkrenderer.h" +#ifdef VK_USE_PLATFORM_XLIB_KHR static qboolean XVK_SetupSurface_XLib(void); +#endif +#ifdef VK_USE_PLATFORM_XCB_KHR static qboolean XVK_SetupSurface_XCB(void); #endif +#endif #ifdef GLQUAKE #include #ifdef USE_EGL diff --git a/engine/qclib/pr_edict.c b/engine/qclib/pr_edict.c index 3e527f824..f7588f076 100644 --- a/engine/qclib/pr_edict.c +++ b/engine/qclib/pr_edict.c @@ -1477,7 +1477,7 @@ char *ED_WriteGlobals(progfuncs_t *progfuncs, char *buf, size_t *bufofs, size_t ddef32_t *def32; ddef16_t *def16; unsigned int i; - unsigned int j; +// unsigned int j; const char *name; int type; int curprogs = prinst.pr_typecurrent; @@ -1538,13 +1538,13 @@ char *ED_WriteGlobals(progfuncs_t *progfuncs, char *buf, size_t *bufofs, size_t v = (int *)¤t_progstate->globals[def16->ofs]; - // make sure the value is not null, where there's no point in saving +/* // make sure the value is not null, where there's no point in saving for (j=0 ; jfuncs, def16->type&~DEF_SAVEGLOBAL, (eval_t *)v)); AddS("\"\n"); } @@ -1594,12 +1594,12 @@ char *ED_WriteGlobals(progfuncs_t *progfuncs, char *buf, size_t *bufofs, size_t v = (int *)¤t_progstate->globals[def32->ofs]; - // make sure the value is not null, where there's no point in saving +/* // make sure the value is not null, where there's no point in saving for (j=0 ; jfuncs, def32->type&~DEF_SAVEGLOBAL, (eval_t *)v)); AddS("\"\n"); } @@ -1748,6 +1748,9 @@ char *PR_SaveCallStack (progfuncs_t *progfuncs, char *buf, size_t *bufofs, size_ //there are two ways of saving everything. //0 is to save just the entities. //1 is to save the entites, and all the progs info so that all the variables are saved off, and it can be reloaded to exactly how it was (provided no files or data has been changed outside, like the progs.dat for example) +//2 is for vanilla-compatible saved games +//3 is a (human-readable) coredump +//4 is binary saved games. char *PDECL PR_SaveEnts(pubprogfuncs_t *ppf, char *buf, size_t *bufofs, size_t bufmax, int alldata) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; @@ -1764,8 +1767,12 @@ char *PDECL PR_SaveEnts(pubprogfuncs_t *ppf, char *buf, size_t *bufofs, size_t b } *bufofs = 0; - if (alldata == 2) - { //special Q1 savegame compatability mode. + switch(alldata) + { + default: + return NULL; + case 2: + //special Q1 savegame compatability mode. //engine will need to store references to progs type and will need to preload the progs and inti the ents itself before loading. //Make sure there is only 1 progs loaded. @@ -1785,9 +1792,7 @@ char *PDECL PR_SaveEnts(pubprogfuncs_t *ppf, char *buf, size_t *bufofs, size_t b oldprogs = prinst.pr_typecurrent; PR_SwitchProgs(progfuncs, 0); - ED_WriteGlobals(progfuncs, buf, bufofs, bufmax); - PR_SwitchProgs(progfuncs, oldprogs); AddS ("}\n"); @@ -1807,10 +1812,11 @@ char *PDECL PR_SaveEnts(pubprogfuncs_t *ppf, char *buf, size_t *bufofs, size_t b } return buf; - } - if (alldata) - { + case 0: //Writes entities only + break; + case 1: + case 3: AddS("general {\n"); AddS(qcva("\"maxprogs\" \"%i\"\n", prinst.maxprogs)); // AddS(qcva("\"maxentities\" \"%i\"\n", maxedicts)); @@ -1856,6 +1862,7 @@ char *PDECL PR_SaveEnts(pubprogfuncs_t *ppf, char *buf, size_t *bufofs, size_t b } PR_SwitchProgs(progfuncs, oldprogs); } + for (a = 0; a < sv_num_edicts; a++) { edictrun_t *ed = (edictrun_t *)EDICT_NUM(progfuncs, a); diff --git a/engine/qclib/qcc_cmdlib.c b/engine/qclib/qcc_cmdlib.c index 35c460102..3f350ae22 100644 --- a/engine/qclib/qcc_cmdlib.c +++ b/engine/qclib/qcc_cmdlib.c @@ -838,7 +838,7 @@ long ParseNum (char *str) #define MAXQCCFILES 3 struct { - char name[64]; + char *name; FILE *stdio; char *buff; int buffsize; @@ -848,16 +848,11 @@ struct { int SafeOpenWrite (char *filename, int maxsize) { int i; - if (strlen(filename) >= sizeof(qccfile[0].name)) - { - QCC_Error(ERR_TOOMANYOPENFILES, "Filename %s too long", filename); - return -1; - } for (i = 0; i < MAXQCCFILES; i++) { if (!qccfile[i].stdio && !qccfile[i].buff) { - strcpy(qccfile[i].name, filename); + qccfile[i].name = strdup(filename); qccfile[i].buffsize = maxsize; qccfile[i].maxofs = 0; qccfile[i].ofs = 0; @@ -926,6 +921,8 @@ pbool SafeClose(int hand) ret = externs->WriteFile(qccfile[hand].name, qccfile[hand].buff, qccfile[hand].maxofs); free(qccfile[hand].buff); } + free(qccfile[hand].name); + qccfile[hand].name = NULL; qccfile[hand].buff = NULL; qccfile[hand].stdio = NULL; return ret; diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index 286a1924e..9fc074fc2 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -6935,6 +6935,41 @@ static QCC_ref_t *QCC_PR_ParseField(QCC_ref_t *refbuf, QCC_ref_t *lhs) return lhs; } +//this is more complex than it needs to be, in order to ensure that anon unions/structs can be handled. +struct QCC_typeparam_s *QCC_PR_FindStructMember(QCC_type_t *t, const char *membername, unsigned int *out_ofs) +{ + unsigned int nofs; + int i; + struct QCC_typeparam_s *r = NULL, *n; + for (i = 0; i < t->num_parms; i++) + { + if ((!t->params[i].paramname || !*t->params[i].paramname) && (t->params[i].type->type == ev_struct || t->params[i].type->type == ev_union)) + { //anonymous structs/unions can nest + n = QCC_PR_FindStructMember(t->params[i].type, membername, &nofs); + if (n) + { + if (r) + break; + r = n; + *out_ofs = t->params[i].ofs + nofs; + } + } + else if (flag_caseinsensitive?!stricmp (t->params[i].paramname, membername):!STRCMP(t->params[i].paramname, membername)) + { + if (r) + break; + r = t->params+i; + *out_ofs = r->ofs; + } + } + if (i < t->num_parms) + { + QCC_PR_ParseError(0, "multiple members found matching %s.%s", t->name, membername); + return NULL; + } + return r; +} + /*checks for: [X] [X].foo @@ -7177,8 +7212,9 @@ vectorarrayindex: else if (((t->type == ev_pointer && !arraysize) || (t->type == ev_field && (t->aux_type->type == ev_struct || t->aux_type->type == ev_union)) || t->type == ev_struct || t->type == ev_union) && (QCC_PR_CheckToken(".") || QCC_PR_CheckToken("->"))) { char *tname; - unsigned int i; + unsigned int ofs; pbool fld = t->type == ev_field; + struct QCC_typeparam_s *p; if (!idx.cast && t->type == ev_pointer && !arraysize) { t = t->aux_type; @@ -7200,18 +7236,14 @@ vectorarrayindex: else QCC_PR_ParseError(0, "indirection in something that is not a struct or union", tname); - for (i = 0; i < t->num_parms; i++) - { - if (QCC_PR_CheckName(t->params[i].paramname)) - break; - } - if (i == t->num_parms) + p = QCC_PR_FindStructMember(t, QCC_PR_ParseName(), &ofs); + if (!p) QCC_PR_ParseError(0, "%s is not a member of %s", pr_token, tname); - if (!t->params[i].ofs && idx.cast) + if (!ofs && idx.cast) ; else if (QCC_OPCodeValid(&pr_opcodes[OP_ADD_I])) { - tmp = QCC_MakeIntConst(t->params[i].ofs); + tmp = QCC_MakeIntConst(ofs); if (idx.cast) idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], QCC_SupplyConversion(idx, ev_integer, true), QCC_SupplyConversion(tmp, ev_integer, true), NULL); else @@ -7219,14 +7251,14 @@ vectorarrayindex: } else { - tmp = QCC_MakeFloatConst(t->params[i].ofs); + tmp = QCC_MakeFloatConst(ofs); if (idx.cast) idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], QCC_SupplyConversion(idx, ev_float, true), QCC_SupplyConversion(tmp, ev_float, true), NULL); else idx = tmp; } - arraysize = t->params[i].arraysize; - t = t->params[i].type; + arraysize = p->arraysize; + t = p->type; if (fld) t = QCC_PR_FieldType(t); diff --git a/engine/qclib/qcc_pr_lex.c b/engine/qclib/qcc_pr_lex.c index 1d4a60d56..cd404bf1f 100644 --- a/engine/qclib/qcc_pr_lex.c +++ b/engine/qclib/qcc_pr_lex.c @@ -3657,7 +3657,10 @@ void QCC_PR_Lex (void) QCC_PR_LexWhitespace (false); - pr_token_line_last = pr_token_line; + if (currentchunk) + pr_token_line_last = currentchunk->currentlinenumber-1 + pr_token_line; + else + pr_token_line_last = pr_token_line; pr_token_line = pr_source_line; if (!pr_file_p) diff --git a/engine/qclib/qccgui.c b/engine/qclib/qccgui.c index 3a6b2a3e5..0fdbac612 100644 --- a/engine/qclib/qccgui.c +++ b/engine/qclib/qccgui.c @@ -2727,6 +2727,8 @@ static LRESULT CALLBACK EditorWndProc(HWND hWnd,UINT message, { CHARRANGE chrg; + if (!editor->modified) + editor->oldline=~0; editor->modified = true; if (EditorModified(editor)) if (MessageBox(NULL, "warning: file was modified externally. reload?", "Modified!", MB_YESNO) == IDYES) @@ -2835,9 +2837,11 @@ static LRESULT CALLBACK EditorWndProc(HWND hWnd,UINT message, Scin_HandleCharAdded(editor, not, pos); break; case SCN_SAVEPOINTREACHED: + editor->oldline=~0; editor->modified = false; break; case SCN_SAVEPOINTLEFT: + editor->oldline=~0; editor->modified = true; if (EditorModified(editor)) @@ -3338,6 +3342,7 @@ int EditorSave(editor_t *edit) edit->filemodifiedtime = sbuf.st_mtime; //remove the * in a silly way. + edit->oldline=~0; UpdateEditorTitle(edit); return true; @@ -5833,8 +5838,23 @@ static LRESULT CALLBACK MainWndProc(HWND hWnd,UINT message, int i; RECT rect; PAINTSTRUCT ps; + editor_t *editor; switch (message) { + case WM_CLOSE: + //if any child editors are still open, send close requests to them first. + //this allows them to display prompts, instead of silently losing changes. + for (editor = editors; editor;) + { + editor_t *n = editor->next; + if (editor->window) + SendMessage(editor->window, WM_CLOSE, 0, 0); + editor = n; + } + //okay, they're all dead. we can kill ourselves now. + if (!editors) + DestroyWindow(hWnd); + return 0; case WM_CREATE: { CLIENTCREATESTRUCT ccs; diff --git a/engine/qclib/qccmain.c b/engine/qclib/qccmain.c index d2092413a..a49e11d40 100644 --- a/engine/qclib/qccmain.c +++ b/engine/qclib/qccmain.c @@ -397,6 +397,7 @@ struct { } targets[] = { {QCF_STANDARD, "standard"}, {QCF_STANDARD, "q1"}, + {QCF_STANDARD, "id"}, {QCF_STANDARD, "quakec"}, {QCF_HEXEN2, "hexen2"}, {QCF_HEXEN2, "h2"}, @@ -4141,6 +4142,8 @@ void QCC_PR_CommandLinePrecompilerOptions (void) { flag_acc = true; } + else if (!strcmp(myargv[i]+5, "frikqcc")) + keyword_state = true; else if (!strcmp(myargv[i]+5, "fteqcc")) ; //as above, its the default. else if (!strcmp(myargv[i]+5, "id")) diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index e2f9bdaa2..d538c1c13 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -12733,7 +12733,7 @@ void PR_DumpPlatform_f(void) "accessor infostring : string\n{\n" "\tget string[string] = infoget;\n" #ifdef QCGC - "\tinline set* string[string fld] = {(*this) = infoadd(*this, fld, value);};\n" + "\tinline seti& string[string fld] = {this = infoadd(this, fld, value);};\n" #endif "};\n"); VFS_PRINTF(f, diff --git a/engine/server/savegame.c b/engine/server/savegame.c index ccae6b290..1815e5deb 100644 --- a/engine/server/savegame.c +++ b/engine/server/savegame.c @@ -9,6 +9,9 @@ extern cvar_t coop; extern cvar_t teamplay; extern cvar_t pr_enable_profiling; +cvar_t sv_savefmt = CVARFD("sv_savefmt", "1", CVAR_SAVE, "Specifies the format used for the saved game.\n0=legacy.\n1=fte\n2=binary"); +cvar_t sv_autosave = CVARFD("sv_autosave", "5", CVAR_SAVE, "Interval for autosaves, in minutes. Set to 0 to disable autosave."); + void SV_Savegame_f (void); //Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current @@ -73,6 +76,7 @@ void SV_SavegameComment (char *text, size_t textsize) text[textsize-1] = '\0'; } +#ifndef QUAKETC //expects the version to have already been parsed void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) { @@ -282,7 +286,7 @@ void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) // the rest of the file is sent directly to the progs engine. if (version == 5 || version == 6) - Q_InitProgs(); //reinitialize progs entirly. + ;//Q_InitProgs(); //reinitialize progs entirly. else { Q_SetProgsParms(false); @@ -360,7 +364,7 @@ void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) } } -void SV_LegacySavegame_f (void) +static void SV_LegacySavegame (const char *savename) { size_t len; char *s = NULL; @@ -375,18 +379,6 @@ void SV_LegacySavegame_f (void) int i; char comment[SAVEGAME_COMMENT_LENGTH+1]; - if (Cmd_Argc() != 2) - { - Con_TPrintf ("save : save a game\n"); - return; - } - - if (strstr(Cmd_Argv(1), "..")) - { - Con_TPrintf ("Relative pathnames are not allowed\n"); - return; - } - if (sv.state != ss_active) { Con_TPrintf("Can't apply: Server isn't running or is still loading\n"); @@ -401,12 +393,12 @@ void SV_LegacySavegame_f (void) return; } - sprintf (name, "%s", Cmd_Argv(1)); + sprintf (name, "%s", savename); COM_RequireExtension (name, ".sav", sizeof(name)); if (!FS_NativePath(name, FS_GAMEONLY, native, sizeof(native))) return; Con_TPrintf (U8("Saving game to %s...\n"), native); - f = FS_OpenVFS(name, "wb", FS_GAMEONLY); + f = FS_OpenVFS(name, "wbp", FS_GAMEONLY); if (!f) { Con_TPrintf ("ERROR: couldn't open %s.\n", name); @@ -486,13 +478,16 @@ void SV_LegacySavegame_f (void) svprogfuncs->parms->memfree(s); VFS_CLOSE(f); -} + FS_FlushFSHashWritten(name); +} +#endif #define CACHEGAME_VERSION_OLD 513 #define CACHEGAME_VERSION_VERBOSE 514 +#define CACHEGAME_VERSION_BINARY 515 @@ -1065,7 +1060,7 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) } } - if (version == CACHEGAME_VERSION_VERBOSE) + if (version >= CACHEGAME_VERSION_VERBOSE) { char buf[8192]; char *mode = "?"; @@ -1102,6 +1097,8 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) for (i = 0; i < sizeof(sv.strings.vw_model_precache)/sizeof(sv.strings.vw_model_precache[0]); i++) VFS_PRINTF (f, "vwep %i %s\n", i, COM_QuotedString(sv.strings.vw_model_precache[i], buf, sizeof(buf), false)); + PR_Common_SaveGame(f, svprogfuncs, version >= CACHEGAME_VERSION_BINARY); + //FIXME: string buffers //FIXME: hash tables //FIXME: skeletal objects? @@ -1109,9 +1106,10 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) //FIXME: midi track //FIXME: custom temp-ents? //FIXME: pending uri_gets? (if only just to report fails) + //FIXME: routing calls? //FIXME: sql queries? //FIXME: frik files? - //FIXME: threads? + //FIXME: qc threads? VFS_PRINTF (f, "entities\n"); } @@ -1150,10 +1148,18 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) VFS_PRINTF (f,"\n"); } - s = PR_SaveEnts(svprogfuncs, NULL, &len, 0, 1); - VFS_PUTS(f, s); - VFS_PUTS(f, "\n"); - svprogfuncs->parms->memfree(s); + if (version >= CACHEGAME_VERSION_BINARY) + { + VFS_PUTS(f, va("%i\n", svprogfuncs->stringtablesize)); + VFS_WRITE(f, svprogfuncs->stringtable, svprogfuncs->stringtablesize); + } + else + { + s = PR_SaveEnts(svprogfuncs, NULL, &len, 0, 1); + VFS_PUTS(f, s); + VFS_PUTS(f, "\n"); + svprogfuncs->parms->memfree(s); + } VFS_CLOSE (f); @@ -1167,11 +1173,12 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) } } - FS_FlushFSHashFull(); + FS_FlushFSHashWritten(name); } #define FTESAVEGAME_VERSION 25000 +//mapchange is true for Q2's map-change autosaves. void SV_Savegame (const char *savename, qboolean mapchange) { extern cvar_t nomonsters; @@ -1199,6 +1206,14 @@ void SV_Savegame (const char *savename, qboolean mapchange) char str[MAX_LOCALINFO_STRING+1]; char *savefilename; +#ifndef QUAKETC + if (!sv_savefmt.ival && !mapchange) + { + SV_LegacySavegame(savename); + return; + } +#endif + if (!sv.state || sv.state == ss_clustermode) { Con_Printf("Server is not active - unable to save\n"); @@ -1240,7 +1255,7 @@ void SV_Savegame (const char *savename, qboolean mapchange) savefilename = va("saves/%s/info.fsv", savename); FS_CreatePath(savefilename, FS_GAMEONLY); - f = FS_OpenVFS(savefilename, "wb", FS_GAMEONLY); + f = FS_OpenVFS(savefilename, "wbp", FS_GAMEONLY); if (!f) { Con_Printf("Couldn't open file saves/%s/info.fsv\n", savename); @@ -1417,6 +1432,7 @@ static int QDECL CompleteSaveList (const char *name, qofs_t flags, time_t mtime, } return true; } +#ifndef QUAKETC static int QDECL CompleteSaveListLegacy (const char *name, qofs_t flags, time_t mtime, void *parm, searchpathfuncs_t *spath) { struct xcommandargcompletioncb_s *ctx = parm; @@ -1425,24 +1441,39 @@ static int QDECL CompleteSaveListLegacy (const char *name, qofs_t flags, time_t ctx->cb(stripped, NULL, NULL, ctx); return true; } +#endif void SV_Savegame_c(int argn, const char *partial, struct xcommandargcompletioncb_s *ctx) { if (argn == 1) { COM_EnumerateFiles(va("saves/%s*/info.fsv", partial), CompleteSaveList, ctx); +#ifndef QUAKETC COM_EnumerateFiles(va("%s*.sav", partial), CompleteSaveListLegacy, ctx); +#endif } } void SV_Savegame_f (void) { if (Cmd_Argc() <= 2) - SV_Savegame(Cmd_Argv(1), false); + { + const char *savename = Cmd_Argv(1); + if (strstr(savename, "..")) + { + Con_TPrintf ("Relative pathnames are not allowed\n"); + return; + } +#ifndef QUAKETC + if (!Q_strcasecmp(Cmd_Argv(0), "savegame_legacy")) + SV_LegacySavegame(savename); + else +#endif + SV_Savegame(savename, false); + } else Con_Printf("%s: invalid number of arguments\n", Cmd_Argv(0)); } -cvar_t sv_autosave = CVARFD("sv_autosave", "5", CVAR_SAVE, "Interval for autosaves, in minutes. Set to 0 to disable autosave."); void SV_AutoSave(void) { #ifndef NOBUILTINMENUS @@ -1512,21 +1543,44 @@ void SV_Loadgame_f (void) gametype_e gametype; int len; + struct + { + char *pattern; + flocation_t loc; + } savefiles[] = + { + {"saves/%s/info.fsv"}, +#ifndef QUAKETC + {"%s.sav"}, +#endif + }; + int bd,best; + +#ifndef SERVERONLY + if (!Renderer_Started() && !isDedicated) + { + Cbuf_AddText(va("wait;%s %s\n", Cmd_Argv(0), Cmd_Args()), Cmd_ExecLevel); + return; + } +#endif Q_strncpyz(savename, Cmd_Argv(1), sizeof(savename)); if (!*savename || strstr(savename, "..")) strcpy(savename, "quick"); - Q_snprintfz (filename, sizeof(filename), "saves/%s/info.fsv", savename); - f = FS_OpenVFS (filename, "rb", FS_GAME); - if (!f) + for (len = 0, bd=0x7fffffff,best=0; len < countof(savefiles); len++) { - f = FS_OpenVFS (va("%s.sav", savename), "rb", FS_GAME); - if (f) - Q_snprintfz (filename, sizeof(filename), "%s.sav", savename); + int d = FS_FLocateFile(va(savefiles[len].pattern, savename), FSLF_DONTREFERENCE|FSLF_DEEPONFAILURE, &savefiles[len].loc); + if (bd > d) + { + bd = d; + best = len; + } } - + + Q_snprintfz (filename, sizeof(filename), savefiles[best].pattern, savename); + f = FS_OpenReadLocation(&savefiles[best].loc); if (!f) { Con_TPrintf ("ERROR: couldn't open %s.\n", filename); @@ -1541,7 +1595,12 @@ void SV_Loadgame_f (void) version = atoi(str); if (version < FTESAVEGAME_VERSION || version >= FTESAVEGAME_VERSION+GT_MAX) { +#ifdef QUAKETC + VFS_CLOSE (f); + Con_TPrintf ("Unable to load savegame of version %i\n", version); +#else SV_Loadgame_Legacy(filename, f, version); +#endif return; } diff --git a/engine/server/server.h b/engine/server/server.h index 3f35d26c2..b20eb9507 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -1581,6 +1581,7 @@ void SV_Loadgame_f (void); void SV_AutoSave(void); void SV_FlushLevelCache(void); extern cvar_t sv_autosave; +extern cvar_t sv_savefmt; int SV_RateForClient(client_t *cl); diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 9765394bf..dbe7105e0 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -2542,6 +2542,33 @@ static void SV_Gamedir (void) Info_SetValueForStarKey (svs.info, "*gamedir", dir, MAX_SERVERINFO_STRING); } +static int QDECL CompleteGamedirPath (const char *name, qofs_t flags, time_t mtime, void *parm, searchpathfuncs_t *spath) +{ + struct xcommandargcompletioncb_s *ctx = parm; + char dirname[MAX_QPATH]; + if (*name) + { + size_t l = strlen(name)-1; + if (l < countof(dirname) && name[l] == '/') + { //directories are marked with an explicit trailing slash. because we're weird. + memcpy(dirname, name, l); + dirname[l] = 0; + ctx->cb(dirname, NULL, NULL, ctx); + } + } + return true; +} +static void SV_Gamedir_c(int argn, const char *partial, struct xcommandargcompletioncb_s *ctx) +{ + extern qboolean com_homepathenabled; + if (argn == 1) + { + if (com_homepathenabled) + Sys_EnumerateFiles(com_homepath, va("%s*", partial), CompleteGamedirPath, ctx, NULL); + Sys_EnumerateFiles(com_gamepath, va("%s*", partial), CompleteGamedirPath, ctx, NULL); + } +} + /* ================ SV_Gamedir_f @@ -3045,8 +3072,8 @@ void SV_InitOperatorCommands (void) Cmd_AddCommand ("heartbeat", SV_Heartbeat_f); Cmd_AddCommand ("localinfo", SV_Localinfo_f); - Cmd_AddCommandD ("gamedir", SV_Gamedir_f, "Change the current gamedir."); - Cmd_AddCommand ("sv_gamedir", SV_Gamedir); + Cmd_AddCommandAD ("gamedir", SV_Gamedir_f, SV_Gamedir_c, "Change the current gamedir."); + Cmd_AddCommandAD ("sv_gamedir", SV_Gamedir, SV_Gamedir_c, "Change the gamedir reported to clients, without changing any actual paths on the server."); Cmd_AddCommand ("sv_settimer", SV_SetTimer_f); Cmd_AddCommand ("stuffcmd", SV_StuffToClient_f); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index fb39e7334..9db45c37d 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -5213,7 +5213,10 @@ void SV_InitLocal (void) Cvar_Register(&sv_autosave, cvargroup_servercontrol); #endif #endif - Cmd_AddCommandAD ("savegame_legacy", SV_LegacySavegame_f, SV_Savegame_c, "Saves the game in a format compatible with vanilla Quake. Anything not supported by that format will be lost."); + Cvar_Register(&sv_savefmt, cvargroup_servercontrol); +#ifndef QUAKETC + Cmd_AddCommandAD ("savegame_legacy", SV_Savegame_f, SV_Savegame_c, "Saves the game in a format compatible with vanilla Quake. Anything not supported by that format will be lost."); +#endif Cmd_AddCommandAD ("savegame", SV_Savegame_f, SV_Savegame_c, "Saves the game to the named location."); Cmd_AddCommandAD ("loadgame", SV_Loadgame_f, SV_Savegame_c, "Loads an existing saved game."); Cmd_AddCommandAD ("save", SV_Savegame_f, SV_Savegame_c, "Saves the game to the named location.");