diff --git a/engine/Makefile b/engine/Makefile index 3267ea4be..504c7c2a5 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -1552,11 +1552,11 @@ m-profile: _qcc-tmp: $(REQDIR) @$(MAKE) $(TYPE) EXE_NAME="$(EXE_NAME)$(EXEPOSTFIX)" PRECOMPHEADERS="" OUT_DIR="$(OUT_DIR)" WCFLAGS="$(CLIENT_ONLY_CFLAGS) $(WCFLAGS)" LDFLAGS="$(LDFLAGS)" OBJS="QCC_OBJS SOBJS" qcc-rel: - @$(MAKE) _qcc-tmp TYPE=_out-rel REQDIR=reldir EXE_NAME="../fteqcc" OUT_DIR="$(RELEASE_DIR)/qcc" SOBJS="qcctui.o" + @$(MAKE) _qcc-tmp TYPE=_out-rel REQDIR=reldir EXE_NAME="../fteqcc" OUT_DIR="$(RELEASE_DIR)/qcc" SOBJS="qcctui.o $(if $(findstring win,$(FTE_TARGET)),fteqcc.o)" qccgui-rel: @$(MAKE) _qcc-tmp TYPE=_out-rel REQDIR=reldir EXE_NAME="../fteqccgui" OUT_DIR="$(RELEASE_DIR)/qcc" SOBJS="qccgui.o qccguistuff.o fteqcc.o" LDFLAGS="$(LDFLAGS) -lole32 -lcomdlg32 -lcomctl32 -lshlwapi -mwindows" qcc-dbg: - @$(MAKE) _qcc-tmp TYPE=_out-dbg REQDIR=debugdir EXE_NAME="../fteqcc" OUT_DIR="$(DEBUG_DIR)/qcc" SOBJS="qcctui.o" + @$(MAKE) _qcc-tmp TYPE=_out-dbg REQDIR=debugdir EXE_NAME="../fteqcc" OUT_DIR="$(DEBUG_DIR)/qcc" SOBJS="qcctui.o $(if $(findstring win,$(FTE_TARGET)),fteqcc.o)" qccgui-dbg: @$(MAKE) _qcc-tmp TYPE=_out-dbg REQDIR=debugdir EXE_NAME="../fteqccgui" OUT_DIR="$(DEBUG_DIR)/qcc" SOBJS="qccgui.o qccguistuff.o fteqcc.o" LDFLAGS="$(LDFLAGS) -lole32 -lcomdlg32 -lcomctl32 -lshlwapi -mwindows" @@ -1577,7 +1577,7 @@ scintilla$(BITS)_static: @$(MAKE) reldir OUT_DIR=$(RELEASE_DIR)/qcc @$(MAKE) $(RELEASE_DIR)/scintilla$(BITS).a VPATH="$(SCINTILLA_DIRS)" CFLAGS="$(SCINTILLA_INC) -DDISABLE_D2D -DSTATIC_BUILD -DSCI_LEXER" OUT_DIR=$(RELEASE_DIR)/qcc WCFLAGS="$(WCFLAGS) -Os" qccgui-scintilla: scintilla$(BITS)_static - @$(MAKE) _qcc-tmp TYPE=_out-rel REQDIR=reldir EXE_NAME="../fteqccgui" OUT_DIR="$(RELEASE_DIR)/qcc" SOBJS="qccgui.o qccguistuff.o" WCFLAGS="$(WCFLAGS) -DSCISTATIC" LDFLAGS="$(LDFLAGS) $(RELEASE_DIR)/scintilla$(BITS).a -static -luuid -lole32 -limm32 -lstdc++ -lcomdlg32 -lcomctl32 -lshlwapi -mwindows" + @$(MAKE) _qcc-tmp TYPE=_out-rel REQDIR=reldir EXE_NAME="../fteqccgui" OUT_DIR="$(RELEASE_DIR)/qcc" SOBJS="qccgui.o qccguistuff.o fteqcc.o" WCFLAGS="$(WCFLAGS) -DSCISTATIC" LDFLAGS="$(LDFLAGS) $(RELEASE_DIR)/scintilla$(BITS).a -static -luuid -lole32 -limm32 -lstdc++ -lcomdlg32 -lcomctl32 -lshlwapi -mwindows" ifdef windir debugdir: diff --git a/engine/client/cl_cam.c b/engine/client/cl_cam.c index d0d29992c..b4a293706 100644 --- a/engine/client/cl_cam.c +++ b/engine/client/cl_cam.c @@ -51,6 +51,7 @@ static void QDECL CL_AutoTrackChanged(cvar_t *v, char *oldval) Cam_AutoTrack_Update(v->string); } // track high fragger +cvar_t cl_autotrack_team = CVARD("cl_autotrack_team", "", "Specifies a team name that should be auto-tracked (players on other teams will not be candidates for autotracking). Accepts * and ? wildcards for awkward chars."); cvar_t cl_autotrack = CVARCD("cl_autotrack", "auto", CL_AutoTrackChanged, "Specifies the default tracking mode at the start of the map. Use the 'autotrack' command to reset/apply an auto-tracking mode without changing the default.\nValid values are: high, ^hkiller^h, mod, user. Other values are treated as weighting scripts for mvd playback, where available."); cvar_t cl_hightrack = CVARD("cl_hightrack", "0", "Obsolete. If you want hightrack, use '[cl_]autotrack high' instead."); @@ -346,12 +347,14 @@ static float CL_TrackScore(player_info_t *pl, char **rule, float *weights, int p *rule = s; return l; } -static qboolean CL_MayTrack(int seat, int player) +static qboolean CL_MayAutoTrack(int seat, int player) { if (player < 0) return false; if (!cl.players[player].userid || !cl.players[player].name[0] || cl.players[player].spectator) return false; + if (*cl_autotrack_team.string && !wildcmp(cl_autotrack_team.string, cl.players[player].team)) + return false; for (seat--; seat >= 0; seat--) { //not valid if an earlier view is tracking it. @@ -418,7 +421,7 @@ static int CL_FindHighTrack(int seat, char *rule) //set a default to the currently tracked player, to reuse the current player we're tracking if someone lower equalises. j = cl.playerview[seat].cam_spec_track; - if (CL_MayTrack(seat, j)) + if (CL_MayAutoTrack(seat, j)) { p = rule; max = CL_TrackScore(&cl.players[j], &p, weights, PRI_TOP); @@ -434,7 +437,7 @@ static int CL_FindHighTrack(int seat, char *rule) s = &cl.players[i]; if (j == i) //this was our default. continue; - if (!CL_MayTrack(seat, i)) + if (!CL_MayAutoTrack(seat, i)) continue; if (cl.teamplay && seat && cl.playerview[0].cam_spec_track >= 0 && strcmp(cl.players[cl.playerview[0].cam_spec_track].team, s->team)) continue; //when using multiview, keep tracking the team @@ -449,12 +452,13 @@ static int CL_FindHighTrack(int seat, char *rule) if (j == -1 && cl.teamplay && seat) { //do it again, but with the teamplay check inverted + //fixme: should probably reduce seat count instead, at least in demos. for (i = 0; i < cl.allocated_client_slots; i++) { s = &cl.players[i]; if (j == i) //this was our default. continue; - if (!CL_MayTrack(seat, i)) + if (!CL_MayAutoTrack(seat, i)) continue; if (!(cl.teamplay && seat && cl.playerview[0].cam_spec_track >= 0 && strcmp(cl.players[cl.playerview[0].cam_spec_track].team, s->team))) continue; //when using multiview, keep tracking the team @@ -468,7 +472,7 @@ static int CL_FindHighTrack(int seat, char *rule) } } - if (j != -1 && CL_MayTrack(seat, cl.playerview[seat].cam_spec_track) && !instant) + if (j != -1 && CL_MayAutoTrack(seat, cl.playerview[seat].cam_spec_track) && !instant) { i = cl.playerview[seat].cam_spec_track; //extra hacks to prevent 'random' switching mid-game @@ -1323,6 +1327,7 @@ void Cam_Track4_f(void) void CL_InitCam(void) { Cvar_Register (&cl_autotrack, cl_spectatorgroup); + Cvar_Register (&cl_autotrack_team, cl_spectatorgroup); Cvar_Register (&cl_hightrack, cl_spectatorgroup); Cvar_Register (&cl_chasecam, cl_spectatorgroup); // Cvar_Register (&cl_camera_maxpitch, cl_spectatorgroup); diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 347c94bbb..558a3b3de 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. void CL_FinishTimeDemo (void); float demtime; +float recdemostart; //keyed to Sys_DoubleTime int demoframe; int cls_lastto; @@ -102,12 +103,12 @@ void CL_WriteDemoCmd (usercmd_t *pcmd) q1usercmd_t cmd; //nq doesn't have this info - if (cls.demorecording != 1) + if (cls.demorecording != DPB_QUAKEWORLD) return; //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, demtime); - fl = LittleFloat((float)demtime); + fl = LittleFloat(Sys_DoubleTime()-recdemostart); VFS_WRITE (cls.demooutfile, &fl, sizeof(fl)); c = dem_cmd; @@ -156,10 +157,10 @@ void CL_WriteDemoMessage (sizebuf_t *msg, int payloadoffset) switch (cls.demorecording) { - case 0: + default: return; - case 1: //QW - fl = LittleFloat((float)demtime); + case DPB_QUAKEWORLD: //QW + fl = LittleFloat(Sys_DoubleTime()-recdemostart); VFS_WRITE (cls.demooutfile, &fl, sizeof(fl)); c = dem_read; @@ -187,7 +188,15 @@ void CL_WriteDemoMessage (sizebuf_t *msg, int payloadoffset) VFS_WRITE (cls.demooutfile, msg->data + msg_readcount, msg->cursize - msg_readcount); } break; - case 2: //NQ +#ifdef Q2CLIENT + case DPB_QUAKE2: + len = LittleLong (net_message.cursize - payloadoffset); + VFS_WRITE(cls.demooutfile, &len, sizeof(len)); + VFS_WRITE(cls.demooutfile, net_message.data + payloadoffset, net_message.cursize - payloadoffset); + break; +#endif +#ifdef NQPROT + case DPB_NETQUAKE: //NQ len = LittleLong (net_message.cursize - payloadoffset); VFS_WRITE(cls.demooutfile, &len, sizeof(len)); for (i=0 ; i<3 ; i++) @@ -197,6 +206,7 @@ void CL_WriteDemoMessage (sizebuf_t *msg, int payloadoffset) } VFS_WRITE(cls.demooutfile, net_message.data + payloadoffset, net_message.cursize - payloadoffset); break; +#endif } if (record_flush.ival) VFS_FLUSH (cls.demooutfile); @@ -295,7 +305,7 @@ int readdemobytes(int *readpos, void *data, int len) } trybytes = sizeof(demobuffer)-demooffset-demobuffersize; - if (trybytes) + if (trybytes > 4096) i = VFS_READ(cls.demoinfile, demobuffer+demooffset+demobuffersize, trybytes); else i = 0; @@ -940,11 +950,22 @@ void CL_Stop_f (void) } // write a disconnect message to the demo file - SZ_Clear (&net_message); - MSG_WriteLong (&net_message, -1); // -1 sequence means out of band - MSG_WriteByte (&net_message, svc_disconnect); - MSG_WriteString (&net_message, "EndOfDemo"); - CL_WriteDemoMessage (&net_message, sizeof(int)); +#ifdef Q2CLIENT + if (cls.demorecording == DPB_QUAKE2) + { //q2 demos signify eof with a -1 size + int len = ~0; + VFS_WRITE(cls.demooutfile, &len, sizeof(len)); + } + else +#endif + { + SZ_Clear (&net_message); + msg_readcount = 0; + MSG_WriteLong (&net_message, -1); // -1 sequence means out of band + MSG_WriteByte (&net_message, svc_disconnect); + MSG_WriteString (&net_message, "EndOfDemo"); + CL_WriteDemoMessage (&net_message, sizeof(int)); + } // finish up VFS_CLOSE (cls.demooutfile); @@ -955,7 +976,13 @@ void CL_Stop_f (void) FS_FlushFSHash(); } +void CL_WriteRecordQ2DemoMessage(sizebuf_t *msg) +{ //q2 is really simple, and doesn't have timings in its demo format. + int len = LittleLong (msg->cursize); + VFS_WRITE (cls.demooutfile, &len, 4); + VFS_WRITE (cls.demooutfile, msg->data, msg->cursize); +} /* ==================== CL_WriteDemoMessage @@ -975,7 +1002,7 @@ void CL_WriteRecordDemoMessage (sizebuf_t *msg, int seq) if (!cls.demorecording) return; - fl = LittleFloat((float)demtime); + fl = LittleFloat(Sys_DoubleTime()-recdemostart); VFS_WRITE (cls.demooutfile, &fl, sizeof(fl)); c = dem_read; @@ -1003,10 +1030,10 @@ void CL_WriteSetDemoMessage (void) //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, demtime); - if (cls.demorecording != 1) + if (cls.demorecording != DPB_QUAKEWORLD) return; - fl = LittleFloat((float)demtime); + fl = LittleFloat(Sys_DoubleTime()-recdemostart); VFS_WRITE (cls.demooutfile, &fl, sizeof(fl)); c = dem_set; @@ -1033,6 +1060,12 @@ void CL_RecordMap_f (void) char demoname[MAX_QPATH]; char mapname[MAX_QPATH]; char demoext[8]; + + if (Cmd_Argc() < 3) + { + Con_Printf("%s: demoname mapname\n", Cmd_Argv(0)); + return; + } Q_strncpyz(demoname, Cmd_Argv(1), sizeof(demoname)); Q_strncpyz(mapname, Cmd_Argv(2), sizeof(mapname)); CL_Disconnect_f(); @@ -1059,16 +1092,18 @@ void CL_RecordMap_f (void) } if (!strcmp(demoext, "dem")) { - cls.demorecording = 2; + cls.demorecording = DPB_NETQUAKE; VFS_PUTS(cls.demooutfile, "-1\n"); } else - cls.demorecording = 1; + cls.demorecording = DPB_QUAKEWORLD; CL_WriteSetDemoMessage(); } } #endif +const char *Get_Q2ConfigString(int i); + /* ==================== CL_Record_f @@ -1082,21 +1117,23 @@ void CL_Record_f (void) char name[MAX_OSPATH]; sizebuf_t buf; char buf_data[MAX_QWMSGLEN]; - int n, i, j; + int n, i, j, seat; char *s, *p, *fname; entity_t *ent; entity_state_t *es; player_info_t *player; extern char gamedirfile[]; int seq = 1; + const char *defaultext; c = Cmd_Argc(); if (c > 2) { #ifndef CLIENTONLY CL_RecordMap_f(); -#endif +#else Con_Printf ("record \n"); +#endif return; } @@ -1106,6 +1143,18 @@ void CL_Record_f (void) return; } + if (cls.protocol == CP_QUAKE2) + defaultext = ".dm2"; +// else if (cls.protocol == CP_NETQUAKE) +// defaultext = ".dem"; + else if (cls.protocol == CP_QUAKEWORLD) + defaultext = ".qwd"; + else + { + Con_Printf("Unable to record mid-map - try a different network protocol\n"); + return; + } + if (cls.demorecording) CL_Stop_f(); @@ -1177,7 +1226,7 @@ void CL_Record_f (void) name[sizeof(name)-1-8] = '\0'; //make a unique name (unless the user specified it). - strcat (name, ".qwd"); //we have the space + strcat (name, defaultext); //we have the space if (c != 2) { vfsfile_t *f; @@ -1210,22 +1259,25 @@ void CL_Record_f (void) Con_Printf ("ERROR: couldn't open.\n"); return; } + cls.demohadkeyframe = false; Con_Printf ("recording to %s.\n", name); - cls.demorecording = true; + + recdemostart = Sys_DoubleTime(); /*-------------------------------------------------*/ + memset(&buf, 0, sizeof(buf)); + buf.data = buf_data; + buf.maxsize = sizeof(buf_data); + buf.prim = cls.netchan.netprim; switch(cls.protocol) { case CP_QUAKEWORLD: + cls.demorecording = DPB_QUAKEWORLD; // serverdata // send the info about the new client to all connected clients - memset(&buf, 0, sizeof(buf)); - buf.data = buf_data; - buf.maxsize = sizeof(buf_data); - buf.prim = cls.netchan.netprim; // send the serverdata MSG_WriteByte (&buf, svc_serverdata); @@ -1250,9 +1302,7 @@ void CL_Record_f (void) MSG_WriteByte (&buf, cl.allocated_client_slots); MSG_WriteByte (&buf, cl.splitclients | (cl.spectator?128:0)); for (i = 0; i < cl.splitclients; i++) - { MSG_WriteByte (&buf, cl.playerview[i].playernum); - } } else { @@ -1286,9 +1336,35 @@ void CL_Record_f (void) MSG_WriteByte (&buf, svc_stufftext); MSG_WriteString (&buf, va("fullserverinfo \"%s\"\n", cl.serverinfo) ); - // send music (delayed) - MSG_WriteByte (&buf, svc_cdtrack); - MSG_WriteByte (&buf, 0); // none in demos + // send music + Media_WriteCurrentTrack(&buf); + + //paknames + { + char buffer[1024]; + FS_GetPackNames(buffer, sizeof(buffer), 2, true); /*retain extensions, or we'd have to assume pk3*/ + MSG_WriteByte(&buf, svc_stufftext); + SZ_Write(&buf, "//paknames ", 11); + SZ_Write(&buf, buffer, strlen(buffer)); + MSG_WriteString(&buf, "\n"); + } + //Paks + { + char buffer[1024]; + FS_GetPackHashes(buffer, sizeof(buffer), false); + MSG_WriteByte(&buf, svc_stufftext); + SZ_Write(&buf, "//paks ", 7); + SZ_Write(&buf, buffer, strlen(buffer)); + MSG_WriteString(&buf, "\n"); + } + + //FIXME: //at + //FIXME: //wps + //FIXME: //it + + + MSG_WriteByte (&buf, svc_setpause); + MSG_WriteByte (&buf, cl.paused); #ifdef PEXT_SETVIEW if (cl.playerview[0].viewentity != cl.playerview[0].playernum+1) //tell the player if we have a different view entity @@ -1310,14 +1386,22 @@ void CL_Record_f (void) while (*s) { MSG_WriteString (&buf, s); - if (buf.cursize > MAX_QWMSGLEN/2) + if (buf.cursize > buf.maxsize/2 && (n&0xff)) { MSG_WriteByte (&buf, 0); MSG_WriteByte (&buf, n); CL_WriteRecordDemoMessage (&buf, seq++); SZ_Clear (&buf); - MSG_WriteByte (&buf, svc_soundlist); - MSG_WriteByte (&buf, n + 1); + if (n + 1 > 0xff) + { + MSG_WriteByte (&buf, svcfte_soundlistshort); + MSG_WriteShort (&buf, n + 1); + } + else + { + MSG_WriteByte (&buf, svc_soundlist); + MSG_WriteByte (&buf, n + 1); + } } n++; s = cl.sound_name[n+1]; @@ -1330,6 +1414,8 @@ void CL_Record_f (void) SZ_Clear (&buf); } + //FIXME: vweps + // modellist MSG_WriteByte (&buf, svc_modellist); MSG_WriteByte (&buf, 0); @@ -1339,14 +1425,22 @@ void CL_Record_f (void) while (*s) { MSG_WriteString (&buf, s); - if (buf.cursize > MAX_QWMSGLEN/2) + if (buf.cursize > buf.maxsize/2 && (n&0xff)) { MSG_WriteByte (&buf, 0); MSG_WriteByte (&buf, n); CL_WriteRecordDemoMessage (&buf, seq++); SZ_Clear (&buf); - MSG_WriteByte (&buf, svc_modellist); - MSG_WriteByte (&buf, n + 1); + if (n + 1 > 0xff) + { + MSG_WriteByte (&buf, svcfte_modellistshort); + MSG_WriteShort (&buf, n + 1); + } + else + { + MSG_WriteByte (&buf, svc_modellist); + MSG_WriteByte (&buf, n + 1); + } } n++; s = cl.model_name[n+1]; @@ -1359,39 +1453,68 @@ void CL_Record_f (void) SZ_Clear (&buf); } - // spawnstatic - - for (i = 0; i < cl.num_statics; i++) + //particleeffectnum stuff + for (i = 1; i < MAX_SSPARTICLESPRE; i++) { - ent = &cl_static_entities[i].ent; + if (!cl.particle_ssname[i]) + break; + MSG_WriteByte(&buf, svcfte_precache); + MSG_WriteShort(&buf, PC_PARTICLE | i); + MSG_WriteString(&buf, cl.particle_ssname[i]); - MSG_WriteByte (&buf, svc_spawnstatic); - - for (j = 1; j < MAX_PRECACHE_MODELS; j++) - if (ent->model == cl.model_precache[j]) - break; - if (j == MAX_PRECACHE_MODELS) - MSG_WriteByte (&buf, 0); - else - MSG_WriteByte (&buf, j); - - MSG_WriteByte (&buf, ent->framestate.g[FS_REG].frame[0]); - MSG_WriteByte (&buf, 0); - MSG_WriteByte (&buf, ent->skinnum); - for (j=0 ; j<3 ; j++) - { - MSG_WriteCoord (&buf, ent->origin[j]); - MSG_WriteAngle (&buf, ent->angles[j]); - } - - if (buf.cursize > MAX_QWMSGLEN/2) + if (buf.cursize > buf.maxsize/2) { CL_WriteRecordDemoMessage (&buf, seq++); SZ_Clear (&buf); } } - // spawnstaticsound + //FIXME: custom tents (needed for hexen2) + + // spawnstatic + + for (i = 0; i < cl.num_statics; i++) + { + ent = &cl_static_entities[i].ent; + +#ifndef CLIENTONLY //FIXME + if (cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) + { + MSG_WriteByte(&buf, svcfte_spawnstatic2); + SVFTE_EmitBaseline(&cl_static_entities[i].state, false, &buf, cls.fteprotocolextensions2); + } + //else if (cls.fteprotocolextensions & PEXT_SPAWNSTATIC2) //qw deltas + else +#endif + { + MSG_WriteByte (&buf, svc_spawnstatic); + + for (j = 1; j < MAX_PRECACHE_MODELS; j++) + if (ent->model == cl.model_precache[j]) + break; + if (j == MAX_PRECACHE_MODELS) + MSG_WriteByte (&buf, 0); + else + MSG_WriteByte (&buf, j); + + MSG_WriteByte (&buf, ent->framestate.g[FS_REG].frame[0]); + MSG_WriteByte (&buf, 0); + MSG_WriteByte (&buf, ent->skinnum); + for (j=0 ; j<3 ; j++) + { + MSG_WriteCoord (&buf, ent->origin[j]); + MSG_WriteAngle (&buf, ent->angles[j]); + } + } + + if (buf.cursize > buf.maxsize/2) + { + CL_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + } + } + + // FIXME: static sounds // static sounds are skipped in demos, life is hard // baselines @@ -1402,20 +1525,30 @@ void CL_Record_f (void) if (memcmp(es, &nullentitystate, sizeof(nullentitystate))) { - MSG_WriteByte (&buf,svc_spawnbaseline); - MSG_WriteEntity (&buf, i); - - MSG_WriteByte (&buf, es->modelindex); - MSG_WriteByte (&buf, es->frame); - MSG_WriteByte (&buf, es->colormap); - MSG_WriteByte (&buf, es->skinnum); - for (j=0 ; j<3 ; j++) +#ifndef CLIENTONLY //FIXME + if (cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) { - MSG_WriteCoord(&buf, es->origin[j]); - MSG_WriteAngle(&buf, es->angles[j]); + MSG_WriteByte(&buf, svcfte_spawnbaseline2); + SVFTE_EmitBaseline(es, true, &buf, cls.fteprotocolextensions2); } + //else if (cls.fteprotocolextensions & PEXT_SPAWNSTATIC2) //qw deltas + else +#endif + { + MSG_WriteByte (&buf,svc_spawnbaseline); + MSG_WriteEntity (&buf, i); - if (buf.cursize > MAX_QWMSGLEN/2) + MSG_WriteByte (&buf, es->modelindex); + MSG_WriteByte (&buf, es->frame); + MSG_WriteByte (&buf, es->colormap); + MSG_WriteByte (&buf, es->skinnum); + for (j=0 ; j<3 ; j++) + { + MSG_WriteCoord(&buf, es->origin[j]); + MSG_WriteAngle(&buf, es->angles[j]); + } + } + if (buf.cursize > buf.maxsize/2) { CL_WriteRecordDemoMessage (&buf, seq++); SZ_Clear (&buf); @@ -1474,7 +1607,7 @@ void CL_Record_f (void) MSG_WriteString (&buf, player->userinfo); } - if (buf.cursize > MAX_QWMSGLEN/2) + if (buf.cursize > buf.maxsize/2) { CL_WriteRecordDemoMessage (&buf, seq++); SZ_Clear (&buf); @@ -1506,33 +1639,118 @@ void CL_Record_f (void) MSG_WriteByte (&buf, (unsigned char)i); MSG_WriteString (&buf, cl_lightstyle[i].map); } - } - for (i = ((cls.fteprotocolextensions&PEXT_HEXEN2)?MAX_QW_STATS:MAX_CL_STATS); i >= 0; i--) - { - if (!cl.playerview[0].stats[i]) - continue; - MSG_WriteByte (&buf, svcqw_updatestatlong); - MSG_WriteByte (&buf, i); - MSG_WriteLong (&buf, cl.playerview[0].stats[i]); - if (buf.cursize > MAX_QWMSGLEN/2) + if (buf.cursize > buf.maxsize/2) { CL_WriteRecordDemoMessage (&buf, seq++); SZ_Clear (&buf); } } + for (seat = 0; seat < cl.splitclients; seat++) + { + //higher stats should be 0 and thus not be sent, if not valid. + for (i = 0; i < MAX_CL_STATS; i++) + { + if (cl.playerview[seat].stats[i] || cl.playerview[seat].statsf[i]) + { + double fs = cl.playerview[seat].statsf[i]; + double is = cl.playerview[seat].stats[i]; + if (seat) + { + MSG_WriteByte (&buf, svcfte_choosesplitclient); + MSG_WriteByte (&buf, seat); + } + if ((int)fs == is) + { + MSG_WriteByte (&buf, svcqw_updatestatlong); + MSG_WriteByte (&buf, i); + MSG_WriteLong (&buf, is); + } + else + { + MSG_WriteByte (&buf, svcfte_updatestatfloat); + MSG_WriteByte (&buf, i); + MSG_WriteLong (&buf, fs); + } + } + if (cl.playerview[seat].statsstr[i]) + { + if (seat) + { + MSG_WriteByte (&buf, svcfte_choosesplitclient); + MSG_WriteByte (&buf, seat); + } + MSG_WriteByte (&buf, svcfte_updatestatstring); + MSG_WriteByte (&buf, i); + MSG_WriteString (&buf, cl.playerview[seat].statsstr[i]); + } + + if (buf.cursize > buf.maxsize/2) + { + CL_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + } + } + } + // get the client to check and download skins // when that is completed, a begin command will be issued MSG_WriteByte (&buf, svc_stufftext); - MSG_WriteString (&buf, va("skins\n") ); + MSG_WriteString (&buf, "skins\n"); CL_WriteRecordDemoMessage (&buf, seq++); CL_WriteSetDemoMessage(); + //FIXME: make sure the deltas are reset + // done break; +#ifdef Q2CLIENT + case CP_QUAKE2: + cls.demorecording = DPB_QUAKE2; + + MSG_WriteByte (&buf, svcq2_serverdata); + if (cls.fteprotocolextensions) //maintain demo compatability + { + MSG_WriteLong (&buf, PROTOCOL_VERSION_FTE); + MSG_WriteLong (&buf, cls.fteprotocolextensions); + } + if (cls.fteprotocolextensions2) //maintain demo compatability + { + MSG_WriteLong (&buf, PROTOCOL_VERSION_FTE2); + MSG_WriteLong (&buf, cls.fteprotocolextensions2); + } + MSG_WriteLong (&buf, cls.protocol_q2); + MSG_WriteLong (&buf, 0x80000000 + cl.servercount); + MSG_WriteByte (&buf, 1); + MSG_WriteString (&buf, gamedirfile); + MSG_WriteShort (&buf, cl.playerview[0].playernum); + MSG_WriteString (&buf, cl.levelname); + + for (i = 0; i < Q2MAX_CONFIGSTRINGS; i++) + { + const char *cs = Get_Q2ConfigString(i); + if (buf.cursize + 4 + strlen(cs) > buf.maxsize) + { + CL_WriteRecordQ2DemoMessage (&buf); + SZ_Clear (&buf); + } + + MSG_WriteByte (&buf, svcq2_configstring); + MSG_WriteShort (&buf, i); + MSG_WriteString (&buf, cs); + } + + CLQ2_WriteDemoBaselines(&buf); + + MSG_WriteByte (&buf, svcq2_stufftext); + MSG_WriteString (&buf, "precache\n"); + CL_WriteRecordQ2DemoMessage (&buf); + break; +#endif + case CP_NETQUAKE: //FIXME default: Con_Printf("Unable to begin demo recording with this network protocol\n"); CL_Stop_f(); @@ -1580,17 +1798,36 @@ void CL_ReRecord_f (void) // // open the demo file // - COM_RequireExtension (name, ".qwd", sizeof(name)); + switch (cls.protocol) + { + default: + case CP_QUAKEWORLD: + cls.demorecording = DPB_QUAKEWORLD; + COM_RequireExtension (name, ".qwd", sizeof(name)); + break; +#ifdef NQPROT + case CP_NETQUAKE: + cls.demorecording = DPB_NETQUAKE; + COM_RequireExtension (name, ".dem", sizeof(name)); + break; +#endif +#ifdef Q2CLIENT + case CP_QUAKE2: + cls.demorecording = DPB_QUAKE2; + COM_RequireExtension (name, ".dm2", sizeof(name)); + break; +#endif + } cls.demooutfile = FS_OpenVFS (name, "wb", FS_GAME); if (!cls.demooutfile) { Con_Printf ("ERROR: couldn't open.\n"); + cls.demorecording = DPB_NONE; return; } Con_Printf ("recording to %s.\n", name); - cls.demorecording = true; CL_Disconnect(); CL_BeginServerReconnect(); diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 41888fe30..f091fcfb5 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -1699,7 +1699,7 @@ void CL_RotateAroundTag(entity_t *ent, int entnum, int parenttagent, int parentt vec3_t axis[3]; float transform[12], parent[12], result[12], old[12], temp[12]; - int model; + model_t *model; framestate_t fstate; if (parenttagent >= cl.maxlerpents) @@ -1708,6 +1708,20 @@ void CL_RotateAroundTag(entity_t *ent, int entnum, int parenttagent, int parentt return; } + //old is the entity's relative transform (relative to the parent entity's tag) + old[0] = ent->axis[0][0]; + old[1] = ent->axis[1][0]; + old[2] = ent->axis[2][0]; + old[3] = ent->origin[0]; + old[4] = ent->axis[0][1]; + old[5] = ent->axis[1][1]; + old[6] = ent->axis[2][1]; + old[7] = ent->origin[1]; + old[8] = ent->axis[0][2]; + old[9] = ent->axis[1][2]; + old[10] = ent->axis[2][2]; + old[11] = ent->origin[2]; + memset(&fstate, 0, sizeof(fstate)); //for visibility checks @@ -1716,12 +1730,44 @@ void CL_RotateAroundTag(entity_t *ent, int entnum, int parenttagent, int parentt ps = CL_FindPacketEntity(parenttagent); if (ps) { - if (ps->tagentity) - CL_RotateAroundTag(ent, entnum, ps->tagentity, ps->tagindex); + if (parenttagent >= cl.maxlerpents) + { + org = ps->origin; + ang = ps->angles; + } + else + { + lerpents_t *le = &cl.lerpents[parenttagent]; + org = le->origin; + ang = le->angles; + } - org = ps->origin; - ang = ps->angles; - model = ps->modelindex; + if (ps->modelindex <= countof(cl.model_precache) && cl.model_precache[ps->modelindex] && cl.model_precache[ps->modelindex]->loadstate == MLS_LOADED) + model = cl.model_precache[ps->modelindex]; + else + model = NULL; + if (model && model->type == mod_alias) + { + ang[0]*=-1; + AngleVectors(ang, axis[0], axis[1], axis[2]); + ang[0]*=-1; + } + else + AngleVectors(ang, axis[0], axis[1], axis[2]); + VectorInverse(axis[1]); + + parent[0] = axis[0][0]; + parent[1] = axis[1][0]; + parent[2] = axis[2][0]; + parent[3] = org[0]; + parent[4] = axis[0][1]; + parent[5] = axis[1][1]; + parent[6] = axis[2][1]; + parent[7] = org[1]; + parent[8] = axis[0][2]; + parent[9] = axis[1][2]; + parent[10] = axis[2][2]; + parent[11] = org[2]; CL_LerpNetFrameState(FS_REG, &fstate, &cl.lerpents[parenttagent]); @@ -1755,7 +1801,7 @@ void CL_RotateAroundTag(entity_t *ent, int entnum, int parenttagent, int parentt org = cl.inframes[parsecountmod].playerstate[parenttagent-1].origin; ang = cl.inframes[parsecountmod].playerstate[parenttagent-1].viewangles; } - model = cl.inframes[parsecountmod].playerstate[parenttagent-1].modelindex; + model = cl.model_precache[cl.inframes[parsecountmod].playerstate[parenttagent-1].modelindex]; CL_LerpNetFrameState(FS_REG, &fstate, &cl.lerpplayers[parenttagent-1]); } @@ -1766,47 +1812,17 @@ void CL_RotateAroundTag(entity_t *ent, int entnum, int parenttagent, int parentt } } - if (ang) { - ang[0]*=-1; - AngleVectors(ang, axis[0], axis[1], axis[2]); - ang[0]*=-1; - VectorInverse(axis[1]); - // fstate.g[FS_REG].lerpfrac = CL_EntLerpFactor(tagent); // fstate.g[FS_REG].frametime[0] = cl.time - cl.lerpents[tagent].framechange; // fstate.g[FS_REG].frametime[1] = cl.time - cl.lerpents[tagent].oldframechange; - if (Mod_GetTag(cl.model_precache[model], parenttagnum, &fstate, transform)) + if (Mod_GetTag(model, parenttagnum, &fstate, transform)) { - old[0] = ent->axis[0][0]; - old[1] = ent->axis[1][0]; - old[2] = ent->axis[2][0]; - old[3] = ent->origin[0]; - old[4] = ent->axis[0][1]; - old[5] = ent->axis[1][1]; - old[6] = ent->axis[2][1]; - old[7] = ent->origin[1]; - old[8] = ent->axis[0][2]; - old[9] = ent->axis[1][2]; - old[10] = ent->axis[2][2]; - old[11] = ent->origin[2]; +// parent -> transform -> old - parent[0] = axis[0][0]; - parent[1] = axis[1][0]; - parent[2] = axis[2][0]; - parent[3] = org[0]; - parent[4] = axis[0][1]; - parent[5] = axis[1][1]; - parent[6] = axis[2][1]; - parent[7] = org[1]; - parent[8] = axis[0][2]; - parent[9] = axis[1][2]; - parent[10] = axis[2][2]; - parent[11] = org[2]; - - R_ConcatTransforms((void*)old, (void*)parent, (void*)temp); - R_ConcatTransforms((void*)temp, (void*)transform, (void*)result); + R_ConcatTransforms((void*)parent, (void*)transform, (void*)temp); + R_ConcatTransforms((void*)temp, (void*)old, (void*)result); ent->axis[0][0] = result[0]; ent->axis[1][0] = result[1]; @@ -1823,32 +1839,6 @@ void CL_RotateAroundTag(entity_t *ent, int entnum, int parenttagent, int parentt } else //hrm. { - old[0] = ent->axis[0][0]; - old[1] = ent->axis[1][0]; - old[2] = ent->axis[2][0]; - old[3] = ent->origin[0]; - old[4] = ent->axis[0][1]; - old[5] = ent->axis[1][1]; - old[6] = ent->axis[2][1]; - old[7] = ent->origin[1]; - old[8] = ent->axis[0][2]; - old[9] = ent->axis[1][2]; - old[10] = ent->axis[2][2]; - old[11] = ent->origin[2]; - - parent[0] = axis[0][0]; - parent[1] = axis[1][0]; - parent[2] = axis[2][0]; - parent[3] = org[0]; - parent[4] = axis[0][1]; - parent[5] = axis[1][1]; - parent[6] = axis[2][1]; - parent[7] = org[1]; - parent[8] = axis[0][2]; - parent[9] = axis[1][2]; - parent[10] = axis[2][2]; - parent[11] = org[2]; - R_ConcatTransforms((void*)parent, (void*)old, (void*)result); ent->axis[0][0] = result[0]; @@ -1865,6 +1855,9 @@ void CL_RotateAroundTag(entity_t *ent, int entnum, int parenttagent, int parentt ent->origin[2] = result[11]; } } + + if (ps && ps->tagentity) + CL_RotateAroundTag(ent, entnum, ps->tagentity, ps->tagindex); } void V_AddAxisEntity(entity_t *in) @@ -3652,7 +3645,7 @@ void CL_LinkPacketEntities (void) ent->flags |= RF_ADDITIVE; if (state->effects & EF_NODEPTHTEST) ent->flags |= RF_NODEPTHTEST; - if (state->effects & DPEF_NOSHADOW) + if (state->effects & EF_NOSHADOW) ent->flags |= RF_NOSHADOW; if (state->trans != 0xff) ent->flags |= RF_TRANSLUCENT; @@ -4840,7 +4833,22 @@ void CL_LinkViewModel(void) #ifdef Q2CLIENT if (cls.protocol == CP_QUAKE2) + { + V_ClearEntity(&ent); + ent.model = pv->vm.oldmodel; + + ent.framestate.g[FS_REG].frame[0] = pv->vm.prevframe; + ent.framestate.g[FS_REG].frame[1] = pv->vm.oldframe; + ent.framestate.g[FS_REG].frametime[0] = pv->vm.lerptime; + ent.framestate.g[FS_REG].frametime[1] = pv->vm.oldlerptime; + ent.framestate.g[FS_REG].lerpweight[0] = 1 - cl.lerpfrac; + ent.framestate.g[FS_REG].lerpweight[1] = cl.lerpfrac; + + ent.flags |= RF_WEAPONMODEL|RF_DEPTHHACK|RF_NOSHADOW; + + V_AddEntity (&ent); return; + } #endif if (!r_drawentities.ival) @@ -4863,7 +4871,7 @@ void CL_LinkViewModel(void) else alpha = 1; - if ((r_refdef.playerview->stats[STAT_ITEMS] & IT_INVISIBILITY) + if ((pv->stats[STAT_ITEMS] & IT_INVISIBILITY) && r_drawviewmodelinvis.value > 0 && r_drawviewmodelinvis.value < 1) alpha *= r_drawviewmodelinvis.value; diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index 18e0464b3..5c98d21f5 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -52,13 +52,11 @@ usercmd_t independantphysics[MAX_SPLITS]; vec3_t mousemovements[MAX_SPLITS]; /*kinda a hack...*/ -static int con_splitmodifier; +int con_splitmodifier; cvar_t cl_forceseat = CVARAD("in_forceseat", "0", "in_forcesplitclient", "Overrides the device identifiers to control a specific client from any device. This can be used for debugging mods, where you only have one keyboard/mouse."); extern cvar_t cl_splitscreen; int CL_TargettedSplit(qboolean nowrap) { - char *c; - int pnum; int mod; //explicitly targetted at some seat number from the server @@ -376,6 +374,8 @@ void IN_WriteButtons(vfsfile_t *f, qboolean all) VFS_PRINTF(f, "-p%i %s\n", s+1, buttons[b].name); } } + + //FIXME: save device remappings to config. } //is this useful? @@ -465,6 +465,8 @@ void IN_Restart (void) { IN_Shutdown(); IN_ReInit(); + + //FIXME: re-assert explicit device re-mappings } /* @@ -855,7 +857,7 @@ void CL_ClampPitch (int pnum) if (cls.protocol == CP_QUAKE2) { float pitch; - pitch = SHORT2ANGLE(cl.q2frame.playerstate.pmove.delta_angles[PITCH]); + pitch = SHORT2ANGLE(cl.q2frame.playerstate[pnum].pmove.delta_angles[PITCH]); if (pitch > 180) pitch -= 360; @@ -1387,6 +1389,41 @@ static void QDECL CL_SpareMsec_Callback (struct cvar_s *var, char *oldvalue) } } +void CL_UpdateSeats(void) +{ + if (!cls.netchan.message.cursize && cl.allocated_client_slots > 1 && cls.state == ca_active && cl.splitclients && (cls.fteprotocolextensions & PEXT_SPLITSCREEN) && cl.worldmodel) + { + int targ = cl_splitscreen.ival+1; + if (targ > MAX_SPLITS) + targ = MAX_SPLITS; + if (cl.splitclients < targ) + { + char buffer[2048]; + char newinfo[2048]; + Q_strncpyz(newinfo, cls.userinfo[cl.splitclients], sizeof(newinfo)); + + //some userinfos should always have a value + if (!*Info_ValueForKey(newinfo, "name")) //$name-2 + Info_SetValueForKey(newinfo, "name", va("%s-%i\n", Info_ValueForKey(cls.userinfo[0], "name"), cl.splitclients+1), sizeof(newinfo)); + if (cls.protocol != CP_QUAKE2) + { + if (!*Info_ValueForKey(newinfo, "team")) //put players on the same team by default. this avoids team damage in coop, and if you're playing on the same computer then you probably want to be on the same team anyway. + Info_SetValueForKey(newinfo, "team", Info_ValueForKey(cls.userinfo[0], "team"), sizeof(newinfo)); + if (!*Info_ValueForKey(newinfo, "bottomcolor")) //bottom colour implies team in nq + Info_SetValueForKey(newinfo, "bottomcolor", Info_ValueForKey(cls.userinfo[0], "bottomcolor"), sizeof(newinfo)); + if (!*Info_ValueForKey(newinfo, "topcolor")) //should probably pick a random top colour or something + Info_SetValueForKey(newinfo, "topcolor", Info_ValueForKey(cls.userinfo[0], "topcolor"), sizeof(newinfo)); + } + if (!*Info_ValueForKey(newinfo, "skin")) //give players the same skin by default, because we can. q2 cares for teams. qw might as well (its not like anyone actually uses them thanks to enemy-skin forcing). + Info_SetValueForKey(newinfo, "skin", Info_ValueForKey(cls.userinfo[0], "skin"), sizeof(newinfo)); + + CL_SendClientCommand(true, "addseat %i %s", cl.splitclients, COM_QuotedString(newinfo, buffer, sizeof(buffer), false)); + } + else if (cl.splitclients > targ) + CL_SendClientCommand(true, "addseat %i", targ); + } +} + /* ================= @@ -1428,76 +1465,91 @@ qboolean CL_WriteDeltas (int plnum, sizebuf_t *buf) qboolean CLQ2_SendCmd (sizebuf_t *buf) { int seq_hash; - qboolean dontdrop; + qboolean dontdrop = false; usercmd_t *cmd; int checksumIndex, i; int lightlev; + int seat; cl.movesequence = cls.netchan.outgoing_sequence; //make sure its correct even over map changes. seq_hash = cl.movesequence; -// send this and the previous cmds in the message, so -// if the last packet was dropped, it can be recovered - i = cl.movesequence & UPDATE_MASK; - cmd = &cl.outframes[i].cmd[0]; - - //q2admin is retarded and kicks you if you get a stall. - if (cmd->msec > 100) - cmd->msec = 100; - - if (cls.resendinfo) + for (seat = 0; seat < cl.splitclients; seat++) { - MSG_WriteByte (&cls.netchan.message, clcq2_userinfo); - MSG_WriteString (&cls.netchan.message, cls.userinfo[0]); + // send this and the previous cmds in the message, so + // if the last packet was dropped, it can be recovered + i = cl.movesequence & UPDATE_MASK; + cmd = &cl.outframes[i].cmd[seat]; - cls.resendinfo = false; - } + //q2admin is retarded and kicks you if you get a stall. + if (cmd->msec > 100) + cmd->msec = 100; - MSG_WriteByte (buf, clcq2_move); + if (cls.resendinfo) + { + MSG_WriteByte (&cls.netchan.message, clcq2_userinfo); + MSG_WriteString (&cls.netchan.message, cls.userinfo[seat]); + cls.resendinfo = false; + } - // save the position for a checksum qbyte - if (cls.protocol_q2 == PROTOCOL_VERSION_R1Q2 || cls.protocol_q2 == PROTOCOL_VERSION_Q2PRO) - checksumIndex = -1; - else - { - checksumIndex = buf->cursize; - MSG_WriteByte (buf, 0); - } + MSG_WriteByte (buf, clcq2_move); - if (!cl.q2frame.valid || cl_nodelta.ival) - MSG_WriteLong (buf, -1); // no compression - else - MSG_WriteLong (buf, cl.q2frame.serverframe); + if (seat) + { + //multi-seat still has an extra clc_move per seat + //but no checksum (pointless when its opensource anyway) + //no sequence (only seat 0 reports that) + checksumIndex = -1; + } + else + { + // save the position for a checksum qbyte + if (cls.protocol_q2 == PROTOCOL_VERSION_R1Q2 || cls.protocol_q2 == PROTOCOL_VERSION_Q2PRO) + checksumIndex = -1; + else + { + checksumIndex = buf->cursize; + MSG_WriteByte (buf, 0); + } - lightlev = R_LightPoint(cl.playerview[0].simorg); + if (!cl.q2frame.valid || cl_nodelta.ival || (cls.demorecording && !cls.demohadkeyframe)) + MSG_WriteLong (buf, -1); // no compression + else + MSG_WriteLong (buf, cl.q2frame.serverframe); + } -// msecs = msecs - (double)msecstouse; + lightlev = R_LightPoint(cl.playerview[seat].simorg); - i = cls.netchan.outgoing_sequence & UPDATE_MASK; - cmd = &cl.outframes[i].cmd[0]; - *cmd = independantphysics[0]; - - cmd->lightlevel = (lightlev>255)?255:lightlev; + // msecs = msecs - (double)msecstouse; - cl.outframes[i].senttime = realtime; - cl.outframes[i].latency = -1; - memset(&independantphysics[0], 0, sizeof(independantphysics[0])); + i = cls.netchan.outgoing_sequence & UPDATE_MASK; + cmd = &cl.outframes[i].cmd[seat]; + *cmd = independantphysics[seat]; + + cmd->lightlevel = (lightlev>255)?255:lightlev; - if (cmd->buttons) - cmd->buttons |= 128; //fixme: this isn't really what's meant by the anykey. + cl.outframes[i].senttime = realtime; + cl.outframes[i].latency = -1; + memset(&independantphysics[seat], 0, sizeof(independantphysics[0])); -// calculate a checksum over the move commands - dontdrop = CL_WriteDeltas(0, buf); + if (cmd->buttons) + cmd->buttons |= 128; //fixme: this isn't really what's meant by the anykey. - if (checksumIndex >= 0) - { - buf->data[checksumIndex] = Q2COM_BlockSequenceCRCByte( - buf->data + checksumIndex + 1, buf->cursize - checksumIndex - 1, - seq_hash); + // calculate a checksum over the move commands + dontdrop |= CL_WriteDeltas(seat, buf); + + if (checksumIndex >= 0) + { + buf->data[checksumIndex] = Q2COM_BlockSequenceCRCByte( + buf->data + checksumIndex + 1, buf->cursize - checksumIndex - 1, + seq_hash); + } } if (cl.sendprespawn || !cls.protocol_q2) buf->cursize = 0; //tastyspleen.net is alergic. + else + CL_UpdateSeats(); return dontdrop; } @@ -1608,22 +1660,7 @@ qboolean CLQW_SendCmd (sizebuf_t *buf, qboolean actuallysend) if (cl.sendprespawn || !actuallysend) buf->cursize = st; //don't send movement commands while we're still supposedly downloading. mvdsv does not like that. else - { - //FIXME: user a timer. - if (!cls.netchan.message.cursize && cl.allocated_client_slots > 1 && cl.splitclients && (cls.fteprotocolextensions & PEXT_SPLITSCREEN)) - { - int targ = cl_splitscreen.ival+1; - if (targ > MAX_SPLITS) - targ = MAX_SPLITS; - if (cl.splitclients < targ) - { - char buffer[2048]; - CL_SendClientCommand(true, "addseat %i %s", cl.splitclients, COM_QuotedString(cls.userinfo[cl.splitclients], buffer, sizeof(buffer), false)); - } - else if (cl.splitclients > targ) - CL_SendClientCommand(true, "addseat %i", targ); - } - } + CL_UpdateSeats(); return dontdrop; } diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index e10300973..75a5bb8f6 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -35,6 +35,8 @@ void QDECL Name_Callback(struct cvar_s *var, char *oldvalue); #define Name_Callback NULL #endif +void CL_ForceStopDownload (qdownload_t *dl, qboolean finish); + // we need to declare some mouse variables here, because the menu system // references them even when on a unix system. @@ -157,7 +159,7 @@ cvar_t cl_proxyaddr = CVAR("cl_proxyaddr", ""); cvar_t cl_sendguid = CVARD("cl_sendguid", "0", "Send a randomly generated 'globally unique' id to servers, which can be used by servers for score rankings and stuff. Different servers will see different guids. Delete the 'qkey' file in order to appear as a different user.\nIf set to 2, all servers will see the same guid. Be warned that this can show other people the guid that you're using."); cvar_t cl_downloads = CVARFD("cl_downloads", "1", CVAR_NOTFROMSERVER, "Allows you to block all automatic downloads."); cvar_t cl_download_csprogs = CVARFD("cl_download_csprogs", "1", CVAR_NOTFROMSERVER, "Download updated client gamecode if available. Warning: If you clear this to avoid downloading vm code, you should also clear cl_download_packages."); -cvar_t cl_download_redirection = CVARFD("cl_download_redirection", "2", CVAR_NOTFROMSERVER, "Follow download redirection to download packages instead of individual files. 2 allows redirection only to named packages files. Also allows the server to send nearly arbitary download commands."); +cvar_t cl_download_redirection = CVARFD("cl_download_redirection", "2", CVAR_NOTFROMSERVER, "Follow download redirection to download packages instead of individual files. Also allows the server to send nearly arbitary download commands.\n2: allows redirection only to named packages files (and demos/*.mvd), which is a bit safer."); cvar_t cl_download_mapsrc = CVARD("cl_download_mapsrc", "", "Specifies an http location prefix for map downloads. EG: \"http://bigfoot.morphos-team.net/misc/quakemaps/\""); cvar_t cl_download_packages = CVARFD("cl_download_packages", "1", CVAR_NOTFROMSERVER, "0=Do not download packages simply because the server is using them. 1=Download and load packages as needed (does not affect games which do not use this package). 2=Do download and install permanently (use with caution!)"); cvar_t requiredownloads = CVARFD("requiredownloads","1", CVAR_ARCHIVE, "0=join the game before downloads have even finished (might be laggy). 1=wait for all downloads to complete before joining."); @@ -535,15 +537,26 @@ void CL_SendConnectPacket (netadr_t *to, int mtu, } #ifdef PROTOCOL_VERSION_FTE - CL_SupportedFTEExtensions(&fteprotextsupported, &fteprotextsupported2); - - fteprotextsupported &= ftepext; - fteprotextsupported2 &= ftepext2; - #ifdef Q2CLIENT - if (connectinfo.protocol != CP_QUAKEWORLD) - fteprotextsupported = 0; + if (connectinfo.protocol == CP_QUAKE2) + { + fteprotextsupported = ftepext & (PEXT_MODELDBL|PEXT_SOUNDDBL|PEXT_SPLITSCREEN); + fteprotextsupported2 = 0; + } + else #endif + { + CL_SupportedFTEExtensions(&fteprotextsupported, &fteprotextsupported2); + + fteprotextsupported &= ftepext; + fteprotextsupported2 &= ftepext2; + + if (connectinfo.protocol != CP_QUAKEWORLD) + { + fteprotextsupported = 0; + fteprotextsupported2 = 0; + } + } connectinfo.fteext1 = fteprotextsupported; connectinfo.fteext2 = fteprotextsupported2; @@ -602,7 +615,7 @@ void CL_SendConnectPacket (netadr_t *to, int mtu, clients = MAX_SPLITS; #ifdef Q2CLIENT - if (connectinfo.protocol == CP_QUAKE2) //sorry - too lazy. + if (connectinfo.protocol == CP_QUAKE2) //q2 only supports after-connect seats clients = 1; #endif @@ -740,6 +753,8 @@ void CL_CheckForResend (void) case GT_QUAKE2: connectinfo.protocol = CP_QUAKE2; connectinfo.subprotocol = PROTOCOL_VERSION_Q2; + connectinfo.fteext1 = PEXT_MODELDBL|PEXT_SOUNDDBL|PEXT_SPLITSCREEN; + connectinfo.fteext2 = 0; break; #endif default: @@ -838,19 +853,26 @@ void CL_CheckForResend (void) } //make sure the protocol within demos is actually correct/sane - if (cls.demorecording == 1 && cls.protocol != CP_QUAKEWORLD) + if (cls.demorecording == DPB_QUAKEWORLD && cls.protocol != CP_QUAKEWORLD) { connectinfo.protocol = CP_QUAKEWORLD; connectinfo.subprotocol = PROTOCOL_VERSION_QW; connectinfo.fteext1 = Net_PextMask(1, false); connectinfo.fteext2 = Net_PextMask(2, false); } - else if (cls.demorecording == 2 && cls.protocol != CP_NETQUAKE) + else if (cls.demorecording == DPB_NETQUAKE && cls.protocol != CP_NETQUAKE) { connectinfo.protocol = CP_NETQUAKE; connectinfo.subprotocol = CPNQ_FITZ666; //FIXME: use pext. } + else if (cls.demorecording == DPB_QUAKE2 && cls.protocol != CP_NETQUAKE) + { + connectinfo.protocol = CP_QUAKE2; + connectinfo.subprotocol = PROTOCOL_VERSION_Q2; + connectinfo.fteext1 = PEXT_MODELDBL|PEXT_SOUNDDBL|PEXT_SPLITSCREEN; + //FIXME: use pext. + } break; } @@ -1485,6 +1507,14 @@ void CL_ClearState (void) } } +#ifdef Q2CLIENT + for (i = 0; i < countof(cl.configstring_general); i++) + { + if (cl.configstring_general) + Z_Free(cl.configstring_general[i]); + } +#endif + for (i = 0; i < MAX_SPLITS; i++) { for (j = 0; j < MAX_CL_STATS; j++) @@ -1765,7 +1795,7 @@ void CL_Users_f (void) { if (cl.players[i].name[0]) { - Con_TPrintf ("%6i %4i %s\n", cl.players[i].userid, cl.players[i].frags, cl.players[i].name); + Con_TPrintf ("%6i %4i ^[%s\\player\\%i^]\n", cl.players[i].userid, cl.players[i].frags, cl.players[i].name, i); c++; } } @@ -1832,7 +1862,7 @@ void CL_Color_f (void) // else if (server_owns_colour) // Cvar_LockFromServer(&topcolor, num); else - Cvar_Set (&topcolor, num); + CL_SetInfo(pnum, "topcolor", num); Q_snprintfz (num, sizeof(num), (bottom&0xff000000)?"0x%06x":"%i", bottom & 0xffffff); if (bottom == 0) *num = '\0'; @@ -1841,7 +1871,7 @@ void CL_Color_f (void) else if (server_owns_colour) Cvar_LockFromServer(&bottomcolor, num); else - Cvar_Set (&bottomcolor, num); + CL_SetInfo (pnum, "bottomcolor", num); #ifdef NQPROT if (cls.protocol == CP_NETQUAKE) Cmd_ForwardToServer(); @@ -2167,8 +2197,13 @@ void CL_FullInfo_f (void) while (*s) { o = key; - while (*s && *s != '\\') + while (*s && *s != '\\' && o < key + sizeof(key)) *o++ = *s++; + if (o == key + sizeof(key)) + { + Con_Printf ("key length too long\n"); + return; + } *o = 0; if (!*s) @@ -2179,8 +2214,13 @@ void CL_FullInfo_f (void) o = value; s++; - while (*s && *s != '\\') + while (*s && *s != '\\' && o < value + sizeof(value)) *o++ = *s++; + if (o == value + sizeof(value)) + { + Con_Printf ("value length too long\n"); + return; + } *o = 0; if (*s) @@ -2209,11 +2249,15 @@ void CL_SetInfo (int pnum, char *key, char *value) Info_SetValueForStarKey (cls.userinfo[pnum], key, value, sizeof(cls.userinfo[pnum])); if (cls.state >= ca_connected && !cls.demoplayback) { + if (pnum >= cl.splitclients) + return; + #ifdef Q2CLIENT - if (cls.protocol == CP_QUAKE2 || cls.protocol == CP_QUAKE3) + if ((cls.protocol == CP_QUAKE2 || cls.protocol == CP_QUAKE3) && !cls.fteprotocolextensions) cls.resendinfo = true; else #endif + if (cls.protocol != CP_NETQUAKE || (cls.fteprotocolextensions & PEXT_BIGUSERINFOS)) { if (pnum) CL_SendClientCommand(true, "%i setinfo %s \"%s\"", pnum+1, key, value); @@ -3433,7 +3477,7 @@ qboolean CL_AllowArbitaryDownload(char *localfile) { char ext[8]; COM_FileExtension(localfile, ext, sizeof(ext)); - if (!strcmp(ext, "pak") || !strcmp(ext, "pk3") || !strcmp(ext, "pk4")) + if (!strcmp(ext, "pak") || !strcmp(ext, "pk3") || !strcmp(ext, "pk4") || (!strncmp(localfile, "demos/", 6) && !strcmp(ext, "mvd"))) return true; else { @@ -3508,6 +3552,9 @@ void CL_Download_f (void) if (Cmd_IsInsecure()) //mark server specified downloads. { + if (cls.download && cls.download->method == DL_QWPENDING) + DL_Abort(cls.download, QDL_FAILED); + //don't let gamecode order us to download random junk if (!CL_AllowArbitaryDownload(localname)) return; @@ -3584,8 +3631,13 @@ void CL_ForceStopDownload (qdownload_t *dl, qboolean finish) if (!dl->file) { - Con_Printf("No files downloading by QW protocol\n"); - return; + if (dl->method == DL_QWPENDING) + finish = false; + else + { + Con_Printf("No files downloading by QW protocol\n"); + return; + } } if (finish) @@ -4826,7 +4878,10 @@ double Host_Frame (double time) if (r_blockvidrestart) { if (waitingformanifest) + { + COM_MainThreadWork(); return 0.1; + } Host_FinishLoading(); return 0; } @@ -4865,6 +4920,10 @@ double Host_Frame (double time) double idlesec = 1.0 / cl_idlefps.value; if (idlesec > 0.1) idlesec = 0.1; // limit to at least 10 fps +#if !defined(NOMEDIA) + if (Media_Capturing()) + idlesec = 0; +#endif if ((realtime - oldrealtime) < idlesec) { #ifndef CLIENTONLY @@ -5332,9 +5391,10 @@ void CL_ExecInitialConfigs(char *resetcommand) Cbuf_AddText("\n", RESTRICT_LOCAL); COM_ParsePlusSets(true); + def = COM_FDepthFile("default.cfg", true); //q2/q3/tc #ifdef QUAKETC Cbuf_AddText ("exec default.cfg\n", RESTRICT_LOCAL); - if (COM_FCheckExists ("config.cfg")) + if (COM_FDepthFile ("config.cfg", true) <= def) Cbuf_AddText ("exec config.cfg\n", RESTRICT_LOCAL); if (COM_FCheckExists ("autoexec.cfg")) Cbuf_AddText ("exec autoexec.cfg\n", RESTRICT_LOCAL); @@ -5342,23 +5402,32 @@ void CL_ExecInitialConfigs(char *resetcommand) //who should we imitate? qrc = COM_FDepthFile("quake.rc", true); //q1 hrc = COM_FDepthFile("hexen.rc", true); //h2 - def = COM_FDepthFile("default.cfg", true); //q2/q3 if (qrc <= def && qrc <= hrc && qrc!=0x7fffffff) + { Cbuf_AddText ("exec quake.rc\n", RESTRICT_LOCAL); + def = qrc; + } else if (hrc <= def && hrc!=0x7fffffff) + { Cbuf_AddText ("exec hexen.rc\n", RESTRICT_LOCAL); + def = hrc; + } else { //they didn't give us an rc file! // Cbuf_AddText ("bind ` toggleconsole\n", RESTRICT_LOCAL); //in case default.cfg does not exist. :( Cbuf_AddText ("exec default.cfg\n", RESTRICT_LOCAL); - if (COM_FCheckExists ("config.cfg")) + if (COM_FDepthFile ("config.cfg", true) <= def) Cbuf_AddText ("exec config.cfg\n", RESTRICT_LOCAL); - if (COM_FCheckExists ("q3config.cfg")) + if (COM_FDepthFile ("q3config.cfg", true) <= def) Cbuf_AddText ("exec q3config.cfg\n", RESTRICT_LOCAL); Cbuf_AddText ("exec autoexec.cfg\n", RESTRICT_LOCAL); } - Cbuf_AddText ("exec fte.cfg\n", RESTRICT_LOCAL); + qrc = COM_FDepthFile("fte.cfg", true); + if (qrc <= def) //don't use it if we're running a mod with a default.cfg that is in a stronger path than fte.cfg, as this indicates that fte.cfg is from fte/ and not $currentmod/. + Cbuf_AddText ("exec fte.cfg\n", RESTRICT_LOCAL); + else if (qrc != 0x7fffffff) + Cbuf_AddText ("echo skipping fte.cfg from wrong gamedir\n", RESTRICT_LOCAL); #endif #ifdef QUAKESPYAPI if (COM_FCheckExists ("frontend.cfg")) @@ -5380,6 +5449,9 @@ void CL_ExecInitialConfigs(char *resetcommand) //the configs should be fully loaded. //so convert the backwards compable commandline parameters in cvar sets. CL_ArgumentOverrides(); +#ifndef CLIENTONLY + SV_ArgumentOverrides(); +#endif //and disable the 'you have unsaved stuff' prompt. Cvar_Saved(); @@ -5403,6 +5475,9 @@ void Host_FinishLoading(void) Cbuf_Execute (); CL_ArgumentOverrides(); +#ifndef CLIENTONLY + SV_ArgumentOverrides(); +#endif Con_Printf ("\n%s\n", version_string()); @@ -5605,7 +5680,7 @@ void Host_Shutdown(void) Memory_DeInit(); #ifndef CLIENTONLY - memset(&sv, 0, sizeof(sv)); + SV_WipeServerState(); memset(&svs, 0, sizeof(svs)); #endif Sys_Shutdown(); diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 84b0b3053..8160d387e 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -589,10 +589,14 @@ qboolean CL_EnqueDownload(const char *filename, const char *localname, unsigned Q_strncpyz(dl->rname, filename, sizeof(dl->rname)); Q_strncpyz(dl->localname, localname, sizeof(dl->localname)); dl->next = cl.downloadlist; - cl.downloadlist = dl; dl->size = 0; dl->flags = flags | DLLF_SIZEUNKNOWN; + if (!cl.downloadlist) + flags &= ~DLLF_VERBOSE; + + cl.downloadlist = dl; + if (!webdl && (cls.fteprotocolextensions & (PEXT_CHUNKEDDOWNLOADS #ifdef PEXT_PK3DOWNLOADS | PEXT_PK3DOWNLOADS @@ -1068,7 +1072,6 @@ void Model_CheckDownloads (void) // Con_TPrintf (TLC_CHECKINGMODELS); - R_SetSky(cl.skyname); #ifdef Q2CLIENT if (cls.protocol == CP_QUAKE2) { @@ -1573,13 +1576,18 @@ void CL_RequestNextDownload (void) if (cl.worldmodel && cl.worldmodel->loadstate == MLS_LOADING) COM_WorkerPartialSync(cl.worldmodel, &cl.worldmodel->loadstate, MLS_LOADING); -#ifdef warningmsg -#pragma warningmsg("timedemo timer should start here") -#endif - if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED) { - Con_Printf("\n\n-------------\nCouldn't download %s - cannot fully connect\n", cl.worldmodel->name); + Con_Printf("\n\n-------------\n" CON_ERROR "Couldn't download \"%s\" - cannot fully connect\n", cl.worldmodel?cl.worldmodel->name:"unknown"); +#if !defined(NOMEDIA) + if (cls.demoplayback && Media_Capturing()) + { + Con_Printf(CON_ERROR "Aborting capture\n"); + CL_StopPlayback(); + } +#endif + //else should probably force the demo speed really fast or something + SCR_SetLoadingStage(LS_NONE); return; } @@ -3105,6 +3113,9 @@ void CLQ2_ParseServerData (void) memset(&cls.netchan.netprim, 0, sizeof(cls.netchan.netprim)); cls.netchan.netprim.coordsize = 2; cls.netchan.netprim.anglesize = 1; + cls.fteprotocolextensions = 0; + cls.fteprotocolextensions2 = 0; + cls.demohadkeyframe = true; //assume that it did, so this stuff all gets recorded. Con_DPrintf ("Serverdata packet received.\n"); // @@ -3117,6 +3128,25 @@ void CLQ2_ParseServerData (void) // parse protocol version number i = MSG_ReadLong (); + + if (i == PROTOCOL_VERSION_FTE) + { + cls.fteprotocolextensions = i = MSG_ReadLong(); +// if (i & PEXT_FLOATCOORDS) +// i -= PEXT_FLOATCOORDS; + if (i & PEXT_SOUNDDBL) + i -= PEXT_SOUNDDBL; + if (i & PEXT_MODELDBL) + i -= PEXT_MODELDBL; + if (i & PEXT_SPLITSCREEN) + i -= PEXT_SPLITSCREEN; + if (i) + Host_EndGame ("Unsupported q2 protocol extensions: %x", i); + i = MSG_ReadLong (); + + if (cls.fteprotocolextensions & PEXT_FLOATCOORDS) + cls.netchan.netprim.coordsize = 4; + } cls.protocol_q2 = i; if (i == PROTOCOL_VERSION_R1Q2) @@ -3178,7 +3208,7 @@ void CLQ2_ParseServerData (void) MSG_ReadByte(); //strafejump hack if (r1q2ver >= 1905) - cls.netchan.netprim.q2flags |= NPQ2_SIZE32; + cls.netchan.netprim.q2flags |= NPQ2_SOLID32; } else if (cls.protocol_q2 == PROTOCOL_VERSION_Q2PRO) { @@ -3189,7 +3219,7 @@ void CLQ2_ParseServerData (void) MSG_ReadByte(); //strafejump hack MSG_ReadByte(); //q2pro qw-mode. kinda silly for us tbh. if (q2prover >= 1014) - cls.netchan.netprim.q2flags |= NPQ2_SIZE32; + cls.netchan.netprim.q2flags |= NPQ2_SOLID32; if (q2prover >= 1018) cls.netchan.netprim.q2flags |= NPQ2_ANG16; if (q2prover >= 1015) @@ -3882,14 +3912,38 @@ void CLQ2_ParseClientinfo(int i, char *s) void CLQ2_ParseConfigString (void) { - int i; + unsigned int i; char *s; // char olds[MAX_QPATH]; - i = MSG_ReadShort (); + i = (unsigned short)MSG_ReadShort (); + s = MSG_ReadString(); + + if (i >= 0x8000 && i < 0x8000+MAX_PRECACHE_MODELS) + { + Q_strncpyz(cl.model_name[i-0x8000], s, MAX_QPATH); + if (cl.model_name[i-0x8000][0] == '#') + { + if (cl.numq2visibleweapons < Q2MAX_VISIBLE_WEAPONS) + { + cl.q2visibleweapons[cl.numq2visibleweapons] = cl.model_name[i-0x8000]+1; + cl.numq2visibleweapons++; + } + cl.model_precache[i-0x8000] = NULL; + } + else + cl.model_precache[i-0x8000] = Mod_ForName (cl.model_name[i-0x8000], MLV_WARN); + return; + } + else if (i >= 0xc000 && i < 0xc000+MAX_PRECACHE_SOUNDS) + { + Q_strncpyz(cl.sound_name[i-0xc000], s, MAX_QPATH); + cl.sound_precache[i-0xc000] = S_PrecacheSound (s); + return; + } + if (i < 0 || i >= Q2MAX_CONFIGSTRINGS) Host_EndGame ("configstring > Q2MAX_CONFIGSTRINGS"); - s = MSG_ReadString(); // strncpy (olds, cl.configstrings[i], sizeof(olds)); // olds[sizeof(olds) - 1] = 0; @@ -4196,7 +4250,7 @@ void CL_ParseStatic (int version) ent->flags |= RF_ADDITIVE; if (es.effects & EF_NODEPTHTEST) ent->flags |= RF_NODEPTHTEST; - if (es.effects & DPEF_NOSHADOW) + if (es.effects & EF_NOSHADOW) ent->flags |= RF_NOSHADOW; if (es.trans != 0xff) ent->flags |= RF_TRANSLUCENT; @@ -4340,7 +4394,11 @@ void CLQ2_ParseStartSoundPacket(void) sfx_t *sfx; flags = MSG_ReadByte (); - sound_num = MSG_ReadByte (); + + if ((flags & Q2SND_LARGEIDX) && (cls.fteprotocolextensions & PEXT_SOUNDDBL)) + sound_num = MSG_ReadShort(); + else + sound_num = MSG_ReadByte (); if (flags & Q2SND_VOLUME) volume = MSG_ReadByte () / 255.0; @@ -4374,7 +4432,14 @@ void CLQ2_ParseStartSoundPacket(void) if (flags & Q2SND_POS) { // positioned in space - MSG_ReadPos (pos_v); + if ((flags & Q2SND_LARGEPOS) && (cls.fteprotocolextensions & PEXT_FLOATCOORDS)) + { + pos_v[0] = MSG_ReadFloat(); + pos_v[1] = MSG_ReadFloat(); + pos_v[2] = MSG_ReadFloat(); + } + else + MSG_ReadPos (pos_v); pos = pos_v; } @@ -4426,6 +4491,9 @@ void CLNQ_ParseStartSoundPacket(void) field_mask = MSG_ReadByte(); + if (field_mask & FTESND_MOREFLAGS) + field_mask |= MSG_ReadByte()<<8; + if (field_mask & NQSND_VOLUME) volume = MSG_ReadByte (); else @@ -4446,10 +4514,8 @@ void CLNQ_ParseStartSoundPacket(void) else timeofs = 0; -// if (field_mask & FTESND_FLAGS) -// flags = MSG_ReadByte(); -// else - flags = 0; + flags = field_mask>>8; + flags &= CF_FORCELOOP; if (field_mask & DPSND_LARGEENTITY) { @@ -4469,7 +4535,7 @@ void CLNQ_ParseStartSoundPacket(void) if (field_mask & DPSND_LARGESOUND) sound_num = (unsigned short)MSG_ReadShort(); else - sound_num = MSG_ReadByte (); + sound_num = (unsigned char)MSG_ReadByte (); if (ent > MAX_EDICTS) Host_EndGame ("CL_ParseStartSoundPacket: ent = %i", ent); @@ -5262,11 +5328,11 @@ void CLQ2_ParseMuzzleFlash2 (void) CLQ2_RunMuzzleFlash2(ent, flash_number); } -void CLQ2_ParseInventory (void) +void CLQ2_ParseInventory (int seat) { unsigned int i; for (i=0 ; i= MAX_SPLITS) + Host_EndGame ("CLQ2_ParseServerMessage: Invalid seat", cmd); + cmd = MSG_ReadByte (); + } + if (cmd == -1) { msg_readcount++; // so the EOM showner has the right value @@ -6795,13 +6874,13 @@ void CLQ2_ParseServerMessage (void) break; case svcq2_layout: s = MSG_ReadString (); - Q_strncpyz (cl.q2layout, s, sizeof(cl.q2layout)); + Q_strncpyz (cl.q2layout[seat], s, sizeof(cl.q2layout[seat])); #ifdef VM_UI UI_Q2LayoutChanged(); #endif break; case svcq2_inventory: - CLQ2_ParseInventory(); + CLQ2_ParseInventory(seat); break; // the rest are private to the client and server @@ -6858,9 +6937,9 @@ void CLQ2_ParseServerMessage (void) s = MSG_ReadString(); #ifdef PLUGINS - if (Plug_CenterPrintMessage(s, 0)) + if (Plug_CenterPrintMessage(s, seat)) #endif - SCR_CenterPrint (0, s, false); + SCR_CenterPrint (seat, s, false); break; case svcq2_download: //16 // [short] size [size bytes] CL_ParseDownload(); @@ -6880,6 +6959,9 @@ void CLQ2_ParseServerMessage (void) } } CL_SetSolidEntities (); + + if (cls.demohadkeyframe) + CL_WriteDemoMessage(&net_message, startpos); //FIXME: incomplete frames might be awkward } #endif diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index 1e8bc0e78..348de2f22 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -47,7 +47,7 @@ void VARGS Q2_Pmove (q2pmove_t *pmove); #define Q2PMF_NO_PREDICTION 64 // temporarily disables prediction (used for grappling hook) #endif -vec3_t cl_predicted_origins[UPDATE_BACKUP]; +vec3_t cl_predicted_origins[MAX_SPLITS][UPDATE_BACKUP]; /* @@ -62,34 +62,43 @@ void CLQ2_CheckPredictionError (void) int delta[3]; int i; int len; + int seat; + q2player_state_t *ps; + playerview_t *pv; - if (cl_nopred.value || (cl.q2frame.playerstate.pmove.pm_flags & Q2PMF_NO_PREDICTION)) - return; - - // calculate the last usercmd_t we sent that the server has processed - frame = cls.netchan.incoming_acknowledged; - frame &= (UPDATE_MASK); - - // compare what the server returned with what we had predicted it to be - VectorSubtract (cl.q2frame.playerstate.pmove.origin, cl_predicted_origins[frame], delta); - - // save the prediction error for interpolation - len = abs(delta[0]) + abs(delta[1]) + abs(delta[2]); - if (len > 640) // 80 world units - { // a teleport or something - VectorClear (cl.prediction_error); - } - else + for (seat = 0; seat < cl.splitclients; seat++) { -// if (/*cl_showmiss->value && */(delta[0] || delta[1] || delta[2]) ) -// Con_Printf ("prediction miss on %i: %i\n", cl.q2frame.serverframe, -// delta[0] + delta[1] + delta[2]); + ps = &cl.q2frame.playerstate[seat]; + pv = &cl.playerview[seat]; - VectorCopy (cl.q2frame.playerstate.pmove.origin, cl_predicted_origins[frame]); + if (cl_nopred.value || (ps->pmove.pm_flags & Q2PMF_NO_PREDICTION)) + continue; - // save for error itnerpolation - for (i=0 ; i<3 ; i++) - cl.prediction_error[i] = delta[i]*0.125; + // calculate the last usercmd_t we sent that the server has processed + frame = cls.netchan.incoming_acknowledged; + frame &= (UPDATE_MASK); + + // compare what the server returned with what we had predicted it to be + VectorSubtract (ps->pmove.origin, cl_predicted_origins[seat][frame], delta); + + // save the prediction error for interpolation + len = abs(delta[0]) + abs(delta[1]) + abs(delta[2]); + if (len > 640) // 80 world units + { // a teleport or something + VectorClear (pv->prediction_error); + } + else + { +// if (/*cl_showmiss->value && */(delta[0] || delta[1] || delta[2]) ) +// Con_Printf ("prediction miss on %i: %i\n", cl.q2frame.serverframe, +// delta[0] + delta[1] + delta[2]); + + VectorCopy (ps->pmove.origin, cl_predicted_origins[seat][frame]); + + // save for error itnerpolation + for (i=0 ; i<3 ; i++) + pv->prediction_error[i] = delta[i]*0.125; + } } } @@ -100,6 +109,7 @@ CL_ClipMoveToEntities ==================== */ +int predignoreentitynum; void CLQ2_ClipMoveToEntities ( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, trace_t *tr ) { int i, x, zd, zu; @@ -118,7 +128,7 @@ void CLQ2_ClipMoveToEntities ( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t en if (!ent->solid) continue; - if (ent->number == cl.playerview[0].playernum+1) + if (ent->number == predignoreentitynum) continue; if (ent->solid == ES_SOLID_BSP) @@ -130,7 +140,7 @@ void CLQ2_ClipMoveToEntities ( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t en } else { // encoded bbox - if (cls.netchan.netprim.q2flags & NPQ2_SIZE32) + if (cls.netchan.netprim.q2flags & NPQ2_SOLID32) { x = ent->solid & 255; zd = (ent->solid >> 8) & 255; @@ -232,7 +242,7 @@ CL_PredictMovement Sets cl.predicted_origin and cl.predicted_angles ================= */ -void CLQ2_PredictMovement (void) //q2 doesn't support split clients. +static void CLQ2_PredictMovement (int seat) //q2 doesn't support split clients. { #ifdef Q2BSPS int ack, current; @@ -244,7 +254,8 @@ void CLQ2_PredictMovement (void) //q2 doesn't support split clients. int oldz; #endif int i; - int pnum = 0; + q2player_state_t *ps = &cl.q2frame.playerstate[seat]; + playerview_t *pv = &cl.playerview[seat]; if (cls.state != ca_active) return; @@ -253,12 +264,12 @@ void CLQ2_PredictMovement (void) //q2 doesn't support split clients. // return; #ifdef Q2BSPS - if (cl_nopred.value || cls.demoplayback || (cl.q2frame.playerstate.pmove.pm_flags & Q2PMF_NO_PREDICTION)) + if (cl_nopred.value || cls.demoplayback || (ps->pmove.pm_flags & Q2PMF_NO_PREDICTION)) #endif { // just set angles for (i=0 ; i<3 ; i++) { - cl.predicted_angles[i] = cl.playerview[pnum].viewangles[i] + SHORT2ANGLE(cl.q2frame.playerstate.pmove.delta_angles[i]); + pv->predicted_angles[i] = pv->viewangles[i] + SHORT2ANGLE(ps->pmove.delta_angles[i]); } return; } @@ -281,53 +292,55 @@ void CLQ2_PredictMovement (void) //q2 doesn't support split clients. pm_airaccelerate = atof(Get_Q2ConfigString(Q2CS_AIRACCEL)); - pm.s = cl.q2frame.playerstate.pmove; + pm.s = ps->pmove; // SCR_DebugGraph (current - ack - 1, 0); frame = 0; + predignoreentitynum = cl.q2frame.clientnum[seat]+1;//cl.playerview[seat].playernum+1; + // run frames while (++ack < current) { frame = ack & (UPDATE_MASK); - cmd = (q2usercmd_t*)&cl.outframes[frame].cmd[0]; - cmd->msec = cl.outframes[frame].cmd[0].msec; + cmd = (q2usercmd_t*)&cl.outframes[frame].cmd[seat]; + cmd->msec = cl.outframes[frame].cmd[seat].msec; pm.cmd = *cmd; Q2_Pmove (&pm); // save for debug checking - VectorCopy (pm.s.origin, cl_predicted_origins[frame]); + VectorCopy (pm.s.origin, cl_predicted_origins[seat][frame]); } - if (independantphysics[0].msec) + if (independantphysics[seat].msec) { - cmd = (q2usercmd_t*)&independantphysics[0]; - cmd->msec = independantphysics[0].msec; + cmd = (q2usercmd_t*)&independantphysics[seat]; + cmd->msec = independantphysics[seat].msec; pm.cmd = *cmd; Q2_Pmove (&pm); } oldframe = (ack-1) & (UPDATE_MASK); - oldz = cl_predicted_origins[oldframe][2]; + oldz = cl_predicted_origins[seat][oldframe][2]; step = pm.s.origin[2] - oldz; if (step > 63 && step < 160 && (pm.s.pm_flags & Q2PMF_ON_GROUND) ) { - cl.predicted_step = step * 0.125; - cl.predicted_step_time = realtime;// - host_frametime;// * 0.5; + pv->predicted_step = step * 0.125; + pv->predicted_step_time = realtime;// - host_frametime;// * 0.5; } - cl.playerview[0].onground = !!(pm.s.pm_flags & Q2PMF_ON_GROUND); + pv->onground = !!(pm.s.pm_flags & Q2PMF_ON_GROUND); // copy results out for rendering - cl.predicted_origin[0] = pm.s.origin[0]*0.125; - cl.predicted_origin[1] = pm.s.origin[1]*0.125; - cl.predicted_origin[2] = pm.s.origin[2]*0.125; + pv->predicted_origin[0] = pm.s.origin[0]*0.125; + pv->predicted_origin[1] = pm.s.origin[1]*0.125; + pv->predicted_origin[2] = pm.s.origin[2]*0.125; - VectorCopy (pm.viewangles, cl.predicted_angles); + VectorCopy (pm.viewangles, pv->predicted_angles); #endif } @@ -927,7 +940,7 @@ void CL_PredictMovePNum (int seat) if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED) return; pv->crouch = 0; - CLQ2_PredictMovement(); + CLQ2_PredictMovement(seat); return; } #endif diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index dcac21665..91d8c2eae 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -184,19 +184,20 @@ float scr_conlines; // lines of console to display qboolean scr_con_forcedraw; -extern cvar_t scr_viewsize; -extern cvar_t scr_fov; -extern cvar_t scr_conspeed; -extern cvar_t scr_centertime; -extern cvar_t scr_showturtle; +extern cvar_t scr_viewsize; +extern cvar_t scr_fov; +extern cvar_t scr_conspeed; +extern cvar_t scr_centertime; +extern cvar_t scr_logcenterprint; +extern cvar_t scr_showturtle; extern cvar_t scr_turtlefps; -extern cvar_t scr_showpause; -extern cvar_t scr_printspeed; +extern cvar_t scr_showpause; +extern cvar_t scr_printspeed; extern cvar_t scr_allowsnap; extern cvar_t scr_sshot_type; extern cvar_t scr_sshot_prefix; extern cvar_t scr_sshot_compression; -extern cvar_t crosshair; +extern cvar_t crosshair; extern cvar_t scr_consize; cvar_t scr_neticontimeout = CVAR("scr_neticontimeout", "0.3"); @@ -470,6 +471,17 @@ void SCR_CenterPrint (int pnum, char *str, qboolean skipgamecode) break; str += 2; } + + if (((scr_logcenterprint.ival && !cl.deathmatch) || scr_logcenterprint.ival == 2) && !(p->flags & CPRINT_PERSIST)) + { + //don't spam too much. + if (*str && strncmp(cl.lastcenterprint, str, sizeof(cl.lastcenterprint)-1)) + { + Q_strncpyz(cl.lastcenterprint, str, sizeof(cl.lastcenterprint)); + Con_CenterPrint(str); + } + } + p->charcount = COM_ParseFunString(CON_WHITEMASK, str, p->string, sizeof(p->string), false) - p->string; p->time_off = scr_centertime.value; p->time_start = cl.time; @@ -1384,13 +1396,13 @@ void SCR_DrawTurtle (void) void SCR_DrawDisk (void) { - if (!draw_disc) +// if (!draw_disc) return; - if (!COM_HasWork()) - return; +// if (!COM_HasWork()) +// return; - R2D_ScalePic (scr_vrect.x + vid.width-24, scr_vrect.y, 24, 24, draw_disc); +// R2D_ScalePic (scr_vrect.x + vid.width-24, scr_vrect.y, 24, 24, draw_disc); } /* @@ -1883,7 +1895,7 @@ void SCR_EndLoadingPlaque (void) scr_drawloading = false; } -void SCR_ImageName (char *mapname) +void SCR_ImageName (const char *mapname) { strcpy(levelshotname, "levelshots/"); COM_FileBase(mapname, levelshotname + strlen(levelshotname), sizeof(levelshotname)-strlen(levelshotname)); @@ -2392,6 +2404,11 @@ void SCR_ScreenShot_Mega_f(void) #ifdef CSQC_DAT if (!okay && CSQC_DrawView()) okay = true; +// if (!*r_refdef.rt_destcolour[0].texname) + { //csqc protects its own. lazily. + Q_strncpyz(r_refdef.rt_destcolour[0].texname, "megascreeny", sizeof(r_refdef.rt_destcolour[0].texname)); + BE_RenderToTextureUpdate2d(true); + } #endif if (!okay && r_worldentity.model) { @@ -2607,12 +2624,13 @@ void SCR_BringDownConsole (void) int pnum; for (pnum = 0; pnum < cl.splitclients; pnum++) + { scr_centerprint[pnum].charcount = 0; + cl.playerview[pnum].cshifts[CSHIFT_CONTENTS].percent = 0; // no area contents palette on next frame + } - for (i=0 ; i<20 && scr_conlines != scr_con_current ; i++) - SCR_UpdateScreen (); - - cl.cshifts[CSHIFT_CONTENTS].percent = 0; // no area contents palette on next frame +// for (i=0 ; i<20 && scr_conlines != scr_con_current ; i++) +// SCR_UpdateScreen (); } void SCR_TileClear (int skipbottom) @@ -2683,8 +2701,6 @@ void SCR_DrawTwoDimensional(int uimenu, qboolean nohud) } else { - R2D_DrawCrosshair(); - SCR_DrawNet (); SCR_DrawDisk(); SCR_DrawFPS (); diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index a8b270b31..09b08a0dc 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -66,7 +66,7 @@ static const char *q2efnames[] = "TEQ2_FORCEWALL", NULL,//"TEQ2_HEATBEAM", NULL,//"TEQ2_MONSTER_HEATBEAM", - "TEQ2_STEAM", + NULL,//"TEQ2_STEAM", "TEQ2_BUBBLETRAIL2", "TEQ2_MOREBLOOD", "TEQ2_HEATBEAM_SPARKS", @@ -76,8 +76,8 @@ static const char *q2efnames[] = "TEQ2_TRACKER_EXPLOSION", "TEQ2_TELEPORT_EFFECT", "TEQ2_DBALL_GOAL", - "TEQ2_WIDOWBEAMOUT", - "TEQ2_NUKEBLAST", + NULL,//"TEQ2_WIDOWBEAMOUT", + NULL,//"TEQ2_NUKEBLAST", "TEQ2_WIDOWSPLASH", "TEQ2_EXPLOSION1_BIG", "TEQ2_EXPLOSION1_NP", @@ -2408,6 +2408,27 @@ void CL_Laser (vec3_t start, vec3_t end, int colors) ex->framerate = 100; // smoother fading } +void CLQ2_ParseSteam(void) +{ + vec3_t pos, dir; + qbyte colour; + short magnitude; + unsigned int duration; + signed int id = MSG_ReadShort(); + qbyte count = MSG_ReadByte(); + MSG_ReadPos(pos); + MSG_ReadPos(dir); + colour = MSG_ReadByte(); + magnitude = MSG_ReadShort(); + + if (id == -1) + duration = MSG_ReadLong(); + else + duration = 0; + + Con_Printf("FIXME: CLQ2_ParseSteam: stub\n"); +} + static struct{ qbyte colour; char *name; @@ -2579,12 +2600,12 @@ void CLQ2_ParseTEnt (void) case Q2TE_FORCEWALL: break; - case Q2TE_STEAM: - break; - case Q2TE_WIDOWBEAMOUT: break; */ + case Q2TE_STEAM: + CLQ2_ParseSteam(); + break; default: @@ -3471,9 +3492,11 @@ void CL_UpdateBeams (void) // vieworg = pl->origin; // } // else +#ifdef Q2CLIENT if (cls.protocol == CP_QUAKE2) - vieworg = cl.predicted_origin; + vieworg = pv->predicted_origin; else +#endif vieworg = pv->simorg; if (cl_truelightning.ival > 1 && cl.movesequence > cl_truelightning.ival) @@ -3557,6 +3580,8 @@ void CL_UpdateBeams (void) VectorCopy (b->start, org); } + else + VectorCopy (b->start, org); VectorAdd(org, b->offset, org); // calculate pitch and yaw @@ -3663,14 +3688,17 @@ void CL_UpdateExplosions (void) continue; lastrunningexplosion = i; - if (ex->model->loadstate == MLS_LOADING) - continue; - if (ex->model->loadstate != MLS_LOADED) + if (ex->model) { - ex->model = NULL; - ex->flags = 0; - P_DelinkTrailstate(&(ex->trailstate)); - continue; + if (ex->model->loadstate == MLS_LOADING) + continue; + if (ex->model->loadstate != MLS_LOADED) + { + ex->model = NULL; + ex->flags = 0; + P_DelinkTrailstate(&(ex->trailstate)); + continue; + } } f = ex->framerate*(cl.time - ex->start); diff --git a/engine/client/client.h b/engine/client/client.h index 2b898d1c7..0f1385750 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -113,7 +113,7 @@ typedef struct } q2pmove_state_t; typedef struct -{ //shared with q2 dll +{ //shared with q2 dll so cannot be changed q2pmove_state_t pmove; // for prediction @@ -251,7 +251,8 @@ typedef struct int servertime; // server time the message is valid for (in msec) int deltaframe; qbyte areabits[MAX_Q2MAP_AREAS/8]; // portalarea visibility bits - q2player_state_t playerstate; + q2player_state_t playerstate[MAX_SPLITS]; + int clientnum[MAX_SPLITS]; int num_entities; int parse_entities; // non-masked index into cl_parse_entities array } q2frame_t; @@ -480,7 +481,6 @@ typedef struct // demo recording info must be here, because record is started before // entering a map (and clearing client_state_t) - int demorecording; //1=QW, 2=NQ vfsfile_t *demooutfile; enum{DPB_NONE,DPB_QUAKEWORLD,DPB_MVD,DPB_EZTV, @@ -490,7 +490,8 @@ typedef struct #ifdef Q2CLIENT DPB_QUAKE2 #endif - } demoplayback; + } demoplayback, demorecording; + qboolean demohadkeyframe; //q2 needs to wait for a packet with a key frame, supposedly. qboolean demoseeking; float demoseektime; qboolean timedemo; @@ -638,6 +639,14 @@ struct playerview_s float viewheight; int waterlevel; //for smartjump +#ifdef Q2CLIENT + vec3_t predicted_origin; + vec3_t predicted_angles; + vec3_t prediction_error; + float predicted_step_time; + float predicted_step; +#endif + float punchangle; // temporary view kick from weapon firing float v_dmg_time; //various view knockbacks. @@ -663,7 +672,10 @@ struct playerview_s CAM_EYECAM = 3 //locked, cl_chasecam=1. we know where they are, we're in their eyes. #define CAM_ISLOCKED(pv) ((pv)->cam_state > CAM_PENDING) - } cam_state; // + } cam_state; + + cshift_t cshifts[NUM_CSHIFTS]; // color shifts for damage, powerups and content types + vec4_t screentint; vec3_t vw_axis[3]; //weapons should be positioned relative to this vec3_t vw_origin; //weapons should be positioned relative to this @@ -743,8 +755,6 @@ typedef struct int lerpentssequence; lerpents_t lerpplayers[MAX_CLIENTS]; - cshift_t cshifts[NUM_CSHIFTS]; // color shifts for damage, powerups and content types - //when running splitscreen, we have multiple viewports all active at once int splitclients; //we are running this many clients split screen. playerview_t playerview[MAX_SPLITS]; @@ -798,7 +808,7 @@ typedef struct char *configstring_general[Q2MAX_CLIENTS|Q2MAX_GENERAL]; char *image_name[Q2MAX_IMAGES]; char *item_name[Q2MAX_ITEMS]; - short inventory[Q2MAX_ITEMS]; + short inventory[MAX_SPLITS][Q2MAX_ITEMS]; #endif struct model_s *model_precache_vwep[MAX_VWEP_MODELS]; @@ -841,15 +851,15 @@ typedef struct qboolean gamedirchanged; +#ifdef Q2CLIENT char q2statusbar[1024]; - char q2layout[1024]; + char q2layout[MAX_SPLITS][1024]; int parse_entities; float lerpfrac; - vec3_t predicted_origin; - vec3_t predicted_angles; - vec3_t prediction_error; - float predicted_step_time; - float predicted_step; +#endif + + char lastcenterprint[1024]; //prevents too much spam with console centerprint logging. + struct itemtimer_s @@ -1139,6 +1149,7 @@ qboolean CL_GetMessage (void); void CL_WriteDemoCmd (usercmd_t *pcmd); void CL_Demo_ClientCommand(char *commandtext); //for QTV. +void CL_WriteRecordQ2DemoMessage(sizebuf_t *msg); void CL_Stop_f (void); void CL_Record_f (void); void CL_ReRecord_f (void); @@ -1351,7 +1362,7 @@ void CSQC_CvarChanged(cvar_t *var); // void CL_InitPrediction (void); void CL_PredictMove (void); -void CL_PredictUsercmd (int pnum, int entnum, player_state_t *from, player_state_t *to, usercmd_t *u); +void CL_PredictUsercmd (int seat, int entnum, player_state_t *from, player_state_t *to, usercmd_t *u); #ifdef Q2CLIENT void CLQ2_CheckPredictionError (void); #endif @@ -1470,6 +1481,7 @@ extern qboolean care_f_modified; //random files (fixme: clean up) #ifdef Q2CLIENT +unsigned int CLQ2_GatherSounds(vec3_t *positions, unsigned int *entnums, sfx_t **sounds, unsigned int max); void CLQ2_ParseTEnt (void); void CLQ2_AddEntities (void); void CLQ2_ParseBaseline (void); @@ -1477,6 +1489,7 @@ void CLQ2_ClearParticleState(void); void CLQ2_ParseFrame (int extrabits); void CLQ2_RunMuzzleFlash2 (int ent, int flash_number); int CLQ2_RegisterTEntModels (void); +void CLQ2_WriteDemoBaselines(sizebuf_t *buf); #endif #ifdef HLCLIENT diff --git a/engine/client/clq2_ents.c b/engine/client/clq2_ents.c index 07a0d9a83..120d42bea 100644 --- a/engine/client/clq2_ents.c +++ b/engine/client/clq2_ents.c @@ -210,6 +210,45 @@ entity_state_t clq2_parse_entities[MAX_PARSE_ENTITIES]; void CL_SmokeAndFlash(vec3_t origin); +void CLQ2_WriteDemoBaselines(sizebuf_t *buf) +{ + int i; + q2entity_state_t nullstate = {0}; + for (i = 0; i < MAX_Q2EDICTS; i++) + { + q2entity_state_t es; + entity_state_t *base = &cl_entities[i].baseline; + if (!base->modelindex) + continue; + + //I brought these copies on myself... + es.number = i; + VectorCopy(base->origin, es.origin); + VectorCopy(base->angles, es.angles); + VectorCopy(base->u.q2.old_origin, es.old_origin); + es.modelindex = base->modelindex; + es.modelindex2 = base->modelindex2; + es.modelindex3 = base->u.q2.modelindex3; + es.modelindex4 = base->u.q2.modelindex4; + es.frame = base->frame; + es.skinnum = base->skinnum; + es.effects = base->effects; + es.renderfx = base->u.q2.renderfx; + es.solid = base->solid; + es.sound = base->u.q2.sound; + es.event = base->u.q2.event; + + if (buf->cursize > buf->maxsize/2) + { + CL_WriteRecordQ2DemoMessage (buf); + SZ_Clear (buf); + } + + MSG_WriteByte (buf, svcq2_spawnbaseline); + MSGQ2_WriteDeltaEntity(&nullstate, &es, buf, true, true); + } +} + void CLQ2_ClearState(void) { memset(cl_entities, 0, sizeof(cl_entities)); @@ -668,13 +707,33 @@ void CLQ2_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int to->number = number; if (bits & Q2U_MODEL) - to->modelindex = MSG_ReadByte (); + { + if (bits & Q2UX_INDEX16) + to->modelindex = MSG_ReadShort(); + else + to->modelindex = MSG_ReadByte (); + } if (bits & Q2U_MODEL2) - to->modelindex2 = MSG_ReadByte (); + { + if (bits & Q2UX_INDEX16) + to->modelindex2 = MSG_ReadShort(); + else + to->modelindex2 = MSG_ReadByte (); + } if (bits & Q2U_MODEL3) - to->u.q2.modelindex3 = MSG_ReadByte (); + { + if (bits & Q2UX_INDEX16) + to->u.q2.modelindex3 = MSG_ReadShort(); + else + to->u.q2.modelindex3 = MSG_ReadByte (); + } if (bits & Q2U_MODEL4) - to->u.q2.modelindex4 = MSG_ReadByte (); + { + if (bits & Q2UX_INDEX16) + to->u.q2.modelindex4 = MSG_ReadShort(); + else + to->u.q2.modelindex4 = MSG_ReadByte (); + } if (bits & Q2U_FRAME8) to->frame = MSG_ReadByte (); @@ -732,7 +791,12 @@ void CLQ2_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int MSG_ReadPos (to->u.q2.old_origin); if (bits & Q2U_SOUND) - to->u.q2.sound = MSG_ReadByte (); + { + if (bits & Q2UX_INDEX16) + to->u.q2.sound = MSG_ReadShort(); + else + to->u.q2.sound = MSG_ReadByte (); + } if (bits & Q2U_EVENT) to->u.q2.event = MSG_ReadByte (); @@ -741,7 +805,7 @@ void CLQ2_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int if (bits & Q2U_SOLID) { - if (net_message.prim.q2flags & NPQ2_SIZE32) + if (net_message.prim.q2flags & NPQ2_SOLID32) to->solid = MSG_ReadLong(); else to->solid = MSG_ReadShort (); @@ -973,22 +1037,30 @@ void CLQ2_ParseBaseline (void) CL_ParsePlayerstate =================== */ -void CLQ2_ParsePlayerstate (q2frame_t *oldframe, q2frame_t *newframe, int extflags) +void CLQ2_ParsePlayerstate (int seat, q2frame_t *oldframe, q2frame_t *newframe, int extflags) { int flags; q2player_state_t *state; int i; int statbits; - state = &newframe->playerstate; + state = &newframe->playerstate[seat]; // clear to old value before delta parsing if (oldframe) - *state = oldframe->playerstate; + { + *state = oldframe->playerstate[seat]; + newframe->clientnum[seat] = oldframe->clientnum[seat]; + } else + { memset (state, 0, sizeof(*state)); + newframe->clientnum[seat] = cl.playerview[seat].playernum; + } - flags = MSG_ReadShort (); + flags = (unsigned short)MSG_ReadShort (); + if (flags & Q2PS_EXTRABITS) + flags |= MSG_ReadByte()<<16; // // parse the pmove_state_t @@ -1064,12 +1136,18 @@ void CLQ2_ParsePlayerstate (q2frame_t *oldframe, q2frame_t *newframe, int extfla if (flags & Q2PS_WEAPONINDEX) { - state->gunindex = MSG_ReadByte (); + if (flags & Q2PS_INDEX16) + state->gunindex = MSG_ReadShort (); + else + state->gunindex = MSG_ReadByte (); } if (flags & Q2PS_WEAPONFRAME) { - state->gunframe = MSG_ReadByte (); + if (flags & Q2PS_INDEX16) + state->gunframe = MSG_ReadShort (); + else + state->gunframe = MSG_ReadByte (); if (extflags & Q2PSX_OLD) { state->gunoffset[0] = MSG_ReadChar ()*0.25; @@ -1119,8 +1197,8 @@ void CLQ2_ParsePlayerstate (q2frame_t *oldframe, q2frame_t *newframe, int extfla state->stats[i] = MSG_ReadShort(); } - if (extflags & Q2PSX_CLIENTNUM) - /*state->viewent =*/ MSG_ReadByte(); + if ((extflags & Q2PSX_CLIENTNUM) || (flags & Q2PS_CLIENTNUM)) + newframe->clientnum[seat] = MSG_ReadByte(); } @@ -1213,7 +1291,7 @@ void CLQ2_ParseFrame (int extrabits) { cl.q2frame.valid = true; // uncompressed frame old = NULL; -// cls.demowaiting = false; // we can start recording now + cls.demohadkeyframe = true; //yay! all is right with the world! } else { @@ -1247,26 +1325,35 @@ void CLQ2_ParseFrame (int extrabits) len = MSG_ReadByte (); MSG_ReadData (&cl.q2frame.areabits, len); - // read playerinfo + // normally playerstate then packet entities + //in splitscreen we may have multiple player states, one per player. if (cls.protocol_q2 != PROTOCOL_VERSION_R1Q2 && cls.protocol_q2 != PROTOCOL_VERSION_Q2PRO) { - cmd = MSG_ReadByte (); - // SHOWNET(svc_strings[cmd]); - if (cmd != svcq2_playerinfo) - Host_EndGame ("CL_ParseFrame: not playerinfo"); - } - CLQ2_ParsePlayerstate (old, &cl.q2frame, extrabits); - - // read packet entities - if (cls.protocol_q2 != PROTOCOL_VERSION_R1Q2 && cls.protocol_q2 != PROTOCOL_VERSION_Q2PRO) - { - cmd = MSG_ReadByte (); -// SHOWNET(svc_strings[cmd]); + cl.splitclients = 0; + for (cl.splitclients = 0; ; ) + { + cmd = MSG_ReadByte (); +// SHOWNET(svc_strings[cmd]); + if (cmd == svcq2_playerinfo && cl.splitclients < MAX_SPLITS) + CLQ2_ParsePlayerstate (cl.splitclients++, old, &cl.q2frame, extrabits); + else + break; + } + if (!cl.splitclients) + Host_EndGame ("CL_ParseFrame: no playerinfo"); if (cmd != svcq2_packetentities) Host_EndGame ("CL_ParseFrame: not packetentities"); } + else + { + cl.splitclients = 1; + CLQ2_ParsePlayerstate (0, old, &cl.q2frame, extrabits); + } CLQ2_ParsePacketEntities (old, &cl.q2frame); + for (cmd = 0; cmd < MAX_SPLITS; cmd++) + cl.playerview[cmd].viewentity = cl.q2frame.clientnum[cmd]+1; + // save the frame off in the backup array for later delta comparisons cl.q2frames[cl.q2frame.serverframe & Q2UPDATE_MASK] = cl.q2frame; @@ -1279,10 +1366,10 @@ void CLQ2_ParseFrame (int extrabits) CL_MakeActive("Quake2"); // cl.force_refdef = true; - cl.predicted_origin[0] = cl.q2frame.playerstate.pmove.origin[0]*0.125; - cl.predicted_origin[1] = cl.q2frame.playerstate.pmove.origin[1]*0.125; - cl.predicted_origin[2] = cl.q2frame.playerstate.pmove.origin[2]*0.125; - VectorCopy (cl.q2frame.playerstate.viewangles, cl.predicted_angles); +// cl.predicted_origin[0] = cl.q2frame.playerstate[0].pmove.origin[0]*0.125; +// cl.predicted_origin[1] = cl.q2frame.playerstate[0].pmove.origin[1]*0.125; +// cl.predicted_origin[2] = cl.q2frame.playerstate[0].pmove.origin[2]*0.125; +// VectorCopy (cl.q2frame.playerstate[0].viewangles, cl.predicted_angles); // if (cls.disable_servercount != cl.servercount // && cl.refresh_prepped) SCR_EndLoadingPlaque (); // get rid of loading plaque @@ -1357,13 +1444,45 @@ struct model_s *S_RegisterSexedModel (entity_state_t *ent, char *base) */ +//returns a list of all the ents currently trying to play a sound. +unsigned int CLQ2_GatherSounds(vec3_t *positions, unsigned int *entnums, sfx_t **sounds, unsigned int max) +{ + entity_state_t *s1; + sfx_t *sfx; + unsigned int pnum; + unsigned int count = 0; + q2frame_t *frame = &cl.q2frame; + for (pnum = 0 ; pnumnum_entities ; pnum++) + { + s1 = &clq2_parse_entities[(frame->parse_entities+pnum)&(MAX_PARSE_ENTITIES-1)]; + if (s1->u.q2.sound > 0 && s1->u.q2.sound < MAX_PRECACHE_SOUNDS) + { + sfx = cl.sound_precache[s1->u.q2.sound]; + if (sfx) + { + if (count == max) + { + Con_DPrintf("Exceeded limit of %d looped sounds\n", max); + break; + } + //fixme: sexed sounds + entnums[count] = s1->number; + VectorCopy(s1->origin, positions[count]); + sounds[count] = sfx; + count++; + } + } + } + return count; +} + /* =============== CL_AddPacketEntities =============== */ -void CLQ2_AddPacketEntities (q2frame_t *frame) +static void CLQ2_AddPacketEntities (q2frame_t *frame) { entity_t ent; entity_state_t *s1; @@ -1971,12 +2090,14 @@ void CLQ2_AddPacketEntities (q2frame_t *frame) CL_AddViewWeapon ============== */ -void CLQ2_AddViewWeapon (q2player_state_t *ps, q2player_state_t *ops) +static void CLQ2_AddViewWeapon (int seat, q2player_state_t *ps, q2player_state_t *ops) { entity_t gun; // view model extern cvar_t cl_gunx, cl_guny, cl_gunz; extern cvar_t cl_gunanglex, cl_gunangley, cl_gunanglez; - playerview_t *pv = &cl.playerview[0]; + playerview_t *pv = &cl.playerview[seat]; + + pv->vm.oldmodel = NULL; // allow the gun to be completely removed if (!r_drawviewmodel.value) @@ -1990,19 +2111,22 @@ void CLQ2_AddViewWeapon (q2player_state_t *ps, q2player_state_t *ops) return; //generate root matrix.. - VectorCopy(cl.playerview[0].simorg, pv->vw_origin); - AngleVectors(cl.playerview[0].simangles, pv->vw_axis[0], pv->vw_axis[1], pv->vw_axis[2]); + VectorCopy(pv->simorg, pv->vw_origin); + AngleVectors(pv->simangles, pv->vw_axis[0], pv->vw_axis[1], pv->vw_axis[2]); VectorInverse(pv->vw_axis[1]); memset (&gun, 0, sizeof(gun)); -// if (gun_model) -// gun.model = gun_model; // development tool -// else - gun.model = cl.model_precache[ps->gunindex]; - if (!gun.model) + pv->vm.oldmodel = cl.model_precache[ps->gunindex]; + if (!pv->vm.oldmodel) return; + pv->vm.oldframe = ps->gunframe; + if (ps->gunindex != ops->gunindex) + pv->vm.prevframe = ps->gunframe; + else + pv->vm.prevframe = ops->gunframe; +/* gun.shaderRGBAf[0] = 1; gun.shaderRGBAf[1] = 1; gun.shaderRGBAf[2] = 1; @@ -2037,6 +2161,7 @@ void CLQ2_AddViewWeapon (q2player_state_t *ps, q2player_state_t *ops) gun.framestate.g[FS_REG].lerpweight[1] = 1-cl.lerpfrac; VectorCopy (gun.origin, gun.oldorigin); // don't lerp at all V_AddEntity (&gun); +*/ } @@ -2047,7 +2172,7 @@ CL_CalcViewValues Sets r_refdef view values =============== */ -void CLQ2_CalcViewValues (void) +void CLQ2_CalcViewValues (int seat) { extern cvar_t v_gunkick_q2; int i; @@ -2055,6 +2180,7 @@ void CLQ2_CalcViewValues (void) q2frame_t *oldframe; q2player_state_t *ps, *ops; extern cvar_t gl_cshiftenabled; + playerview_t *pv = &cl.playerview[seat]; r_refdef.areabitsknown = true; memcpy(r_refdef.areabits, cl.q2frame.areabits, sizeof(r_refdef.areabits)); @@ -2062,12 +2188,12 @@ void CLQ2_CalcViewValues (void) r_refdef.useperspective = true; // find the previous frame to interpolate from - ps = &cl.q2frame.playerstate; + ps = &cl.q2frame.playerstate[seat]; i = (cl.q2frame.serverframe - 1) & Q2UPDATE_MASK; oldframe = &cl.q2frames[i]; if (oldframe->serverframe != cl.q2frame.serverframe-1 || !oldframe->valid) oldframe = &cl.q2frame; // previous frame was dropped or involid - ops = &oldframe->playerstate; + ops = &oldframe->playerstate[seat]; // see if the player entity was teleported this frame if ( fabs(ops->pmove.origin[0] - ps->pmove.origin[0]) > 256*8 @@ -2078,49 +2204,47 @@ void CLQ2_CalcViewValues (void) lerp = cl.lerpfrac; // calculate the origin - if (cl.worldmodel && (!cl_nopred.value) && !(cl.q2frame.playerstate.pmove.pm_flags & Q2PMF_NO_PREDICTION) && !cls.demoplayback) + if (cl.worldmodel && (!cl_nopred.value) && !(cl.q2frame.playerstate[seat].pmove.pm_flags & Q2PMF_NO_PREDICTION) && !cls.demoplayback) { // use predicted values float delta; backlerp = 1.0 - lerp; for (i=0 ; i<3 ; i++) { - r_refdef.vieworg[i] = cl.predicted_origin[i] + ops->viewoffset[i] + pv->simorg[i] = pv->predicted_origin[i] + ops->viewoffset[i] + cl.lerpfrac * (ps->viewoffset[i] - ops->viewoffset[i]) - - backlerp * cl.prediction_error[i]; + - backlerp * pv->prediction_error[i]; } // smooth out stair climbing - delta = realtime - cl.predicted_step_time; + delta = realtime - pv->predicted_step_time; if (delta < 0.1) - r_refdef.vieworg[2] -= cl.predicted_step * (0.1 - delta)*10; + pv->simorg[2] -= pv->predicted_step * (0.1 - delta)*10; } else { // just use interpolated values for (i=0 ; i<3 ; i++) - r_refdef.vieworg + pv->simorg [i] = ops->pmove.origin[i]*0.125 + ops->viewoffset[i] + lerp * (ps->pmove.origin[i]*0.125 + ps->viewoffset[i] - (ops->pmove.origin[i]*0.125 + ops->viewoffset[i]) ); } // if not running a demo or on a locked frame, add the local angle movement - if (cl.worldmodel && cl.q2frame.playerstate.pmove.pm_type < Q2PM_DEAD && !cls.demoplayback) + if (cl.worldmodel && ps->pmove.pm_type < Q2PM_DEAD && !cls.demoplayback) { // use predicted values for (i=0 ; i<3 ; i++) - r_refdef.viewangles[i] = cl.predicted_angles[i]; + pv->simangles[i] = pv->predicted_angles[i]; } else { // just use interpolated values for (i=0 ; i<3 ; i++) - r_refdef.viewangles[i] = LerpAngle (ops->viewangles[i], ps->viewangles[i], lerp); + pv->simangles[i] = LerpAngle (ops->viewangles[i], ps->viewangles[i], lerp); } for (i=0 ; i<3 ; i++) - r_refdef.viewangles[i] += v_gunkick_q2.value * LerpAngle (ops->kick_angles[i], ps->kick_angles[i], lerp); + pv->simangles[i] += v_gunkick_q2.value * LerpAngle (ops->kick_angles[i], ps->kick_angles[i], lerp); - VectorCopy(r_refdef.vieworg, cl.playerview[0].simorg); - VectorCopy(r_refdef.viewangles, cl.playerview[0].simangles); // VectorCopy(r_refdef.viewangles, cl.viewangles); // AngleVectors (r_refdef.viewangles, v_forward, v_right, v_up); @@ -2128,13 +2252,17 @@ void CLQ2_CalcViewValues (void) // interpolate field of view r_refdef.fov_x = ops->fov + lerp * (ps->fov - ops->fov); + //do interpolate blend alpha, but only if the rgb didn't change // don't interpolate blend color for (i=0 ; i<3 ; i++) - sw_blend[i] = ps->blend[i]; - sw_blend[3] = ps->blend[3]*gl_cshiftenabled.value; + pv->screentint[i] = ps->blend[i]; + if (ps->blend[0] == ops->blend[0] && ps->blend[1] == ops->blend[1] && ps->blend[2] == ops->blend[2] && (!ps->blend[3]) == (!ops->blend[3])) + pv->screentint[3] = (ops->blend[3] + lerp * (ps->blend[3]-ops->blend[3]))*gl_cshiftenabled.value; + else + pv->screentint[3] = ps->blend[3]*gl_cshiftenabled.value; // add the weapon - CLQ2_AddViewWeapon (ps, ops); + CLQ2_AddViewWeapon (seat, ps, ops); } /* @@ -2147,6 +2275,7 @@ Emits all entities, particles, and lights to the refresh void CLQ2_AddEntities (void) { extern cvar_t chase_active, chase_back, chase_up; + int seat; if (cls.state != ca_active) return; @@ -2171,7 +2300,8 @@ void CLQ2_AddEntities (void) else cl.lerpfrac = 1.0 - (cl.q2frame.servertime - cl.time*1000) * 0.01; */ - CLQ2_CalcViewValues (); + for (seat = 0; seat < cl.splitclients; seat++) + CLQ2_CalcViewValues (seat); CLQ2_AddPacketEntities (&cl.q2frame); #if 0 CLQ2_AddProjectiles (); diff --git a/engine/client/console.c b/engine/client/console.c index ad849899c..dc5bfb7dd 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -538,7 +538,19 @@ void Con_Clear_f (void) } - +void Cmd_ConEchoCenter_f(void) +{ + console_t *con; + con = Con_FindConsole(Cmd_Argv(1)); + if (!con) + con = Con_Create(Cmd_Argv(1), 0); + if (con) + { + Cmd_ShiftArgs(1, false); + Con_PrintCon(con, Cmd_Args(), con->parseflags|PFS_NONOTIFY|PFS_CENTERED ); + Con_PrintCon(con, "\n", con->parseflags|PFS_NONOTIFY|PFS_CENTERED); + } +} void Cmd_ConEcho_f(void) { console_t *con; @@ -651,6 +663,7 @@ void Con_Init (void) Cmd_AddCommand ("qterm", Con_QTerm_f); #endif + Cmd_AddCommand ("conecho_center", Cmd_ConEchoCenter_f); Cmd_AddCommand ("conecho", Cmd_ConEcho_f); Cmd_AddCommand ("conclear", Cmd_ConClear_f); Cmd_AddCommand ("conclose", Cmd_ConClose_f); @@ -688,7 +701,7 @@ If no console is visible, the notify window will pop up. ================ */ -void Con_PrintConChars (console_t *con, conchar_t *c, int len) +static void Con_PrintConChars (console_t *con, conchar_t *c, int len) { conline_t *oc; conchar_t *o; @@ -717,7 +730,7 @@ void Con_PrintConChars (console_t *con, conchar_t *c, int len) con->current->length+=len; } -void Con_PrintCon (console_t *con, char *txt, unsigned int parseflags) +void Con_PrintCon (console_t *con, const char *txt, unsigned int parseflags) { conchar_t expanded[4096]; conchar_t *c; @@ -768,10 +781,11 @@ void Con_PrintCon (console_t *con, char *txt, unsigned int parseflags) con->linecount--; } con->linecount++; - if (con->flags & CONF_NOTIMES) + if ((con->flags & CONF_NOTIMES) || (parseflags & PFS_NONOTIFY)) con->current->time = 0; else con->current->time = realtime + con->notif_t; + con->current->flags = (parseflags & PFS_CENTERED)?CONL_CENTERED:0; #if defined(_WIN32) && !defined(NOMEDIA) && !defined(WINRT) if (con->current) @@ -803,7 +817,7 @@ void Con_PrintCon (console_t *con, char *txt, unsigned int parseflags) con->cr = false; } - if (!con->current->length && con_timestamps.ival) + if (!con->current->length && con_timestamps.ival && !(parseflags & PFS_CENTERED)) { char timeasc[64]; conchar_t timecon[64], *timeconend; @@ -843,17 +857,25 @@ void Con_PrintCon (console_t *con, char *txt, unsigned int parseflags) } - if (con->flags & CONF_NOTIMES) + if ((con->flags & CONF_NOTIMES) || (parseflags & PFS_NONOTIFY)) con->current->time = 0; else con->current->time = realtime + con->notif_t; } -void Con_Print (char *txt) +void Con_CenterPrint(const char *txt) +{ + int flags = con_main.parseflags|PFS_NONOTIFY|PFS_CENTERED; + Con_PrintCon(&con_main, "^Ue01d^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01f\n", flags); + Con_PrintCon(&con_main, txt, flags); //client console + Con_PrintCon(&con_main, "\n^Ue01d^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01f\n", flags); +} + +void Con_Print (const char *txt) { Con_PrintCon(&con_main, txt, con_main.parseflags); //client console } -void Con_PrintFlags(char *txt, unsigned int setflags, unsigned int clearflags) +void Con_PrintFlags(const char *txt, unsigned int setflags, unsigned int clearflags) { setflags |= con_main.parseflags; setflags &= ~clearflags; @@ -938,7 +960,7 @@ void VARGS Con_Printf (const char *fmt, ...) Con_Print (msg); } -void VARGS Con_SafePrintf (char *fmt, ...) +void VARGS Con_SafePrintf (const char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; @@ -1035,7 +1057,7 @@ void VARGS Con_DPrintf (const char *fmt, ...) } /*description text at the bottom of the console*/ -void Con_Footerf(console_t *con, qboolean append, char *fmt, ...) +void Con_Footerf(console_t *con, qboolean append, const char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; @@ -1924,7 +1946,7 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in } - l->lines = linecount; + l->numlines = linecount; while(linecount-- > 0) { @@ -1944,8 +1966,8 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in { int sstart; int send; - sstart = sx+picw; - send = sstart; + int center; + send = sstart = picw; for (c = s; c < e; ) { c = Font_Decode(c, &codeflags, &codepoint); @@ -1956,6 +1978,10 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in if (send == sstart) send = Font_CharEndCoord(font_console, send, CON_WHITEMASK, ' '); + center = sx; + if (l->flags&CONL_CENTERED) + center += ((ex-sx) - send)/2; + if (y+charh >= seley && y < selsy) { //if they're both on the same line, make sure sx is to the left of ex, so our stuff makes sense if (selex < selsx) @@ -1974,7 +2000,7 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in c = Font_Decode(c, &codeflags, &codepoint); send = Font_CharEndCoord(font_console, send, codeflags, codepoint); - if (send > selex) + if (send+center > selex) break; } @@ -1990,7 +2016,7 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in { Font_Decode(c, &codeflags, &codepoint); x = Font_CharEndCoord(font_console, sstart, codeflags, codepoint); - if (x > selsx) + if (x+center > selsx) break; c = Font_Decode(c, &codeflags, &codepoint); sstart = x; @@ -2003,6 +2029,9 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in con->selstartoffset = 0; } + sstart += center; + send += center; + if (selactive == 1) { R2D_ImagePaletteColour(0, 1.0); @@ -2018,6 +2047,19 @@ static int Con_DrawConsoleLines(console_t *con, conline_t *l, int sx, int ex, in R2D_ImageColours(1.0, 1.0, 1.0, 1.0); x = sx + picw; + + if (l->flags&CONL_CENTERED) + { + int send = 0; + for (c = s; c < e; ) + { + c = Font_Decode(c, &codeflags, &codepoint); + send = Font_CharEndCoord(font_console, send, codeflags, codepoint); + } + + x += ((ex-sx) - send)/2; + } + Font_LineDraw(x, y, s, e); if (y < top) @@ -2402,9 +2444,9 @@ char *Con_CopyConsole(console_t *con, qboolean nomarkup, qboolean onlyiflink) if (outlen+3 > maxlen) break; -#ifdef _WIN32 - result[outlen++] = '\r'; -#endif +//#ifdef _WIN32 +// result[outlen++] = '\r'; +//#endif result[outlen++] = '\n'; cur = (conchar_t*)(l+1); } diff --git a/engine/client/in_generic.c b/engine/client/in_generic.c index 5e1aac210..1913c7b43 100644 --- a/engine/client/in_generic.c +++ b/engine/client/in_generic.c @@ -215,20 +215,33 @@ struct remapctx char *type; char *devicename; int newdevid; + unsigned int found; + unsigned int failed; }; -static void IN_DeviceIDs_DoRemap(void *vctx, char *type, char *devicename, int *qdevid) +static void IN_DeviceIDs_DoRemap(void *vctx, const char *type, const char *devicename, int *qdevid) { struct remapctx *ctx = vctx; + if (!qdevid) + return; + if (!strcmp(ctx->type, type)) if (!strcmp(ctx->devicename, devicename)) - *qdevid = ctx->newdevid; + { + if (qdevid) + *qdevid = ctx->newdevid; + else + ctx->failed++; + ctx->found++; + } } -void IN_DeviceIDs_Enumerate(void *ctx, char *type, char *devicename, int *qdevid) +void IN_DeviceIDs_Enumerate(void *ctx, const char *type, const char *devicename, int *qdevid) { + char buf[8192]; + devicename = COM_QuotedString(devicename, buf, sizeof(buf), false); if (!qdevid) - Con_Printf("%s (%s): %s\n", type, devicename, "device cannot be remapped"); + Con_Printf("%s\t%s\t%s\n", type, "N/A", devicename); else - Con_Printf("%s (%s): %i\n", type, devicename, *qdevid); + Con_Printf("%s\t%i\t%s\n", type, *qdevid, devicename); } void IN_DeviceIDs_f(void) @@ -237,13 +250,24 @@ void IN_DeviceIDs_f(void) if (Cmd_Argc() > 3) { + ctx.found = 0; ctx.type = Cmd_Argv(1); - ctx.devicename = Cmd_Argv(2); - ctx.newdevid = atoi(Cmd_Argv(3)); + ctx.newdevid = atoi(Cmd_Argv(2)); + ctx.devicename = Cmd_Argv(3); INS_EnumerateDevices(&ctx, IN_DeviceIDs_DoRemap); + + if (ctx.failed) + Con_Printf("device cannot be remapped\n"); + else if (!ctx.found) + Con_Printf("%s \"%s\" not known\n", ctx.type, ctx.devicename); + else if (!cl_warncmd.ival) + Con_Printf("device remapped\n"); } else + { + Con_Printf("Type\tMapping\tName\n"); INS_EnumerateDevices(NULL, IN_DeviceIDs_Enumerate); + } } float IN_DetermineMouseRate(void) diff --git a/engine/client/in_win.c b/engine/client/in_win.c index 1ad32b224..fccad0339 100644 --- a/engine/client/in_win.c +++ b/engine/client/in_win.c @@ -118,6 +118,7 @@ typedef struct { union { HANDLE rawinputhandle; } handles; + char sysname[MAX_OSPATH]; int qdeviceid; } keyboard_t; @@ -126,6 +127,7 @@ typedef struct { union { HANDLE rawinputhandle; // raw input } handles; + char sysname[MAX_OSPATH]; int numbuttons; int oldbuttons; @@ -280,8 +282,8 @@ static int rawkbdcount; static RAWINPUT *raw; static int ribuffersize; -static cvar_t in_rawinput = CVARD("in_rawinput", "0", "Enables rawinput support for mice in XP onwards. Rawinput permits independant device identification (ie: splitscreen clients can each have their own mouse)"); -static cvar_t in_rawinput_keyboard = CVARD("in_rawinput_keyboard", "0", "Enables rawinput support for keyboards in XP onwards as well as just mice. Requires in_rawinput to be set."); +static cvar_t in_rawinput_mice = CVARD("in_rawinput", "0", "Enables rawinput support for mice in XP onwards. Rawinput permits independant device identification (ie: splitscreen clients can each have their own mouse)"); +static cvar_t in_rawinput_keyboard = CVARD("in_rawinput_keyboard", "0", "Enables rawinput support for keyboards in XP onwards as well as just mice."); static cvar_t in_rawinput_rdp = CVARD("in_rawinput_rdp", "0", "Activate Remote Desktop Protocol devices too."); void INS_RawInput_MouseDeRegister(void); @@ -899,25 +901,25 @@ void INS_RawInput_Init(void) _RRID = (pRegisterRawInputDevices)GetProcAddress(user32,"RegisterRawInputDevices"); if (!_RRID) { - Con_SafePrintf("Raw input: function RegisterRawInputDevices could not be registered\n"); + Con_SafePrintf("Raw input: function RegisterRawInputDevices is not available\n"); return; } _GRIDL = (pGetRawInputDeviceList)GetProcAddress(user32,"GetRawInputDeviceList"); if (!_GRIDL) { - Con_SafePrintf("Raw input: function GetRawInputDeviceList could not be registered\n"); + Con_SafePrintf("Raw input: function GetRawInputDeviceList is not available\n"); return; } _GRIDIA = (pGetRawInputDeviceInfoA)GetProcAddress(user32,"GetRawInputDeviceInfoA"); if (!_GRIDIA) { - Con_SafePrintf("Raw input: function GetRawInputDeviceInfoA could not be registered\n"); + Con_SafePrintf("Raw input: function GetRawInputDeviceInfoA is not available\n"); return; } _GRID = (pGetRawInputData)GetProcAddress(user32,"GetRawInputData"); if (!_GRID) { - Con_SafePrintf("Raw input: function GetRawInputData could not be registered\n"); + Con_SafePrintf("Raw input: function GetRawInputData is not available\n"); return; } @@ -958,6 +960,9 @@ void INS_RawInput_Init(void) switch (pRawInputDeviceList[i].dwType) { case RIM_TYPEMOUSE: + if (!in_rawinput_mice.ival) + continue; + mtemp++; break; case RIM_TYPEKEYBOARD: @@ -995,9 +1000,14 @@ void INS_RawInput_Init(void) switch (pRawInputDeviceList[i].dwType) { case RIM_TYPEMOUSE: + if (!in_rawinput_mice.ival) + continue; + // set handle rawmice[rawmicecount].handles.rawinputhandle = pRawInputDeviceList[i].hDevice; - rawmice[rawmicecount].numbuttons = 10; + Q_strncpyz(rawmice[rawmicecount].sysname, dname, sizeof(rawmice[rawmicecount].sysname)); + rawmice[rawmicecount].numbuttons = 16; + rawmice[rawmicecount].oldbuttons = 0; rawmice[rawmicecount].qdeviceid = rawmicecount; rawmicecount++; break; @@ -1006,6 +1016,7 @@ void INS_RawInput_Init(void) continue; rawkbd[rawkbdcount].handles.rawinputhandle = pRawInputDeviceList[i].hDevice; + Q_strncpyz(rawkbd[rawkbdcount].sysname, dname, sizeof(rawkbd[rawkbdcount].sysname)); rawkbd[rawkbdcount].qdeviceid = rawkbdcount; rawkbdcount++; break; @@ -1023,7 +1034,7 @@ void INS_RawInput_Init(void) break; } } - Con_SafePrintf("Raw input type %i: [%i] %s\n", (int)pRawInputDeviceList[i].dwType, i, dname); + Con_DPrintf("Raw input type %i: [%i] %s\n", (int)pRawInputDeviceList[i].dwType, i, dname); } @@ -1108,7 +1119,7 @@ INS_Init void INS_ReInit (void) { #ifdef USINGRAWINPUT - if (in_rawinput.value) + if (in_rawinput_mice.ival || in_rawinput_keyboard.ival) { INS_RawInput_Init(); } @@ -1164,7 +1175,7 @@ void INS_Init (void) uiWheelMessage = RegisterWindowMessageA ( "MSWHEEL_ROLLMSG" ); #ifdef USINGRAWINPUT - Cvar_Register (&in_rawinput, "Input Controls"); + Cvar_Register (&in_rawinput_mice, "Input Controls"); Cvar_Register (&in_rawinput_keyboard, "Input Controls"); Cvar_Register (&in_rawinput_rdp, "Input Controls"); #endif @@ -1967,9 +1978,9 @@ void INS_EnumerateDevices(void *ctx, void(*callback)(void *ctx, char *type, char int idx; for (idx = 0; idx < rawmicecount; idx++) - callback(ctx, "mouse", va("raw%i", idx), &rawmice[idx].qdeviceid); + callback(ctx, "mouse", rawmice[idx].sysname?rawmice[idx].sysname:va("raw%i", idx), &rawmice[idx].qdeviceid); for (idx = 0; idx < rawkbdcount; idx++) - callback(ctx, "keyboard", va("raw%i", idx), &rawkbd[idx].qdeviceid); + callback(ctx, "keyboard", rawkbd[idx].sysname?rawkbd[idx].sysname:va("rawk%i", idx), &rawkbd[idx].qdeviceid); #if (DIRECTINPUT_VERSION >= DINPUT_VERSION_DX7) if (dinput >= DINPUT_VERSION_DX7 && g_pMouse7) diff --git a/engine/client/keys.c b/engine/client/keys.c index e3651e763..4dad8ff88 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -752,7 +752,7 @@ void Key_DefaultLinkClicked(console_t *con, char *text, char *info) else { char cmdprefix[6]; - snprintf(cmdprefix, sizeof(cmdprefix), "%i ", i); + snprintf(cmdprefix, sizeof(cmdprefix), "%i ", i+1); //hey look! its you! @@ -762,7 +762,7 @@ void Key_DefaultLinkClicked(console_t *con, char *text, char *info) } else { - Con_Footerf(con, true, " ^[Suicide\\cmd\\kill^]"); + Con_Footerf(con, true, " ^[Suicide\\cmd\\%skill^]", cmdprefix); #ifndef CLIENTONLY if (!sv.state) Con_Footerf(con, true, " ^[Disconnect\\cmd\\disconnect^]"); @@ -772,9 +772,9 @@ void Key_DefaultLinkClicked(console_t *con, char *text, char *info) if (cls.allow_cheats) #endif { - Con_Footerf(con, true, " ^[Noclip\\cmd\\noclip^]"); - Con_Footerf(con, true, " ^[Fly\\cmd\\fly^]"); - Con_Footerf(con, true, " ^[God\\cmd\\god^]"); + Con_Footerf(con, true, " ^[Noclip\\cmd\\%snoclip^]", cmdprefix); + Con_Footerf(con, true, " ^[Fly\\cmd\\%sfly^]", cmdprefix); + Con_Footerf(con, true, " ^[God\\cmd\\%sgod^]", cmdprefix); Con_Footerf(con, true, " ^[Give\\impulse\\9^]"); } } @@ -2213,10 +2213,9 @@ void Key_Event (int devid, int key, unsigned int unicode, qboolean down) // Con_Printf ("%i : %i : %i\n", key, unicode, down); //@@@ - //bug: my keyboard doesn't fire release events if the other shift is already pressed. + //bug: two of my keyboard doesn't fire release events if the other shift is already pressed (so I assume this is a common thing). //hack around that by just force-releasing eg left if right is pressed, but only on inital press to avoid potential infinite loops if the state got bad. //ctrl+alt don't seem to have the problem. - //you can still see a difference in that if (key == K_LSHIFT && !keydown[K_LSHIFT] && keydown[K_RSHIFT]) Key_Event(devid, K_RSHIFT, 0, false); if (key == K_RSHIFT && !keydown[K_RSHIFT] && keydown[K_LSHIFT]) @@ -2488,7 +2487,9 @@ void Key_Event (int devid, int key, unsigned int unicode, qboolean down) return; //first player is normally assumed anyway. - if (devid) + if (cl_forceseat.ival>0) + Q_snprintfz (p, sizeof(p), "p %i ", cl_forceseat.ival); + else if (devid) Q_snprintfz (p, sizeof(p), "p %i ", devid+1); else *p = 0; diff --git a/engine/client/m_mp3.c b/engine/client/m_mp3.c index dad64bd54..95e033605 100644 --- a/engine/client/m_mp3.c +++ b/engine/client/m_mp3.c @@ -84,6 +84,13 @@ static qboolean Media_Changed (unsigned int mediatype) return true; } +void Media_WriteCurrentTrack(sizebuf_t *buf) +{ + //fixme: for demo playback + MSG_WriteByte (buf, svc_cdtrack); + MSG_WriteByte (buf, 0); +} + //fake cd tracks. qboolean Media_NamedTrack(const char *track, const char *looptrack) { @@ -4628,7 +4635,7 @@ double Media_TweekCaptureFrameTime(double oldtime, double time) { return oldtime void Media_RecordFrame (void) {} void Media_CaptureDemoEnd(void) {} void Media_RecordDemo_f(void) {} -void Media_RecordAudioFrame (short *sample_buffer, int samples) {} +//void Media_RecordAudioFrame (short *sample_buffer, int samples) {} void Media_StopRecordFilm_f (void) {} void Media_RecordFilm_f (void){} void M_Menu_Media_f (void) {} diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 3726aac83..43350ec08 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -182,7 +182,7 @@ void M_Options_Remove(menu_t *m) //options menu. void M_Menu_Options_f (void) { - extern cvar_t crosshair, r_projection; + extern cvar_t crosshair, r_projection, sv_autosave; int y; menuoption_t *updatecbo; @@ -211,6 +211,25 @@ void M_Menu_Options_f (void) NULL }; + static const char *autosaveopts[] = { + "Off", + "30 secs", + "60 secs", + "90 secs", + "120 secs", + "5 mins", + NULL + }; + static const char *autsavevals[] = { + "0", + "0.5", + "1", + "1.5", + "2", + "5", + NULL + }; + menubulk_t bulk[] = { MB_CONSOLECMD("Customize controls", "menu_keys\n", "Modify keyboard and mouse inputs."), MB_CONSOLECMD("Go to console", "toggleconsole\nplay misc/menu2.wav\n", "Open up the engine console."), @@ -226,7 +245,10 @@ void M_Menu_Options_f (void) MB_CHECKBOXCVAR("Lookspring", lookspring, 0), MB_CHECKBOXCVAR("Lookstrafe", lookstrafe, 0), MB_CHECKBOXCVAR("Windowed Mouse", _windowed_mouse, 0), - MB_COMBORETURN("Auto Update", autoupopts, Sys_GetAutoUpdateSetting(), updatecbo, "Hello World"), + MB_COMBORETURN("Auto Update", autoupopts, Sys_GetAutoUpdateSetting(), updatecbo, "This downloads engine updates from the internet, when a new build is available."), +#ifndef CLIENTONLY + MB_COMBOCVAR("Auto Save", sv_autosave, autosaveopts, autsavevals, NULL), +#endif MB_SPACING(4), // removed hud options (cl_sbar, cl_hudswap, old-style chat, old-style msg) MB_CONSOLECMD("Video Options", "menu_video\n", "Set video resolution, color depth, refresh rate, and anti-aliasing options."), @@ -244,7 +266,7 @@ void M_Menu_Options_f (void) #endif #ifdef TEXTEDITOR //this option is a bit strange in q2. - MB_CHECKBOXCVAR("QC Debugger", pr_debugger, 0), +// MB_CHECKBOXCVAR("QC Debugger", pr_debugger, 0), #endif // removed downloads (is this still appropriate?) // removed teamplay @@ -1050,9 +1072,11 @@ void M_Menu_Render_f (void) "1", NULL }; + static const char *logcenteropts[] = {"Off", "Singleplayer", "Always", NULL}; + static const char *logcentervalues[] = {"0", "1", "2", NULL}; menu_t *menu; - extern cvar_t r_novis, cl_item_bobbing, r_waterwarp, r_nolerp, r_noframegrouplerp, r_fastsky, gl_nocolors, gl_lerpimages, r_wateralpha, r_drawviewmodel, gl_cshiftenabled, r_hdr_irisadaptation; + extern cvar_t r_novis, cl_item_bobbing, r_waterwarp, r_nolerp, r_noframegrouplerp, r_fastsky, gl_nocolors, gl_lerpimages, r_wateralpha, r_drawviewmodel, gl_cshiftenabled, r_hdr_irisadaptation, scr_logcenterprint; #ifdef GLQUAKE extern cvar_t r_bloom; #endif @@ -1073,6 +1097,7 @@ void M_Menu_Render_f (void) MB_SLIDER("Viewmodel Alpha", r_drawviewmodel, 0, 1, 0.1, NULL), MB_CHECKBOXCVAR("Poly Blending", gl_cshiftenabled, 0), MB_CHECKBOXCVAR("Disable Colormap", gl_nocolors, 0), + MB_COMBOCVAR("Log Centerprints", scr_logcenterprint, logcenteropts, logcentervalues, "Display centerprints in the console also."), #ifdef GLQUAKE MB_CHECKBOXCVAR("Bloom", r_bloom, 0), #endif diff --git a/engine/client/m_single.c b/engine/client/m_single.c index d6f6e58d7..529bb3832 100644 --- a/engine/client/m_single.c +++ b/engine/client/m_single.c @@ -19,67 +19,120 @@ typedef struct { shader_t *picshader; } loadsavemenuinfo_t; -#define MAX_SAVEGAMES 20 +#define SAVEFIRST_AUTO 1 +#define SAVECOUNT_AUTO 3 +#define SAVEFIRST_STANDARD (SAVEFIRST_AUTO + SAVECOUNT_AUTO) +#define SAVECOUNT_STANDARD 20 +#define MAX_SAVEGAMES (1+SAVECOUNT_AUTO+SAVECOUNT_STANDARD) struct { - char map[22+1]; + qboolean loadable; + qbyte saveable; //0=autosave, 1=regular, 2=quick + char sname[9]; + char desc[22+1]; char kills[39-22+1]; char time[64]; + char map[32]; } m_saves[MAX_SAVEGAMES]; -int loadable[MAX_SAVEGAMES]; -void M_ScanSaves (void) +static void M_ScanSave(unsigned int slot, const char *name, qboolean savable) { - int i, j; + char *in, *out, *end; + int j; char line[MAX_OSPATH]; vfsfile_t *f; int version; - for (i=0 ; i= FTESAVEGAME_VERSION+GT_MAX)) - { - Q_strncpyz (m_saves[i].map, "Incompatible version", sizeof(m_saves[i].map)); - VFS_CLOSE (f); - continue; - } - - // read the desc, change _ back to space, fill the separate fields - VFS_GETS(f, line, sizeof(line)); - for (j=0 ; line[j] ; j++) - if (line[j] == '_') - line[j] = ' '; - for (; j < sizeof(line[j]); j++) - line[j] = '\0'; - memcpy(m_saves[i].map, line, 22); - m_saves[i].map[22] = 0; - memcpy(m_saves[i].kills, line+22, 39-22); - m_saves[i].kills[39-22] = 0; - Q_strncpyz(m_saves[i].time, line+39, sizeof(m_saves[i].time)); - - - loadable[i] = true; - VFS_CLOSE (f); - - continue; - } } + if (f) + { + VFS_GETS(f, line, sizeof(line)); + version = atoi(line); + if (version != 5 && version != 6 && (version < FTESAVEGAME_VERSION || version >= FTESAVEGAME_VERSION+GT_MAX)) + { + Q_strncpyz (m_saves[slot].desc, "Incompatible version", sizeof(m_saves[slot].desc)); + VFS_CLOSE (f); + return; + } + + // read the desc, change _ back to space, fill the separate fields + VFS_GETS(f, line, sizeof(line)); + for (j=0 ; line[j] ; j++) + if (line[j] == '_') + line[j] = ' '; + for (; j < sizeof(line[j]); j++) + line[j] = '\0'; + memcpy(m_saves[slot].desc, line, 22); + m_saves[slot].desc[22] = 0; + + for (in = line+22, out = m_saves[slot].kills, end = line+39; in < end && *in == ' '; ) + in++; + for (out = m_saves[slot].kills; in < end; ) + *out++ = *in++; + for (end = m_saves[slot].kills; out > end && out[-1] == ' '; ) + out--; + *out = 0; + + Q_strncpyz(m_saves[slot].time, line+39, sizeof(m_saves[slot].time)); + + + if (version == 5 || version == 6) + { + for (j = 0; j < 16; j++) + VFS_GETS(f, line, sizeof(line)); //16 parms + VFS_GETS(f, line, sizeof(line)); //skill + VFS_GETS(f, m_saves[slot].map, sizeof(m_saves[slot].map)); + } + + m_saves[slot].loadable = true; + VFS_CLOSE (f); + } +} + +const char *M_ChooseAutoSave(void) +{ + int i, j; + + for (i = SAVEFIRST_AUTO; i < SAVEFIRST_AUTO+SAVECOUNT_AUTO; i++) + { + M_ScanSave(i, va("a%i", i-SAVEFIRST_AUTO), false); + if (!m_saves[i].loadable) + return m_saves[i].sname; + } + + for (i = SAVEFIRST_AUTO; i < SAVEFIRST_AUTO+SAVECOUNT_AUTO; i++) + { + for (j = SAVEFIRST_AUTO; j < SAVEFIRST_AUTO+SAVECOUNT_AUTO; j++) + if (strcmp(m_saves[i].time, m_saves[j].time) > 0) + break; + if (j == SAVEFIRST_AUTO+SAVECOUNT_AUTO) + return m_saves[i].sname; + } + + return m_saves[SAVEFIRST_AUTO].sname; +} + +static void M_ScanSaves (void) +{ + int i; + M_ScanSave(0, "quick", 2); + for (i=SAVEFIRST_AUTO ; ipicshader->defaulttextures->base); R_UnloadShader(info->picshader); } - info->picshader = R_RegisterPic(va("saves/s%i/screeny.tga", slot)); + info->picshader = R_RegisterPic(va("saves/%s/screeny.tga", m_saves[slot].sname)); } if (info->picshader) { - if (R_GetShaderSizes(info->picshader, &width, &height, false) > 0) + shader_t *pic = NULL; + switch(R_GetShaderSizes(info->picshader, &width, &height, false)) { - //FIXME: maintain aspect - R2D_ScalePic (x, y, 160,120/*item->common.width, item->common.height*/, info->picshader); + case 1: + pic = info->picshader; + break; + case 0: + if (*m_saves[slot].map) + pic = R_RegisterPic(va("levelshots/%s", m_saves[slot].map)); + break; + } + if (pic) + { + int w = 160; + int h = 120; + if (R_GetShaderSizes(pic, &width, &height, false) <= 0) + { + width = 64; + height = 64; + } + + if ((float)width/height > (float)w/h) + { + w = 160; + h = ((float)w*height) / width; + } + else + { + h = 120; + w = ((float)h*width) / height; + } + R2D_ScalePic (x + (160-w)/2, y + (120-h)/2, w, h, pic); } } Draw_FunStringWidth(x, y+120+0, m_saves[slot].time, 160, 2, false); Draw_FunStringWidth(x, y+120+8, m_saves[slot].kills, 160, 2, false); + + switch(m_saves[slot].saveable) + { + case 2: + Draw_FunStringWidth(x, y+120+16, "Quick Save", 160, 2, false); + break; + case 0: + Draw_FunStringWidth(x, y+120+16, "Autosave", 160, 2, false); + break; + } + Draw_FunStringWidth(x, y+120+24, m_saves[slot].sname, 160, 2, false); } } @@ -151,7 +243,10 @@ void M_Menu_Save_f (void) for (i=0 ; i< MAX_SAVEGAMES; i++) { - op = (menuoption_t *)MC_AddConsoleCommandf(menu, 16, 192, 32+8*i, false, m_saves[i].map, "savegame s%i\nclosemenu\n", i); + if (m_saves[i].saveable) + op = (menuoption_t *)MC_AddConsoleCommandf(menu, 16, 192, 32+8*i, false, m_saves[i].desc, "savegame %s\nclosemenu\n", m_saves[i].sname); + else + MC_AddWhiteText(menu, 16, 170, 32+8*i, m_saves[i].desc, false); if (!menu->selecteditem) menu->selecteditem = op; } @@ -171,21 +266,23 @@ void M_Menu_Load_f (void) menu->data = menu+1; MC_AddCenterPicture(menu, 4, 24, "gfx/p_load.lmp"); - menu->cursoritem = (menuoption_t *)MC_AddRedText(menu, 8, 0, 32, NULL, false); menu->remove = M_Menu_LoadSave_Remove; M_ScanSaves (); for (i=0 ; i< MAX_SAVEGAMES; i++) { - if (loadable[i]) - op = (menuoption_t *)MC_AddConsoleCommandf(menu, 16, 170, 32+8*i, false, m_saves[i].map, "loadgame s%i\nclosemenu\n", i); + if (m_saves[i].loadable) + op = (menuoption_t *)MC_AddConsoleCommandf(menu, 16, 170, 32+8*i, false, m_saves[i].desc, "loadgame %s\nclosemenu\n", m_saves[i].sname); else - MC_AddWhiteText(menu, 16, 170, 32+8*i, m_saves[i].map, false); + MC_AddWhiteText(menu, 16, 170, 32+8*i, m_saves[i].desc, false); if (!menu->selecteditem && op) menu->selecteditem = op; } + if (menu->selecteditem) + menu->cursoritem = (menuoption_t *)MC_AddRedText(menu, 8, 0, menu->selecteditem->common.posy, NULL, false); + MC_AddCustom(menu, 192, 60-16, NULL, 0)->draw = M_Menu_LoadSave_Preview_Draw; } diff --git a/engine/client/menu.c b/engine/client/menu.c index 773f2e291..2ffa792b0 100644 --- a/engine/client/menu.c +++ b/engine/client/menu.c @@ -416,6 +416,7 @@ bindnames_t q2bindnames[] = { {"+attack", "attack "}, {"cmd weapnext", "next weapon "}, +{"cmd weapprev", "prev weapon "}, {"+forward", "walk forward "}, {"+back", "backpedal "}, {"+left", "turn left "}, @@ -506,6 +507,7 @@ void M_Menu_Keys_f (void) int y; menu_t *menu; vfsfile_t *bindslist; + extern cvar_t cl_splitscreen; Key_Dest_Add(kdm_emenu); m_state = m_complex; @@ -534,7 +536,7 @@ void M_Menu_Keys_f (void) break; } - if (cl_forceseat.ival) + if (cl.splitclients || cl_splitscreen.ival || cl_forceseat.ival) { static char *texts[MAX_SPLITS+2] = { diff --git a/engine/client/menu.h b/engine/client/menu.h index 3960c608e..90ff3f1af 100644 --- a/engine/client/menu.h +++ b/engine/client/menu.h @@ -380,6 +380,7 @@ void M_Complex_Draw(void); void M_Script_Init(void); void M_Serverlist_Init(void); +const char *M_ChooseAutoSave(void); void M_Menu_Main_f (void); void M_Menu_SinglePlayer_f (void); void M_Menu_Load_f (void); diff --git a/engine/client/p_script.c b/engine/client/p_script.c index 3cfade34e..a71458dd9 100644 --- a/engine/client/p_script.c +++ b/engine/client/p_script.c @@ -279,7 +279,8 @@ typedef struct part_type_s { SM_LAVASPLASH, //lavasplash = q1-style lavasplash SM_UNICIRCLE, //unicircle = uniform circle SM_FIELD, //field = synced field (brightfield, etc) - SM_DISTBALL // uneven distributed ball + SM_DISTBALL, // uneven distributed ball + SM_MESHSURFACE //distributed roughly evenly over the surface of the mesh } spawnmode; float gravity; @@ -1186,7 +1187,7 @@ void P_ParticleEffect_f(void) float tscale; tscale = atof(Cmd_Argv(5)); - if (tscale < 0) + if (tscale <= 0) tscale = 1; ptype->s1 = atof(value)/tscale; @@ -1200,6 +1201,51 @@ void P_ParticleEffect_f(void) if (ptype->randsmax < 1 || ptype->texsstride == 0) ptype->randsmax = 1; } + else if (!strcmp(var, "atlas")) + { //atlas countineachaxis first [last] + int dims; + int i; + int m; + + dims = atof(Cmd_Argv(1)); + i = atoi(Cmd_Argv(2)); + m = atoi(Cmd_Argv(3)); + if (dims < 1) + dims = 1; + + if (m > (m/dims)*dims+dims-1) + { + m = (m/dims)*dims+dims-1; + Con_Printf("effect %s wraps across an atlased line\n", ptype->name); + } + if (m < i) + m = i; + + ptype->s1 = 1.0/dims * (i%dims); + ptype->s2 = 1.0/dims * (1+(i%dims)); + ptype->t1 = 1.0/dims * (i/dims); + ptype->t2 = 1.0/dims * (1+(i/dims)); + + ptype->randsmax = m-i; + ptype->texsstride = ptype->s2-ptype->s1; + + //its modulo + ptype->randsmax++; + } + else if (!strcmp(var, "rotation")) + { + ptype->rotationstartmin = atof(value)*M_PI/180; + if (Cmd_Argc()>2) + ptype->rotationstartrand = atof(Cmd_Argv(2))*M_PI/180-ptype->rotationstartmin; + else + ptype->rotationstartrand = 0; + + ptype->rotationmin = atof(Cmd_Argv(3))*M_PI/180; + if (Cmd_Argc()>4) + ptype->rotationrand = atof(Cmd_Argv(4))*M_PI/180-ptype->rotationmin; + else + ptype->rotationrand = 0; + } else if (!strcmp(var, "rotationstart")) { ptype->rotationstartmin = atof(value)*M_PI/180; @@ -1517,23 +1563,66 @@ parsefluid: } else if (!strcmp(var, "sound")) { + char *e; ptype->sounds = BZ_Realloc(ptype->sounds, sizeof(partsounds_t)*(ptype->numsounds+1)); Q_strncpyz(ptype->sounds[ptype->numsounds].name, Cmd_Argv(1), sizeof(ptype->sounds[ptype->numsounds].name)); if (*ptype->sounds[ptype->numsounds].name) S_PrecacheSound(ptype->sounds[ptype->numsounds].name); - ptype->sounds[ptype->numsounds].vol = atof(Cmd_Argv(2)); - if (!ptype->sounds[ptype->numsounds].vol) - ptype->sounds[ptype->numsounds].vol = 1; - ptype->sounds[ptype->numsounds].atten = atof(Cmd_Argv(3)); - if (!ptype->sounds[ptype->numsounds].atten) - ptype->sounds[ptype->numsounds].atten = 1; - ptype->sounds[ptype->numsounds].pitch = atof(Cmd_Argv(4)); - if (!ptype->sounds[ptype->numsounds].pitch) - ptype->sounds[ptype->numsounds].pitch = 100; - ptype->sounds[ptype->numsounds].delay = atof(Cmd_Argv(5)); - if (!ptype->sounds[ptype->numsounds].delay) - ptype->sounds[ptype->numsounds].delay = 0; - ptype->sounds[ptype->numsounds].weight = atof(Cmd_Argv(6)); + + ptype->sounds[ptype->numsounds].vol = 1; + ptype->sounds[ptype->numsounds].atten = 1; + ptype->sounds[ptype->numsounds].pitch = 100; + ptype->sounds[ptype->numsounds].delay = 0; + ptype->sounds[ptype->numsounds].weight = 0; + + strtoul(Cmd_Argv(2), &e, 0); + while(*e == ' ' || *e == '\t') + e++; + if (*e) + { + int p; + for(p = 2; p < Cmd_Argc(); p++) + { + e = Cmd_Argv(p); + + if (!Q_strncasecmp(e, "vol=", 4) || !Q_strncasecmp(e, "volume=", 7)) + ptype->sounds[ptype->numsounds].vol = atof(strchr(e, '=')+1); + else if (!Q_strncasecmp(e, "attn=", 5) || !Q_strncasecmp(e, "atten=", 6) || !Q_strncasecmp(e, "attenuation=", 12)) + { + e = strchr(e, '=')+1; + if (!strcmp(e, "none")) + ptype->sounds[ptype->numsounds].atten = 0; + else if (!strcmp(e, "normal")) + ptype->sounds[ptype->numsounds].atten = 1; + else + ptype->sounds[ptype->numsounds].atten = atof(e); + } + else if (!Q_strncasecmp(e, "pitch=", 6)) + ptype->sounds[ptype->numsounds].pitch = atof(strchr(e, '=')+1); + else if (!Q_strncasecmp(e, "delay=", 6)) + ptype->sounds[ptype->numsounds].delay = atof(strchr(e, '=')+1); + else if (!Q_strncasecmp(e, "weight=", 7)) + ptype->sounds[ptype->numsounds].weight = atof(strchr(e, '=')+1); + else + Con_Printf("Bad named argument: %s\n", e); + } + } + else + { + ptype->sounds[ptype->numsounds].vol = atof(Cmd_Argv(2)); + if (!ptype->sounds[ptype->numsounds].vol) + ptype->sounds[ptype->numsounds].vol = 1; + ptype->sounds[ptype->numsounds].atten = atof(Cmd_Argv(3)); + if (!ptype->sounds[ptype->numsounds].atten) + ptype->sounds[ptype->numsounds].atten = 1; + ptype->sounds[ptype->numsounds].pitch = atof(Cmd_Argv(4)); + if (!ptype->sounds[ptype->numsounds].pitch) + ptype->sounds[ptype->numsounds].pitch = 100; + ptype->sounds[ptype->numsounds].delay = atof(Cmd_Argv(5)); + if (!ptype->sounds[ptype->numsounds].delay) + ptype->sounds[ptype->numsounds].delay = 0; + ptype->sounds[ptype->numsounds].weight = atof(Cmd_Argv(6)); + } if (!ptype->sounds[ptype->numsounds].weight) ptype->sounds[ptype->numsounds].weight = 1; ptype->numsounds++; @@ -2729,7 +2818,6 @@ static void P_ImportEffectInfo(char *config, char *line) ptype->colorindex = -1; ptype->spawnchance = 1; - ptype->randsmax = 1; ptype->looks.scalefactor = 2; ptype->looks.invscalefactor = 0; ptype->looks.type = PT_NORMAL; @@ -2738,6 +2826,14 @@ static void P_ImportEffectInfo(char *config, char *line) ptype->looks.stretch = 1; ptype->dl_time = 0; + + i = 63; //default texture is 63. + ptype->s1 = teximages[i][0]; + ptype->s2 = teximages[i][1]; + ptype->t1 = teximages[i][2]; + ptype->t2 = teximages[i][3]; + ptype->texsstride = 0; + ptype->randsmax = 1; } else if (!ptype) { @@ -2887,7 +2983,8 @@ static void P_ImportEffectInfo(char *config, char *line) else if (!strcmp(arg[0], "bounce") && args == 2) { ptype->clipbounce = atof(arg[1]); - ptype->cliptype = ptype - part_type; + if (ptype->clipbounce < 0) + ptype->cliptype = ptype - part_type; } else if (!strcmp(arg[0], "airfriction") && args == 2) ptype->friction[2] = ptype->friction[1] = ptype->friction[0] = atof(arg[1]); @@ -2974,16 +3071,16 @@ static void P_ImportEffectInfo(char *config, char *line) ptype->dl_corona_scale = atof(arg[2]); } #if 1 - else if (!strcmp(arg[0], "staincolor") && args == 3) - Con_Printf("Particle effect token %s not supported\n", arg[0]); - else if (!strcmp(arg[0], "stainalpha") && args == 3) - Con_Printf("Particle effect token %s not supported\n", arg[0]); - else if (!strcmp(arg[0], "stainsize") && args == 3) - Con_Printf("Particle effect token %s not supported\n", arg[0]); - else if (!strcmp(arg[0], "staintex") && args == 3) - Con_Printf("Particle effect token %s not supported\n", arg[0]); + else if (!strcmp(arg[0], "staincolor") && args == 3) //stainmaps multiplier + Con_DPrintf("Particle effect token %s not supported\n", arg[0]); + else if (!strcmp(arg[0], "stainalpha") && args == 3) //affects stainmaps AND stain-decals. + Con_DPrintf("Particle effect token %s not supported\n", arg[0]); + else if (!strcmp(arg[0], "stainsize") && args == 3) //affects stainmaps AND stain-decals. + Con_DPrintf("Particle effect token %s not supported\n", arg[0]); + else if (!strcmp(arg[0], "staintex") && args == 3) //actually spawns a decal + Con_DPrintf("Particle effect token %s not supported\n", arg[0]); else if (!strcmp(arg[0], "stainless") && args == 2) - Con_Printf("Particle effect token %s not supported\n", arg[0]); + Con_DPrintf("Particle effect token %s not supported\n", arg[0]); #endif else if (!strcmp(arg[0], "rotate") && args == 5) { @@ -3839,8 +3936,8 @@ static void PScript_ApplyOrgVel(vec3_t oorg, vec3_t ovel, vec3_t eforg, vec3_t a } } - j = 0; - m = 0; + j = pno%NUMVERTEXNORMALS; + m = pno/NUMVERTEXNORMALS; break; default: //others don't need intitialisation break; @@ -4035,6 +4132,7 @@ static void PScript_ApplyOrgVel(vec3_t oorg, vec3_t ovel, vec3_t eforg, vec3_t a ovel[2] += crand() * ptype->velwrand[2]; VectorAdd(ovel, ptype->velbias, ovel); } + VectorAdd(oorg, ptype->orgbias, oorg); } @@ -4208,6 +4306,8 @@ static int PScript_RunParticleEffectState (vec3_t org, vec3_t dir, float count, particle_t *p; beamseg_t *b, *bfirst; vec3_t ofsvec, arsvec; // offsetspread vec, areaspread vec + + float orgadd, veladd; trailstate_t *ts; if (typenum >= FALLBACKBIAS && fallback) @@ -4414,6 +4514,13 @@ static int PScript_RunParticleEffectState (vec3_t org, vec3_t dir, float count, j = 0; m = 0; break; +// case SM_MESHSURFACE: +// meshsurface = querymesh; +// totalarea = gah; +// density = count / totalarea; +// area = 0; +// tri = -1; +// break; default: //others don't need intitialisation break; } @@ -4518,17 +4625,29 @@ static int PScript_RunParticleEffectState (vec3_t org, vec3_t dir, float count, p->rgba[1] += p->org[1]*ptype->rgbrand[1] + ptype->rgbchange[1]*p->die; p->rgba[2] += p->org[2]*ptype->rgbrand[2] + ptype->rgbchange[2]*p->die; -#if 1 +#if 0 PScript_ApplyOrgVel(p->org, p->vel, org, axis, i, pcount, ptype); #else - // randomvel - p->vel[0] = crandom()*ptype->randomvel; - p->vel[1] = crandom()*ptype->randomvel; - p->vel[2] = crandom()*ptype->randomvelvert + ptype->randomvelvertbias; + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; // handle spawn modes (org/vel) switch (ptype->spawnmode) { +/* case SM_MESHSURFACE: + if (area <= 0) + { + tri++; + area += calcarea(tri); + arsvec[] = calcnormal(tri); + } + + ofsvec[] = randompointintriangle(tri); + + area -= density; + break; +*/ case SM_BOX: ofsvec[0] = crandom(); ofsvec[1] = crandom(); @@ -4701,8 +4820,19 @@ static int PScript_RunParticleEffectState (vec3_t org, vec3_t dir, float count, p->org[2] -= orgadd; } #endif + if (ptype->flags & PT_WORLDSPACERAND) + { + p->org[0] += crand() * ptype->orgwrand[0]; + p->org[1] += crand() * ptype->orgwrand[1]; + p->org[2] += crand() * ptype->orgwrand[2]; + p->vel[0] += crand() * ptype->velwrand[0]; + p->vel[1] += crand() * ptype->velwrand[1]; + p->vel[2] += crand() * ptype->velwrand[2]; + VectorAdd(p->vel, ptype->velbias, p->vel); + } VectorAdd(p->org, ptype->orgbias, p->org); #endif + p->die = particletime + ptype->die - p->die; } @@ -6759,7 +6889,12 @@ static void PScript_DrawParticleTypes (void) p->rgba[0]*-10+p->rgba[1]*-10, 30*p->rgba[3]*r_bloodstains.value); - if (part_type + type->cliptype == type) + if (type->clipbounce < 0) + { + p->die = -1; + continue; + } + else if (part_type + type->cliptype == type) { //bounce dist = DotProduct(p->vel, normal);// * (-1-(rand()/(float)0x7fff)/2); dist *= -type->clipbounce; @@ -6776,6 +6911,7 @@ static void PScript_DrawParticleTypes (void) { p->die = -1; VectorNormalize(p->vel); + if (type->clipbounce) { VectorScale(normal, type->clipbounce, normal); diff --git a/engine/client/pr_clcmd.c b/engine/client/pr_clcmd.c index 233351451..f11d8b6ef 100644 --- a/engine/client/pr_clcmd.c +++ b/engine/client/pr_clcmd.c @@ -390,7 +390,19 @@ void QCBUILTIN PF_cl_getcursormode (pubprogfuncs_t *prinst, struct globalvars_s void QCBUILTIN PF_cl_playingdemo (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { - G_FLOAT(OFS_RETURN) = !!cls.demoplayback; + switch(cls.demoplayback) + { + case DPB_NONE: + G_FLOAT(OFS_RETURN) = 0; + break; + case DPB_MVD: + case DPB_EZTV: + G_FLOAT(OFS_RETURN) = 2; + break; + default: + G_FLOAT(OFS_RETURN) = 1; + break; + } } void QCBUILTIN PF_cl_runningserver (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index af497d62b..abef4ef62 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -632,6 +632,33 @@ static void QCBUILTIN PF_NoCSQC (pubprogfuncs_t *prinst, struct globalvars_s *pr prinst->RunError(prinst, "\nBuiltin %i:%s does not make sense in csqc.\nCSQC is not compatible.", binum, fname); PR_BIError (prinst, "bulitin not implemented"); } +static void QCBUILTIN PF_checkbuiltin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + func_t funcref = G_INT(OFS_PARM0); + char *funcname = NULL; + int args; + int builtinno; + if (prinst->GetFunctionInfo(prinst, funcref, &args, &builtinno, funcname, sizeof(funcname))) + { //qc defines the function at least. nothing weird there... + if (builtinno > 0 && builtinno < prinst->parms->numglobalbuiltins) + { + if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme || prinst->parms->globalbuiltins[builtinno] == PF_NoCSQC) + G_FLOAT(OFS_RETURN) = false; //the builtin with that number isn't defined. + else + { + G_FLOAT(OFS_RETURN) = true; //its defined, within the sane range, mapped, everything. all looks good. + //we should probably go through the available builtins and validate that the qc's name matches what would be expected + //this is really intended more for builtins defined as #0 though, in such cases, mismatched assumptions are impossible. + } + } + else + G_FLOAT(OFS_RETURN) = false; //not a valid builtin (#0 builtins get remapped according to the function name) + } + else + { //not valid somehow. + G_FLOAT(OFS_RETURN) = false; + } +} static void QCBUILTIN PF_cl_cprint (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -704,7 +731,7 @@ static qboolean CopyCSQCEdictToEntity(csqcedict_t *in, entity_t *out) effects = in->v->effects; if (effects & NQEF_ADDITIVE) out->flags |= RF_ADDITIVE; - if (effects & DPEF_NOSHADOW) + if (effects & EF_NOSHADOW) out->flags |= RF_NOSHADOW; if (effects & EF_NODEPTHTEST) out->flags |= RF_NODEPTHTEST; @@ -1885,7 +1912,6 @@ static void QCBUILTIN PF_R_RenderScene(pubprogfuncs_t *prinst, struct globalvars V_ApplyRefdef(); R_RenderView(); R2D_PolyBlend (); - R_DrawNameTags(); if (r_refdef.grect.x || r_refdef.grect.y || r_refdef.grect.width != vid.fbvwidth || r_refdef.grect.height != vid.fbvheight) { @@ -1903,6 +1929,8 @@ static void QCBUILTIN PF_R_RenderScene(pubprogfuncs_t *prinst, struct globalvars else scissored = false; + R_DrawNameTags(); + if (r_refdef.drawsbar) { #ifdef PLUGINS @@ -4516,7 +4544,7 @@ static void QCBUILTIN PF_DeltaListen(pubprogfuncs_t *prinst, struct globalvars_s func_t func = G_INT(OFS_PARM1); unsigned int flags = G_FLOAT(OFS_PARM2); - if (prinst->GetFuncArgCount(prinst, func) < 0) + if (!prinst->GetFunctionInfo(prinst, func, NULL, NULL, NULL, 0)) { Con_Printf("PF_DeltaListen: Bad function index\n"); return; @@ -5215,6 +5243,7 @@ static struct { {"findfloat", PF_FindFloat, 98}, // #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT) {"findentity", PF_FindFloat, 98}, // #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT) {"checkextension", PF_checkextension, 99}, // #99 float(string extname) checkextension (EXT_CSQC) + {"checkbuiltin", PF_checkbuiltin, 0}, {"anglemod", PF_anglemod, 102}, //110 @@ -5806,7 +5835,7 @@ void VARGS CSQC_Abort (char *format, ...) //an error occured. if (pr_csqc_coreonerror.value) { - int size = 1024*1024*8; + size_t size = 1024*1024*8; char *buffer = BZ_Malloc(size); csqcprogs->save_ents(csqcprogs, buffer, &size, size, 3); COM_WriteFile("csqccore.txt", FS_GAMEONLY, buffer, size); @@ -6503,7 +6532,7 @@ void CSQC_CoreDump(void) } { - int size = 1024*1024*8; + size_t size = 1024*1024*8; char *buffer = BZ_Malloc(size); csqcprogs->save_ents(csqcprogs, buffer, &size, size, 3); COM_WriteFile("csqccore.txt", FS_GAMEONLY, buffer, size); diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index 306b43b48..6985c28d3 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -1270,6 +1270,33 @@ static void QCBUILTIN PF_Fixme (pubprogfuncs_t *prinst, struct globalvars_s *pr_ prinst->RunError(prinst, "\nBuiltin %i:%s not implemented.\nMenu is not compatible.", binum, fname); PR_BIError (prinst, "bulitin not implemented"); } +static void QCBUILTIN PF_checkbuiltin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + func_t funcref = G_INT(OFS_PARM0); + char *funcname = NULL; + int args; + int builtinno; + if (prinst->GetFunctionInfo(prinst, funcref, &args, &builtinno, funcname, sizeof(funcname))) + { //qc defines the function at least. nothing weird there... + if (builtinno > 0 && builtinno < prinst->parms->numglobalbuiltins) + { + if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme) + G_FLOAT(OFS_RETURN) = false; //the builtin with that number isn't defined. + else + { + G_FLOAT(OFS_RETURN) = true; //its defined, within the sane range, mapped, everything. all looks good. + //we should probably go through the available builtins and validate that the qc's name matches what would be expected + //this is really intended more for builtins defined as #0 though, in such cases, mismatched assumptions are impossible. + } + } + else + G_FLOAT(OFS_RETURN) = false; //not a valid builtin (#0 builtins get remapped according to the function name) + } + else + { //not valid somehow. + G_FLOAT(OFS_RETURN) = false; + } +} @@ -1875,6 +1902,7 @@ static struct { int ebfsnum; } BuiltinList[] = { {"checkextension", PF_menu_checkextension, 1}, + {"checkbuiltin", PF_checkbuiltin, 0}, {"error", PF_error, 2}, {"objerror", PF_nonfatalobjerror, 3}, {"print", PF_print, 4}, @@ -2308,7 +2336,7 @@ void VARGS Menu_Abort (char *format, ...) if (pr_menuqc_coreonerror.value) { char *buffer; - int size = 1024*1024*8; + size_t size = 1024*1024*8; buffer = Z_Malloc(size); menu_world.progs->save_ents(menu_world.progs, buffer, &size, size, 3); COM_WriteFile("menucore.txt", FS_GAMEONLY, buffer, size); @@ -2504,7 +2532,7 @@ void MP_CoreDump_f(void) } { - int size = 1024*1024*8; + size_t size = 1024*1024*8; char *buffer = BZ_Malloc(size); menu_world.progs->save_ents(menu_world.progs, buffer, &size, size, 3); COM_WriteFile("menucore.txt", FS_GAMEONLY, buffer, size); diff --git a/engine/client/r_2d.c b/engine/client/r_2d.c index f93ad0d16..ae1471118 100644 --- a/engine/client/r_2d.c +++ b/engine/client/r_2d.c @@ -1183,13 +1183,13 @@ R_PolyBlend //bright flashes and stuff, game only, doesn't touch sbar void R2D_PolyBlend (void) { - if (!sw_blend[3]) + if (!r_refdef.playerview->screentint[3]) return; if (r_refdef.flags & RDF_NOWORLDMODEL) return; - R2D_ImageColours (sw_blend[0], sw_blend[1], sw_blend[2], sw_blend[3]); + R2D_ImageColours (r_refdef.playerview->screentint[0], r_refdef.playerview->screentint[1], r_refdef.playerview->screentint[2], r_refdef.playerview->screentint[3]); R2D_ScalePic(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, shader_polyblend); R2D_ImageColours (1, 1, 1, 1); } diff --git a/engine/client/r_part.c b/engine/client/r_part.c index 46f55116f..d47ad0ff3 100644 --- a/engine/client/r_part.c +++ b/engine/client/r_part.c @@ -766,7 +766,7 @@ entity_t *TraceLineR (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal) vec3_t delta, ts, te; entity_t *pe; entity_t *result=NULL; - vec3_t axis[3]; +// vec3_t axis[3]; vec3_t movemins, movemaxs; memset (&trace, 0, sizeof(trace)); diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c index eec1fa4ce..36e3dc88d 100644 --- a/engine/client/r_surf.c +++ b/engine/client/r_surf.c @@ -578,7 +578,7 @@ static void Surf_AddDynamicLightsColours (msurface_t *surf) -static void Surf_BuildDeluxMap (msurface_t *surf, qbyte *dest, unsigned int lmwidth, vec3_t *blocknormals) +static void Surf_BuildDeluxMap (model_t *wmodel, msurface_t *surf, qbyte *dest, unsigned int lmwidth, vec3_t *blocknormals) { int smax, tmax; int i, j, size; @@ -601,7 +601,7 @@ static void Surf_BuildDeluxMap (msurface_t *surf, qbyte *dest, unsigned int lmwi lightmap = surf->samples; // set to full bright if no light data - if (!currentmodel->deluxdata) + if (!wmodel->deluxdata) { for (i=0 ; iengineflags & MDLF_RGBLIGHTING) - deluxmap = surf->samples - currentmodel->lightdata + currentmodel->deluxdata; + if (wmodel->engineflags & MDLF_RGBLIGHTING) + deluxmap = surf->samples - wmodel->lightdata + wmodel->deluxdata; else - deluxmap = (surf->samples - currentmodel->lightdata)*3 + currentmodel->deluxdata; + deluxmap = (surf->samples - wmodel->lightdata)*3 + wmodel->deluxdata; // clear to no light @@ -629,9 +629,9 @@ static void Surf_BuildDeluxMap (msurface_t *surf, qbyte *dest, unsigned int lmwi // add all the lightmaps if (lightmap) { - if (currentmodel->engineflags & MDLF_RGBLIGHTING) + if (wmodel->engineflags & MDLF_RGBLIGHTING) { - deluxmap = surf->samples - currentmodel->lightdata + currentmodel->deluxdata; + deluxmap = surf->samples - wmodel->lightdata + wmodel->deluxdata; for (maps = 0 ; maps < MAXQ1LIGHTMAPS && surf->styles[maps] != 255 ; maps++) @@ -650,7 +650,7 @@ static void Surf_BuildDeluxMap (msurface_t *surf, qbyte *dest, unsigned int lmwi } else { - deluxmap = (surf->samples - currentmodel->lightdata)*3 + currentmodel->deluxdata; + deluxmap = (surf->samples - wmodel->lightdata)*3 + wmodel->deluxdata; for (maps = 0 ; maps < MAXQ1LIGHTMAPS && surf->styles[maps] != 255 ; maps++) @@ -900,7 +900,7 @@ static void Surf_BuildLightMap (msurface_t *surf, qbyte *dest, qbyte *deluxdest, } if (currentmodel->deluxdata) - Surf_BuildDeluxMap(surf, deluxdest, lmwidth, blocknormals); + Surf_BuildDeluxMap(currentmodel, surf, deluxdest, lmwidth, blocknormals); #ifdef PEXT_LIGHTSTYLECOL if (lightmap_bytes == 4 || lightmap_bytes == 3) @@ -1170,7 +1170,7 @@ static void Surf_BuildLightMap (msurface_t *surf, qbyte *dest, qbyte *deluxdest, } -static void Surf_BuildLightMap_Worker (msurface_t *surf, qbyte *dest, qbyte *deluxdest, stmap *stainsrc, int shift, int ambient, unsigned int lmwidth) +static void Surf_BuildLightMap_Worker (model_t *wmodel, msurface_t *surf, qbyte *dest, qbyte *deluxdest, stmap *stainsrc, int shift, int ambient, unsigned int lmwidth, int *d_lightstylevalue) { int smax, tmax; int t; @@ -1202,8 +1202,8 @@ static void Surf_BuildLightMap_Worker (msurface_t *surf, qbyte *dest, qbyte *del blocklights = BZ_Realloc(blocklights, maxblocksize * 3*sizeof(*blocklights)); } - if (currentmodel->deluxdata) - Surf_BuildDeluxMap(surf, deluxdest, lmwidth, blocknormals); + if (wmodel->deluxdata) + Surf_BuildDeluxMap(wmodel, surf, deluxdest, lmwidth, blocknormals); #ifdef PEXT_LIGHTSTYLECOL if (lightmap_bytes == 4 || lightmap_bytes == 3) @@ -1230,7 +1230,7 @@ static void Surf_BuildLightMap_Worker (msurface_t *surf, qbyte *dest, qbyte *del blocklights[i] = r_fullbright.value*255*256; } } - else if (!currentmodel->lightdata) + else if (!wmodel->lightdata) { /*fullbright if map is not lit. but not overbright*/ for (i=0 ; ifromgame == fg_quake3) //rgb + if (wmodel->fromgame == fg_quake3) //rgb { /*q3 lightmaps are meant to be pre-built this code is misguided, and ought never be executed anyway. @@ -1282,7 +1282,7 @@ static void Surf_BuildLightMap_Worker (msurface_t *surf, qbyte *dest, qbyte *del } } } - else if (currentmodel->engineflags & MDLF_RGBLIGHTING) //rgb + else if (wmodel->engineflags & MDLF_RGBLIGHTING) //rgb { for (maps = 0 ; maps < MAXQ1LIGHTMAPS && surf->styles[maps] != 255 ; maps++) @@ -1412,7 +1412,7 @@ static void Surf_BuildLightMap_Worker (msurface_t *surf, qbyte *dest, qbyte *del #endif { // set to full bright if no light data - if (!surf->samples || !currentmodel->lightdata) + if (!surf->samples || !wmodel->lightdata) { for (i=0 ; iengineflags & MDLF_RGBLIGHTING) //rgb + if (wmodel->engineflags & MDLF_RGBLIGHTING) //rgb for (maps = 0 ; maps < MAXQ1LIGHTMAPS && surf->styles[maps] != 255 ; maps++) { @@ -1570,7 +1570,7 @@ dynamic: } #ifdef THREADEDWORLD -static void Surf_RenderDynamicLightmaps_Worker (msurface_t *fa) +static void Surf_RenderDynamicLightmaps_Worker (model_t *wmodel, msurface_t *fa, int *d_lightstylevalue) { qbyte *base, *luxbase; stmap *stainbase; @@ -1629,7 +1629,7 @@ dynamic: base += (fa->light_t[0] * lm->width + fa->light_s[0]) * lightmap_bytes; stainbase = lm->stainmaps; stainbase += (fa->light_t[0] * lm->width + fa->light_s[0]) * 3; - Surf_BuildLightMap_Worker (fa, base, luxbase, stainbase, lightmap_shift, r_ambient.value*255, lm->width); + Surf_BuildLightMap_Worker (wmodel, fa, base, luxbase, stainbase, lightmap_shift, r_ambient.value*255, lm->width, d_lightstylevalue); if (dlm) { @@ -2496,9 +2496,7 @@ void Surf_SetupFrame(void) //first scene is the 'main' scene and audio defaults to that (unless overridden later in the frame) r_refdef.playerview->audio.defaulted = false; VectorCopy(r_refdef.vieworg, r_refdef.playerview->audio.origin); - VectorCopy(vpn, r_refdef.playerview->audio.forward); - VectorCopy(vright, r_refdef.playerview->audio.right); - VectorCopy(vup, r_refdef.playerview->audio.up); + AngleVectors(r_refdef.viewangles, r_refdef.playerview->audio.forward,r_refdef.playerview->audio.right, r_refdef.playerview->audio.up); if (r_viewcontents & FTECONTENTS_FLUID) r_refdef.playerview->audio.inwater = true; else @@ -2645,7 +2643,7 @@ struct webostate_s int ebo; size_t idxcount; int numbatches; - double goodtime; //time that the webo is no longer 'good', resulting in an update (lightstyles). + int lightstylevalues[MAX_LIGHTSTYLES]; //when using workers that only reprocessing lighting at 10fps, things get too ugly when things go out of sync batch_t *rbatches[SHADER_SORT_COUNT]; @@ -2735,7 +2733,8 @@ static void Surf_SimpleWorld(struct webostate_s *es, qbyte *pvs) mesh_t *mesh; int l; int fc = -r_framecount; - for (leaf = es->wmodel->leafs+es->wmodel->numclusters, l = es->wmodel->numclusters; l-- > 0; leaf--) + model_t *wmodel = es->wmodel; + for (leaf = wmodel->leafs+wmodel->numclusters, l = wmodel->numclusters; l-- > 0; leaf--) { if ((pvs[l>>3] & (1u<<(l&7))) && leaf->nummarksurfaces) { @@ -2749,7 +2748,7 @@ static void Surf_SimpleWorld(struct webostate_s *es, qbyte *pvs) int i; struct wesbatch_s *eb; surf->visframe = fc; - Surf_RenderDynamicLightmaps_Worker (surf); + Surf_RenderDynamicLightmaps_Worker (wmodel, surf, es->lightstylevalues); mesh = surf->mesh; eb = &es->batches[surf->sbatch->ebobatch]; @@ -2841,20 +2840,29 @@ void Surf_DrawWorld (void) Surf_LightmapShift(cl.worldmodel); #ifdef THREADEDWORLD - if (r_dynamic.ival < 0 && !r_refdef.recurse && cl.worldmodel->type == mod_brush && cl.worldmodel->fromgame == fg_quake) + if ((r_dynamic.ival < 0 || cl.worldmodel->numbatches) && !r_refdef.recurse && cl.worldmodel->type == mod_brush && cl.worldmodel->fromgame == fg_quake) { + int i = MAX_LIGHTSTYLES; if (webostate && webostate->wmodel != cl.worldmodel) { R_DestroyWorldEBO(webostate); webostate = NULL; } - if (webostate && webostate->leaf[0] == r_viewleaf && webostate->leaf[1] == r_viewleaf2 && webostate->goodtime > cl.time) + + if (webostate && !webogenerating) + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + if (webostate->lightstylevalues[i] != d_lightstylevalue[i]) + break; + } + if (webostate && webostate->leaf[0] == r_viewleaf && webostate->leaf[1] == r_viewleaf2 && i == MAX_LIGHTSTYLES) { } else { if (!webogenerating) { + int i; if (!cl.worldmodel->numbatches) { int sortid; @@ -2872,11 +2880,8 @@ void Surf_DrawWorld (void) webogenerating->wmodel = cl.worldmodel; webogenerating->leaf[0] = r_viewleaf; webogenerating->leaf[1] = r_viewleaf2; - webogenerating->goodtime = cl.time; //stupid lightstyle animations. - if (r_lightstylesmooth.ival) - webogenerating->goodtime += 1.0/60; - else - webogenerating->goodtime += 0.1; + for (i = 0; i < MAX_LIGHTSTYLES; i++) + webogenerating->lightstylevalues[i] = d_lightstylevalue[i]; Q_strncpyz(webogenerating->dbgid, "webostate", sizeof(webogenerating->dbgid)); COM_AddWork(1, R_GenWorldEBO, webogenerating, NULL, 0, 0); } diff --git a/engine/client/render.h b/engine/client/render.h index 6db2c1cf1..c82cab709 100644 --- a/engine/client/render.h +++ b/engine/client/render.h @@ -277,7 +277,7 @@ void R_GAlias_GenerateBatches(entity_t *e, struct batch_s **batches); void R_LightArraysByte_BGR(const entity_t *entity, vecV_t *coords, byte_vec4_t *colours, int vertcount, vec3_t *normals); void R_LightArrays(const entity_t *entity, vecV_t *coords, vec4_t *colours, int vertcount, vec3_t *normals, float scale); -void R_DrawSkyChain (struct batch_s *batch); /*called from the backend, and calls back into it*/ +qboolean R_DrawSkyChain (struct batch_s *batch); /*called from the backend, and calls back into it*/ void R_InitSky (shader_t *shader, const char *skyname, qbyte *src, unsigned int width, unsigned int height); /*generate q1 sky texnums*/ void R_Clutter_Emit(struct batch_s **batches); @@ -485,6 +485,7 @@ void Media_RecordFrame (void); qboolean Media_PausedDemo (qboolean fortiming); int Media_Capturing (void); double Media_TweekCaptureFrameTime(double oldtime, double time); +void Media_WriteCurrentTrack(sizebuf_t *buf); void MYgluPerspective(double fovx, double fovy, double zNear, double zFar); diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 942d68591..07a4ff826 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -186,6 +186,7 @@ cvar_t scr_allowsnap = CVARF ("scr_allowsnap", "1", CVAR_NOTFROMSERVER); cvar_t scr_centersbar = CVAR ("scr_centersbar", "2"); cvar_t scr_centertime = CVAR ("scr_centertime", "2"); +cvar_t scr_logcenterprint = CVARD ("con_logcenterprint", "1", ""); cvar_t scr_chatmodecvar = CVAR ("scr_chatmode", "0"); cvar_t scr_conalpha = CVARC ("scr_conalpha", "0.7", Cvar_Limiter_ZeroToOne_Callback); @@ -756,6 +757,7 @@ void Renderer_Init(void) Cvar_Register (&scr_turtlefps, SCREENOPTIONS); Cvar_Register (&scr_showpause, SCREENOPTIONS); Cvar_Register (&scr_centertime, SCREENOPTIONS); + Cvar_Register (&scr_logcenterprint, SCREENOPTIONS); Cvar_Register (&scr_printspeed, SCREENOPTIONS); Cvar_Register (&scr_allowsnap, SCREENOPTIONS); Cvar_Register (&scr_consize, SCREENOPTIONS); diff --git a/engine/client/sbar.c b/engine/client/sbar.c index 5c6a5f8a5..ea942918e 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -341,7 +341,7 @@ char *Get_Q2ConfigString(int i) if (i >= Q2CS_MODELS && i < Q2CS_MODELS + Q2MAX_MODELS) return cl.model_name [i-Q2CS_MODELS]; if (i >= Q2CS_SOUNDS && i < Q2CS_SOUNDS + Q2MAX_SOUNDS) - return cl.model_name [i-Q2CS_SOUNDS]; + return cl.sound_name [i-Q2CS_SOUNDS]; if (i == Q2CS_AIRACCEL) return "4"; if (i >= Q2CS_PLAYERSKINS && i < Q2CS_GENERAL+Q2MAX_GENERAL) @@ -352,7 +352,7 @@ char *Get_Q2ConfigString(int i) //#define Q2CS_GENERAL (Q2CS_PLAYERSKINS +Q2MAX_CLIENTS) return ""; } -void Sbar_ExecuteLayoutString (char *s) +void Sbar_ExecuteLayoutString (char *s, int seat) { int x, y; int value; @@ -361,6 +361,7 @@ void Sbar_ExecuteLayoutString (char *s) int pw, ph; // q2clientinfo_t *ci; mpic_t *p; + q2player_state_t *ps = &cl.q2frame.playerstate[seat]; if (cls.state != ca_active) return; @@ -416,7 +417,7 @@ void Sbar_ExecuteLayoutString (char *s) if (!strcmp(com_token, "pic")) { // draw a pic from a stat number s = COM_Parse (s); - value = cl.q2frame.playerstate.stats[atoi(com_token)]; + value = ps->stats[atoi(com_token)]; if (value >= Q2MAX_IMAGES || value < 0) Host_EndGame ("Pic >= Q2MAX_IMAGES"); if (*Get_Q2ConfigString(Q2CS_IMAGES+value)) @@ -435,9 +436,9 @@ void Sbar_ExecuteLayoutString (char *s) int score, ping, time; s = COM_Parse (s); - x = sbar_rect.width/2 - 160 + atoi(com_token); + x = sbar_rect.x + sbar_rect.width/2 - 160 + atoi(com_token); s = COM_Parse (s); - y = sbar_rect.height/2 - 120 + atoi(com_token); + y = sbar_rect.y + sbar_rect.height/2 - 120 + atoi(com_token); // SCR_AddDirtyPoint (x, y); // SCR_AddDirtyPoint (x+159, y+31); @@ -474,9 +475,9 @@ void Sbar_ExecuteLayoutString (char *s) char block[80]; s = COM_Parse (s); - x = sbar_rect.width/2 - 160 + atoi(com_token); + x = sbar_rect.x + sbar_rect.width/2 - 160 + atoi(com_token); s = COM_Parse (s); - y = sbar_rect.height/2 - 120 + atoi(com_token); + y = sbar_rect.y + sbar_rect.height/2 - 120 + atoi(com_token); // SCR_AddDirtyPoint (x, y); // SCR_AddDirtyPoint (x+159, y+31); @@ -518,7 +519,7 @@ void Sbar_ExecuteLayoutString (char *s) s = COM_Parse (s); width = atoi(com_token); s = COM_Parse (s); - value = cl.q2frame.playerstate.stats[atoi(com_token)]; + value = ps->stats[atoi(com_token)]; SCR_DrawField (x, y, 0, width, value); continue; } @@ -528,7 +529,7 @@ void Sbar_ExecuteLayoutString (char *s) int color; width = 3; - value = cl.q2frame.playerstate.stats[Q2STAT_HEALTH]; + value = ps->stats[Q2STAT_HEALTH]; if (value > 25) color = 0; // green else if (value > 0) @@ -536,7 +537,7 @@ void Sbar_ExecuteLayoutString (char *s) else color = 1; - if (cl.q2frame.playerstate.stats[Q2STAT_FLASHES] & 1) + if (ps->stats[Q2STAT_FLASHES] & 1) { p = Sbar_Q2CachePic("field_3"); if (p && R_GetShaderSizes(p, &pw, &ph, false)>0) @@ -552,7 +553,7 @@ void Sbar_ExecuteLayoutString (char *s) int color; width = 3; - value = cl.q2frame.playerstate.stats[Q2STAT_AMMO]; + value = ps->stats[Q2STAT_AMMO]; if (value > 5) color = 0; // green else if (value >= 0) @@ -560,7 +561,7 @@ void Sbar_ExecuteLayoutString (char *s) else continue; // negative number = don't show - if (cl.q2frame.playerstate.stats[Q2STAT_FLASHES] & 4) + if (ps->stats[Q2STAT_FLASHES] & 4) { p = Sbar_Q2CachePic("field_3"); if (p && R_GetShaderSizes(p, &pw, &ph, false)>0) @@ -576,13 +577,13 @@ void Sbar_ExecuteLayoutString (char *s) int color; width = 3; - value = cl.q2frame.playerstate.stats[Q2STAT_ARMOR]; + value = ps->stats[Q2STAT_ARMOR]; if (value < 1) continue; color = 0; // green - if (cl.q2frame.playerstate.stats[Q2STAT_FLASHES] & 2) + if (ps->stats[Q2STAT_FLASHES] & 2) R2D_ScalePic (x, y, 64, 64, R2D_SafeCachePic("field_3")); SCR_DrawField (x, y, color, width, value); @@ -596,7 +597,7 @@ void Sbar_ExecuteLayoutString (char *s) index = atoi(com_token); if (index < 0 || index >= Q2MAX_CONFIGSTRINGS) Host_EndGame ("Bad stat_string index"); - index = cl.q2frame.playerstate.stats[index]; + index = ps->stats[index]; if (index < 0 || index >= Q2MAX_CONFIGSTRINGS) Host_EndGame ("Bad stat_string index"); Draw_FunString (x, y, Get_Q2ConfigString(index)); @@ -634,7 +635,7 @@ void Sbar_ExecuteLayoutString (char *s) if (!strcmp(com_token, "if")) { // draw a number s = COM_Parse (s); - value = cl.q2frame.playerstate.stats[atoi(com_token)]; + value = ps->stats[atoi(com_token)]; if (!value) { // skip to endif while (s && strcmp(com_token, "endif") ) @@ -650,17 +651,20 @@ void Sbar_ExecuteLayoutString (char *s) } } -static void Sbar_Q2DrawInventory(void) +static void Sbar_Q2DrawInventory(int seat) { int keys[1], keymods[1]; char cmd[1024]; const char *boundkey; - unsigned int validlist[Q2MAX_ITEMS], rows, i, item, selected = cl.q2frame.playerstate.stats[Q2STAT_SELECTED_ITEM]; + q2player_state_t *ps = &cl.q2frame.playerstate[seat]; + unsigned int validlist[Q2MAX_ITEMS], rows, i, item, selected = ps->stats[Q2STAT_SELECTED_ITEM]; int first; unsigned int maxrows = ((240-24*2-8*2)/8); //draw background - float x = (vid.width - 256)/2; - float y = (vid.height - 240)/2; + float x = sbar_rect.x + (sbar_rect.width - 256)/2; + float y = sbar_rect.y + (sbar_rect.height - 240)/2; + if (y < sbar_rect.y) + y = sbar_rect.y; //try to fix small-res 3-way splitscreen slightly R2D_ScalePic(x, y, 256, 240, Sbar_Q2CachePic("inventory")); //move into the frame x += 24; @@ -669,7 +673,7 @@ static void Sbar_Q2DrawInventory(void) //figure out which items we have for (i = 0, rows = 0, first = -1; i < Q2MAX_ITEMS; i++) { - if (!cl.inventory[i]) + if (!cl.inventory[seat][i]) continue; if (i <= selected) first = rows; @@ -693,7 +697,7 @@ static void Sbar_Q2DrawInventory(void) else boundkey = Key_KeynumToString(keys[0], keymods[0]); - Q_snprintfz(cmd, sizeof(cmd), "%6s %3i %s", boundkey, cl.inventory[item], Get_Q2ConfigString(Q2CS_ITEMS+item)); + Q_snprintfz(cmd, sizeof(cmd), "%6s %3i %s", boundkey, cl.inventory[seat][item], Get_Q2ConfigString(Q2CS_ITEMS+item)); Draw_FunStringWidth(x, y, cmd, 256-24*2+8, false, item != selected); y+=8; } } @@ -2756,14 +2760,19 @@ void Sbar_Draw (playerview_t *pv) #ifdef Q2CLIENT if (cls.protocol == CP_QUAKE2) { + int seat = pv - cl.playerview; + if (seat >= cl.splitclients) + seat = cl.splitclients-1; + if (seat < 0) + seat = 0; sbar_rect = r_refdef.grect; R2D_ImageColours(1, 1, 1, 1); if (*cl.q2statusbar) - Sbar_ExecuteLayoutString(cl.q2statusbar); - if (*cl.q2layout && (cl.q2frame.playerstate.stats[Q2STAT_LAYOUTS] & 1)) - Sbar_ExecuteLayoutString(cl.q2layout); - if (cl.q2frame.playerstate.stats[Q2STAT_LAYOUTS] & 2) - Sbar_Q2DrawInventory(); + Sbar_ExecuteLayoutString(cl.q2statusbar, seat); + if (*cl.q2layout && (cl.q2frame.playerstate[seat].stats[Q2STAT_LAYOUTS] & 1)) + Sbar_ExecuteLayoutString(cl.q2layout[seat], seat); + if (cl.q2frame.playerstate[seat].stats[Q2STAT_LAYOUTS] & 2) + Sbar_Q2DrawInventory(seat); return; } #endif diff --git a/engine/client/screen.h b/engine/client/screen.h index 2a9a28b80..d5aa69f37 100644 --- a/engine/client/screen.h +++ b/engine/client/screen.h @@ -49,7 +49,7 @@ qboolean SCR_RSShot (void); void GLSCR_UpdateScreen (void); #endif -void SCR_ImageName (char *mapname); +void SCR_ImageName (const char *mapname); //this stuff is internal to the screen systems. void RSpeedShow(void); diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index df4090175..8d78ee7bb 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -138,6 +138,7 @@ cvar_t snd_voip_micamp = CVARAFDC("cl_voip_micamp", "2", NULL, CVAR_ARCHIVE, " cvar_t snd_voip_codec = CVARAFDC("cl_voip_codec", "0", NULL, CVAR_ARCHIVE, "0: speex(@11khz). 1: raw. 2: opus. 3: speex(@8khz). 4: speex(@16). 5:speex(@32).", 0); cvar_t snd_voip_noisefilter = CVARAFDC("cl_voip_noisefilter", "1", NULL, CVAR_ARCHIVE, "Enable the use of the noise cancelation filter.", 0); cvar_t snd_voip_autogain = CVARAFDC("cl_voip_autogain", "0", NULL, CVAR_ARCHIVE, "Attempts to normalize your voice levels to a standard level. Useful for lazy people, but interferes with voice activation levels.", 0); +cvar_t snd_voip_bitrate = CVARAFDC("cl_voip_bitrate", "0", NULL, CVAR_ARCHIVE, "For codecs with non-specific bitrates, this specifies the target bitrate to use (in kb).", 0); #endif extern vfsfile_t *rawwritefile; @@ -297,12 +298,14 @@ static struct float voiplevel; /*your own voice level*/ unsigned int dumps; /*trigger a new generation thing after a bit*/ unsigned int keeps; /*for vad_delay*/ + int curbitrate; snd_capture_driver_t *cdriver;/*capture driver's functions*/ void *cdriverctx; /*capture driver context*/ } s_voip; #define OPUS_APPLICATION_VOIP 2048 +#define OPUS_SET_BITRATE_REQUEST 4002 #define OPUS_RESET_STATE 4028 #ifdef OPUS_STATIC #include "opus.h" @@ -1024,18 +1027,20 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) if (!s_voip.encoder) return; -// opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate_bps)); -// opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND)); -// opus_encoder_ctl(enc, OPUS_SET_VBR(use_vbr)); -// opus_encoder_ctl(enc, OPUS_SET_VBR_CONSTRAINT(cvbr)); -// opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); -// opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(use_inbandfec)); -// opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(forcechannels)); -// opus_encoder_ctl(enc, OPUS_SET_DTX(use_dtx)); -// opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(packet_loss_perc)); + s_voip.curbitrate = 0; -// opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&skip)); -// opus_encoder_ctl(enc, OPUS_SET_LSB_DEPTH(16)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_BITRATE(bitrate_bps)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_VBR(use_vbr)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_VBR_CONSTRAINT(cvbr)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_COMPLEXITY(complexity)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_INBAND_FEC(use_inbandfec)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_FORCE_CHANNELS(forcechannels)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_DTX(use_dtx)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_PACKET_LOSS_PERC(packet_loss_perc)); + +// opus_encoder_ctl(s_voip.encoder, OPUS_GET_LOOKAHEAD(&skip)); +// opus_encoder_ctl(s_voip.encoder, OPUS_SET_LSB_DEPTH(16)); break; @@ -1192,6 +1197,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) { //opus rtp only supports/allows a single chunk in each packet. int frames; + int nrate; //densely pack the frames. start = (short*)(s_voip.capturebuf + encpos); frames = (s_voip.capturepos-encpos)/2; @@ -1213,6 +1219,16 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) Con_Printf("invalid Opus frame size\n"); frames = 0; } + + nrate = snd_voip_bitrate.value * 1000; + if (nrate != s_voip.curbitrate) + { + s_voip.curbitrate = nrate; + if (nrate == 0) + nrate = -1000; + qopus_encoder_ctl(s_voip.encoder, OPUS_SET_BITRATE_REQUEST, (int)nrate); + } + // Con_Printf("Encoding %i frames", frames); level += S_Voip_Preprocess(start, frames, micamp); len = qopus_encode(s_voip.encoder, start, frames, outbuf+outpos, sizeof(outbuf) - outpos); @@ -1660,6 +1676,8 @@ static soundcardinfo_t *SNDDMA_Init(char *driver, char *device, int seat) else snd_speed = sc->sn.speed; + if (sc->seat == -1 && sc->ListenerUpdate) + sc->seat = 0; //hardware rendering won't cope with seat=-1 sc->next = sndcardinfo; sndcardinfo = sc; return sc; @@ -1757,7 +1775,7 @@ void S_Startup (void) sep = strchr(com_token, ':'); if (sep) *sep++ = 0; - SNDDMA_Init(com_token, sep, 0); + SNDDMA_Init(com_token, sep, -1); } } if (!sndcardinfo && !nodefault) @@ -1766,7 +1784,7 @@ void S_Startup (void) INS_SetupControllerAudioDevices(); #endif if (!sndcardinfo) - SNDDMA_Init(NULL, NULL, 0); + SNDDMA_Init(NULL, NULL, -1); } sound_started = true; @@ -2213,25 +2231,25 @@ SND_PickChannel */ channel_t *SND_PickChannel(soundcardinfo_t *sc, int entnum, int entchannel) { - int ch_idx; - int oldestpos; - int oldest; + int ch_idx; + int oldestpos; + int oldest; // Check for replacement sound, or find the best one to replace - oldest = -1; - oldestpos = -1; - for (ch_idx=DYNAMIC_FIRST; ch_idx < DYNAMIC_STOP ; ch_idx++) - { + oldest = -1; + oldestpos = -1; + for (ch_idx=DYNAMIC_FIRST; ch_idx < DYNAMIC_STOP ; ch_idx++) + { if (entchannel != 0 // channel 0 never overrides && sc->channel[ch_idx].entnum == entnum - && (sc->channel[ch_idx].entchannel == entchannel || entchannel == -1)) + && sc->channel[ch_idx].entchannel == entchannel) { // always override sound from same entity oldest = ch_idx; break; } // don't let monster sounds override player sounds - if (sc->channel[ch_idx].entnum == listener[sc->seat].entnum && entnum != listener[sc->seat].entnum && sc->channel[ch_idx].sfx) + if (sc->seat != -1 && sc->channel[ch_idx].entnum == listener[sc->seat].entnum && entnum != listener[sc->seat].entnum && sc->channel[ch_idx].sfx) continue; if (!sc->channel[ch_idx].sfx) @@ -2254,15 +2272,10 @@ channel_t *SND_PickChannel(soundcardinfo_t *sc, int entnum, int entchannel) if (sc->total_chans <= oldest) sc->total_chans = oldest+1; - return &sc->channel[oldest]; + return &sc->channel[oldest]; } -/* -================= -SND_Spatialize -================= -*/ -void SND_Spatialize(soundcardinfo_t *sc, channel_t *ch) +static void SND_AccumulateSpacialization(soundcardinfo_t *sc, channel_t *ch, vec3_t origin) { vec3_t listener_vec; vec_t dist; @@ -2270,27 +2283,141 @@ void SND_Spatialize(soundcardinfo_t *sc, channel_t *ch) vec3_t world_vec; int i, v; float volscale; + int seat; + if (ch->flags & CF_ABSVOLUME) + volscale = 1; + else + volscale = volume.value * voicevolumemod; + + if (sc->seat == -1) + { + seat = 0; + VectorSubtract(origin, listener[seat].origin, world_vec); + dist = DotProduct(world_vec,world_vec); + for (i = 1; i < cl.splitclients; i++) + { + VectorSubtract(origin, listener[i].origin, world_vec); + scale = DotProduct(world_vec,world_vec); + if (scale < dist) + { + dist = scale; + seat = i; + } + } + } + else + { + seat = sc->seat; + } + + // anything coming from the view entity will always be full volume + if (ch->entnum == listener[seat].entnum) + { + v = ch->master_vol * (ruleset_allow_localvolume.value ? snd_playersoundvolume.value : 1) * volscale; + v = bound(0, v, 255); + for (i = 0; i < sc->sn.numchannels; i++) + ch->vol[i] = v; + return; + } + +// calculate stereo seperation and distance attenuation + VectorSubtract(origin, listener[seat].origin, world_vec); + + dist = VectorNormalize(world_vec) * ch->dist_mult; + + if (ch->flags & CF_NOSPACIALISE) + { + scale = 1; + scale = (1.0 - dist) * scale; + v = ch->master_vol * scale * volscale; + for (i = 0; i < sc->sn.numchannels; i++) + ch->vol[i] += bound(0, v, 255); + return; + } + + //rotate the world_vec into listener space, so that the audio direction stored in the speakerdir array can be used directly. + listener_vec[0] = DotProduct(listener[seat].forward, world_vec); + listener_vec[1] = DotProduct(listener[seat].right, world_vec); + listener_vec[2] = DotProduct(listener[seat].up, world_vec); + + if (snd_leftisright.ival) + listener_vec[1] = -listener_vec[1]; + + for (i = 0; i < sc->sn.numchannels; i++) + { + scale = 1 + DotProduct(listener_vec, sc->speakerdir[i]); + scale = (1.0 - dist) * scale * sc->dist[i]; + v = ch->master_vol * scale * volscale; + ch->vol[i] += bound(0, v, 255); + } +} +/* +================= +SND_Spatialize +================= +*/ +static void SND_Spatialize(soundcardinfo_t *sc, channel_t *ch) +{ + vec3_t listener_vec; + vec_t dist; + vec_t scale; + vec3_t world_vec; + int i, v; + float volscale; + int seat; + +/* + if ((ch->flags & CF_FOLLOW) && ch->entnum > 0 && ch->entnum < cl.maxlerpents) + { //sounds following ents should update their position to match that ent's position. + //its important that they do not snap back to where they were if the entity vanishes, so we just overwrite the channel origin for that. its simpler. + lerpents_t *le = cl.lerpents+ch->entnum; + if (le->sequence == cl.lerpentssequence) + VectorCopy(le->origin, ch->origin); //fixme: bmodels should use their center rather than their origin. check le->state->solid? + + //FIXME: update rate to provide doppler + } +*/ //sounds with absvolume ignore all volume etc cvars+settings if (ch->flags & CF_ABSVOLUME) volscale = 1; else volscale = volume.value * voicevolumemod; + if (sc->seat == -1) + { + seat = 0; + VectorSubtract(ch->origin, listener[seat].origin, world_vec); + dist = DotProduct(world_vec,world_vec); + for (i = 1; i < cl.splitclients; i++) + { + VectorSubtract(ch->origin, listener[i].origin, world_vec); + scale = DotProduct(world_vec,world_vec); + if (scale < dist) + { + dist = scale; + seat = i; + } + } + } + else + { + seat = sc->seat; + } + // anything coming from the view entity will always be full volume - if (ch->entnum == listener[sc->seat].entnum) + // (no, I don't like this hack) + if (ch->entnum == listener[seat].entnum) { v = ch->master_vol * (ruleset_allow_localvolume.value ? snd_playersoundvolume.value : 1) * volscale; v = bound(0, v, 255); for (i = 0; i < sc->sn.numchannels; i++) - { ch->vol[i] = v; - } return; } // calculate stereo seperation and distance attenuation - VectorSubtract(ch->origin, listener[sc->seat].origin, world_vec); + VectorSubtract(ch->origin, listener[seat].origin, world_vec); dist = VectorNormalize(world_vec) * ch->dist_mult; @@ -2305,9 +2432,9 @@ void SND_Spatialize(soundcardinfo_t *sc, channel_t *ch) } //rotate the world_vec into listener space, so that the audio direction stored in the speakerdir array can be used directly. - listener_vec[0] = DotProduct(listener[sc->seat].forward, world_vec); - listener_vec[1] = DotProduct(listener[sc->seat].right, world_vec); - listener_vec[2] = DotProduct(listener[sc->seat].up, world_vec); + listener_vec[0] = DotProduct(listener[seat].forward, world_vec); + listener_vec[1] = DotProduct(listener[seat].right, world_vec); + listener_vec[2] = DotProduct(listener[seat].up, world_vec); if (snd_leftisright.ival) listener_vec[1] = -listener_vec[1]; @@ -2352,7 +2479,14 @@ static void S_UpdateSoundCard(soundcardinfo_t *sc, qboolean updateonly, channel_ memset (target_chan, 0, sizeof(*target_chan)); if (!origin) { - VectorCopy(listener[sc->seat].origin, target_chan->origin); + if (sc->seat == -1) + { + VectorClear(target_chan->origin); + attenuation = 0; + flags |= CF_NOSPACIALISE; + } + else + VectorCopy(listener[sc->seat].origin, target_chan->origin); } else { @@ -2763,9 +2897,9 @@ void S_UpdateAmbientSounds (soundcardinfo_t *sc) { mleaf_t *l; float vol, oldvol; - int ambient_channel; channel_t *chan; int i; + int ambientlevel[NUM_AMBIENTS]; if (!snd_ambient) return; @@ -2835,55 +2969,67 @@ void S_UpdateAmbientSounds (soundcardinfo_t *sc) if (!cl.worldmodel || cl.worldmodel->type != mod_brush || cl.worldmodel->fromgame != fg_quake || cl.worldmodel->loadstate != MLS_LOADED) return; - l = Q1BSP_LeafForPoint(cl.worldmodel, listener[sc->seat].origin); - if (!l || ambient_level.value <= 0) + for (i = 0; i < NUM_AMBIENTS; i++) + ambientlevel[i] = 0; + if (ambient_level.value) { - for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++) + if (sc->seat < 0) { - chan = &sc->channel[AMBIENT_FIRST+ambient_channel]; - chan->sfx = NULL; - if (sc->ChannelUpdate) - sc->ChannelUpdate(sc, chan, true); + int seat = max(1,cl.splitclients); + while(seat --> 0) + { + l = Q1BSP_LeafForPoint(cl.worldmodel, listener[seat].origin); + if (!l) + continue; + + for (i = 0; i < NUM_AMBIENTS; i++) + ambientlevel[i] = max(ambientlevel[i], l->ambient_sound_level[i]); + } + } + else + { + l = Q1BSP_LeafForPoint(cl.worldmodel, listener[sc->seat].origin); + if (l) + for (i = 0; i < NUM_AMBIENTS; i++) + ambientlevel[i] = l->ambient_sound_level[i]; } - return; } - for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++) + for (i = 0 ; i< NUM_AMBIENTS ; i++) { - static float level[NUM_AMBIENTS]; - chan = &sc->channel[AMBIENT_FIRST+ambient_channel]; - chan->sfx = ambient_sfx[AMBIENT_FIRST+ambient_channel]; - chan->entnum = -1; + chan = &sc->channel[AMBIENT_FIRST+i]; + chan->sfx = ambient_sfx[AMBIENT_FIRST+i]; + chan->entnum = 0; chan->flags = CF_FORCELOOP | CF_NOSPACIALISE; chan->rate = 1<seat].origin, chan->origin); + VectorClear(chan->origin); - vol = ambient_level.value * l->ambient_sound_level[ambient_channel]; + vol = ambient_level.value * ambientlevel[i]; if (vol < 8) vol = 0; - oldvol = level[ambient_channel]; + oldvol = sc->ambientlevels[i]; // don't adjust volume too fast - if (level[ambient_channel] < vol) + if (sc->ambientlevels[i] < vol) { - level[ambient_channel] += host_frametime * ambient_fade.value; - if (level[ambient_channel] > vol) - level[ambient_channel] = vol; + sc->ambientlevels[i] += host_frametime * ambient_fade.value; + if (sc->ambientlevels[i] > vol) + sc->ambientlevels[i] = vol; } else if (chan->master_vol > vol) { - level[ambient_channel] -= host_frametime * ambient_fade.value; - if (level[ambient_channel] < vol) - level[ambient_channel] = vol; + sc->ambientlevels[i] -= host_frametime * ambient_fade.value; + if (sc->ambientlevels[i] < vol) + sc->ambientlevels[i] = vol; } - chan->master_vol = level[ambient_channel]; + chan->master_vol = sc->ambientlevels[i]; chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = bound(0, chan->master_vol * (volume.value*voicevolumemod), 255); if (sc->ChannelUpdate) - sc->ChannelUpdate(sc, chan, (oldvol == 0) ^ (level[ambient_channel] == 0)); + sc->ChannelUpdate(sc, chan, (oldvol == 0) ^ (sc->ambientlevels[i] == 0)); } } @@ -2897,14 +3043,14 @@ Called once each time through the main loop void S_UpdateListener(int seat, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, qboolean underwater) { soundcardinfo_t *sc; - listener[seat].entnum = cl.playerview[seat].playernum+1; + listener[seat].entnum = cl.playerview[seat].viewentity; VectorCopy(origin, listener[seat].origin); VectorCopy(forward, listener[seat].forward); VectorCopy(right, listener[seat].right); VectorCopy(up, listener[seat].up); for (sc = sndcardinfo; sc; sc=sc->next) - if (sc->SetWaterDistortion && sc->seat == seat) + if (sc->SetWaterDistortion && (sc->seat == seat || (sc->seat == -1 && seat == 0))) sc->SetWaterDistortion(sc, underwater); } @@ -2916,6 +3062,102 @@ void S_GetListenerInfo(int seat, float *origin, float *forward, float *right, fl VectorCopy(listener[seat].up, up); } +static void S_Q2_AddEntitySounds(soundcardinfo_t *sc) +{ + vec3_t positions[2048]; + int entnums[countof(positions)]; + sfx_t *sounds[countof(positions)]; + unsigned int count; + unsigned int j; + channel_t *c; + +#ifdef Q2CLIENT + if (cls.protocol == CP_QUAKE2) + count = CLQ2_GatherSounds(positions, entnums, sounds, countof(sounds)); + else +#endif + return; + + while(count --> 0) + { + sfx_t *sfx = sounds[count]; + if (!sfx) + continue; + if (sfx->loadstate == SLS_NOTLOADED) + S_LoadSound(sfx); + if (sfx->loadstate != SLS_LOADED) + continue; //not ready yet + + if (sc->ChannelUpdate) + { + for (c = NULL, j=DYNAMIC_FIRST; j < DYNAMIC_STOP ; j++) + { + if (sc->channel[j].entnum == entnums[count] && !sc->channel[j].entchannel && (sc->channel[j].flags & CF_AUTOSOUND)) + { + c = &sc->channel[j]; + break; + } + } + } + else + { + for (c = NULL, j=DYNAMIC_FIRST; j < DYNAMIC_STOP ; j++) + { + if (sc->channel[j].sfx == sfx && (sc->channel[j].flags & CF_AUTOSOUND)) + { + c = &sc->channel[j]; + break; + } + } + } + if (!c) + { + c = SND_PickChannel(sc, 0, 0); + if (!c) + continue; + c->flags = CF_AUTOSOUND|CF_FORCELOOP; + c->entnum = sc->ChannelUpdate?entnums[count]:0; + c->entchannel = 0; + c->dist_mult = 3 / sound_nominal_clip_dist; + c->master_vol = 255 * 1; + c->pos = 0<rate = 1<vol); j++) + c->vol[j] = 0; + c->sfx = NULL; + } + if (sc->ChannelUpdate) + { //hardware mixing doesn't support merging + SND_Spatialize(sc, c); + } + else + { //merge with any other ents, if we can + for (j = 0; j <= count; j++) + { + if (sounds[j] == sfx) + { + sounds[j] = NULL; + SND_AccumulateSpacialization(sc, c, positions[j]); + } + } + } + if (!c->sfx) + { + for (j = 0; j < countof(c->vol); j++) + if (c->vol[j]) + break; + if (j == countof(c->vol)) + c->sfx = NULL; //err, never mind + else + { + c->sfx = sfx; + if (sc->ChannelUpdate) + sc->ChannelUpdate(sc, c, true); + } + } + } +} + static void S_UpdateCard(soundcardinfo_t *sc) { int i, j; @@ -2949,6 +3191,17 @@ static void S_UpdateCard(soundcardinfo_t *sc) { if (!ch->sfx) continue; + if (ch->flags & CF_AUTOSOUND) + { + if (!ch->vol[0] && !ch->vol[1] && !ch->vol[2] && !ch->vol[3] && !ch->vol[4] && !ch->vol[5]) + { + ch->sfx = NULL; + if (sc->ChannelUpdate) + sc->ChannelUpdate(sc, ch, true); + } + ch->vol[0] = ch->vol[1] = ch->vol[2] = ch->vol[3] = ch->vol[4] = ch->vol[5] = 0; + continue; + } if (sc->ChannelUpdate) { @@ -3004,6 +3257,8 @@ static void S_UpdateCard(soundcardinfo_t *sc) } } + S_Q2_AddEntitySounds(sc); + // // debugging output // @@ -3186,7 +3441,7 @@ console functions */ void S_Play(void) -{ +{ //plays a sound located around the player int i; char name[256]; sfx_t *sfx; @@ -3202,7 +3457,7 @@ void S_Play(void) else Q_strncpyz(name, Cmd_Argv(i), sizeof(name)); sfx = S_PrecacheSound(name); - S_StartSound(listener[0].entnum, -1, sfx, vec3_origin, 1.0, 0.0, 0, 0, CF_NOSPACIALISE); + S_StartSound(0, -1, sfx, NULL, 1.0, 0.0, 0, 0, CF_NOSPACIALISE); i++; } } @@ -3226,7 +3481,7 @@ void S_PlayVol(void) Q_strncpy(name, Cmd_Argv(i), sizeof(name)); sfx = S_PrecacheSound(name); vol = Q_atof(Cmd_Argv(i+1)); - S_StartSound(listener[0].entnum, -1, sfx, vec3_origin, vol, 0.0, 0, 0, CF_NOSPACIALISE); + S_StartSound(0, -1, sfx, NULL, vol, 0.0, 0, 0, CF_NOSPACIALISE); i+=2; } } @@ -3297,7 +3552,7 @@ void S_LocalSound (const char *sound) Con_Printf ("S_LocalSound: can't cache %s\n", sound); return; } - S_StartSound (0, -1, sfx, vec3_origin, 1, 1, 0, 0, CF_NOSPACIALISE); + S_StartSound (0, -1, sfx, NULL, 1, 0, 0, 0, CF_NOSPACIALISE); } @@ -3505,7 +3760,7 @@ void S_RawAudio(int sourceid, qbyte *data, int speed, int samples, int channels, if (c) { c->flags = CF_ABSVOLUME|CF_NOSPACIALISE; - c->entnum = -1; + c->entnum = 0; c->entchannel = 0; c->dist_mult = 0; c->master_vol = 255 * volume; diff --git a/engine/client/sound.h b/engine/client/sound.h index bc96016f7..e126c32fc 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -91,10 +91,13 @@ typedef struct #define CF_RELIABLE 1 // serverside only. yeah, evil. screw you. #define CF_FORCELOOP 2 // forces looping. set on static sounds. -#define CF_NOSPACIALISE 4 // these sounds are played at a fixed volume +#define CF_NOSPACIALISE 4 // these sounds are played at a fixed volume in both speakers, but still gets quieter with distance. //#define CF_PAUSED 8 // rate = 0. or something. #define CF_ABSVOLUME 16 // ignores volume cvar. +#define CF_UNICAST 256 // serverside only. the sound is sent to msg_entity only. +#define CF_AUTOSOUND 512 // generated from q2 entities, which avoids breaking regular sounds, using it outside the sound system will probably break things. + typedef struct { sfx_t *sfx; // sfx number @@ -188,9 +191,6 @@ qboolean ResampleSfx (sfx_t *sfx, int inrate, int inchannels, int inwidth, int i // picks a channel based on priorities, empty slots, number of channels channel_t *SND_PickChannel(soundcardinfo_t *sc, int entnum, int entchannel); -// spatializes a channel -void SND_Spatialize(soundcardinfo_t *sc, channel_t *ch); - void SND_ResampleStream (void *in, int inrate, int inwidth, int inchannels, int insamps, void *out, int outrate, int outwidth, int outchannels, int resampstyle); // restart entire sound subsystem (doesn't flush old sounds, so make sure that happens) @@ -296,6 +296,8 @@ struct soundcardinfo_s { //windows has one defined AFTER directsound channel_t channel[MAX_CHANNELS]; int total_chans; + float ambientlevels[NUM_AMBIENTS]; //we use a float instead of the channel's int volume value to avoid framerate dependancies with slow transitions. + //mixer volatile dma_t sn; //why is this volatile? qboolean inactive_sound; //continue mixing for this card even when the window isn't active. diff --git a/engine/client/sys_win.c b/engine/client/sys_win.c index 7687dc73a..858f8a2c9 100644 --- a/engine/client/sys_win.c +++ b/engine/client/sys_win.c @@ -1553,14 +1553,25 @@ void VARGS Sys_Printf (char *fmt, ...) out = wide; in = msg; wlen = 0; - for (in = msg; in < end; ) + for (in = msg; in < end && wlen+3 < countof(wide); ) { unsigned int flags, cp; in = Font_Decode(in, &flags, &cp); if (!(flags & CON_HIDDEN)) { - *out++ = COM_DeQuake(cp); - wlen++; + cp = COM_DeQuake(cp); + if (cp > 0xffff) + { + cp -= 0x10000; + *out++ = 0xD800 | (cp>>10); + *out++ = 0xDC00 | (cp&0x3ff); + wlen += 2; + } + else + { + *out++ = cp; + wlen++; + } } } *out = 0; @@ -1788,7 +1799,7 @@ void Sys_SaveClipboard(char *text) return; EmptyClipboard(); - if (com_parseutf8.ival > 0) + if (WinNT) { glob = GlobalAlloc(GMEM_MOVEABLE, (strlen(text) + 1)*4); if (glob) @@ -1799,7 +1810,7 @@ void Sys_SaveClipboard(char *text) int error; while(*text) { - codepoint = utf8_decode(&error, text, &text); + codepoint = unicode_decode(&error, text, &text, false); if (codepoint == '\n') { //windows is stupid and annoying. *tempw++ = '\r'; @@ -1824,7 +1835,7 @@ void Sys_SaveClipboard(char *text) } else { - glob = GlobalAlloc(GMEM_MOVEABLE, strlen(text) + 1); + glob = GlobalAlloc(GMEM_MOVEABLE, strlen(text)*2 + 1); if (glob) { //yes, quake chars will get mangled horribly. @@ -1834,8 +1845,10 @@ void Sys_SaveClipboard(char *text) int error; while (*text) { - //NOTE: should be \r\n and not just \n - *temp++ = utf8_decode(&error, text, &text) & 0xff; + codepoint = unicode_decode(&error, text, &text, false); + if (codepoint == '\n') + *temp++ = '\r'; + *temp++ = codepoint; } *temp = 0; strcpy(temp, text); diff --git a/engine/client/view.c b/engine/client/view.c index f4aad00b6..1726d87ab 100644 --- a/engine/client/view.c +++ b/engine/client/view.c @@ -324,7 +324,6 @@ qbyte gammatable[256]; // palette is sent through this unsigned short ramps[3][256]; //extern qboolean gammaworks; -float sw_blend[4]; // rgba 0.0 - 1.0 float hw_blend[4]; // rgba 0.0 - 1.0 /* void BuildGammaTable (float g) @@ -459,29 +458,29 @@ void V_ParseDamage (playerview_t *pv) if (v_damagecshift.value >= 0) count *= v_damagecshift.value; - cl.cshifts[CSHIFT_DAMAGE].percent += 3*count; - if (cl.cshifts[CSHIFT_DAMAGE].percent < 0) - cl.cshifts[CSHIFT_DAMAGE].percent = 0; - if (cl.cshifts[CSHIFT_DAMAGE].percent > 150) - cl.cshifts[CSHIFT_DAMAGE].percent = 150; + pv->cshifts[CSHIFT_DAMAGE].percent += 3*count; + if (pv->cshifts[CSHIFT_DAMAGE].percent < 0) + pv->cshifts[CSHIFT_DAMAGE].percent = 0; + if (pv->cshifts[CSHIFT_DAMAGE].percent > 150) + pv->cshifts[CSHIFT_DAMAGE].percent = 150; if (armor > blood) { - cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200; - cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100; - cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100; + pv->cshifts[CSHIFT_DAMAGE].destcolor[0] = 200; + pv->cshifts[CSHIFT_DAMAGE].destcolor[1] = 100; + pv->cshifts[CSHIFT_DAMAGE].destcolor[2] = 100; } else if (armor) { - cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220; - cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50; - cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50; + pv->cshifts[CSHIFT_DAMAGE].destcolor[0] = 220; + pv->cshifts[CSHIFT_DAMAGE].destcolor[1] = 50; + pv->cshifts[CSHIFT_DAMAGE].destcolor[2] = 50; } else { - cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255; - cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0; - cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0; + pv->cshifts[CSHIFT_DAMAGE].destcolor[0] = 255; + pv->cshifts[CSHIFT_DAMAGE].destcolor[1] = 0; + pv->cshifts[CSHIFT_DAMAGE].destcolor[2] = 0; } // @@ -510,6 +509,7 @@ V_cshift_f void V_cshift_f (void) { int r, g, b, p; + playerview_t *pv = &cl.playerview[CL_TargettedSplit(true)]; r = g = b = p = 0; @@ -551,10 +551,10 @@ void V_cshift_f (void) Con_DPrintf("broken v_cshift from gamecode\n"); // ensure we always clear out or set for nehahra - cl.cshifts[CSHIFT_SERVER].destcolor[0] = r; - cl.cshifts[CSHIFT_SERVER].destcolor[1] = g; - cl.cshifts[CSHIFT_SERVER].destcolor[2] = b; - cl.cshifts[CSHIFT_SERVER].percent = p; + pv->cshifts[CSHIFT_SERVER].destcolor[0] = r; + pv->cshifts[CSHIFT_SERVER].destcolor[1] = g; + pv->cshifts[CSHIFT_SERVER].destcolor[2] = b; + pv->cshifts[CSHIFT_SERVER].percent = p; return; } @@ -580,6 +580,7 @@ When you run over an item, the server sends this command */ void V_BonusFlash_f (void) { + playerview_t *pv = &cl.playerview[CL_TargettedSplit(true)]; float frac; if (!gl_cshiftenabled.ival) frac = 0; @@ -601,22 +602,23 @@ void V_BonusFlash_f (void) { if (Cmd_Argc() > 1) { //this is how I understand DP expects them. - cl.cshifts[CSHIFT_BONUS].destcolor[0] = atof(Cmd_Argv(1))*255; - cl.cshifts[CSHIFT_BONUS].destcolor[1] = atof(Cmd_Argv(2))*255; - cl.cshifts[CSHIFT_BONUS].destcolor[2] = atof(Cmd_Argv(3))*255; - cl.cshifts[CSHIFT_BONUS].percent = atof(Cmd_Argv(4))*255*frac; + pv->cshifts[CSHIFT_BONUS].destcolor[0] = atof(Cmd_Argv(1))*255; + pv->cshifts[CSHIFT_BONUS].destcolor[1] = atof(Cmd_Argv(2))*255; + pv->cshifts[CSHIFT_BONUS].destcolor[2] = atof(Cmd_Argv(3))*255; + pv->cshifts[CSHIFT_BONUS].percent = atof(Cmd_Argv(4))*255*frac; } else { - cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215; - cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186; - cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69; - cl.cshifts[CSHIFT_BONUS].percent = 50*frac; + pv->cshifts[CSHIFT_BONUS].destcolor[0] = 215; + pv->cshifts[CSHIFT_BONUS].destcolor[1] = 186; + pv->cshifts[CSHIFT_BONUS].destcolor[2] = 69; + pv->cshifts[CSHIFT_BONUS].percent = 50*frac; } } } void V_DarkFlash_f (void) { + playerview_t *pv = &cl.playerview[CL_TargettedSplit(true)]; float frac; if (!gl_cshiftenabled.ival) frac = 0; @@ -624,13 +626,14 @@ void V_DarkFlash_f (void) frac = 1; frac *= gl_cshiftpercent.value / 100.0; - cl.cshifts[CSHIFT_BONUS].destcolor[0] = 0; - cl.cshifts[CSHIFT_BONUS].destcolor[1] = 0; - cl.cshifts[CSHIFT_BONUS].destcolor[2] = 0; - cl.cshifts[CSHIFT_BONUS].percent = 255*frac; + pv->cshifts[CSHIFT_BONUS].destcolor[0] = 0; + pv->cshifts[CSHIFT_BONUS].destcolor[1] = 0; + pv->cshifts[CSHIFT_BONUS].destcolor[2] = 0; + pv->cshifts[CSHIFT_BONUS].percent = 255*frac; } void V_WhiteFlash_f (void) { + playerview_t *pv = &cl.playerview[CL_TargettedSplit(true)]; float frac; if (!gl_cshiftenabled.ival) frac = 0; @@ -638,10 +641,10 @@ void V_WhiteFlash_f (void) frac = 1; frac *= gl_cshiftpercent.value / 100.0; - cl.cshifts[CSHIFT_BONUS].destcolor[0] = 255; - cl.cshifts[CSHIFT_BONUS].destcolor[1] = 255; - cl.cshifts[CSHIFT_BONUS].destcolor[2] = 255; - cl.cshifts[CSHIFT_BONUS].percent = 255*frac; + pv->cshifts[CSHIFT_BONUS].destcolor[0] = 255; + pv->cshifts[CSHIFT_BONUS].destcolor[1] = 255; + pv->cshifts[CSHIFT_BONUS].destcolor[2] = 255; + pv->cshifts[CSHIFT_BONUS].percent = 255*frac; } /* @@ -656,25 +659,27 @@ FIXME: Uses Q1 contents void V_SetContentsColor (int contents) { int i; + playerview_t *pv = r_refdef.playerview; + if (contents & FTECONTENTS_LAVA) - cl.cshifts[CSHIFT_CONTENTS] = cshift_lava; + pv->cshifts[CSHIFT_CONTENTS] = cshift_lava; else if (contents & (FTECONTENTS_SLIME | FTECONTENTS_SOLID)) - cl.cshifts[CSHIFT_CONTENTS] = cshift_slime; + pv->cshifts[CSHIFT_CONTENTS] = cshift_slime; else if (contents & FTECONTENTS_WATER) - cl.cshifts[CSHIFT_CONTENTS] = cshift_water; + pv->cshifts[CSHIFT_CONTENTS] = cshift_water; else - cl.cshifts[CSHIFT_CONTENTS] = cshift_empty; + pv->cshifts[CSHIFT_CONTENTS] = cshift_empty; - cl.cshifts[CSHIFT_CONTENTS].percent *= v_contentblend.value; + pv->cshifts[CSHIFT_CONTENTS].percent *= v_contentblend.value; - if (cl.cshifts[CSHIFT_CONTENTS].percent) + if (pv->cshifts[CSHIFT_CONTENTS].percent) { //bound contents so it can't go negative - if (cl.cshifts[CSHIFT_CONTENTS].percent < 0) - cl.cshifts[CSHIFT_CONTENTS].percent = 0; + if (pv->cshifts[CSHIFT_CONTENTS].percent < 0) + pv->cshifts[CSHIFT_CONTENTS].percent = 0; for (i = 0; i < 3; i++) - if (cl.cshifts[CSHIFT_CONTENTS].destcolor[0] < 0) - cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0; + if (pv->cshifts[CSHIFT_CONTENTS].destcolor[0] < 0) + pv->cshifts[CSHIFT_CONTENTS].destcolor[0] = 0; } } @@ -683,50 +688,44 @@ void V_SetContentsColor (int contents) V_CalcPowerupCshift ============= */ -void V_CalcPowerupCshift (void) +void V_CalcPowerupCshift (playerview_t *pv) { #ifdef QUAKESTATS - int im = 0; - int s; - - //we only have one palette, so combine the mask - - for (s = 0; s < cl.splitclients; s++) - im |= cl.playerview[s].stats[STAT_ITEMS]; + int im = pv->stats[STAT_ITEMS]; if (im & IT_QUAD) { - cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; - cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0; - cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255; - cl.cshifts[CSHIFT_POWERUP].percent = 30*v_quadcshift.value; + pv->cshifts[CSHIFT_POWERUP].destcolor[0] = 0; + pv->cshifts[CSHIFT_POWERUP].destcolor[1] = 0; + pv->cshifts[CSHIFT_POWERUP].destcolor[2] = 255; + pv->cshifts[CSHIFT_POWERUP].percent = 30*v_quadcshift.value; } else if (im & IT_SUIT) { - cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; - cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; - cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; - cl.cshifts[CSHIFT_POWERUP].percent = 20*v_suitcshift.value; + pv->cshifts[CSHIFT_POWERUP].destcolor[0] = 0; + pv->cshifts[CSHIFT_POWERUP].destcolor[1] = 255; + pv->cshifts[CSHIFT_POWERUP].destcolor[2] = 0; + pv->cshifts[CSHIFT_POWERUP].percent = 20*v_suitcshift.value; } else if (im & IT_INVISIBILITY) { - cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100; - cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100; - cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100; - cl.cshifts[CSHIFT_POWERUP].percent = 100*v_ringcshift.value; + pv->cshifts[CSHIFT_POWERUP].destcolor[0] = 100; + pv->cshifts[CSHIFT_POWERUP].destcolor[1] = 100; + pv->cshifts[CSHIFT_POWERUP].destcolor[2] = 100; + pv->cshifts[CSHIFT_POWERUP].percent = 100*v_ringcshift.value; } else if (im & IT_INVULNERABILITY) { - cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255; - cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; - cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; - cl.cshifts[CSHIFT_POWERUP].percent = 30*v_pentcshift.value; + pv->cshifts[CSHIFT_POWERUP].destcolor[0] = 255; + pv->cshifts[CSHIFT_POWERUP].destcolor[1] = 255; + pv->cshifts[CSHIFT_POWERUP].destcolor[2] = 0; + pv->cshifts[CSHIFT_POWERUP].percent = 30*v_pentcshift.value; } else - cl.cshifts[CSHIFT_POWERUP].percent = 0; + pv->cshifts[CSHIFT_POWERUP].percent = 0; - if (cl.cshifts[CSHIFT_POWERUP].percent<0) - cl.cshifts[CSHIFT_POWERUP].percent=0; + if (pv->cshifts[CSHIFT_POWERUP].percent<0) + pv->cshifts[CSHIFT_POWERUP].percent=0; #endif } @@ -740,58 +739,71 @@ void V_CalcBlend (float *hw_blend) { extern qboolean r2d_canhwgamma; float a2; - int j; + int j, seat; float *blend; + playerview_t *pv; memset(hw_blend, 0, sizeof(float)*4); - memset(sw_blend, 0, sizeof(float)*4); - - //don't apply it to the server, we'll blend the two later if the user has no hardware gamma (if they do have it, we use just the server specified value) This way we avoid winnt users having a cheat with flashbangs and stuff. - for (j=0 ; jscreentint, 0, sizeof(pv->screentint)); + + //don't apply it to the server, we'll blend the two later if the user has no hardware gamma (if they do have it, we use just the server specified value) This way we avoid winnt users having a cheat with flashbangs and stuff. + for (j=0 ; jcshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0; + } + else + { + a2 = pv->cshifts[j].percent / 255.0; //don't allow modification of this one. + } + + if (!a2) continue; - a2 = ((cl.cshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0; - } - else - { - a2 = cl.cshifts[j].percent / 255.0; //don't allow modification of this one. + if (j == CSHIFT_SERVER) + { + /*server blend always goes into sw, ALWAYS*/ + blend = pv->screentint; + } + else + { + /*flashing things should not change hardware gamma ramps - windows is too slow*/ + /*splitscreen should only use hw gamma ramps if they're all equal, and they're probably not*/ + /*hw blends may also not be supported or may be disabled*/ + if (j == CSHIFT_BONUS || j == CSHIFT_DAMAGE || gl_nohwblend.ival || !r2d_canhwgamma || cl.splitclients > 1) + blend = pv->screentint; + else //powerup or contents? + blend = hw_blend; + } + + blend[3] = blend[3] + a2*(1-blend[3]); + a2 = a2/blend[3]; + blend[0] = blend[0]*(1-a2) + pv->cshifts[j].destcolor[0]*a2/255.0; + blend[1] = blend[1]*(1-a2) + pv->cshifts[j].destcolor[1]*a2/255.0; + blend[2] = blend[2]*(1-a2) + pv->cshifts[j].destcolor[2]*a2/255.0; } - if (!a2) - continue; - - if (j == CSHIFT_SERVER) - { - /*server blend always goes into sw, ALWAYS*/ - blend = sw_blend; - } - else - { - if (j == CSHIFT_BONUS || j == CSHIFT_DAMAGE || gl_nohwblend.ival || !r2d_canhwgamma) - blend = sw_blend; - else //powerup or contents? - blend = hw_blend; - } - - blend[3] = blend[3] + a2*(1-blend[3]); - a2 = a2/blend[3]; - blend[0] = blend[0]*(1-a2) + cl.cshifts[j].destcolor[0]*a2/255.0; - blend[1] = blend[1]*(1-a2) + cl.cshifts[j].destcolor[1]*a2/255.0; - blend[2] = blend[2]*(1-a2) + cl.cshifts[j].destcolor[2]*a2/255.0; + if (pv->screentint[3] > 1) + pv->screentint[3] = 1; + if (pv->screentint[3] < 0) + pv->screentint[3] = 0; + } + for (; seat < MAX_SPLITS; seat++) + { + pv = &cl.playerview[seat]; + memset(pv->screentint, 0, sizeof(pv->screentint)); } - if (hw_blend[3] > 1) hw_blend[3] = 1; if (hw_blend[3] < 0) hw_blend[3] = 0; - if (sw_blend[3] > 1) - sw_blend[3] = 1; - if (sw_blend[3] < 0) - sw_blend[3] = 0; } /* @@ -815,17 +827,20 @@ void V_UpdatePalette (qboolean force) if (ftime < 0) ftime = 0; - V_CalcPowerupCshift (); + for (i = 0; i < MAX_SPLITS; i++) + { + playerview_t *pv = &cl.playerview[i]; + V_CalcPowerupCshift(pv); + // drop the damage value + pv->cshifts[CSHIFT_DAMAGE].percent -= ftime*150; + if (pv->cshifts[CSHIFT_DAMAGE].percent <= 0) + pv->cshifts[CSHIFT_DAMAGE].percent = 0; -// drop the damage value - cl.cshifts[CSHIFT_DAMAGE].percent -= ftime*150; - if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0) - cl.cshifts[CSHIFT_DAMAGE].percent = 0; - -// drop the bonus value - cl.cshifts[CSHIFT_BONUS].percent -= ftime*100; - if (cl.cshifts[CSHIFT_BONUS].percent <= 0) - cl.cshifts[CSHIFT_BONUS].percent = 0; + // drop the bonus value + pv->cshifts[CSHIFT_BONUS].percent -= ftime*100; + if (pv->cshifts[CSHIFT_BONUS].percent <= 0) + pv->cshifts[CSHIFT_BONUS].percent = 0; + } V_CalcBlend(newhw_blend); @@ -877,10 +892,11 @@ V_UpdatePalette void V_ClearCShifts (void) { - int i; + int i, seat; - for (i = 0; i < NUM_CSHIFTS; i++) - cl.cshifts[i].percent = 0; + for (seat = 0; seat < MAX_SPLITS; seat++) + for (i = 0; i < NUM_CSHIFTS; i++) + cl.playerview[seat].cshifts[i].percent = 0; } /* @@ -1351,7 +1367,11 @@ void V_CalcRefdef (playerview_t *pv) #ifdef Q2CLIENT if (cls.protocol == CP_QUAKE2) + { + VectorCopy (pv->simorg, r_refdef.vieworg); + VectorCopy (pv->simangles, r_refdef.viewangles); return; + } #endif if (v_viewheight.value < -7) @@ -1619,7 +1639,9 @@ static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) VectorCopy(org, tagcenter); tagcenter[2] += 32; if (!Matrix4x4_CM_Project(tagcenter, center, r_refdef.viewangles, r_refdef.vieworg, r_refdef.fov_x, r_refdef.fov_y)) - return; //offscreen + return; //behind the camera + if (center[0] < 0 || center[0] > 1 || center[1] < 0 || center[1] > 1) + return; //off the side of the screen obscured = !TP_IsPlayerVisible(org); @@ -1980,6 +2002,9 @@ void V_RenderPlayerViews(playerview_t *pv) R2D_PolyBlend (); R_DrawNameTags(); + if(cl.intermissionmode == IM_NONE) + R2D_DrawCrosshair(); + cl_numvisedicts = oldnuments; cl_numstris = oldstris; diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 3f4550fea..ae189124c 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -713,11 +713,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //#define MAX_EDICTS ((1<<22)-1) // expandable up to 22 bits #define MAX_EDICTS ((1<<18)-1) // expandable up to 22 bits #endif -#define MAX_LIGHTSTYLES 255 //255 = 'invalid', and thus only 0-254 are the valid indexes. +#define MAX_LIGHTSTYLES 255 // 8bit. 255 = 'invalid', and thus only 0-254 are the valid indexes. #define MAX_STANDARDLIGHTSTYLES 64 -#define MAX_PRECACHE_MODELS 2048 // these are sent over the net as bytes/shorts -#define MAX_PRECACHE_SOUNDS 1024 // so they cannot be blindly increased -#define MAX_SSPARTICLESPRE 1024 // precached particle effect names, for server-side pointparticles/trailparticles. +#define MAX_PRECACHE_MODELS 2048 // 14bit. +#define MAX_PRECACHE_SOUNDS 1024 // 14bit. +#define MAX_SSPARTICLESPRE 1024 // 14bit. precached particle effect names, for server-side pointparticles/trailparticles. #define MAX_VWEP_MODELS 32 #define MAX_CSMODELS 1024 // these live entirly clientside diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 15da37411..76fa78a7d 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -2138,7 +2138,7 @@ void Cmd_ForwardToServer_f (void) if (Cmd_Argc() > 1) { - int split = CL_TargettedSplit(true); + int split = CL_TargettedSplit(false); if (split) CL_SendClientCommand(true, "%i %s", split+1, Cmd_Args()); else @@ -2229,6 +2229,7 @@ void Cmd_ExecuteString (char *text, int level) #ifndef SERVERONLY //an emergency escape mechansim, to avoid infinatly recursing aliases. extern qboolean keydown[]; + extern int con_splitmodifier; if (keydown[K_SHIFT] && (keydown[K_LCTRL]||keydown[K_RCTRL]) && (keydown[K_LALT]||keydown[K_RALT])) return; @@ -2264,6 +2265,15 @@ void Cmd_ExecuteString (char *text, int level) Cmd_ExpandStringArguments (a->value, dest, sizeof(dest)); Cbuf_InsertText (dest, execlevel, false); +#ifndef SERVERONLY + if (con_splitmodifier > 0) + { //if the alias was execed via p1/p2 etc, make sure that propagates properly (at least for simple aliases like impulses) + //fixme: should probably prefix each line. that may have different issues however. + //don't need to care about + etc + Cbuf_InsertText (va("p %i ", con_splitmodifier), execlevel, false); + } +#endif + Con_DPrintf("Execing alias %s:\n%s\n", a->name, a->value); return; } @@ -3054,6 +3064,9 @@ void Cmd_set_f(void) else docalc = false; + if (!strncmp(Cmd_Argv(0), "seta", 4) && !Cmd_FromGamecode()) + forceflags |= CVAR_ARCHIVE; + Q_strncpyz(name, Cmd_Argv(1), sizeof(name)); if (!strcmp(Cmd_Argv(0), "setfl") || Cmd_FromGamecode()) //AAHHHH!!! Q2 set command is different @@ -3109,7 +3122,7 @@ void Cmd_set_f(void) } } - forceflags = 0; + forceflags |= 0; } var = Cvar_Get (name, text, CVAR_TEAMPLAYTAINT, "Custom variables"); @@ -3147,9 +3160,6 @@ void Cmd_set_f(void) if (Cmd_ExecLevel == RESTRICT_TEAMPLAY) var->flags |= CVAR_TEAMPLAYTAINT; - - if (!strncmp(Cmd_Argv(0), "seta", 4)) - var->flags |= CVAR_ARCHIVE; } } else @@ -3163,12 +3173,10 @@ void Cmd_set_f(void) Cvar_LockFromServer(var, text); } else - var = Cvar_Get(Cmd_Argv(1), text, 0, "User variables"); - } + var = Cvar_Get(Cmd_Argv(1), text, CVAR_USERCREATED, "User variables"); - if (var && !Cmd_FromGamecode()) - if (!strncmp(Cmd_Argv(0), "seta", 4)) - var->flags |= CVAR_ARCHIVE|CVAR_USERCREATED; + var->flags |= forceflags; + } If_Token_Clear(mark); } @@ -3234,7 +3242,7 @@ void Cmd_WriteConfig_f(void) snprintf(fname, sizeof(fname), "fte.cfg"); #endif - f = FS_OpenWithFriends(fname, sysname, sizeof(sysname), 3, "quake.rc", "*.cfg", "configs/*.cfg"); + f = FS_OpenWithFriends(fname, sysname, sizeof(sysname), 3, "quake.rc", "hexen.rc", "*.cfg", "configs/*.cfg"); all = cfg_save_all.ival; } diff --git a/engine/common/com_mesh.c b/engine/common/com_mesh.c index fcb6dbc9c..8c902c56c 100644 --- a/engine/common/com_mesh.c +++ b/engine/common/com_mesh.c @@ -2791,9 +2791,14 @@ void Mod_LoadAliasShaders(model_t *mod) extern float r_avertexnormals[NUMVERTEXNORMALS][3]; // mdltype 0 = q1, 1 = qtest, 2 = rapo/h2 -static void Q1MDL_LoadPose(galiasinfo_t *galias, dmdl_t *pq1inmodel, vecV_t *verts, vec3_t *normals, vec3_t *svec, vec3_t *tvec, dtrivertx_t *pinframe, int *seamremaps, int mdltype) +static void Q1MDL_LoadPose(galiasinfo_t *galias, dmdl_t *pq1inmodel, vecV_t *verts, vec3_t *normals, vec3_t *svec, vec3_t *tvec, dtrivertx_t *pinframe, int *seamremaps, int mdltype, unsigned int bbox[6]) { int j; +#ifdef _DEBUG + bbox[0] = bbox[1] = bbox[2] = ~0; + bbox[3] = bbox[4] = bbox[5] = 0; +#endif + #ifdef HEXEN2 if (mdltype == 2) { @@ -2812,6 +2817,14 @@ static void Q1MDL_LoadPose(galiasinfo_t *galias, dmdl_t *pq1inmodel, vecV_t *ver { for (j = 0; j < pq1inmodel->numverts; j++) { +#ifdef _DEBUG + bbox[0] = min(bbox[0], pinframe[j].v[0]); + bbox[1] = min(bbox[1], pinframe[j].v[1]); + bbox[2] = min(bbox[2], pinframe[j].v[2]); + bbox[3] = max(bbox[3], pinframe[j].v[0]); + bbox[4] = max(bbox[4], pinframe[j].v[1]); + bbox[5] = max(bbox[5], pinframe[j].v[2]); +#endif verts[j][0] = pinframe[j].v[0]*pq1inmodel->scale[0]+pq1inmodel->scale_origin[0]; verts[j][1] = pinframe[j].v[1]*pq1inmodel->scale[1]+pq1inmodel->scale_origin[1]; verts[j][2] = pinframe[j].v[2]*pq1inmodel->scale[2]+pq1inmodel->scale_origin[2]; @@ -2868,6 +2881,8 @@ static void *Q1MDL_LoadFrameGroup (galiasinfo_t *galias, dmdl_t *pq1inmodel, mod vecV_t *verts; int aliasframesize = (mdltype == 1) ? sizeof(daliasframe_t)-16 : sizeof(daliasframe_t); + unsigned int bbox[6]; + #ifdef SERVERONLY normals = NULL; svec = NULL; @@ -2913,10 +2928,22 @@ static void *Q1MDL_LoadFrameGroup (galiasinfo_t *galias, dmdl_t *pq1inmodel, mod } else { - Q1MDL_LoadPose(galias, pq1inmodel, verts, normals, svec, tvec, pinframe, seamremaps, mdltype); + Q1MDL_LoadPose(galias, pq1inmodel, verts, normals, svec, tvec, pinframe, seamremaps, mdltype, bbox); pframetype = (daliasframetype_t *)&pinframe[pq1inmodel->numverts]; } +#ifdef _DEBUG + if ((bbox[3] > frameinfo->bboxmax.v[0] || bbox[4] > frameinfo->bboxmax.v[1] || bbox[5] > frameinfo->bboxmax.v[2] || + bbox[0] < frameinfo->bboxmin.v[0] || bbox[1] < frameinfo->bboxmin.v[1] || bbox[2] < frameinfo->bboxmin.v[2]) && !galias->warned) +#else + if (pinframe[0].v[2] > frameinfo->bboxmax.v[2] && !galias->warned) +#endif + { + Con_Printf(CON_WARNING"%s has incorrect frame bounds\n", loadmodel->name); + galias->warned = true; + } + + // GL_GenerateNormals((float*)verts, (float*)normals, (int *)((char *)galias + galias->ofs_indexes), galias->numindexes/3, galias->numverts); break; @@ -2975,10 +3002,23 @@ static void *Q1MDL_LoadFrameGroup (galiasinfo_t *galias, dmdl_t *pq1inmodel, mod } else { - Q1MDL_LoadPose(galias, pq1inmodel, verts, normals, svec, tvec, pinframe, seamremaps, mdltype); + Q1MDL_LoadPose(galias, pq1inmodel, verts, normals, svec, tvec, pinframe, seamremaps, mdltype, bbox); pinframe += pq1inmodel->numverts; } +#ifdef _DEBUG + if ((bbox[3] > frameinfo->bboxmax.v[0] || bbox[4] > frameinfo->bboxmax.v[1] || bbox[5] > frameinfo->bboxmax.v[2] || + bbox[0] < frameinfo->bboxmin.v[0] || bbox[1] < frameinfo->bboxmin.v[1] || bbox[2] < frameinfo->bboxmin.v[2] || +#else + if ((pinframe[0].v[2] > frameinfo->bboxmax.v[2] || +#endif + frameinfo->bboxmin.v[0] < ingroup->bboxmin.v[0] || frameinfo->bboxmin.v[1] < ingroup->bboxmin.v[1] || frameinfo->bboxmin.v[2] < ingroup->bboxmin.v[2] || + frameinfo->bboxmax.v[0] > ingroup->bboxmax.v[0] || frameinfo->bboxmax.v[1] > ingroup->bboxmax.v[1] || frameinfo->bboxmax.v[2] > ingroup->bboxmax.v[2]) && !galias->warned) + { + Con_Printf(CON_WARNING"%s has incorrect frame bounds\n", loadmodel->name); + galias->warned = true; + } + #ifndef SERVERONLY verts = (vecV_t*)&tvec[galias->numverts]; normals = (vec3_t*)&verts[galias->numverts]; @@ -3594,17 +3634,25 @@ qboolean QDECL Mod_LoadQ1Model (model_t *mod, void *buffer, size_t fsize) galias->ofs_indexes = indexes; for (i=0 ; inumtris ; i++) { + unsigned int v1 = LittleLong(pinq1triangles[i].vertindex[0]); + unsigned int v2 = LittleLong(pinq1triangles[i].vertindex[1]); + unsigned int v3 = LittleLong(pinq1triangles[i].vertindex[2]); + if (v1 >= pq1inmodel->numverts || v2 >= pq1inmodel->numverts || v3 >= pq1inmodel->numverts) + { + Con_Printf(CON_ERROR"%s has invalid triangle (%u %u %u > %u)\n", mod->name, v1, v2, v3, pq1inmodel->numverts); + v1 = v2 = v3 = 0; + } if (!pinq1triangles[i].facesfront) { - indexes[i*3+0] = seamremap[LittleLong(pinq1triangles[i].vertindex[0])]; - indexes[i*3+1] = seamremap[LittleLong(pinq1triangles[i].vertindex[1])]; - indexes[i*3+2] = seamremap[LittleLong(pinq1triangles[i].vertindex[2])]; + indexes[i*3+0] = seamremap[v1]; + indexes[i*3+1] = seamremap[v2]; + indexes[i*3+2] = seamremap[v3]; } else { - indexes[i*3+0] = LittleLong(pinq1triangles[i].vertindex[0]); - indexes[i*3+1] = LittleLong(pinq1triangles[i].vertindex[1]); - indexes[i*3+2] = LittleLong(pinq1triangles[i].vertindex[2]); + indexes[i*3+0] = v1; + indexes[i*3+1] = v2; + indexes[i*3+2] = v3; } } end = &pinq1triangles[pq1inmodel->numtris]; @@ -4269,6 +4317,8 @@ int Mod_TagNumForName(model_t *model, const char *name) if (!model) return 0; + if (model->loadstate != MLS_LOADED) + return 0; #ifdef HALFLIFEMODELS if (model->type == mod_halflife) return HLMod_BoneForName(model, name); diff --git a/engine/common/com_mesh.h b/engine/common/com_mesh.h index 72f27df32..2830589d6 100644 --- a/engine/common/com_mesh.h +++ b/engine/common/com_mesh.h @@ -171,6 +171,7 @@ typedef struct galiasinfo_s int numtagframes; int numtags; md3tag_t *ofstags; + unsigned int warned; //passed around at load time, so we don't spam warnings } galiasinfo_t; typedef struct diff --git a/engine/common/common.c b/engine/common/common.c index 51dccce15..38d5c7878 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -1006,6 +1006,11 @@ void MSG_WriteCoord (sizebuf_t *sb, float f) coorddata i = MSG_ToCoord(f, sb->prim.coordsize); SZ_Write (sb, (void*)&i, sb->prim.coordsize); } +void MSG_WriteCoord24 (sizebuf_t *sb, float f) +{ + coorddata i = MSG_ToCoord(f, 3); + SZ_Write (sb, (void*)&i, 3); +} void MSG_WriteAngle16 (sizebuf_t *sb, float f) { @@ -1565,6 +1570,12 @@ float MSG_ReadCoord (void) MSG_ReadData(&c, net_message.prim.coordsize); return MSG_FromCoord(c, net_message.prim.coordsize); } +float MSG_ReadCoord24 (void) +{ + coorddata c = {{0}}; + MSG_ReadData(&c, 3); + return MSG_FromCoord(c, 3); +} void MSG_ReadPos (vec3_t pos) { @@ -3408,10 +3419,8 @@ messedup: //unicode-to-ascii is not provided. you're expected to utf-8 the result or something. //does not handle colour codes or hidden chars. add your own escape sequences if you need that. //does not guarentee removal of control codes if eg the code was specified as an explicit unicode char. -unsigned int COM_DeQuake(conchar_t chr) +unsigned int COM_DeQuake(unsigned int chr) { - chr &= CON_CHARMASK; - /*only this range are quake chars*/ if (chr >= 0xe000 && chr < 0xe100) { @@ -5189,15 +5198,15 @@ void COM_WorkerPartialSync(void *priorityctx, int *address, int value) // Con_Printf("Waited %f for %s\n", Sys_DoubleTime() - time1, priorityctx); } -static void COM_WorkerPing(void *ctx, void *data, size_t a, size_t b) +static void COM_WorkerPong(void *ctx, void *data, size_t a, size_t b) { double *timestamp = data; - if (!b) - COM_AddWork(WG_MAIN, COM_WorkerPing, ctx, data, 0, 1); - else - { - Con_Printf("Ping: %g\n", Sys_DoubleTime() - *timestamp); - } + Con_Printf("Ping: %g\n", Sys_DoubleTime() - *timestamp); + Z_Free(timestamp); +} +static void COM_WorkerPing(void *ctx, void *data, size_t a, size_t b) +{ + COM_AddWork(WG_MAIN, COM_WorkerPong, ctx, data, 0, 0); } static void COM_WorkerTest_f(void) { diff --git a/engine/common/common.h b/engine/common/common.h index 5228c25f0..68c8c7ef9 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -110,8 +110,8 @@ struct netprim_s { qbyte coordsize; qbyte anglesize; -#define NPQ2_ANG16 (1<<0) -#define NPQ2_SIZE32 (1<<0) +#define NPQ2_ANG16 (1u<<0) +#define NPQ2_SOLID32 (1u<<1) qbyte q2flags; qbyte pad; }; @@ -347,12 +347,14 @@ char *COM_DeFunString(conchar_t *str, conchar_t *stop, char *out, int outsize, q #define PFS_FORCEUTF8 2 //force utf-8 decoding #define PFS_NOMARKUP 4 //strip markup completely #define PFS_EZQUAKEMARKUP 8 //aim for compat with ezquake instead of q3 compat +#define PFS_CENTERED 16 //flag used by console prints (text should remain centered) +#define PFS_NONOTIFY 32 //flag used by console prints (text won't be visible other than by looking at the console) conchar_t *COM_ParseFunString(conchar_t defaultflags, const char *str, conchar_t *out, int outsize, int keepmarkup); //ext is usually CON_WHITEMASK, returns its null terminator unsigned int utf8_decode(int *error, const void *in, char **out); unsigned int utf8_encode(void *out, unsigned int unicode, int maxlen); unsigned int iso88591_encode(char *out, unsigned int unicode, int maxlen, qboolean markup); unsigned int qchar_encode(char *out, unsigned int unicode, int maxlen, qboolean markup); -unsigned int COM_DeQuake(conchar_t chr); +unsigned int COM_DeQuake(unsigned int unichar); void COM_BiDi_Shutdown(void); diff --git a/engine/common/console.h b/engine/common/console.h index fb3d5688c..bc5ecc8e9 100644 --- a/engine/common/console.h +++ b/engine/common/console.h @@ -98,12 +98,14 @@ extern conchar_t q3codemasks[MAXQ3COLOURS]; #define isextendedcode(x) ((x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || x == '-') #define ishexcode(x) ((x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f')) +#define CONL_CENTERED (1u<<0) typedef struct conline_s { struct conline_s *older; struct conline_s *newer; unsigned short length; unsigned short maxlength; - unsigned short lines; + unsigned char numlines; //updated so we scroll properly + unsigned char flags; unsigned short id; float time; } conline_t; @@ -204,13 +206,14 @@ struct font_s; void Con_DrawOneConsole(console_t *con, qboolean focused, struct font_s *font, float fx, float fy, float fsx, float fsy); void Con_DrawConsole (int lines, qboolean noback); char *Con_CopyConsole(console_t *con, qboolean nomarkup, qboolean onlyiflink); -void Con_Print (char *txt); -void Con_PrintFlags(char *text, unsigned int setflags, unsigned int clearflags); +void Con_Print (const char *txt); +void Con_CenterPrint(const char *txt); +void Con_PrintFlags(const char *text, unsigned int setflags, unsigned int clearflags); void VARGS Con_Printf (const char *fmt, ...) LIKEPRINTF(1); void VARGS Con_TPrintf (translation_t text, ...); void VARGS Con_DPrintf (const char *fmt, ...) LIKEPRINTF(1); -void VARGS Con_SafePrintf (char *fmt, ...) LIKEPRINTF(1); -void Con_Footerf(console_t *con, qboolean append, char *fmt, ...) LIKEPRINTF(3); +void VARGS Con_SafePrintf (const char *fmt, ...) LIKEPRINTF(1); +void Con_Footerf(console_t *con, qboolean append, const char *fmt, ...) LIKEPRINTF(3); void Con_Clear_f (void); void Con_DrawNotify (void); void Con_ClearNotify (void); @@ -229,7 +232,7 @@ qboolean Con_NameForNum(int num, char *buffer, int buffersize); console_t *Con_FindConsole(const char *name); console_t *Con_Create(const char *name, unsigned int flags); void Con_SetVisible (console_t *con); -void Con_PrintCon (console_t *con, char *txt, unsigned int parseflags); +void Con_PrintCon (console_t *con, const char *txt, unsigned int parseflags); void Con_NotifyBox (char *text); // during startup for sound / cd warnings diff --git a/engine/common/fs.c b/engine/common/fs.c index c4ab2a85c..b232b3d2a 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -2631,7 +2631,8 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths) FS_ChangeGame(man, cfg_reload_on_gamedir.ival, false); } -#define QCFG "set com_parseutf8 0\nset allow_download_refpackages 0\nset sv_bigcoords \"\"\nmap_autoopenportals 1\nsv_port "STRINGIFY(PORT_QWSERVER)" "STRINGIFY(PORT_NQSERVER)"\n" +/*quake requires a few settings for compatibility*/ +#define QCFG "set com_parseutf8 0\nset allow_download_refpackages 0\nset sv_bigcoords \"\"\nmap_autoopenportals 1\n" "sv_port "STRINGIFY(PORT_QWSERVER)" "STRINGIFY(PORT_NQSERVER)"\n" /*stuff that makes dp-only mods work a bit better*/ #define DPCOMPAT QCFG "set _cl_playermodel \"\"\n set dpcompat_set 1\nset dpcompat_corruptglobals 1\nset vid_pixelheight 1\n" /*nexuiz/xonotic has a few quirks/annoyances...*/ @@ -2667,55 +2668,58 @@ const gamemode_info_t gamemode_info[] = { //whereas the quake mission packs replace start.bsp making the original episodes unreachable. //for quake, we also allow extracting all files from paks. some people think it loads faster that way or something. - //cmdline switch exename protocol name(dpmaster) identifying file exec dir1 dir2 dir3 dir(fte) full name - {"-quake", "q1", MASTER_PREFIX"Quake", {"id1/pak0.pak", - "id1/quake.rc"}, QCFG, {"id1", "qw", "*fte"}, "Quake"/*, "id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, - {"-hipnotic", "hipnotic", MASTER_PREFIX"Hipnotic",{"hipnotic/pak0.pak"}, QCFG, {"id1", "qw", "hipnotic", "*fte"}, "Quake: Scourge of Armagon"}, - {"-rogue", "rogue", MASTER_PREFIX"Rogue", {"rogue/pak0.pak"}, QCFG, {"id1", "qw", "rogue", "*fte"}, "Quake: Dissolution of Eternity"}, + //cmdline switch exename protocol name(dpmaster) identifying file exec dir1 dir2 dir3 dir(fte) full name + {"-quake", "q1", MASTER_PREFIX"Quake", {"id1/pak0.pak", "id1/quake.rc"},QCFG, {"id1", "qw", "*fte"}, "Quake"/*, "id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, + //quake's mission packs should not be favoured over the base game nor autodetected + //third part mods also tend to depend upon the mission packs for their huds, even if they don't use any other content. + //and q2 also has a rogue/pak0.pak file that we don't want to find and cause quake2 to look like dissolution of eternity + //so just make these require the same files as good ol' quake. + {"-hipnotic", "hipnotic", MASTER_PREFIX"Hipnotic",{"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "hipnotic", "*fte"}, "Quake: Scourge of Armagon"}, + {"-rogue", "rogue", MASTER_PREFIX"Rogue", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "rogue", "*fte"}, "Quake: Dissolution of Eternity"}, - //various quake-based mods. - {"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"}, "Nexuiz"}, - {"-xonotic", "xonotic", "Xonotic", {"xonotic.exe"}, NEXCFG, {"data", "*ftedata"}, "Xonotic"}, + //various quake-based standalone mods. + {"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"}, "Nexuiz"}, + {"-xonotic", "xonotic", "Xonotic", {"xonotic.exe"}, NEXCFG, {"data", "*ftedata"}, "Xonotic"}, // {"-spark", "spark", "Spark", {"base/src/progs.src", // "base/qwprogs.dat", -// "base/pak0.pak"}, DMFCFG, {"base", }, "Spark"}, +// "base/pak0.pak"}, DMFCFG, {"base", }, "Spark"}, // {"-scouts", "scouts", "FTE-SJ", {"basesj/src/progs.src", // "basesj/progs.dat", -// "basesj/pak0.pak"}, NULL, {"basesj", }, "Scouts Journey"}, -// {"-rmq", "rmq", "RMQ", {NULL}, RMQCFG, {"id1", "qw", "rmq", "*fte"}, "Remake Quake"}, +// "basesj/pak0.pak"}, NULL, {"basesj", }, "Scouts Journey"}, +// {"-rmq", "rmq", "RMQ", {NULL}, RMQCFG, {"id1", "qw", "rmq", "*fte"}, "Remake Quake"}, #ifdef HEXEN2 //hexen2's mission pack generally takes precedence if both are installed. {"-portals", "h2mp", "FTE-H2MP", {"portals/hexen.rc", - "portals/pak3.pak"}, HEX2CFG,{"data1", "portals", "*fteh2"}, "Hexen II MP"}, - {"-hexen2", "hexen2", "FTE-Hexen2", {"data1/pak0.pak"}, HEX2CFG,{"data1", "*fteh2"}, "Hexen II"}, + "portals/pak3.pak"}, HEX2CFG,{"data1", "portals", "*fteh2"}, "Hexen II MP"}, + {"-hexen2", "hexen2", "FTE-Hexen2", {"data1/pak0.pak"}, HEX2CFG,{"data1", "*fteh2"}, "Hexen II"}, #endif #if defined(Q2CLIENT) || defined(Q2SERVER) - {"-quake2", "q2", "FTE-Quake2", {"baseq2/pak0.pak"}, Q2CFG, {"baseq2", "*fteq2"}, "Quake II"}, + {"-quake2", "q2", "FTE-Quake2", {"baseq2/pak0.pak"}, Q2CFG, {"baseq2", "*fteq2"}, "Quake II"}, //mods of the above that should generally work. - {"-dday", "dday", "FTE-Quake2", {"dday/pak0.pak"}, Q2CFG, {"baseq2", "dday", "*fteq2"}, "D-Day: Normandy"}, + {"-dday", "dday", "FTE-Quake2", {"dday/pak0.pak"}, Q2CFG, {"baseq2", "dday", "*fteq2"}, "D-Day: Normandy"}, #endif #if defined(Q3CLIENT) || defined(Q3SERVER) - {"-quake3", "q3", "FTE-Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena"}, + {"-quake3", "q3", "FTE-Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena"}, //the rest are not supported in any real way. maps-only mostly, if that -// {"-quake4", "q4", "FTE-Quake4", {"q4base/pak00.pk4"}, NULL, {"q4base", "*fteq4"}, "Quake 4"}, -// {"-et", NULL, "FTE-EnemyTerritory", {"etmain/pak0.pk3"}, NULL, {"etmain", "*fteet"}, "Wolfenstein - Enemy Territory"}, +// {"-quake4", "q4", "FTE-Quake4", {"q4base/pak00.pk4"}, NULL, {"q4base", "*fteq4"}, "Quake 4"}, +// {"-et", NULL, "FTE-EnemyTerritory", {"etmain/pak0.pk3"}, NULL, {"etmain", "*fteet"}, "Wolfenstein - Enemy Territory"}, -// {"-jk2", "jk2", "FTE-JK2", {"base/assets0.pk3"}, NULL, {"base", "*ftejk2"}, "Jedi Knight II: Jedi Outcast"}, -// {"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"}, +// {"-jk2", "jk2", "FTE-JK2", {"base/assets0.pk3"}, NULL, {"base", "*ftejk2"}, "Jedi Knight II: Jedi Outcast"}, +// {"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"}, #endif #if !defined(QUAKETC) && !defined(MINIMAL) -// {"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"}, "Doom"}, -// {"-doom2", "doom2", "FTE-Doom2", {"doom2.wad"}, NULL, {"*", "*ftedoom"}, "Doom2"}, -// {"-doom3", "doom3", "FTE-Doom3", {"doom3.wad"}, NULL, {"based3", "*ftedoom3"},"Doom3"}, +// {"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"}, "Doom"}, +// {"-doom2", "doom2", "FTE-Doom2", {"doom2.wad"}, NULL, {"*", "*ftedoom"}, "Doom2"}, +// {"-doom3", "doom3", "FTE-Doom3", {"doom3.wad"}, NULL, {"based3", "*ftedoom3"},"Doom3"}, //for the luls -// {"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"}, +// {"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"}, #endif #if defined(HLSERVER) || defined(HLCLIENT) //can run in windows, needs hl gamecode enabled. maps can always be viewed, but meh. - {"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, NULL, {"valve", "*ftehl"}, "Half-Life"}, + {"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, NULL, {"valve", "*ftehl"}, "Half-Life"}, #endif {NULL} diff --git a/engine/common/fs_win32.c b/engine/common/fs_win32.c index 88efbad87..934821be6 100644 --- a/engine/common/fs_win32.c +++ b/engine/common/fs_win32.c @@ -541,6 +541,17 @@ static unsigned int QDECL VFSW32_FLocate(searchpathfuncs_t *handle, flocation_t FindClose(h); if (loc) { + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) + { //when looking for reparse points, FindFirstFile only reports info about the link, not the file. which means the size is wrong. + HANDLE f = CreateFileW(widen(wide, sizeof(wide), netpath), 0, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (f) + { + LARGE_INTEGER wsize; + GetFileSizeEx(f, &wsize); + CloseHandle(f); + len = qofs_Make(wsize.LowPart, wsize.HighPart); + } + } loc->len = len; loc->offset = 0; loc->index = 0; diff --git a/engine/common/fs_zip.c b/engine/common/fs_zip.c index 7107f4b2f..1f5b3d792 100644 --- a/engine/common/fs_zip.c +++ b/engine/common/fs_zip.c @@ -1060,6 +1060,7 @@ static vfsfile_t *QDECL FSZIP_OpenVFS(searchpathfuncs_t *handle, flocation_t *lo if (flags & ZFL_DEFLATED) { #ifdef ZIPCRYPT + //FIXME: Cvar_Get is not threadsafe. char *password = (flags & ZFL_WEAKENCRYPT)?Cvar_Get("fs_zip_password", "thisispublic", 0, "Filesystem")->string:NULL; #else char *password = NULL; diff --git a/engine/common/gl_q2bsp.c b/engine/common/gl_q2bsp.c index a9bcf9354..021f8d8b3 100644 --- a/engine/common/gl_q2bsp.c +++ b/engine/common/gl_q2bsp.c @@ -1346,12 +1346,10 @@ qboolean CModQ2_LoadTexInfo (model_t *mod, qbyte *mod_base, lump_t *l, char *map if (out->flags & TI_SKY) Q_snprintfz(sname, sizeof(sname), "sky/%s", in->texture); - else if (out->flags & (TI_WARP|TI_FLOWING)) - Q_snprintfz(sname, sizeof(sname), "warp/%s", in->texture); - else if (out->flags & (TI_TRANS33|TI_TRANS66)) - Q_snprintfz(sname, sizeof(sname), "trans/%s", in->texture); else - Q_snprintfz(sname, sizeof(sname), "wall/%s", in->texture); + Q_snprintfz(sname, sizeof(sname), "%s", in->texture); + if (out->flags & (TI_WARP|TI_FLOWING)) + Q_strncatz(sname, "#WARP", sizeof(sname)); if (out->flags & TI_FLOWING) Q_strncatz(sname, "#FLOW", sizeof(sname)); if (out->flags & TI_TRANS66) diff --git a/engine/common/log.c b/engine/common/log.c index 69878a338..0c88648e3 100644 --- a/engine/common/log.c +++ b/engine/common/log.c @@ -72,7 +72,7 @@ void Log_String (logtype_t lognum, char *s) char fbase[MAX_QPATH]; char fname[MAX_QPATH]; conchar_t cline[2048], *c; - unsigned int u; + unsigned int u, flags; f = NULL; switch(lognum) @@ -107,14 +107,13 @@ void Log_String (logtype_t lognum, char *s) COM_ParseFunString(CON_WHITEMASK, s, cline, sizeof(cline), !(log_readable.ival & 2)); t = utf8; - for (c = cline; *c; c++) + for (c = cline; *c; ) { - if ((*c & CON_HIDDEN) && (log_readable.ival & 2)) + c = Font_Decode(c, &flags, &u); + if ((flags & CON_HIDDEN) && (log_readable.ival & 2)) continue; if (log_readable.ival&1) - u = COM_DeQuake(*c); - else - u = *c&CON_CHARMASK; + u = COM_DeQuake(u); //at the start of a new line, we might want a timestamp (so timestamps are correct for the first char of the line, instead of the preceeding \n) if (log_newline[lognum]) diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 2f66348c7..2d655d3dc 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -5823,8 +5823,6 @@ cvar_t sv_port_natpmp = CVARCD("sv_port_natpmp", NATPMP_DEFAULT_PORT, SV_Port_Na void SVNET_RegisterCvars(void) { - int p; - #if defined(TCPCONNECT) && defined(HAVE_IPV4) Cvar_Register (&sv_port_tcp, "networking"); sv_port_tcp.restriction = RESTRICT_MAX; @@ -5849,29 +5847,6 @@ void SVNET_RegisterCvars(void) Cvar_Register (&sv_port_natpmp, "networking"); sv_port_natpmp.restriction = RESTRICT_MAX; #endif - - // parse params for cvars - p = COM_CheckParm ("-port"); - if (!p) - p = COM_CheckParm ("-svport"); - if (p && p < com_argc) - { - int port = atoi(com_argv[p+1]); - if (!port) - port = PORT_QWSERVER; -#ifdef HAVE_IPV4 - if (*sv_port_ipv4.string) - Cvar_SetValue(&sv_port_ipv4, port); -#endif -#ifdef IPPROTO_IPV6 - if (*sv_port_ipv6.string) - Cvar_SetValue(&sv_port_ipv6, port); -#endif -#ifdef USEIPX - if (*sv_port_ipx.string) - Cvar_SetValue(&sv_port_ipx, port); -#endif - } } void NET_CloseServer(void) diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index 3d96557d5..2708e7394 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -1046,6 +1046,8 @@ void QCBUILTIN PF_setattachment(pubprogfuncs_t *prinst, struct globalvars_s *pr_ model = world->Get_CModel(world, tagentity->v->modelindex); if (model) { + if (model && model->loadstate == MLS_LOADING) + COM_WorkerPartialSync(model, &model->loadstate, MLS_LOADING); tagidx = Mod_TagNumForName(model, tagname); if (tagidx == 0) Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i (model \"%s\") but could not find it\n", NUM_FOR_EDICT(prinst, e), NUM_FOR_EDICT(prinst, tagentity), tagname, tagname, NUM_FOR_EDICT(prinst, tagentity), model->name); @@ -1806,7 +1808,7 @@ qboolean QC_FixFileName(const char *name, const char **result, const char **fall *fallbackread = name; //if its a user config, ban any fallback locations so that csqc can't read passwords or whatever. - if ((!strchr(name, '/') || strnicmp(name, "configs/", 8)) && !stricmp(COM_FileExtension(name, ext, sizeof(ext)), "cfg") && strnicmp(name, "particles/", 10)) + if ((!strchr(name, '/') || strnicmp(name, "configs/", 8)) && !stricmp(COM_FileExtension(name, ext, sizeof(ext)), "cfg") && strnicmp(name, "particles/", 10) && strnicmp(name, "models/", 7)) *fallbackread = NULL; *result = va("data/%s", name); return true; @@ -5818,9 +5820,12 @@ lh_extension_t QSG_Extensions[] = { #ifndef SERVERONLY {"DP_CON_SETA", 0, NULL, {NULL}, "The 'seta' console command exists, like the 'set' command, but also marks the cvar for archiving, allowing it to be written into the user's config. Use this command in your default.cfg file."}, #endif + {"DP_EF_ADDITIVE"}, {"DP_EF_BLUE"}, //hah!! This is QuakeWorld!!! {"DP_EF_FULLBRIGHT"}, //Rerouted to hexen2 support. + {"DP_EF_NODEPTHTEST"}, //for cheats {"DP_EF_NODRAW"}, //implemented by sending it with no modelindex + {"DP_EF_NOSHADOW"}, {"DP_EF_RED"}, // {"DP_ENT_COLORMOD"}, {"DP_ENT_CUSTOMCOLORMAP"}, @@ -5901,7 +5906,7 @@ lh_extension_t QSG_Extensions[] = { {"DP_SV_EXTERIORMODELFORCLIENT"}, {"DP_SV_NODRAWTOCLIENT"}, //I prefer my older system. Guess I might as well remove that older system at some point. {"DP_SV_PLAYERPHYSICS", 0, NULL, {NULL}, "Allows reworking parts of NQ player physics. USE AT OWN RISK - this necessitates NQ physics and is thus guarenteed to break prediction."}, - {"DP_SV_POINTPARTICLES", 3, NULL, {"particleeffectnum", "pointparticles", "trailparticles"}, "Specifies that pointparticles (and trailparticles) exists in ssqc as well as csqc (and that dp's trailparticles argument fuckup will normally work). ssqc values can be passed to csqc for use, the reverse is not true. Does NOT mean that DP's effectinfo.txt is supported, only that ssqc has functionality equivelent to csqc."}, +// {"DP_SV_POINTPARTICLES", 3, NULL, {"particleeffectnum", "pointparticles", "trailparticles"}, "Specifies that pointparticles (and trailparticles) exists in ssqc as well as csqc (and that dp's trailparticles argument fuckup will normally work). ssqc values can be passed to csqc for use, the reverse is not true. Does NOT mean that DP's effectinfo.txt is supported, only that ssqc has functionality equivelent to csqc."}, {"DP_SV_POINTSOUND", 1, NULL, {"pointsound"}}, {"DP_SV_PRECACHEANYTIME", 0, NULL, {NULL}, "Specifies that the various precache builtins can be called at any time. WARNING: precaches are sent reliably while sound events, modelindexes, and particle events are not. This can mean sounds and particles might not work the first time around, or models may take a while to appear (after the reliables are received and the model is loaded from disk). Always attempt to precache a little in advance in order to reduce these issues (preferably at the start of the map...)"}, {"DP_SV_SETCOLOR"}, @@ -5929,7 +5934,7 @@ lh_extension_t QSG_Extensions[] = { {"EXT_DIMENSION_GHOST"}, {"FRIK_FILE", 11, NULL, {"stof", "fopen","fclose","fgets","fputs","strlen","strcat","substring","stov","strzone","strunzone"}}, {"FTE_CALLTIMEOFDAY", 1, NULL, {"calltimeofday"}}, - {"FTE_CSQC_ALTCONSOLES_WIP", 4, NULL, {"con_getset", "con_printf", "con_draw", "con_input"}}, + {"FTE_CSQC_ALTCONSOLES", 4, NULL, {"con_getset", "con_printf", "con_draw", "con_input"}, "The engine tracks multiple consoles. These may or may not be directly visible to the user."}, {"FTE_CSQC_BASEFRAME", 0, NULL, {NULL}, "Specifies that .basebone, .baseframe, .baselerpfrac, etc exist. These fields affect all bones in the entity's model with a lower index than the .basebone field, allowing you to give separate control to the legs of a skeletal model, without affecting the torso animations."}, #ifdef HALFLIFEMODELS {"FTE_CSQC_HALFLIFE_MODELS"}, //hl-specific skeletal model control @@ -5940,13 +5945,14 @@ lh_extension_t QSG_Extensions[] = { {"FTE_CSQC_SKELETONOBJECTS", 15, NULL, { "skel_create", "skel_build", "skel_get_numbones", "skel_get_bonename", "skel_get_boneparent", "skel_find_bone", "skel_get_bonerel", "skel_get_boneabs", "skel_set_bone", "skel_mul_bone", "skel_mul_bones", "skel_copybones", "skel_delete", "frameforname", "frameduration"}}, - {"FTE_CSQC_RENDERTARGETS_WIP", 0, NULL, {NULL}, "VF_DESTCOLOUR etc exist and are supported"}, + {"FTE_CSQC_RENDERTARGETS", 0, NULL, {NULL}, "VF_DESTCOLOUR etc exist and are supported"}, {"FTE_ENT_SKIN_CONTENTS", 0, NULL, {NULL}, "self.skin = CONTENTS_WATER; makes a brush entity into water. use -16 for a ladder."}, {"FTE_ENT_UNIQUESPAWNID"}, {"FTE_EXTENDEDTEXTCODES"}, {"FTE_FORCESHADER", 1, NULL, {"shaderforname"}}, //I'd rename this to _CSQC_ but it does technically provide this builtin to menuqc too, not that the forceshader entity field exists there... but whatever. {"FTE_FORCEINFOKEY", 1, NULL, {"forceinfokey"}}, - {"FTE_GFX_QUAKE3SHADERS"}, + {"FTE_GFX_QUAKE3SHADERS", 0, NULL, {NULL}, "specifies that the engine has full support for vanilla quake3 shaders"}, + {"FTE_GFX_REMAPSHADER", 0, NULL, {NULL}, "With the raw power of stuffcmds, the r_remapshader console command is exposed! This mystical command can be used to remap any shader to another. Remapped shaders that specify $diffuse etc in some form will inherit the textures implied by the surface."}, {"FTE_ISBACKBUFFERED", 1, NULL, {"isbackbuffered"}, "Allows you to check if a client has too many reliable messages pending."}, {"FTE_MEMALLOC", 4, NULL, {"memalloc", "memfree", "memcpy", "memfill8"}, "Allows dynamically allocating memory. Use pointers to access this memory. Memory will not be saved into saved games."}, #ifndef NOMEDIA @@ -5958,11 +5964,19 @@ lh_extension_t QSG_Extensions[] = { #endif {"FTE_MULTIPROGS", 5, NULL, {"externcall", "addprogs", "externvalue", "externset", "instr"}, "Multiple progs.dat files can be loaded inside the same qcvm."}, //multiprogs functions are available. {"FTE_MULTITHREADED", 3, NULL, {"sleep", "fork", "abort"}}, + {"FTE_MVD_PLAYERSTATS", 0, NULL, {NULL}, "In csqc, getplayerstat can be used to query any player's stats when playing back MVDs. isdemo will return 2 in this case."}, #ifdef SERVER_DEMO_PLAYBACK {"FTE_MVD_PLAYBACK"}, #endif #ifdef SVCHAT {"FTE_NPCCHAT", 1, NULL, {"chat"}}, //server looks at chat files. It automagically branches through calling qc functions as requested. +#endif +#ifdef PSET_SCRIPT + {"FTE_PART_SCRIPT", 0, NULL, {NULL}, "Specifies that the r_particledesc cvar can be used to select a list of particle effects to load from particles/*.cfg, the format of which is documented elsewhere."}, + {"FTE_PART_NAMESPACES", 0, NULL, {NULL}, "Specifies that the engine can use foo.bar to load effect foo from particle description bar. When used via ssqc, this should cause the client to download whatever effects as needed."}, +#ifndef NOLEGACY + {"FTE_PART_NAMESPACE_EFFECTINFO", 0, NULL, {NULL}, "Specifies that effectinfo.bar can load effects from effectinfo.txt for DP compatibility."}, +#endif #endif {"FTE_QC_CHECKCOMMAND", 1, NULL, {"checkcommand"}, "Provides a way to test if a console command exists, and whether its a command/alias/cvar. Does not say anything about the expected meanings of any arguments or values."}, {"FTE_QC_CHECKPVS", 1, NULL, {"checkpvs"}}, @@ -6001,6 +6015,7 @@ lh_extension_t QSG_Extensions[] = { //reuses the FRIK_FILE builtins (with substring extension) {"FTE_STRINGS", 17, NULL, {"stof", "strlen","strcat","substring","stov","strzone","strunzone", "strstrofs", "str2chr", "chr2str", "strconv", "infoadd", "infoget", "strncmp", "strcasecmp", "strncasecmp", "strpad"}}, + {"FTE_SV_POINTPARTICLES", 3, NULL, {"particleeffectnum", "pointparticles", "trailparticles"}, "Specifies that particleeffectnum, pointparticles, and trailparticles exist in ssqc as well as csqc. particleeffectnum acts as a precache, allowing ssqc values to be networked up with csqc for use. Use in combination with FTE_PART_SCRIPT+FTE_PART_NAMESPACES to use custom effects. This extension is functionally identical to the DP version, but avoids any misplaced assumptions about the format of the client's particle descriptions."}, {"FTE_SV_REENTER"}, {"FTE_TE_STANDARDEFFECTBUILTINS", 14, NULL, {"te_gunshot", "te_spike", "te_superspike", "te_explosion", "te_tarexplosion", "te_wizspike", "te_knightspike", "te_lavasplash", "te_teleport", "te_lightning1", "te_lightning2", "te_lightning3", "te_lightningblood", "te_bloodqw"}}, @@ -6017,6 +6032,7 @@ lh_extension_t QSG_Extensions[] = { {"QW_ENGINE", 3, NULL, {"infokey", "stof", "logfrag"}}, //warning: interpretation of .skin on players can be dodgy, as can some other QW features that differ from NQ. {"QWE_MVD_RECORD"}, //Quakeworld extended get the credit for this one. (mvdsv) {"TEI_MD3_MODEL"}, + {"TENEBRAE_GFX_DLIGHTS", 0, NULL,{NULL}, "Allows ssqc to attach rtlights to entities with various special properties."}, // {"TQ_RAILTRAIL"}, //treat this as the ZQ style railtrails which the client already supports, okay so the preparse stuff needs strengthening. {"ZQ_MOVETYPE_FLY"}, {"ZQ_MOVETYPE_NOCLIP"}, diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index b04897d72..e7af4b0b2 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -526,6 +526,7 @@ pbool QDECL ED_CanFree (edict_t *ed); #define MOVETYPE_6DOF 30 // flightsim mode #define MOVETYPE_WALLWALK 31 // walks up walls and along ceilings #define MOVETYPE_PHYSICS 32 +#define MOVETYPE_FLY_WORLDONLY 33 // edict->solid values #define SOLID_NOT 0 // no interaction with other objects diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 60191f854..969591d11 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -794,7 +794,7 @@ enum clcq2_ops_e #define Q2U_SKIN16 (1<<25) #define Q2U_SOUND (1<<26) #define Q2U_SOLID (1<<27) -#define Q2UX_UNUSED4 (1<<28) +#define Q2UX_INDEX16 (1<<28) //model or sound is 16bit #define Q2UX_UNUSED3 (1<<29) #define Q2UX_UNUSED2 (1<<30) #define Q2UX_UNUSED1 (1<<31) @@ -830,8 +830,9 @@ enum clcq2_ops_e #define NQSND_VOLUME (1<<0) // a qbyte #define NQSND_ATTENUATION (1<<1) // a qbyte //#define DPSND_LOOPING (1<<2) // a long, supposedly -#define FTESND_FLAGS (1<<2) // +#define FTESND_MOREFLAGS (1<<2) // actually, chan flags #define DPSND_LARGEENTITY (1<<3) + #define DPSND_LARGESOUND (1<<4) //#define DPSND_SPEEDUSHORT4000 (1<<5) // ushort speed*4000 (speed is usually 1.0, a value of 0.0 is the same as 1.0) #define FTESND_TIMEOFS (1<<6) //signed short, in milliseconds. @@ -1178,22 +1179,30 @@ typedef struct q1usercmd_s //for the local player -#define Q2PS_M_TYPE (1<<0) +#define Q2PS_M_TYPE (1<<0) #define Q2PS_M_ORIGIN (1<<1) -#define Q2PS_M_VELOCITY (1<<2) -#define Q2PS_M_TIME (1<<3) +#define Q2PS_M_VELOCITY (1<<2) +#define Q2PS_M_TIME (1<<3) #define Q2PS_M_FLAGS (1<<4) -#define Q2PS_M_GRAVITY (1<<5) -#define Q2PS_M_DELTA_ANGLES (1<<6) - -#define Q2PS_VIEWOFFSET (1<<7) -#define Q2PS_VIEWANGLES (1<<8) -#define Q2PS_KICKANGLES (1<<9) -#define Q2PS_BLEND (1<<10) +#define Q2PS_M_GRAVITY (1<<5) +#define Q2PS_M_DELTA_ANGLES (1<<6) +#define Q2PS_VIEWOFFSET (1<<7) +#define Q2PS_VIEWANGLES (1<<8) +#define Q2PS_KICKANGLES (1<<9) +#define Q2PS_BLEND (1<<10) #define Q2PS_FOV (1<<11) #define Q2PS_WEAPONINDEX (1<<12) #define Q2PS_WEAPONFRAME (1<<13) #define Q2PS_RDFLAGS (1<<14) +#define Q2PS_EXTRABITS (1<<15) +#define Q2PS_INDEX16 (1<<16) +#define Q2PS_CLIENTNUM (1<<17) +#define Q2PS_UNUSED6 (1<<18) +#define Q2PS_UNUSED5 (1<<19) +#define Q2PS_UNUSED4 (1<<20) +#define Q2PS_UNUSED3 (1<<21) +#define Q2PS_UNUSED2 (1<<22) +#define Q2PS_UNUSED1 (1<<23) #define Q2PSX_GUNOFFSET (1<<0) #define Q2PSX_GUNANGLES (1<<1) @@ -1254,11 +1263,14 @@ typedef struct q1usercmd_s -#define Q2SND_VOLUME (1<<0) // a qbyte -#define Q2SND_ATTENUATION (1<<1) // a qbyte -#define Q2SND_POS (1<<2) // three coordinates -#define Q2SND_ENT (1<<3) // a short 0-2: channel, 3-12: entity -#define Q2SND_OFFSET (1<<4) // a qbyte, msec offset from frame start +#define Q2SND_VOLUME (1u<<0) // a qbyte +#define Q2SND_ATTENUATION (1u<<1) // a qbyte +#define Q2SND_POS (1u<<2) // three coordinates +#define Q2SND_ENT (1u<<3) // a short 0-2: channel, 3-12: entity +#define Q2SND_OFFSET (1u<<4) // a qbyte, msec offset from frame start +#define Q2SND_LARGEIDX (1u<<5) // idx is a short +#define Q2SND_LARGEPOS (1u<<6) // float coord +#define Q2SND_EXTRABITS (1u<<7) // unused for now, reserved. #define Q2DEFAULT_SOUND_PACKET_VOLUME 1.0 #define Q2DEFAULT_SOUND_PACKET_ATTENUATION 1.0 diff --git a/engine/common/q1bsp.c b/engine/common/q1bsp.c index 7ea8240d3..a98f5169b 100644 --- a/engine/common/q1bsp.c +++ b/engine/common/q1bsp.c @@ -1683,13 +1683,14 @@ static void Q1BSP_RFindTouchedLeafs (model_t *wm, struct pvscache_s *ent, mnode_ int sides; int leafnum; - if (node->contents == Q1CONTENTS_SOLID) - return; - // add an efrag if the node is a leaf - if ( node->contents < 0) + if (node->contents < 0) { + //ignore solid leafs. this should include leaf 0 (which has no pvs info) + if (node->contents == Q1CONTENTS_SOLID) + return; + if (ent->num_leafs >= MAX_ENT_LEAFS) { ent->num_leafs = MAX_ENT_LEAFS+1; //too many. mark it as such so we can trivially accept huge mega-big brush models. diff --git a/engine/d3d/d3d11_backend.c b/engine/d3d/d3d11_backend.c index 3b4b6c235..a15611ed8 100644 --- a/engine/d3d/d3d11_backend.c +++ b/engine/d3d/d3d11_backend.c @@ -3389,19 +3389,18 @@ static void BE_SubmitMeshesPortals(batch_t **worldlist, batch_t *dynamiclist) } } -void D3D11BE_SubmitMeshes (qboolean drawworld, batch_t **blist, int first, int stop) +void D3D11BE_SubmitMeshes (batch_t **worldbatches, batch_t **blist, int first, int stop) { - model_t *model = cl.worldmodel; int i; for (i = first; i < stop; i++) { - if (drawworld) + if (worldbatches) { if (i == SHADER_SORT_PORTAL /*&& !r_noportals.ival*/ && !r_refdef.recurse) - BE_SubmitMeshesPortals(model->batches, blist[i]); + BE_SubmitMeshesPortals(worldbatches, blist[i]); - BE_SubmitMeshesSortList(model->batches[i]); + BE_SubmitMeshesSortList(worldbatches[i]); } BE_SubmitMeshesSortList(blist[i]); } @@ -3412,7 +3411,7 @@ void D3D11BE_BaseEntTextures(void) { batch_t *batches[SHADER_SORT_COUNT]; BE_GenModelBatches(batches, shaderstate.curdlight, shaderstate.mode); - D3D11BE_SubmitMeshes(false, batches, SHADER_SORT_PORTAL, SHADER_SORT_DECAL); + D3D11BE_SubmitMeshes(NULL, batches, SHADER_SORT_PORTAL, SHADER_SORT_DECAL); BE_SelectEntity(&r_worldentity); } @@ -3505,7 +3504,7 @@ void D3D11BE_DoneShadows(void) } #endif -void D3D11BE_DrawWorld (qboolean drawworld, qbyte *vis) +void D3D11BE_DrawWorld (batch_t **worldbatches, qbyte *vis) { batch_t *batches[SHADER_SORT_COUNT]; RSpeedLocals(); @@ -3556,7 +3555,7 @@ void D3D11BE_DrawWorld (qboolean drawworld, qbyte *vis) BE_SelectMode(BEM_STANDARD); RSpeedRemark(); - D3D11BE_SubmitMeshes(true, batches, SHADER_SORT_PORTAL, SHADER_SORT_DECAL); + D3D11BE_SubmitMeshes(worldbatches, batches, SHADER_SORT_PORTAL, SHADER_SORT_DECAL); RSpeedEnd(RSPEED_WORLD); #ifdef RTLIGHTS @@ -3566,13 +3565,13 @@ void D3D11BE_DrawWorld (qboolean drawworld, qbyte *vis) RSpeedEnd(RSPEED_STENCILSHADOWS); #endif - D3D11BE_SubmitMeshes(true, batches, SHADER_SORT_DECAL, SHADER_SORT_COUNT); + D3D11BE_SubmitMeshes(worldbatches, batches, SHADER_SORT_DECAL, SHADER_SORT_COUNT); } else { RSpeedRemark(); shaderstate.identitylighting = 1; - D3D11BE_SubmitMeshes(false, batches, SHADER_SORT_PORTAL, SHADER_SORT_COUNT); + D3D11BE_SubmitMeshes(NULL, batches, SHADER_SORT_PORTAL, SHADER_SORT_COUNT); RSpeedEnd(RSPEED_DRAWENTITIES); } diff --git a/engine/d3d/d3d_backend.c b/engine/d3d/d3d_backend.c index 0a425e0ad..66568b140 100644 --- a/engine/d3d/d3d_backend.c +++ b/engine/d3d/d3d_backend.c @@ -3357,19 +3357,18 @@ static void BE_SubmitMeshesPortals(batch_t **worldlist, batch_t *dynamiclist) } } -void D3D9BE_SubmitMeshes (qboolean drawworld, batch_t **blist, int first, int stop) +void D3D9BE_SubmitMeshes (batch_t **worldbatches, batch_t **blist, int first, int stop) { - model_t *model = cl.worldmodel; int i; for (i = first; i < stop; i++) { - if (drawworld) + if (worldbatches) { if (i == SHADER_SORT_PORTAL /*&& !r_noportals.ival*/ && !r_refdef.recurse) - BE_SubmitMeshesPortals(model->batches, blist[i]); + BE_SubmitMeshesPortals(worldbatches, blist[i]); - BE_SubmitMeshesSortList(model->batches[i]); + BE_SubmitMeshesSortList(worldbatches[i]); } BE_SubmitMeshesSortList(blist[i]); } @@ -3380,7 +3379,7 @@ void D3D9BE_BaseEntTextures(void) { batch_t *batches[SHADER_SORT_COUNT]; BE_GenModelBatches(batches, shaderstate.curdlight, shaderstate.mode); - D3D9BE_SubmitMeshes(false, batches, SHADER_SORT_PORTAL, SHADER_SORT_DECAL); + D3D9BE_SubmitMeshes(NULL, batches, SHADER_SORT_PORTAL, SHADER_SORT_DECAL); BE_SelectEntity(&r_worldentity); } @@ -3410,7 +3409,7 @@ void D3D9BE_RenderShadowBuffer(unsigned int numverts, IDirect3DVertexBuffer9 *vb } #endif -void D3D9BE_DrawWorld (qboolean drawworld, qbyte *vis) +void D3D9BE_DrawWorld (batch_t **worldbatches, qbyte *vis) { batch_t *batches[SHADER_SORT_COUNT]; RSpeedLocals(); @@ -3432,7 +3431,7 @@ void D3D9BE_DrawWorld (qboolean drawworld, qbyte *vis) shaderstate.curdlight = NULL; BE_GenModelBatches(batches, shaderstate.curdlight, BEM_STANDARD); - if (drawworld) + if (worldbatches) { float shaderstate_identitylighting; BE_UploadLightmaps(false); @@ -3447,7 +3446,7 @@ void D3D9BE_DrawWorld (qboolean drawworld, qbyte *vis) r_worldentity.axis[2][2] = 1; #ifdef RTLIGHTS - if (drawworld && r_shadow_realtime_world.ival) + if (worldbatches && r_shadow_realtime_world.ival) shaderstate_identitylighting = r_shadow_realtime_world_lightmaps.value; else #endif @@ -3461,7 +3460,7 @@ void D3D9BE_DrawWorld (qboolean drawworld, qbyte *vis) BE_SelectMode(BEM_STANDARD); RSpeedRemark(); - D3D9BE_SubmitMeshes(true, batches, SHADER_SORT_PORTAL, SHADER_SORT_DECAL); + D3D9BE_SubmitMeshes(worldbatches, batches, SHADER_SORT_PORTAL, SHADER_SORT_DECAL); RSpeedEnd(RSPEED_WORLD); #ifdef RTLIGHTS @@ -3476,12 +3475,12 @@ void D3D9BE_DrawWorld (qboolean drawworld, qbyte *vis) BE_SelectMode(BEM_STANDARD); - D3D9BE_SubmitMeshes(true, batches, SHADER_SORT_DECAL, SHADER_SORT_COUNT); + D3D9BE_SubmitMeshes(worldbatches, batches, SHADER_SORT_DECAL, SHADER_SORT_COUNT); } else { RSpeedRemark(); - D3D9BE_SubmitMeshes(false, batches, SHADER_SORT_PORTAL, SHADER_SORT_COUNT); + D3D9BE_SubmitMeshes(NULL, batches, SHADER_SORT_PORTAL, SHADER_SORT_COUNT); RSpeedEnd(RSPEED_DRAWENTITIES); } diff --git a/engine/d3d/vid_d3d.c b/engine/d3d/vid_d3d.c index 8b58ba3f2..e6cedc4c7 100644 --- a/engine/d3d/vid_d3d.c +++ b/engine/d3d/vid_d3d.c @@ -1183,10 +1183,10 @@ static void D3D9_SetupViewPortProjection(void) d3d9error(IDirect3DDevice9_SetTransform(pD3DDev9, D3DTS_VIEW, (D3DMATRIX*)r_refdef.m_view)); /*d3d projection matricies scale depth to 0 to 1*/ - Matrix4x4_CM_Projection_Inf(d3d_trueprojection, fov_x, fov_y, gl_mindist.value/2); + Matrix4x4_CM_Projection_Inf(d3d_trueprojection, fov_x, fov_y, bound(0.1, gl_mindist.value, 4)/2); d3d9error(IDirect3DDevice9_SetTransform(pD3DDev9, D3DTS_PROJECTION, (D3DMATRIX*)d3d_trueprojection)); /*ogl projection matricies scale depth to -1 to 1, and I would rather my code used consistant culling*/ - Matrix4x4_CM_Projection_Inf(r_refdef.m_projection, fov_x, fov_y, gl_mindist.value); + Matrix4x4_CM_Projection_Inf(r_refdef.m_projection, fov_x, fov_y, bound(0.1, gl_mindist.value, 4)); } static void (D3D9_R_RenderView) (void) diff --git a/engine/d3d/vid_d3d11.c b/engine/d3d/vid_d3d11.c index 4c8d14563..b50406a43 100644 --- a/engine/d3d/vid_d3d11.c +++ b/engine/d3d/vid_d3d11.c @@ -1324,7 +1324,6 @@ static void (D3D11_SCR_UpdateScreen) (void) if (!noworld) { - R2D_PolyBlend (); R2D_BrightenScreen(); } @@ -1434,7 +1433,7 @@ static void D3D11_SetupViewPort(void) /*view matrix*/ Matrix4x4_CM_ModelViewMatrixFromAxis(r_refdef.m_view, vpn, vright, vup, r_refdef.vieworg); - Matrix4x4_CM_Projection_Inf(r_refdef.m_projection, fov_x, fov_y, gl_mindist.value); + Matrix4x4_CM_Projection_Inf(r_refdef.m_projection, fov_x, fov_y, bound(0.1, gl_mindist.value, 4)); } static void (D3D11_R_RenderView) (void) diff --git a/engine/dotnet2005/ftequake.vcproj b/engine/dotnet2005/ftequake.vcproj index 7e22c0dd6..f36f0a293 100644 --- a/engine/dotnet2005/ftequake.vcproj +++ b/engine/dotnet2005/ftequake.vcproj @@ -1688,7 +1688,7 @@ /> prog) - { - R_DrawSkyChain (batch); + if (R_DrawSkyChain(batch)) continue; - } } else if (shaderstate.mode != BEM_FOG && shaderstate.mode != BEM_CREPUSCULAR && shaderstate.mode != BEM_WIREFRAME) continue; diff --git a/engine/gl/gl_heightmap.c b/engine/gl/gl_heightmap.c index 99a682afc..fcd969a32 100644 --- a/engine/gl/gl_heightmap.c +++ b/engine/gl/gl_heightmap.c @@ -3833,7 +3833,7 @@ static void Heightmap_Trace_Square(hmtrace_t *tr, int tx, int ty) if (!s || s->loadstate != TSLS_LOADED) { - if ((tr->hitcontentsmask & tr->hm->exteriorcontents) || s->loadstate != TSLS_FAILED) + if ((tr->hitcontentsmask & tr->hm->exteriorcontents) || (s && s->loadstate != TSLS_FAILED)) { //you're not allowed to walk into sections that have not loaded. //might as well check the entire section instead of just one tile diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c index e9dc926e5..3f9a0b866 100644 --- a/engine/gl/gl_model.c +++ b/engine/gl/gl_model.c @@ -4890,7 +4890,7 @@ SPRITES "if gl_blendsprites\n" \ "program defaultsprite\n" \ "else\n" \ - "program defaultsprite#MASK=1\n" \ + "program defaultsprite#MASK=0.666\n" \ "endif\n" \ "{\n" \ "map $diffuse\n" \ diff --git a/engine/gl/gl_model.h b/engine/gl/gl_model.h index b87727634..d81bc86c8 100644 --- a/engine/gl/gl_model.h +++ b/engine/gl/gl_model.h @@ -175,7 +175,7 @@ m*_t structures are in-memory #define EF_FULLBRIGHT (1<<9) //abslight=1 #define DPEF_FLAME (1<<10) //'onfire' #define DPEF_STARDUST (1<<11) //'showering sparks' -#define DPEF_NOSHADOW (1<<12) //doesn't cast a shadow +#define EF_NOSHADOW (1<<12) //doesn't cast a shadow #define EF_NODEPTHTEST (1<<13) //shows through walls. #define DPEF_SELECTABLE_ (1<<14) //highlights when prydoncursored #define DPEF_DOUBLESIDED_ (1<<15) //disables culling diff --git a/engine/gl/gl_rlight.c b/engine/gl/gl_rlight.c index 7d216aeec..ded783a92 100644 --- a/engine/gl/gl_rlight.c +++ b/engine/gl/gl_rlight.c @@ -150,6 +150,7 @@ DYNAMIC LIGHTS BLEND RENDERING void AddLightBlend (float r, float g, float b, float a2) { float a; + float *sw_blend = r_refdef.playerview->screentint; r = bound(0, r, 1); g = bound(0, g, 1); diff --git a/engine/gl/gl_rmain.c b/engine/gl/gl_rmain.c index b8b435e2d..ca06d39e2 100644 --- a/engine/gl/gl_rmain.c +++ b/engine/gl/gl_rmain.c @@ -499,11 +499,11 @@ void R_SetupGL (float stereooffset) // yfov = 2*atan((float)r_refdef.vrect.height/r_refdef.vrect.width)*(scr_fov.value*2)/M_PI; // MYgluPerspective (yfov, screenaspect, 4, 4096); - Matrix4x4_CM_Projection_Far(r_refdef.m_projection, fov_x, fov_y, gl_mindist.value, gl_maxdist.value); + Matrix4x4_CM_Projection_Far(r_refdef.m_projection, fov_x, fov_y, bound(0.1, gl_mindist.value, 4), gl_maxdist.value); } else { - Matrix4x4_CM_Projection_Inf(r_refdef.m_projection, fov_x, fov_y, gl_mindist.value); + Matrix4x4_CM_Projection_Inf(r_refdef.m_projection, fov_x, fov_y, bound(0.1, gl_mindist.value, 4)); } } else @@ -1407,6 +1407,7 @@ qboolean R_RenderScene_Cubemap(void) int oldfbo = -1; qboolean usefbo = true; //this appears to be a 20% speedup in my tests. static fbostate_t fbostate; //FIXME + qboolean fboreset = false; /*needs glsl*/ if (!gl_config.arb_shader_objects) @@ -1477,6 +1478,7 @@ qboolean R_RenderScene_Cubemap(void) if (ffov.value > 270) facemask |= 1<<5; /*back view*/ } + facemask = 0x3f; break; case 4: shader = R_RegisterShader("postproc_laea", SUF_NONE, @@ -1509,10 +1511,14 @@ qboolean R_RenderScene_Cubemap(void) if (sh_config.texture_non_power_of_two_pic) { - if (prect.width < prect.height) - cmapsize = prect.width; + if (usefbo) + { + cmapsize = prect.width > prect.height?prect.width:prect.height; + if (cmapsize > 4096)//sh_config.texture_maxsize) + cmapsize = 4096;//sh_config.texture_maxsize; + } else - cmapsize = prect.height; + cmapsize = prect.width < prect.height?prect.width:prect.height; } else if (!usefbo) { @@ -1540,6 +1546,13 @@ qboolean R_RenderScene_Cubemap(void) scenepp_postproc_cube = Image_CreateTexture("***fish***", NULL, IF_CUBEMAP|IF_RENDERTARGET|IF_CLAMP|IF_LINEAR); qglGenTextures(1, &scenepp_postproc_cube->num); } + else + { + qglDeleteTextures(1, &scenepp_postproc_cube->num); + scenepp_postproc_cube->num = 0; + GL_MTBind(0, GL_TEXTURE_CUBE_MAP_ARB, scenepp_postproc_cube); + qglGenTextures(1, &scenepp_postproc_cube->num); + } GL_MTBind(0, GL_TEXTURE_CUBE_MAP_ARB, scenepp_postproc_cube); for (i = 0; i < 6; i++) @@ -1550,12 +1563,14 @@ qboolean R_RenderScene_Cubemap(void) qglTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR); scenepp_postproc_cube_size = cmapsize; + + fboreset = true; } vrect = r_refdef.vrect; //save off the old vrect - r_refdef.vrect.width = (cmapsize * vid.width) / vid.pixelwidth; - r_refdef.vrect.height = (cmapsize * vid.height) / vid.pixelheight; + r_refdef.vrect.width = (cmapsize * vid.fbvwidth) / vid.fbpwidth; + r_refdef.vrect.height = (cmapsize * vid.fbvheight) / vid.fbpheight; r_refdef.vrect.x = 0; r_refdef.vrect.y = prect.y; @@ -1578,7 +1593,8 @@ qboolean R_RenderScene_Cubemap(void) if (usefbo) { - int r = GLBE_FBO_Update(&fbostate, FBO_RB_DEPTH, &scenepp_postproc_cube, 1, r_nulltex, cmapsize, cmapsize, i); + int r = GLBE_FBO_Update(&fbostate, FBO_RB_DEPTH|(fboreset?FBO_RESET:0), &scenepp_postproc_cube, 1, r_nulltex, cmapsize, cmapsize, i); + fboreset = false; if (oldfbo < 0) oldfbo = r; } @@ -1601,7 +1617,7 @@ qboolean R_RenderScene_Cubemap(void) if (!usefbo) { GL_MTBind(0, GL_TEXTURE_CUBE_MAP_ARB, scenepp_postproc_cube); - qglCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + i, 0, 0, 0, 0, vid.pixelheight - (prect.y + cmapsize), cmapsize, cmapsize); + qglCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + i, 0, 0, 0, 0, vid.fbpheight - (prect.y + cmapsize), cmapsize, cmapsize); } } @@ -1623,15 +1639,21 @@ qboolean R_RenderScene_Cubemap(void) qglLoadIdentity (); */ // draw it through the shader - if (vrect.width > vrect.height) + if (r_projection.ival == 3) + { + float saspect = .5; + float taspect = vrect.height / vrect.width * ffov.value / 90;//(0.5 * vrect.width) / vrect.height; + R2D_Image(vrect.x, vrect.y, vrect.width, vrect.height, -saspect, taspect, saspect, -taspect, shader); + } + else if (vrect.width > vrect.height) { float aspect = (0.5 * vrect.height) / vrect.width; - R2D_Image(0, 0, vid.width, vid.height, -0.5, aspect, 0.5, -aspect, shader); + R2D_Image(vrect.x, vrect.y, vrect.width, vrect.height, -0.5, aspect, 0.5, -aspect, shader); } else { float aspect = (0.5 * vrect.width) / vrect.height; - R2D_Image(0, 0, vid.width, vid.height, -aspect, 0.5, aspect, -0.5, shader); + R2D_Image(vrect.x, vrect.y, vrect.width, vrect.height, -aspect, 0.5, aspect, -0.5, shader); } //revert the matricies @@ -1719,7 +1741,7 @@ void GLR_RenderView (void) //check if we're underwater (this also limits damage from stereo wallhacks). Surf_SetupFrame(); - r_refdef.flags &= ~RDF_ALLPOSTPROC; + r_refdef.flags &= ~(RDF_ALLPOSTPROC|RDF_RENDERSCALE); if (!(r_refdef.flags & RDF_NOWORLDMODEL)) if (R_CanBloom()) @@ -1885,7 +1907,7 @@ void GLR_RenderView (void) time1 = Sys_DoubleTime (); } - if (!dofbo && !(r_refdef.flags & RDF_NOWORLDMODEL) && R_RenderScene_Cubemap()) + if (!(r_refdef.flags & RDF_NOWORLDMODEL) && R_RenderScene_Cubemap()) { } diff --git a/engine/gl/gl_shader.c b/engine/gl/gl_shader.c index 9580cfe1e..aefefb0ee 100644 --- a/engine/gl/gl_shader.c +++ b/engine/gl/gl_shader.c @@ -5047,14 +5047,15 @@ void Shader_DefaultBSPQ2(const char *shortname, shader_t *s, const void *args) "{\n" "surfaceparm nodlight\n" "skyparms - - -\n" + "surfaceparm nodlight\n" "}\n" ); } - else if (!strncmp(shortname, "warp/", 5) || !strncmp(shortname, "warp33/", 7) || !strncmp(shortname, "warp66/", 7)) + else if (Shader_FloatArgument(s, "#WARP"))//!strncmp(shortname, "warp/", 5) || !strncmp(shortname, "warp33/", 7) || !strncmp(shortname, "warp66/", 7)) { Shader_DefaultScript(shortname, s, Shader_DefaultBSPWater(s, shortname)); } - else if (!strncmp(shortname, "trans/", 6)) + else if (Shader_FloatArgument(s, "#ALPHA"))// !strncmp(shortname, "trans/", 6)) { Shader_DefaultScript(shortname, s, "{\n" diff --git a/engine/gl/gl_warp.c b/engine/gl/gl_warp.c index 91321229a..df68d98d3 100644 --- a/engine/gl/gl_warp.c +++ b/engine/gl/gl_warp.c @@ -67,7 +67,7 @@ void R_SetSky(char *skyname) GL_DrawSkyChain ================= */ -void R_DrawSkyChain (batch_t *batch) +qboolean R_DrawSkyChain (batch_t *batch) { shader_t *skyshader; texid_t *skyboxtex; @@ -77,6 +77,9 @@ void R_DrawSkyChain (batch_t *batch) else skyshader = batch->shader; + if (skyshader->prog) + return false; + if (skyshader->skydome) skyboxtex = skyshader->skydome->farbox_textures; else @@ -112,6 +115,8 @@ void R_DrawSkyChain (batch_t *batch) //you can't please them all. if (r_worldentity.model->fromgame != fg_quake3) GL_SkyForceDepth(batch); + + return true; } /* diff --git a/engine/gl/shader.h b/engine/gl/shader.h index 145b1e035..93c80a378 100644 --- a/engine/gl/shader.h +++ b/engine/gl/shader.h @@ -747,7 +747,7 @@ batch_t *D3D9BE_GetTempBatch(void); void D3D9BE_GenBrushModelVBO(model_t *mod); void D3D9BE_ClearVBO(vbo_t *vbo); void D3D9BE_UploadAllLightmaps(void); -void D3D9BE_DrawWorld (qboolean drawworld, qbyte *vis); +void D3D9BE_DrawWorld (batch_t **worldbatches, qbyte *vis); qboolean D3D9BE_LightCullModel(vec3_t org, model_t *model); void D3D9BE_SelectEntity(entity_t *ent); qboolean D3D9BE_SelectDLight(dlight_t *dl, vec3_t colour, vec3_t axis[3], unsigned int lmode); @@ -771,7 +771,7 @@ batch_t *D3D11BE_GetTempBatch(void); void D3D11BE_GenBrushModelVBO(model_t *mod); void D3D11BE_ClearVBO(vbo_t *vbo); void D3D11BE_UploadAllLightmaps(void); -void D3D11BE_DrawWorld (qboolean drawworld, qbyte *vis); +void D3D11BE_DrawWorld (batch_t **worldbatches, qbyte *vis); qboolean D3D11BE_LightCullModel(vec3_t org, model_t *model); void D3D11BE_SelectEntity(entity_t *ent); qboolean D3D11BE_SelectDLight(dlight_t *dl, vec3_t colour, vec3_t axis[3], unsigned int lmode); diff --git a/engine/partcfgs/q2part.cfg b/engine/partcfgs/q2part.cfg index 39ca83470..506bad4fa 100644 --- a/engine/partcfgs/q2part.cfg +++ b/engine/partcfgs/q2part.cfg @@ -727,4 +727,5 @@ r_part TEQ2_BOSSTPORT gravity -800 rgbf 1 1 1 scalefactor 0.8 + sound "misc/bigtele.wav" 1 0 0 0 1 } diff --git a/engine/qclib/initlib.c b/engine/qclib/initlib.c index 470f99126..2b52923eb 100644 --- a/engine/qclib/initlib.c +++ b/engine/qclib/initlib.c @@ -490,6 +490,8 @@ static void PDECL PR_Configure (pubprogfuncs_t *ppf, size_t addressable_size, in { #if defined(_WIN64) && !defined(WINRT) addressable_size = 0x80000000; //use of virtual address space rather than physical memory means we can just go crazy and use the max of 2gb. +#elif defined(FTE_TARGET_WEB) + addressable_size = 8*1024*1024; #else addressable_size = 32*1024*1024; #endif @@ -549,7 +551,7 @@ struct entvars_s *PDECL PR_entvars (pubprogfuncs_t *ppf, struct edict_s *ed) return (struct entvars_s *)edvars(ed); } -int PDECL PR_GetFuncArgCount(pubprogfuncs_t *ppf, func_t func) +pbool PDECL PR_GetFunctionInfo(pubprogfuncs_t *ppf, func_t func, int *args, int *builtinnum, char *funcname, size_t funcnamesize) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; @@ -561,13 +563,26 @@ int PDECL PR_GetFuncArgCount(pubprogfuncs_t *ppf, func_t func) fnum = (func & 0x00ffffff); if (pnum >= prinst.maxprogs || !pr_progstate[pnum].functions) - return -1; + return false; else if (fnum >= pr_progstate[pnum].progs->numfunctions) - return -1; + return false; else { f = pr_progstate[pnum].functions + fnum; - return f->numparms; + if (args) + *args = f->numparms; + if (builtinnum) + *builtinnum = -f->first_statement; + if (funcname) + { + const char *srcname = PR_StringToNative(ppf, f->s_name); + size_t nlen = strlen(srcname); + if (nlen < funcnamesize) + memcpy(funcname, srcname, nlen+1); + else + *funcname = 0; + } + return true; } } @@ -1327,7 +1342,7 @@ pubprogfuncs_t deffuncs = { QC_AddSharedVar, QC_AddSharedFieldVar, PR_RemoveProgsString, - PR_GetFuncArgCount, + PR_GetFunctionInfo, PR_GenerateStatementString, ED_FieldInfo, PR_UglyValueString, diff --git a/engine/qclib/pr_edict.c b/engine/qclib/pr_edict.c index d4e720d5e..98b9b4e8d 100644 --- a/engine/qclib/pr_edict.c +++ b/engine/qclib/pr_edict.c @@ -3232,7 +3232,7 @@ retry: PR_CleanUpStatements16(progfuncs, st16, hexencalling); break; - case PST_KKQWSV: //24 sucks. Guess why. + case PST_KKQWSV: case PST_FTE32: for (i=0 ; inumstatements ; i++) { diff --git a/engine/qclib/pr_exec.c b/engine/qclib/pr_exec.c index 18cce498a..fea01d1b8 100644 --- a/engine/qclib/pr_exec.c +++ b/engine/qclib/pr_exec.c @@ -1942,8 +1942,20 @@ pbool PDECL PR_GetBuiltinCallInfo (pubprogfuncs_t *ppf, int *builtinnum, char *f int op; int a; const char *fname; - op = pr_statements16[st].op; - a = pr_statements16[st].a; + + switch (current_progstate->structtype) + { + case PST_DEFAULT: + case PST_QTEST: + op = pr_statements16[st].op; + a = pr_statements16[st].a; + break; + case PST_KKQWSV: + case PST_FTE32: + op = pr_statements32[st].op; + a = pr_statements32[st].a; + break; + } *builtinnum = 0; *function = 0; diff --git a/engine/qclib/progsint.h b/engine/qclib/progsint.h index 481a9d1de..521f45d65 100644 --- a/engine/qclib/progsint.h +++ b/engine/qclib/progsint.h @@ -292,7 +292,7 @@ typedef enum PST_DEFAULT,//everything 16bit PST_FTE32, //everything 32bit PST_KKQWSV, //32bit statements, 16bit globaldefs. NO SAVED GAMES. - PST_QTEST, //16bit statements, 32bit globaldefs(differences converted on load) + PST_QTEST, //16bit statements, 32bit globaldefs(other differences converted on load) } progstructtype_t; #ifndef COMPILER diff --git a/engine/qclib/progslib.h b/engine/qclib/progslib.h index 00f85f9dc..1001173b9 100644 --- a/engine/qclib/progslib.h +++ b/engine/qclib/progslib.h @@ -176,7 +176,7 @@ struct pubprogfuncs_s void (PDECL *AddSharedVar) (pubprogfuncs_t *progfuncs, int start, int size); void (PDECL *AddSharedFieldVar) (pubprogfuncs_t *progfuncs, int num, char *relstringtable); char *(PDECL *RemoveProgsString) (pubprogfuncs_t *progfuncs, string_t str); - int (PDECL *GetFuncArgCount) (pubprogfuncs_t *progfuncs, func_t func); //ask how many args a function is meant to have + pbool (PDECL *GetFunctionInfo) (pubprogfuncs_t *progfuncs, func_t func, int *argcount, int *builtinnum, char *funcname, size_t funcnamesize); //queries the interesting info from a function def void (PDECL *GenerateStatementString) (pubprogfuncs_t *progfuncs, int statementnum, char *out, int outlen); //disassembles a specific statement. for debugging reports. fdef_t *(PDECL *FieldInfo) (pubprogfuncs_t *progfuncs, unsigned int *count); char *(PDECL *UglyValueString) (pubprogfuncs_t *progfuncs, etype_t type, union eval_s *val); diff --git a/engine/qclib/qcc.h b/engine/qclib/qcc.h index 769b603f1..54eba760b 100644 --- a/engine/qclib/qcc.h +++ b/engine/qclib/qcc.h @@ -976,6 +976,7 @@ extern int maxtypeinfos; extern int ForcedCRC; extern pbool defaultnoref; +extern pbool defaultnosave; extern pbool defaultstatic; extern int *qcc_tempofs; diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index 149098f0d..81045bffc 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -2274,7 +2274,7 @@ QCC_sref_t QCC_PR_StatementFlags ( QCC_opcode_t *op, QCC_sref_t var_a, QCC_sref_ case OP_MUL_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; - return QCC_MakeIntConst(eval_a->_float * eval_b->_int); + return QCC_MakeFloatConst(eval_a->_float * eval_b->_int); case OP_DIV_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; @@ -12547,8 +12547,8 @@ void QCC_PR_ParseEnum(pbool flags) const char *name; QCC_sref_t sref; pbool wantint = false; - int iv = 0; - float fv = 0; + int iv = flags?1:0; + float fv = iv; if (QCC_PR_CheckKeyword(keyword_integer, "integer") || QCC_PR_CheckKeyword(keyword_int, "int")) wantint = true; @@ -12657,7 +12657,7 @@ void QCC_PR_ParseDefs (char *classname) pbool isconstant = false; pbool isvar = false; pbool noref = defaultnoref; - pbool nosave = false; + pbool nosave = defaultnosave; pbool allocatenew = true; pbool inlinefunction = false; pbool allowinline = false; @@ -13273,7 +13273,7 @@ void QCC_PR_ParseDefs (char *classname) else { if (type->type == ev_function) - isconstant = !isvar; + isconstant |= !isvar && !pr_scope; if (dostrip) { diff --git a/engine/qclib/qcc_pr_lex.c b/engine/qclib/qcc_pr_lex.c index 42fe9fef4..58d996b1f 100644 --- a/engine/qclib/qcc_pr_lex.c +++ b/engine/qclib/qcc_pr_lex.c @@ -239,6 +239,7 @@ void QCC_FindBestInclude(char *newfile, char *currentfile, char *rootpath, pbool } pbool defaultnoref; +pbool defaultnosave; pbool defaultstatic; int ForcedCRC; int QCC_PR_LexInteger (void); @@ -955,13 +956,11 @@ pbool QCC_PR_Precompiler(void) ForcedCRC = atoi(msg); } else if (!QC_strcasecmp(qcc_token, "noref")) - { defaultnoref = !!atoi(msg); - } + else if (!QC_strcasecmp(qcc_token, "nosave")) + defaultnosave = !!atoi(msg); else if (!QC_strcasecmp(qcc_token, "defaultstatic")) - { defaultstatic = !!atoi(msg); - } else if (!QC_strcasecmp(qcc_token, "autoproto")) { if (!autoprototyped) diff --git a/engine/qclib/qccgui.c b/engine/qclib/qccgui.c index dd7871cd1..d0fe26de5 100644 --- a/engine/qclib/qccgui.c +++ b/engine/qclib/qccgui.c @@ -5022,6 +5022,8 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin } } + InitCommonControls(); + if (!fl_acc && !*progssrcname) { strcpy(progssrcname, "preprogs.src"); @@ -5101,7 +5103,6 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin return 0; } - InitCommonControls(); /* outputbox=CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", diff --git a/engine/qclib/qccguistuff.c b/engine/qclib/qccguistuff.c index 8174ede79..7f29a5a5b 100644 --- a/engine/qclib/qccguistuff.c +++ b/engine/qclib/qccguistuff.c @@ -340,10 +340,10 @@ void GUI_LoadConfig(void) QC_strlcpy(enginebasedir, GUI_ParseInPlace(&str), sizeof(enginebasedir)); else if (!stricmp(token, "engineargs")) QC_strlcpy(enginecommandline, GUI_ParseInPlace(&str), sizeof(enginecommandline)); - else if (!stricmp(token, "srcfile")) - QC_strlcpy(progssrcname, GUI_ParseInPlace(&str), sizeof(progssrcname)); - else if (!stricmp(token, "src")) - QC_strlcpy(progssrcdir, GUI_ParseInPlace(&str), sizeof(progssrcdir)); +// else if (!stricmp(token, "srcfile")) +// QC_strlcpy(progssrcname, GUI_ParseInPlace(&str), sizeof(progssrcname)); +// else if (!stricmp(token, "src")) +// QC_strlcpy(progssrcdir, GUI_ParseInPlace(&str), sizeof(progssrcdir)); else if (!stricmp(token, "parameters")) QC_strlcpy(parameters, GUI_ParseInPlace(&str), sizeof(parameters)); diff --git a/engine/qclib/qccmain.c b/engine/qclib/qccmain.c index bf10b3d97..5c73142c6 100644 --- a/engine/qclib/qccmain.c +++ b/engine/qclib/qccmain.c @@ -1435,7 +1435,7 @@ pbool QCC_WriteData (int crc) if (!precache_model[i].used) dupewarncount+=QCC_PR_Warning(WARN_EXTRAPRECACHE, precache_model[i].filename, precache_model[i].fileline, (dupewarncount>10&&!verbose)?NULL:"Model \"%s\" was precached but not directly used%s", precache_model[i].name, dupewarncount?"":" (annotate the usage with the used_model intrinsic to silence this warning)"); else if (!precache_model[i].block) - dupewarncount+=QCC_PR_Warning(WARN_NOTPRECACHED, precache_model[i].filename, precache_model[i].fileline, (dupewarncount>10&&!verbose)?NULL:"Model \"%s\" was used but not precached", precache_model[i].name); + dupewarncount+=QCC_PR_Warning(WARN_NOTPRECACHED, precache_model[i].filename, precache_model[i].fileline, (dupewarncount>10&&!verbose)?NULL:"Model \"%s\" was used but not directly precached", precache_model[i].name); } for (i = 0; i < numsounds; i++) @@ -1443,7 +1443,7 @@ pbool QCC_WriteData (int crc) if (!precache_sound[i].used) dupewarncount+=QCC_PR_Warning(WARN_EXTRAPRECACHE, precache_sound[i].filename, precache_sound[i].fileline, (dupewarncount>10&&!verbose)?NULL:"Sound \"%s\" was precached but not directly used", precache_sound[i].name, dupewarncount?"":" (annotate the usage with the used_sound intrinsic to silence this warning)"); else if (!precache_sound[i].block) - dupewarncount+=QCC_PR_Warning(WARN_NOTPRECACHED, precache_sound[i].filename, precache_sound[i].fileline, (dupewarncount>10&&!verbose)?NULL:"Sound \"%s\" was used but not precached", precache_sound[i].name); + dupewarncount+=QCC_PR_Warning(WARN_NOTPRECACHED, precache_sound[i].filename, precache_sound[i].fileline, (dupewarncount>10&&!verbose)?NULL:"Sound \"%s\" was used but not directly precached", precache_sound[i].name); } if (dupewarncount > 10 && !verbose) diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 095bb52d2..0fe7c9161 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -227,6 +227,13 @@ void PDECL ED_Spawned (struct edict_s *ent, int loading) ent->xv->Version = sv.csqcentversion[ent->entnum]; ent->xv->uniquespawnid = sv.csqcentversion[ent->entnum]; + + if (!ent->baseline.number) + { //make sure it has a valid baseline + extern entity_state_t nullentitystate; + memcpy(&ent->baseline, &nullentitystate, sizeof(ent->baseline)); + ent->baseline.number = ent->entnum; + } } } @@ -2094,7 +2101,7 @@ static void SV_Effect(vec3_t org, int mdlidx, int startframe, int endframe, int #endif } - SV_Multicast(org, MULTICAST_PVS); + SV_MulticastProtExt (org, MULTICAST_PVS, pr_global_struct->dimension_send, 0, 0); } #ifdef HEXEN2 @@ -2848,7 +2855,7 @@ static void QCBUILTIN PF_te_blooddp (pubprogfuncs_t *prinst, globalvars_t *pr_gl MSG_WriteCoord (&sv.multicast, org[0]); MSG_WriteCoord (&sv.multicast, org[1]); MSG_WriteCoord (&sv.multicast, org[2]); - SV_Multicast(org, MULTICAST_PVS); + SV_MulticastProtExt (org, MULTICAST_PVS, pr_global_struct->dimension_send, 0, 0); } #ifdef HEXEN2 @@ -3117,9 +3124,9 @@ static void QCBUILTIN PF_pointsound(pubprogfuncs_t *prinst, struct globalvars_s } //an evil one from telejano. +#ifndef SERVERONLY static void QCBUILTIN PF_LocalSound(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { -#ifndef SERVERONLY sfx_t *sfx; const char * s = PR_GetStringOfs(prinst, OFS_PARM0); @@ -3131,8 +3138,10 @@ static void QCBUILTIN PF_LocalSound(pubprogfuncs_t *prinst, struct globalvars_s if ((sfx = S_PrecacheSound(s))) S_StartSound(cl.playerview[0].playernum, chan, sfx, cl.playerview[0].simorg, vol, 0.0, 0, 0, CF_NOSPACIALISE); } -#endif }; +#else +#define PF_LocalSound PF_Fixme +#endif static void set_trace_globals(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals, trace_t *trace) { @@ -3768,9 +3777,9 @@ void QCBUILTIN PF_sv_particleeffectnum(pubprogfuncs_t *prinst, struct globalvars for (i=1 ; idimension_send, 0, 0); } static void QCBUILTIN PF_Ignore(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) @@ -6136,6 +6145,33 @@ static void QCBUILTIN PF_checkextension (pubprogfuncs_t *prinst, struct globalva G_FLOAT(OFS_RETURN) = false; } +static void QCBUILTIN PF_checkbuiltin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + func_t funcref = G_INT(OFS_PARM0); + char *funcname = NULL; + int args; + int builtinno; + if (prinst->GetFunctionInfo(prinst, funcref, &args, &builtinno, funcname, sizeof(funcname))) + { //qc defines the function at least. nothing weird there... + if (builtinno > 0 && builtinno < prinst->parms->numglobalbuiltins) + { + if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme) + G_FLOAT(OFS_RETURN) = false; //the builtin with that number isn't defined. + else + { + G_FLOAT(OFS_RETURN) = true; //its defined, within the sane range, mapped, everything. all looks good. + //we should probably go through the available builtins and validate that the qc's name matches what would be expected + //this is really intended more for builtins defined as #0 though, in such cases, mismatched assumptions are impossible. + } + } + else + G_FLOAT(OFS_RETURN) = false; //not a valid builtin (#0 builtins get remapped according to the function name) + } + else + { //not valid somehow. + G_FLOAT(OFS_RETURN) = false; + } +} static void QCBUILTIN PF_builtinsupported (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -7699,7 +7735,7 @@ static void QCBUILTIN PF_h2rain_go(pubprogfuncs_t *prinst, struct globalvars_s * MSG_WriteShort(&sv.multicast, min(count, 65535)); // colour MSG_WriteByte(&sv.multicast, (int)colour&0xff); - SV_Multicast(NULL, MULTICAST_ALL); + SV_MulticastProtExt (NULL, MULTICAST_ALL, pr_global_struct->dimension_send, 0, 0); } static void QCBUILTIN PF_h2StopSound(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) @@ -8290,7 +8326,7 @@ static void QCBUILTIN PF_te_spark(pubprogfuncs_t *prinst, struct globalvars_s *p // count MSG_WriteByte(&sv.nqmulticast, bound(0, (int) G_FLOAT(OFS_PARM2), 255)); #endif - SV_Multicast(org, MULTICAST_PVS); + SV_MulticastProtExt (org, MULTICAST_PVS, pr_global_struct->dimension_send, 0, 0); } // #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) @@ -8338,7 +8374,7 @@ static void QCBUILTIN PF_te_customflash(pubprogfuncs_t *prinst, struct globalvar MSG_WriteByte(&sv.nqmulticast, bound(0, G_VECTOR(OFS_PARM3)[1] * 255, 255)); MSG_WriteByte(&sv.nqmulticast, bound(0, G_VECTOR(OFS_PARM3)[2] * 255, 255)); #endif - SV_Multicast(org, MULTICAST_PVS); + SV_MulticastProtExt (org, MULTICAST_PVS, pr_global_struct->dimension_send, 0, 0); } //#408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) @@ -8395,7 +8431,7 @@ static void QCBUILTIN PF_te_particlecube(pubprogfuncs_t *prinst, struct globalva VectorAdd(min, max, org); VectorScale(org, 0.5, org); - SV_Multicast(org, MULTICAST_PVS); + SV_MulticastProtExt (org, MULTICAST_PVS, pr_global_struct->dimension_send, 0, 0); } static void QCBUILTIN PF_te_explosionrgb(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) @@ -8425,7 +8461,7 @@ static void QCBUILTIN PF_te_explosionrgb(pubprogfuncs_t *prinst, struct globalva MSG_WriteByte(&sv.nqmulticast, bound(0, (int) (colour[1] * 255), 255)); MSG_WriteByte(&sv.nqmulticast, bound(0, (int) (colour[2] * 255), 255)); #endif - SV_Multicast(org, MULTICAST_PVS); + SV_MulticastProtExt (org, MULTICAST_PVS, pr_global_struct->dimension_send, 0, 0); } static void QCBUILTIN PF_te_particlerain(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) @@ -8479,7 +8515,7 @@ static void QCBUILTIN PF_te_particlerain(pubprogfuncs_t *prinst, struct globalva MSG_WriteByte(&sv.nqmulticast, colour); #endif - SV_Multicast(NULL, MULTICAST_ALL); + SV_MulticastProtExt (NULL, MULTICAST_ALL, pr_global_struct->dimension_send, 0, 0); } static void QCBUILTIN PF_te_particlesnow(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) @@ -8533,7 +8569,7 @@ static void QCBUILTIN PF_te_particlesnow(pubprogfuncs_t *prinst, struct globalva MSG_WriteByte(&sv.nqmulticast, colour); #endif - SV_Multicast(NULL, MULTICAST_ALL); + SV_MulticastProtExt (NULL, MULTICAST_ALL, pr_global_struct->dimension_send, 0, 0); } // #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) @@ -8580,7 +8616,7 @@ static void QCBUILTIN PF_te_bloodshower(pubprogfuncs_t *prinst, struct globalvar VectorAdd(min, max, org); VectorScale(org, 0.5, org); - SV_Multicast(org, MULTICAST_PVS); //fixme: should this be phs instead? + SV_MulticastProtExt (org, MULTICAST_PVS, pr_global_struct->dimension_send, 0, 0); //fixme: should this be phs instead? } //DP_SV_EFFECT @@ -9706,10 +9742,10 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"logarithm", PF_Logarithm, 0, 0, 0, 0, D("float(float v, optional float base)", "Determines the logarithm of the input value according to the specified base. This can be used to calculate how much something was shifted by.")}, {"tj_cvar_string", PF_cvar_string, 0, 0, 0, 97, D("string(string cvarname)",NULL), true}, //telejano //DP_QC_FINDFLOAT - {"findfloat", PF_FindFloat, 0, 0, 0, 98, D("entity(entity start, .float fld, float match)", "Equivelent to the find builtin, but instead of comparing strings, this builtin compares floats. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value.\nworld is returned when there are no more entities.")}, // #98 (DP_QC_FINDFLOAT) - {"findentity", PF_FindFloat, 0, 0, 0, 98, D("entity(entity start, .entity fld, entity match)", "Equivelent to the find builtin, but instead of comparing strings, this builtin compares entities. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value.\nworld is returned when there are no more entities.")}, // #98 (DP_QC_FINDFLOAT) + {"findfloat", PF_FindFloat, 0, 0, 0, 98, D("#define findentity findfloat\nentity(entity start, .__variant fld, __variant match)", "Equivelent to the find builtin, but instead of comparing strings contents, this builtin compares the raw values. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value.\nworld is returned when there are no more entities.")}, // #98 (DP_QC_FINDFLOAT) {"checkextension", PF_checkextension, 99, 99, 0, 99, D("float(string extname)", "Checks for an extension by its name (eg: checkextension(\"FRIK_FILE\") says that its okay to go ahead and use strcat).\nUse cvar(\"pr_checkextension\") to see if this builtin exists.")}, // #99 //darkplaces system - query a string to see if the mod supports X Y and Z. + {"checkbuiltin", PF_checkbuiltin, 0, 0, 0, 0, D("float(__variant funcref)", "Checks to see if the specified builtin is supported/mapped. This is intended as a way to check for #0 functions, allowing for simple single-builtin functions.")}, {"builtin_find", PF_builtinsupported,100, 100, 0, 100, D("float(string builtinname)", "Looks to see if the named builtin is valid, and returns the builtin number it exists at.")}, // #100 //per builtin system. {"anglemod", PF_anglemod, 0, 0, 0, 102, "float(float value)"}, {"qsg_cvar_string", PF_cvar_string, 0, 0, 0, 103, D("string(string cvarname)","An old/legacy equivelent of more recent/common builtins in order to read a cvar's string value."), true}, @@ -9843,8 +9879,8 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"sqlescape", PF_sqlescape, 0, 0, 0, 256, "string(float serveridx, string data)"}, // sqlescape (FTE_SQL) {"sqlversion", PF_sqlversion, 0, 0, 0, 257, "string(float serveridx)"}, // sqlversion (FTE_SQL) {"sqlreadfloat", PF_sqlreadfloat, 0, 0, 0, 258, "float(float serveridx, float queryidx, float row, float column)"}, // sqlreadfloat (FTE_SQL) - {"sqlreadblob", PF_sqlreadblob, 0, 0, 0, 0, "int(float serveridx, float queryidx, float row, float column, _variant *ptr, int maxsize)"}, - {"sqlescapeblob", PF_sqlescapeblob, 0, 0, 0, 0, "string(float serveridx, _variant *ptr, int maxsize)"}, + {"sqlreadblob", PF_sqlreadblob, 0, 0, 0, 0, "int(float serveridx, float queryidx, float row, float column, __variant *ptr, int maxsize)"}, + {"sqlescapeblob", PF_sqlescapeblob, 0, 0, 0, 0, "string(float serveridx, __variant *ptr, int maxsize)"}, {"stoi", PF_stoi, 0, 0, 0, 259, D("int(string)", "Converts the given string into a true integer. Base 8, 10, or 16 is determined based upon the format of the string.")}, {"itos", PF_itos, 0, 0, 0, 260, D("string(int)", "Converts the passed true integer into a base10 string.")}, @@ -10573,7 +10609,7 @@ void PR_ResetBuiltins(progstype_t type) //fix all nulls to PF_FIXME and add any void PR_SVExtensionList_f(void) { - int i; + int i, j; int ebi; int bi; lh_extension_t *extlist; @@ -10630,6 +10666,8 @@ void PR_SVExtensionList_f(void) { for (bi = 0; BuiltinList[bi].name; bi++) { + if (BuiltinList[bi].bifunc == PF_Fixme) + continue; //this builtin is unusable in ssqc. some of them are listed because of menuqc if (!strcmp(BuiltinList[bi].name, extlist[i].builtinnames[ebi])) break; } @@ -10650,7 +10688,17 @@ void PR_SVExtensionList_f(void) else { if (showflags & SHOW_NOTACTIVEEXT) + { Con_Printf("^4%s was overridden (builtin: %s#%i)\n", extlist[i].name, BuiltinList[bi].name, BuiltinList[bi].ebfsnum); + for (j = 0; BuiltinList[j].name; j++) + { + if (BuiltinList[j].bifunc == pr_builtin[BuiltinList[bi].ebfsnum]) + { + Con_Printf("^4%s is currently %s (#%i)\n", extlist[i].name, BuiltinList[j].name, BuiltinList[j].ebfsnum); + break; + } + } + } } break; } @@ -10659,10 +10707,20 @@ void PR_SVExtensionList_f(void) { if (showflags & SHOW_ACTIVEEXT) { - if (!extlist[i].numbuiltins) - Con_Printf("%s is supported\n", extlist[i].name); - else - Con_Printf("%s is currently active\n", extlist[i].name); + if (extlist[i].description) + { + if (!extlist[i].numbuiltins) + Con_Printf("^[%s\\tip\\%s^] is supported\n", extlist[i].name, extlist[i].description); + else + Con_Printf("^[%s\\tip\\%s^] is currently active\n", extlist[i].name, extlist[i].description); + } + else + { + if (!extlist[i].numbuiltins) + Con_Printf("%s is supported\n", extlist[i].name); + else + Con_Printf("%s is currently active\n", extlist[i].name); + } } } } @@ -11131,12 +11189,13 @@ void PR_DumpPlatform_f(void) {"CHAN_VOICE", "const float", QW|NQ|CS, NULL, CHAN_VOICE}, {"CHAN_ITEM", "const float", QW|NQ|CS, NULL, CHAN_ITEM}, {"CHAN_BODY", "const float", QW|NQ|CS, NULL, CHAN_BODY}, - {"CHANF_RELIABLE", "const float", QW, NULL, 8}, + {"CHANF_RELIABLE", "const float", QW, "Only valid if the flags argument is not specified. The sound will be sent reliably, which is important if it is intended to replace looping sounds on doors etc.", 8}, {"SOUNDFLAG_RELIABLE", "const float", QW|NQ, "The sound will be sent reliably, and without regard to phs.", CF_RELIABLE}, {"SOUNDFLAG_ABSVOLUME", "const float", /*QW|NQ|*/CS,"The sample's volume is not scaled by the volume cvar. Use with caution", CF_ABSVOLUME}, - {"SOUNDFLAG_FORCELOOP", "const float", /*QW|NQ|*/CS,"The sound will restart once it reaches the end of the sample.", CF_FORCELOOP}, + {"SOUNDFLAG_FORCELOOP", "const float", QW|NQ|CS,"The sound will restart once it reaches the end of the sample.", CF_FORCELOOP}, {"SOUNDFLAG_NOSPACIALISE", "const float", /*QW|NQ|*/CS,"The different audio channels are played at the same volume regardless of which way the player is facing, without needing to use 0 attenuation.", CF_NOSPACIALISE}, + {"SOUNDFLAG_UNICAST", "const float", QW|NQ, "The sound will be heard only by the player specified by msg_entity.", CF_UNICAST}, {"ATTN_NONE", "const float", QW|NQ|CS, "Sounds with this attenuation can be heard throughout the map", ATTN_NONE}, {"ATTN_NORM", "const float", QW|NQ|CS, "Standard attenuation", ATTN_NORM}, @@ -11274,6 +11333,7 @@ void PR_DumpPlatform_f(void) {"EF_RED", "const float", QW|NQ|CS, NULL, EF_RED}, {"EF_GREEN", "const float", QW|NQ|CS, NULL, EF_GREEN}, {"EF_FULLBRIGHT", "const float", QW|NQ|CS, NULL, EF_FULLBRIGHT}, + {"EF_NOSHADOW", "const float", QW|NQ|CS, NULL, EF_NOSHADOW}, {"EF_NODEPTHTEST", "const float", QW|NQ|CS, NULL, EF_NODEPTHTEST}, {"EF_NOMODELFLAGS", "const float", QW|NQ, "Surpresses the normal flags specified in the model.", EF_NOMODELFLAGS}, diff --git a/engine/server/q2game.h b/engine/server/q2game.h index fad4bebe7..c56535d46 100644 --- a/engine/server/q2game.h +++ b/engine/server/q2game.h @@ -182,10 +182,10 @@ struct q2edict_s typedef struct { // special messages - void (VARGS *bprintf) (int printlevel, char *fmt, ...) LIKEPRINTF(2); - void (VARGS *dprintf) (char *fmt, ...) LIKEPRINTF(1); - void (VARGS *cprintf) (q2edict_t *ent, int printlevel, char *fmt, ...) LIKEPRINTF(3); - void (VARGS *centerprintf) (q2edict_t *ent, char *fmt, ...) LIKEPRINTF(2); + void (VARGS *bprintf) (int printlevel, const char *fmt, ...) LIKEPRINTF(2); + void (VARGS *dprintf) (const char *fmt, ...) LIKEPRINTF(1); + void (VARGS *cprintf) (q2edict_t *ent, int printlevel, const char *fmt, ...) LIKEPRINTF(3); + void (VARGS *centerprintf) (q2edict_t *ent, const char *fmt, ...) LIKEPRINTF(2); void (VARGS *sound) (q2edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs); void (VARGS *positioned_sound) (vec3_t origin, q2edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs); @@ -193,16 +193,16 @@ typedef struct // and misc data like the sky definition and cdtrack. // All of the current configstrings are sent to clients when // they connect, and changes are sent to all connected clients. - void (VARGS *configstring) (int num, char *string); + void (VARGS *configstring) (int num, const char *string); - void (VARGS *error) (char *fmt, ...) LIKEPRINTF(1); + void (VARGS *error) (const char *fmt, ...) LIKEPRINTF(1); // the *index functions create configstrings and some internal server state - int (VARGS *modelindex) (char *name); - int (VARGS *soundindex) (char *name); - int (VARGS *imageindex) (char *name); + int (VARGS *modelindex) (const char *name); + int (VARGS *soundindex) (const char *name); + int (VARGS *imageindex) (const char *name); - void (VARGS *setmodel) (q2edict_t *ent, char *name); + void (VARGS *setmodel) (q2edict_t *ent, const char *name); // collision detection q2trace_t (VARGS *trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, q2edict_t *passent, int contentmask); @@ -228,7 +228,7 @@ typedef struct void (VARGS *WriteShort) (int c); void (VARGS *WriteLong) (int c); void (VARGS *WriteFloat) (float f); - void (VARGS *WriteString) (char *s); + void (VARGS *WriteString) (const char *s); void (VARGS *WritePosition) (vec3_t pos); // some fractional bits void (VARGS *WriteDir) (vec3_t pos); // single byte encoded, very coarse void (VARGS *WriteAngle) (float f); @@ -239,9 +239,9 @@ typedef struct void (VARGS *FreeTags) (int tag); // console variable interaction - cvar_t *(VARGS *cvar) (char *var_name, char *value, int flags); - cvar_t *(VARGS *cvar_set) (char *var_name, char *value); - cvar_t *(VARGS *cvar_forceset) (char *var_name, char *value); + cvar_t *(VARGS *cvar) (const char *var_name, const char *value, int flags); + cvar_t *(VARGS *cvar_set) (const char *var_name, const char *value); + cvar_t *(VARGS *cvar_forceset) (const char *var_name, const char *value); // ClientCommand and ServerCommand parameter access int (VARGS *argc) (void); @@ -250,7 +250,7 @@ typedef struct // add commands to the server console as if they were typed in // for map changing, etc - void (VARGS *AddCommandString) (char *text); + void (VARGS *AddCommandString) (const char *text); void (VARGS *DebugGraph) (float value, int color); } game_import_t; @@ -269,19 +269,19 @@ typedef struct void (VARGS *Shutdown) (void); // each new level entered will cause a call to SpawnEntities - void (VARGS *SpawnEntities) (char *mapname, char *entstring, char *spawnpoint); + void (VARGS *SpawnEntities) (const char *mapname, const char *entstring, const char *spawnpoint); // Read/Write Game is for storing persistant cross level information // about the world state and the clients. // WriteGame is called every time a level is exited. // ReadGame is called on a loadgame. - void (VARGS *WriteGame) (char *filename, qboolean autosave); - void (VARGS *ReadGame) (char *filename); + void (VARGS *WriteGame) (const char *filename, qboolean autosave); + void (VARGS *ReadGame) (const char *filename); // ReadLevel is called after the default map information has been // loaded with SpawnEntities - void (VARGS *WriteLevel) (char *filename); - void (VARGS *ReadLevel) (char *filename); + void (VARGS *WriteLevel) (const char *filename); + void (VARGS *ReadLevel) (const char *filename); qboolean (VARGS *ClientConnect) (q2edict_t *ent, char *userinfo); void (VARGS *ClientBegin) (q2edict_t *ent); diff --git a/engine/server/savegame.c b/engine/server/savegame.c index 482f39296..0aa4a1690 100644 --- a/engine/server/savegame.c +++ b/engine/server/savegame.c @@ -1,4 +1,5 @@ #include "quakedef.h" +#include "pr_common.h" #ifndef CLIENTONLY @@ -94,6 +95,7 @@ void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) char *file; char *modelnames[MAX_PRECACHE_MODELS]; + char *soundnames[MAX_PRECACHE_SOUNDS]; if (version != 667 && version != 5 && version != 6) //5 for NQ, 6 for ZQ/FQ { @@ -265,6 +267,15 @@ void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) } modelnames[i] = Z_StrDup(sv.strings.model_precache[i]); } + for (i = 1; i < MAX_PRECACHE_SOUNDS; i++) + { + if (!sv.strings.sound_precache[i]) + { + soundnames[i] = NULL; + break; + } + soundnames[i] = Z_StrDup(sv.strings.sound_precache[i]); + } // load the edicts out of the savegame file // the rest of the file is sent directly to the progs engine. @@ -288,6 +299,14 @@ void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) if (!modelnames[i]) break; sv.strings.model_precache[i] = PR_AddString(svprogfuncs, modelnames[i], 0, false); + Z_Free(modelnames[i]); + } + for (i = 1; i < MAX_PRECACHE_SOUNDS; i++) + { + if (!soundnames[i]) + break; + sv.strings.sound_precache[i] = PR_AddString(svprogfuncs, soundnames[i], 0, false); + Z_Free(soundnames[i]); } filepos = VFS_TELL(f); @@ -521,14 +540,14 @@ void LoadModelsAndSounds(vfsfile_t *f) for (; i < MAX_PRECACHE_MODELS; i++) sv.strings.model_precache[i] = NULL; -// sv.sound_precache[0] = PR_AddString(svprogfuncs, "", 0); + sv.strings.sound_precache[0] = PR_AddString(svprogfuncs, "", 0, false); for (i=1; i < MAX_PRECACHE_SOUNDS; i++) { VFS_GETS(f, str, sizeof(str)); if (!*str) break; -// sv.sound_precache[i] = PR_AddString(svprogfuncs, str, 0); + sv.strings.sound_precache[i] = PR_AddString(svprogfuncs, str, 0, false); } if (i == MAX_PRECACHE_SOUNDS) { @@ -537,11 +556,11 @@ void LoadModelsAndSounds(vfsfile_t *f) SV_Error("Too many sound precaches in loadgame cache"); } for (; i < MAX_PRECACHE_SOUNDS; i++) - *sv.strings.sound_precache[i] = 0; + sv.strings.sound_precache[i] = NULL; } /*ignoreplayers - says to not tell gamecode (a loadgame rather than a level change)*/ -qboolean SV_LoadLevelCache(char *savename, char *level, char *startspot, qboolean isloadgame) +qboolean SV_LoadLevelCache(const char *savename, const char *level, const char *startspot, qboolean isloadgame) { eval_t *eval, *e2; @@ -847,7 +866,7 @@ qboolean SV_LoadLevelCache(char *savename, char *level, char *startspot, qboolea return true; //yay } -void SV_SaveLevelCache(char *savedir, qboolean dontharmgame) +void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) { size_t len; char *s; @@ -887,10 +906,9 @@ void SV_SaveLevelCache(char *savedir, qboolean dontharmgame) } if (savedir) - Q_snprintfz (name, sizeof(name), "saves/%s/%s", savedir, svs.name); + Q_snprintfz (name, sizeof(name), "saves/%s/%s.lvc", savedir, svs.name); else - Q_snprintfz (name, sizeof(name), "saves/%s", svs.name); - COM_DefaultExtension (name, ".lvc", sizeof(name)); + Q_snprintfz (name, sizeof(name), "saves/%s.lvc", svs.name); FS_CreatePath(name, FS_GAMEONLY); @@ -985,15 +1003,15 @@ void SV_SaveLevelCache(char *savedir, qboolean dontharmgame) for (i=0 ; istate < cs_spawned && !cl->istobeloaded) //don't save if they are still connecting { VFS_PRINTF(f, "\n"); @@ -1194,7 +1223,8 @@ void SV_Savegame (char *savename) #ifndef SERVERONLY //try to save screenshots automagically. - savefilename = va("saves/%s/screeny.%s", savename, scr_sshot_type.string); + Q_snprintfz(comment, sizeof(comment), "saves/%s/screeny.%s", savename, "tga");//scr_sshot_type.string); + savefilename = comment; FS_Remove(savefilename, FS_GAMEONLY); if (cls.state == ca_active && qrenderer > QR_NONE) { @@ -1255,7 +1285,7 @@ void SV_Savegame (char *savename) char syspath[256]; if (!FS_NativePath(va("saves/%s/game.gsv", savename), FS_GAMEONLY, syspath, sizeof(syspath))) return; - ge->WriteGame(syspath, false); + ge->WriteGame(syspath, mapchange); FS_FlushFSHashReally(true); } else @@ -1269,10 +1299,56 @@ void SV_Savegame (char *savename) void SV_Savegame_f (void) { if (Cmd_Argc() <= 2) - SV_Savegame(Cmd_Argv(1)); + SV_Savegame(Cmd_Argv(1), false); else Con_Printf("%s: invalid number of arguments\n", Cmd_Argv(0)); } + +cvar_t sv_autosave = CVARD("sv_autosave", "1", "Interval for autosaves, in minutes."); +void SV_AutoSave(void) +{ +#ifndef SERVERONLY + const char *autosavename; + int i; + if (sv_autosave.value <= 0) + return; + if (sv.state != ss_active) + return; + + //don't bother to autosave multiplayer games. + //this may be problematic with splitscreen, but coop rules tend to apply there anyway. + if (sv.allocated_client_slots != 1) + return; + + for (i = 0; i < sv.allocated_client_slots; i++) + { + if (svs.clients[i].state == cs_spawned) + { + if (svs.clients[i].edict->v->health <= 0) + return; //autosaves with a dead player are just cruel. + + if ((int)svs.clients[i].edict->v->flags & (FL_GODMODE | FL_NOTARGET)) + return; //autosaves to highlight cheaters is also just spiteful. + + if (svs.clients[i].edict->v->movetype != MOVETYPE_WALK) + return; //noclip|fly are cheaters, toss|bounce are bad at playing. etc. + + if (!((int)svs.clients[i].edict->v->flags & FL_ONGROUND)) + return; //autosaves while people are jumping are awkward. + + if (svs.clients[i].edict->v->velocity[0] || svs.clients[i].edict->v->velocity[1] || svs.clients[i].edict->v->velocity[2]) + return; //people running around are likely to result in poor saves + } + } + + autosavename = M_ChooseAutoSave(); + Con_Printf("Autosaving to %s\n", autosavename); + SV_Savegame(autosavename, false); + + sv.autosave_time = sv.time + sv_autosave.value * 60; +#endif +} + void SV_Loadgame_f (void) { levelcache_t *cache; @@ -1292,7 +1368,7 @@ void SV_Loadgame_f (void) Q_strncpyz(savename, Cmd_Argv(1), sizeof(savename)); if (!*savename || strstr(savename, "..")) - strcpy(savename, "quicksav"); + strcpy(savename, "quick"); Q_snprintfz (filename, sizeof(filename), "saves/%s/info.fsv", savename); f = FS_OpenVFS (filename, "rb", FS_GAME); @@ -1511,5 +1587,7 @@ void SV_Loadgame_f (void) SV_LoadLevelCache(savename, str, "", true); sv.allocated_client_slots = slots; sv.spawned_client_slots += loadzombies; + + sv.autosave_time = sv.time + sv_autosave.value*60; } #endif diff --git a/engine/server/server.h b/engine/server/server.h index ae25b76de..b264808b0 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -116,6 +116,7 @@ typedef struct unsigned int csqcchecksum; qboolean mapchangelocked; + double autosave_time; double time; double starttime; int framenum; @@ -137,18 +138,23 @@ typedef struct union { #ifdef Q2SERVER struct { - char configstring[Q2MAX_CONFIGSTRINGS][MAX_QPATH]; + const char *configstring[Q2MAX_CONFIGSTRINGS]; + const char *q2_extramodels[MAX_PRECACHE_MODELS]; // NULL terminated + const char *q2_extrasounds[MAX_PRECACHE_SOUNDS]; // NULL terminated }; #endif struct { - const char *vw_model_precache[32]; + const char *vw_model_precache[32]; const char *model_precache[MAX_PRECACHE_MODELS]; // NULL terminated - char particle_precache[MAX_SSPARTICLESPRE][MAX_QPATH]; // NULL terminated - char sound_precache[MAX_PRECACHE_SOUNDS][MAX_QPATH]; // NULL terminated - const char *lightstyles[MAX_LIGHTSTYLES]; - vec3_t lightstylecolours[MAX_LIGHTSTYLES]; + const char *particle_precache[MAX_SSPARTICLESPRE]; // NULL terminated + const char *sound_precache[MAX_PRECACHE_SOUNDS]; // NULL terminated + const char *lightstyles[MAX_LIGHTSTYLES]; }; + const char *ptrs[1]; } strings; + qboolean stringsalloced; //if true, we need to free the string pointers safely rather than just memsetting them to 0 + vec3_t lightstylecolours[MAX_LIGHTSTYLES]; + char h2miditrack[MAX_QPATH]; qbyte h2cdtrack; @@ -286,6 +292,7 @@ typedef struct int *csqcentversion;//prevents ent versions from going backwards } server_t; +void SV_WipeServerState(void); typedef enum { @@ -322,7 +329,8 @@ typedef struct //merge? { int areabytes; qbyte areabits[MAX_Q2MAP_AREAS/8]; // portalarea visibility bits - q2player_state_t ps; + q2player_state_t ps[MAX_SPLITS]; //yuck + int clientnum[MAX_SPLITS]; int num_entities; int first_entity; // into the circular sv_packet_entities[] int senttime; // for ping calculations @@ -888,6 +896,7 @@ typedef struct //============================================================================= +/* // edict->movetype values #define MOVETYPE_NONE 0 // never moves #define MOVETYPE_ANGLENOCLIP 1 @@ -920,6 +929,7 @@ typedef struct #define DAMAGE_NO 0 #define DAMAGE_YES 1 #define DAMAGE_AIM 2 +*/ #define PVSF_NORMALPVS 0x0 #define PVSF_NOTRACECHECK 0x1 @@ -1024,6 +1034,7 @@ void SV_DropClient (client_t *drop); struct quakeparms_s; void SV_Init (struct quakeparms_s *parms); void SV_ExecInitialConfigs(char *defaultexec); +void SV_ArgumentOverrides(void); int SV_CalcPing (client_t *cl, qboolean forcecalc); void SV_FullClientUpdate (client_t *client, client_t *to); @@ -1038,9 +1049,9 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id); void SV_GetNewSpawnParms(client_t *cl); void SV_SaveSpawnparms (void); void SV_SaveSpawnparmsClient(client_t *client, float *transferparms); //if transferparms, calls SetTransferParms instead, and does not modify the player. -void SV_SaveLevelCache(char *savename, qboolean dontharmgame); -void SV_Savegame (char *savename); -qboolean SV_LoadLevelCache(char *savename, char *level, char *startspot, qboolean ignoreplayers); +void SV_SaveLevelCache(const char *savename, qboolean dontharmgame); +void SV_Savegame (const char *savename, qboolean autosave); +qboolean SV_LoadLevelCache(const char *savename, const char *level, const char *startspot, qboolean ignoreplayers); void SV_Physics_Client (edict_t *ent, int num); @@ -1101,7 +1112,7 @@ void MSV_Status(void); // // sv_init.c // -void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean usecinematic); +void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, qboolean usecinematic); void SV_UnspawnServer (void); void SV_FlushSignon (void); void SV_UpdateMaxPlayers(int newmax); @@ -1159,12 +1170,12 @@ void SVQ1_StartSound (float *origin, wedict_t *entity, int channel, const char * void SV_PrintToClient(client_t *cl, int level, const char *string); void SV_TPrintToClient(client_t *cl, int level, const char *string); void SV_StuffcmdToClient(client_t *cl, const char *string); -void VARGS SV_ClientPrintf (client_t *cl, int level, char *fmt, ...) LIKEPRINTF(3); +void VARGS SV_ClientPrintf (client_t *cl, int level, const char *fmt, ...) LIKEPRINTF(3); void VARGS SV_ClientTPrintf (client_t *cl, int level, translation_t text, ...); -void VARGS SV_BroadcastPrintf (int level, char *fmt, ...) LIKEPRINTF(2); +void VARGS SV_BroadcastPrintf (int level, const char *fmt, ...) LIKEPRINTF(2); void VARGS SV_BroadcastTPrintf (int level, translation_t fmt, ...); -void VARGS SV_BroadcastCommand (char *fmt, ...) LIKEPRINTF(1); -void SV_SendServerInfoChange(char *key, const char *value); +void VARGS SV_BroadcastCommand (const char *fmt, ...) LIKEPRINTF(1); +void SV_SendServerInfoChange(const char *key, const char *value); void SV_SendMessagesToAll (void); void SV_FindModelNumbers (void); @@ -1227,7 +1238,7 @@ qboolean PR_ShouldTogglePause(client_t *initiator, qboolean pausedornot); // sv_ents.c // void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qboolean ignorepvs); -void SVFTE_EmitBaseline(entity_state_t *to, qboolean numberisimportant, sizebuf_t *msg, client_t *client); +void SVFTE_EmitBaseline(entity_state_t *to, qboolean numberisimportant, sizebuf_t *msg, unsigned int pext2); void SVQ3Q1_BuildEntityPacket(client_t *client, packet_entities_t *pack); int SV_HullNumForPlayer(int h2hull, float *mins, float *maxs); void SV_GibFilterInit(void); @@ -1435,7 +1446,13 @@ void SV_FlushDemoSignon (void); void DestFlush(qboolean compleate); // savegame.c +void SV_LegacySavegame_f(void); +void SV_Savegame_f (void); +void SV_Loadgame_f (void); +void SV_AutoSave(void); void SV_FlushLevelCache(void); +extern cvar_t sv_autosave; + int SV_RateForClient(client_t *cl); diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 8f1a349fe..734f0f0cd 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -18,6 +18,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" +#include "pr_common.h" #ifndef CLIENTONLY @@ -776,7 +777,8 @@ void SV_Map_f (void) if (q2savetos0) { - SV_Savegame("s0"); + if (sv.state != ss_cinematic) //too weird. + SV_Savegame("s0", true); } @@ -1761,7 +1763,7 @@ static void SV_Status_f (void) break; Con_Printf("models : %i/%i\n", count, MAX_PRECACHE_MODELS); for (count = 1; count < MAX_PRECACHE_SOUNDS; count++) - if (!*sv.strings.sound_precache[count]) + if (!sv.strings.sound_precache[count]) break; Con_Printf("sounds : %i/%i\n", count, MAX_PRECACHE_SOUNDS); } @@ -2023,7 +2025,7 @@ for (i = sv.mvdrecording?-1:0; i < sv.allocated_client_slots; i++) \ if ((cl = (i==-1?&demo.recorder:&svs.clients[i]))) \ if ((i == -1) || cl->state >= cs_connected) -void SV_SendServerInfoChange(char *key, const char *value) +void SV_SendServerInfoChange(const char *key, const char *value) { int i; client_t *cl; diff --git a/engine/server/sv_cluster.c b/engine/server/sv_cluster.c index 1a7e04bc5..491633319 100644 --- a/engine/server/sv_cluster.c +++ b/engine/server/sv_cluster.c @@ -220,7 +220,7 @@ void MSV_MapCluster_f(void) NET_InitServer(); //child processes return 0 and fall through - memset(&sv, 0, sizeof(sv)); + SV_WipeServerState(); Q_strncpyz(sv.modelname, Cmd_Argv(1), sizeof(sv.modelname)); if (!*sv.modelname) Q_strncpyz(sv.modelname, "start", sizeof(sv.modelname)); diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index f59208c51..4df8ffce5 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -1167,13 +1167,13 @@ static void SVFTE_WriteUpdate(unsigned int bits, entity_state_t *state, sizebuf_ } /*dump out the delta from baseline (used for baselines and statics, so has no svc)*/ -void SVFTE_EmitBaseline(entity_state_t *to, qboolean numberisimportant, sizebuf_t *msg, client_t *client) +void SVFTE_EmitBaseline(entity_state_t *to, qboolean numberisimportant, sizebuf_t *msg, unsigned int pext2) { unsigned int bits; if (numberisimportant) MSG_WriteEntity(msg, to->number); bits = UF_RESET | SVFTE_DeltaCalcBits(&nullentitystate, to); - SVFTE_WriteUpdate(bits, to, msg, client->fteprotocolextensions2); + SVFTE_WriteUpdate(bits, to, msg, pext2); } /*SVFTE_EmitPacketEntities diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index adba0b805..96aa7105e 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" +#include "pr_common.h" #ifndef CLIENTONLY extern int total_loading_size, current_loading_size, loading_stage; char *T_GetString(int num); @@ -754,6 +755,18 @@ void SV_SetupNetworkBuffers(qboolean bigcoords) sv.num_signon_buffers = 1; } +void SV_WipeServerState(void) +{ + if (sv.stringsalloced) + { + unsigned int i; + for (i = 0; i < sizeof(sv.strings) / sizeof(sv.strings.ptrs[0]); i++) + Z_Free((char*)sv.strings.ptrs[i]); + } + memset (&sv, 0, sizeof(sv)); + sv.logindatabase = -1; +} + /* ================ SV_SpawnServer @@ -764,7 +777,7 @@ clients along with it. This is only called from the SV_Map_f() function. ================ */ -void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean usecinematic) +void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, qboolean usecinematic) { extern cvar_t allow_download_refpackages; func_t f; @@ -880,8 +893,7 @@ void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean us BZ_Free(sv.csqcentversion); // wipe the entire per-level structure - memset (&sv, 0, sizeof(sv)); - sv.logindatabase = -1; + SV_WipeServerState(); SV_SetupNetworkBuffers(sv_bigcoords.ival); @@ -1119,7 +1131,7 @@ void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean us if (svs.gametype == GT_Q1QVM) { int subs; - strcpy(sv.strings.sound_precache[0], ""); + sv.strings.sound_precache[0] = ""; sv.strings.model_precache[0] = ""; subs = sv.world.worldmodel->numsubmodels; @@ -1152,9 +1164,7 @@ void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean us ) { int subs; - strcpy(sv.strings.sound_precache[0], ""); - sv.strings.model_precache[0] = ""; - + sv.strings.model_precache[0] = PR_AddString(svprogfuncs, "", 0, false); sv.strings.model_precache[1] = PR_AddString(svprogfuncs, sv.modelname, 0, false); subs = sv.world.worldmodel->numsubmodels; @@ -1180,32 +1190,38 @@ void SV_SpawnServer (char *server, char *startspot, qboolean noents, qboolean us extern int map_checksum; extern cvar_t sv_airaccelerate; - memset(sv.strings.configstring, 0, sizeof(sv.strings.configstring)); + sv.stringsalloced = true; + memset(&sv.strings, 0, sizeof(sv.strings)); if (deathmatch.value) - sprintf(sv.strings.configstring[Q2CS_AIRACCEL], "%g", sv_airaccelerate.value); + sv.strings.configstring[Q2CS_AIRACCEL] = Z_StrDup(va("%g", sv_airaccelerate.value)); else - strcpy(sv.strings.configstring[Q2CS_AIRACCEL], "0"); + sv.strings.configstring[Q2CS_AIRACCEL] = Z_StrDup("0"); // init map checksum config string but only for Q2/Q3 maps if (sv.world.worldmodel->fromgame == fg_quake2 || sv.world.worldmodel->fromgame == fg_quake3) - sprintf(sv.strings.configstring[Q2CS_MAPCHECKSUM], "%i", map_checksum); + sv.strings.configstring[Q2CS_MAPCHECKSUM] = Z_StrDup(va("%i", map_checksum)); else - strcpy(sv.strings.configstring[Q2CS_MAPCHECKSUM], "0"); + sv.strings.configstring[Q2CS_MAPCHECKSUM] = Z_StrDup("0"); subs = sv.world.worldmodel->numsubmodels; - if (subs > Q2MAX_MODELS-2) + if (subs > MAX_PRECACHE_MODELS-1) { Con_Printf("Warning: worldmodel has too many submodels\n"); - subs = Q2MAX_MODELS-2; + subs = MAX_PRECACHE_MODELS-1; } - strcpy(sv.strings.configstring[Q2CS_MODELS+1], sv.modelname); - for (i=1; inumsubmodels; i++) + sv.strings.configstring[Q2CS_MODELS+1] = Z_StrDup(sv.modelname); + for (i=1; i #ifndef CLIENTONLY -#define Q2EDICT_NUM(i) (q2edict_t*)((char *)ge->edicts+i*ge->edict_size) +#define Q2EDICT_NUM(i) (q2edict_t*)((char *)ge->edicts+(i)*ge->edict_size) -void SV_LegacySavegame_f(void); -void SV_Savegame_f (void); -void SV_Loadgame_f (void); #define INVIS_CHAR1 12 #define INVIS_CHAR2 (char)138 #define INVIS_CHAR3 (char)160 @@ -1742,6 +1739,7 @@ void SV_ClientProtocolExtensionsChanged(client_t *client) int i; int maxpacketentities; extern cvar_t pr_maxedicts; + client_t *seat; client->fteprotocolextensions &= Net_PextMask(1, ISNQCLIENT(client)); client->fteprotocolextensions2 &= Net_PextMask(2, ISNQCLIENT(client)); @@ -1878,6 +1876,14 @@ void SV_ClientProtocolExtensionsChanged(client_t *client) } break; } + + //make sure we have the right limits for splitscreen clients too (mostly for viewmodel safety checks) + for (seat = client->controlled; seat; seat = seat->controlled) + { + seat->max_net_clients = client->max_net_clients; + seat->max_net_ents = client->max_net_ents; + seat->maxmodels = client->maxmodels; + } } @@ -1947,6 +1953,9 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id) cl->fteprotocolextensions2 = controller->fteprotocolextensions2; cl->penalties = controller->penalties; cl->protocol = controller->protocol; + cl->maxmodels = controller->maxmodels; + cl->max_net_clients = controller->max_net_clients; + cl->max_net_ents = controller->max_net_ents; Q_strncatz(cl->guid, va("%s:%i", controller->guid, curclients), sizeof(cl->guid)); @@ -1958,7 +1967,38 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id) cl->playerclass = 0; cl->pendingentbits = NULL; - cl->edict = EDICT_NUM(svprogfuncs, i+1); + + cl->edict = NULL; +#ifdef Q2SERVER + cl->q2edict = NULL; +#endif + switch(svs.gametype) + { +#ifdef Q2SERVER + case GT_QUAKE2: + cl->q2edict = Q2EDICT_NUM(i+1); + + if (!ge->ClientConnect(cl->q2edict, cl->userinfo)) + { + const char *reject = Info_ValueForKey(cl->userinfo, "rejmsg"); + if (*reject) + SV_ClientPrintf(controller, PRINT_HIGH, "Splitscreen Refused: %s\n", reject); + else + SV_ClientPrintf(controller, PRINT_HIGH, "Splitscreen Refused\n"); + Con_DPrintf ("Game rejected a connection.\n"); + + *cl->userinfo = 0; + cl->namebuf[0] = 0; + return NULL; + } + + ge->ClientUserinfoChanged(cl->q2edict, cl->userinfo); + break; +#endif + default: + cl->edict = EDICT_NUM(svprogfuncs, i+1); + break; + } prev->controlled = cl; prev = cl; @@ -1985,7 +2025,8 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id) if (cl->state >= cs_connected) { cl->sendinfo = true; - SV_SetUpClientEdict(cl, cl->edict); + if (svprogfuncs) + SV_SetUpClientEdict(cl, cl->edict); } if (cl->state >= cs_spawned) SV_Begin_Core(cl); @@ -2229,7 +2270,7 @@ client_t *SVC_DirectConnect(void) switch(Q_atoi(Cmd_Argv(0))) { case PROTOCOL_VERSION_FTE: - if (protocol == SCP_QUAKEWORLD) + if (protocol == SCP_QUAKEWORLD || protocol == SCP_QUAKE2) { protextsupported = Q_atoi(Cmd_Argv(1)); Con_DPrintf("Client supports 0x%x fte extensions\n", protextsupported); @@ -4496,7 +4537,12 @@ void SV_MVDStream_Poll(void); isidle = true; #endif if (SV_Physics ()) + { isidle = false; + + if (sv.time > sv.autosave_time) + SV_AutoSave(); + } } else { @@ -4773,6 +4819,9 @@ void SV_InitLocal (void) Cmd_AddCommand ("openroute", SV_OpenRoute_f); +#ifndef SERVERONLY + Cvar_Register(&sv_autosave, cvargroup_servercontrol); +#endif Cmd_AddCommand ("savegame_legacy", SV_LegacySavegame_f); Cmd_AddCommand ("savegame", SV_Savegame_f); Cmd_AddCommand ("loadgame", SV_Loadgame_f); @@ -5142,6 +5191,17 @@ SV_Init void SV_Demo_Init(void); #endif +void SV_ArgumentOverrides(void) +{ + int p; + // parse params for cvars + p = COM_CheckParm ("-svport"); + if (!p) + p = COM_CheckParm ("-port"); + if (p && p < com_argc) + Cvar_Set(Cvar_FindVar("sv_port"), com_argv[p+1]); +} + void SV_ExecInitialConfigs(char *defaultexec) { Cbuf_AddText("cvar_purgedefaults\n", RESTRICT_LOCAL); //reset cvar defaults to their engine-specified values. the tail end of 'exec default.cfg' will update non-cheat defaults to mod-specified values. @@ -5163,6 +5223,8 @@ void SV_ExecInitialConfigs(char *defaultexec) // process command line arguments Cbuf_Execute (); + + SV_ArgumentOverrides(); } void SV_Init (quakeparms_t *parms) diff --git a/engine/server/sv_mvd.c b/engine/server/sv_mvd.c index 72b201f44..4603da333 100644 --- a/engine/server/sv_mvd.c +++ b/engine/server/sv_mvd.c @@ -1990,14 +1990,14 @@ void SV_MVD_SendInitialGamestate(mvddest_t *dest) if (!sv.strings.lightstyles[i]) continue; #ifdef PEXT_LIGHTSTYLECOL - if ((demo.recorder.fteprotocolextensions & PEXT_LIGHTSTYLECOL) && (sv.strings.lightstylecolours[i][0]!=1||sv.strings.lightstylecolours[i][1]!=1||sv.strings.lightstylecolours[i][2]!=1) && sv.strings.lightstyles[i]) + if ((demo.recorder.fteprotocolextensions & PEXT_LIGHTSTYLECOL) && (sv.lightstylecolours[i][0]!=1||sv.lightstylecolours[i][1]!=1||sv.lightstylecolours[i][2]!=1) && sv.strings.lightstyles[i]) { MSG_WriteByte (&buf, svcfte_lightstylecol); MSG_WriteByte (&buf, (unsigned char)i); MSG_WriteByte (&buf, 0x87); - MSG_WriteShort(&buf, sv.strings.lightstylecolours[i][0]*1024); - MSG_WriteShort(&buf, sv.strings.lightstylecolours[i][1]*1024); - MSG_WriteShort(&buf, sv.strings.lightstylecolours[i][2]*1024); + MSG_WriteShort(&buf, sv.lightstylecolours[i][0]*1024); + MSG_WriteShort(&buf, sv.lightstylecolours[i][1]*1024); + MSG_WriteShort(&buf, sv.lightstylecolours[i][2]*1024); MSG_WriteString (&buf, sv.strings.lightstyles[i]); } else diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 80d9d4c2e..9bfb1fe9d 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -417,7 +417,7 @@ static int WPhys_FlyMove (world_t *w, wedict_t *ent, const vec3_t gravitydir, fl impact = trace.ent; } - if (trace.startsolid) + if (trace.allsolid)//should be (trace.startsolid), but that breaks compat. *sigh* { // entity is trapped in another solid VectorClear (ent->v->velocity); return 3; diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index fe915b3c9..b6fa44a7f 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // sv_main.c -- server main program #include "quakedef.h" +#include "pr_common.h" #ifndef CLIENTONLY @@ -353,7 +354,7 @@ Sends text across to be displayed if the level passes Is included in mvds. ================= */ -void VARGS SV_ClientPrintf (client_t *cl, int level, char *fmt, ...) +void VARGS SV_ClientPrintf (client_t *cl, int level, const char *fmt, ...) { va_list argptr; char string[1024]; @@ -416,7 +417,7 @@ SV_BroadcastPrintf Sends text to all active clients ================= */ -void VARGS SV_BroadcastPrintf (int level, char *fmt, ...) +void VARGS SV_BroadcastPrintf (int level, const char *fmt, ...) { va_list argptr; char string[1024]; @@ -515,7 +516,7 @@ SV_BroadcastCommand Sends text to all active clients ================= */ -void VARGS SV_BroadcastCommand (char *fmt, ...) +void VARGS SV_BroadcastCommand (const char *fmt, ...) { va_list argptr; char string[1024]; @@ -571,7 +572,7 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int int cluster; int j; qboolean reliable; - int pnum = -1; + client_t *oneclient = NULL, *split; if (to == MULTICAST_INIT) { @@ -603,6 +604,7 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int } #ifdef Q2BSPS + //in theory, this q2/q3 path is only still different thanks to areas, but it also supports q2 gamecode properly. if (sv.world.worldmodel->fromgame == fg_quake2 || sv.world.worldmodel->fromgame == fg_quake3) { int area1, area2, leafnum; @@ -651,8 +653,10 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int if (svprogfuncs) { edict_t *ent = PROG_TO_EDICT(svprogfuncs, pr_global_struct->msg_entity); - pnum = NUM_FOR_EDICT(svprogfuncs, ent) - 1; + oneclient = svs.clients + NUM_FOR_EDICT(svprogfuncs, ent) - 1; } + else + oneclient = NULL; //unsupported in this game mode mask = NULL; break; @@ -662,54 +666,64 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int } // send the data to all relevent clients - for (j = 0, client = svs.clients; j < svs.allocated_client_slots; j++, client++) + for (j = 0; j < svs.allocated_client_slots; j++) { + client = &svs.clients[j]; if (client->state != cs_spawned) continue; - if (client->protocol == SCP_QUAKEWORLD) - { - if (client->fteprotocolextensions & without) - { - // Con_Printf ("Version supressed multicast - without pext\n"); - continue; - } - if (!(~client->fteprotocolextensions & ~with)) - { - // Con_Printf ("Version supressed multicast - with pext\n"); - continue; - } - } + if (client->controller) + continue; //FIXME: send if at least one of the players is near enough. - if (pnum >= 0) + for (split = client; split; split = split->controlled) { - if (pnum != j) - continue; - } - else if (mask) - { - if (client->penalties & BAN_BLIND) - continue; -#ifdef Q2SERVER - if (ge) - leafnum = CM_PointLeafnum (sv.world.worldmodel, client->q2edict->s.origin); - else -#endif + if (client->protocol == SCP_QUAKEWORLD) { - if (svprogfuncs) + if (client->fteprotocolextensions & without) { - if (!((int)client->edict->xv->dimension_see & dimension_mask)) - continue; + // Con_Printf ("Version supressed multicast - without pext\n"); + continue; + } + if (!(~client->fteprotocolextensions & ~with)) + { + // Con_Printf ("Version supressed multicast - with pext\n"); + continue; } - leafnum = CM_PointLeafnum (sv.world.worldmodel, client->edict->v->origin); } - cluster = CM_LeafCluster (sv.world.worldmodel, leafnum); - area2 = CM_LeafArea (sv.world.worldmodel, leafnum); - if (!CM_AreasConnected (sv.world.worldmodel, area1, area2)) - continue; - if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) - continue; + + if (oneclient) + { + if (oneclient != split) + continue; + } + else if (mask) + { + if (split->penalties & BAN_BLIND) + continue; + #ifdef Q2SERVER + if (ge) + leafnum = CM_PointLeafnum (sv.world.worldmodel, split->q2edict->s.origin); + else + #endif + { + if (svprogfuncs) + { + if (!((int)split->edict->xv->dimension_see & dimension_mask)) + continue; + } + leafnum = CM_PointLeafnum (sv.world.worldmodel, split->edict->v->origin); + } + cluster = CM_LeafCluster (sv.world.worldmodel, leafnum); + area2 = CM_LeafArea (sv.world.worldmodel, leafnum); + if (!CM_AreasConnected (sv.world.worldmodel, area1, area2)) + continue; + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + continue; + } + break; } + if (!split) + continue; switch (client->protocol) { @@ -804,8 +818,10 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int if (svprogfuncs) { edict_t *ent = PROG_TO_EDICT(svprogfuncs, pr_global_struct->msg_entity); - pnum = NUM_FOR_EDICT(svprogfuncs, ent) - 1; + oneclient = svs.clients + NUM_FOR_EDICT(svprogfuncs, ent) - 1; } + else + oneclient = NULL; mask = NULL; break; @@ -821,52 +837,51 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int continue; if (client->controller) - continue; //FIXME: send if at least one of the players is near enough. - - if (client->protocol == SCP_QUAKEWORLD) - { - if (client->fteprotocolextensions & without) - { - // Con_Printf ("Version supressed multicast - without pext\n"); - continue; - } - if (!(client->fteprotocolextensions & with) && with) - { - // Con_Printf ("Version supressed multicast - with pext\n"); - continue; - } - } - - if (client->penalties & BAN_BLIND) continue; - if (pnum >= 0) + for (split = client; split; split = split->controlled) { - if (pnum != j) - continue; - } - else if (svprogfuncs) - { - client_t *seat = client; - for(seat = client; seat; seat = seat->controlled) + if (split->protocol == SCP_QUAKEWORLD) { - if (!((int)seat->edict->xv->dimension_see & dimension_mask)) + if (split->fteprotocolextensions & without) + { + // Con_Printf ("Version supressed multicast - without pext\n"); + continue; + } + if (!(split->fteprotocolextensions & with) && with) + { + // Con_Printf ("Version supressed multicast - with pext\n"); + continue; + } + } + + if (split->penalties & BAN_BLIND) + continue; + + if (oneclient) + { + if (oneclient != split) + continue; + } + else if (svprogfuncs) + { + if (!((int)split->edict->xv->dimension_see & dimension_mask)) continue; if (!mask) //no pvs? broadcast. - goto inrange; + break; if (to == MULTICAST_PHS_R || to == MULTICAST_PHS) { vec3_t delta; - VectorSubtract(origin, seat->edict->v->origin, delta); + VectorSubtract(origin, split->edict->v->origin, delta); if (DotProduct(delta, delta) <= 1024*1024) - goto inrange; + break; } { vec3_t pos; - VectorAdd(seat->edict->v->origin, seat->edict->v->view_ofs, pos); + VectorAdd(split->edict->v->origin, split->edict->v->view_ofs, pos); cluster = sv.world.worldmodel->funcs.ClusterForPoint (sv.world.worldmodel, pos); if (cluster>= 0 && !(mask[cluster>>3] & (1<<(cluster&7)) ) ) { @@ -875,9 +890,11 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int } } } + break; } + if (!split) + continue; - inrange: switch (client->protocol) { case SCP_BAD: @@ -953,17 +970,20 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int //mvds are all reliables really. case MULTICAST_ONE_R: case MULTICAST_ONE: - if (svprogfuncs) { - edict_t *ent = PROG_TO_EDICT(svprogfuncs, pr_global_struct->msg_entity); - pnum = NUM_FOR_EDICT(svprogfuncs, ent) - 1; + int pnum; + if (svprogfuncs) + { + edict_t *ent = PROG_TO_EDICT(svprogfuncs, pr_global_struct->msg_entity); + pnum = NUM_FOR_EDICT(svprogfuncs, ent) - 1; + } + else + { + pnum = 0; //FIXME + Con_Printf("SV_MulticastProtExt: unsupported unicast\n"); + } + msg = MVDWrite_Begin(dem_single, pnum, sv.multicast.cursize); } - else - { - pnum = 0; //FIXME - Con_Printf("SV_MulticastProtExt: unsupported unicast\n"); - } - msg = MVDWrite_Begin(dem_single, pnum, sv.multicast.cursize); break; } SZ_Write(msg, sv.multicast.data, sv.multicast.cursize); @@ -1035,17 +1055,17 @@ void SV_StartSound (int ent, vec3_t origin, int seenmask, int channel, const cha else { for (sound_num=1 ; sound_num 0xff) + extfield_mask |= FTESND_MOREFLAGS; + #ifdef PEXT_SOUNDDBL - if (channel >= 8 || ent >= 2048 || sound_num > 0xff || (pitchadj && pitchadj != 100) || timeofs) + if (channel >= 8 || ent >= 2048 || (extfield_mask & ~(NQSND_VOLUME|NQSND_ATTENUATION))) { //if any of the above conditions evaluates to true, then we can't use standard qw protocols MSG_WriteByte (&sv.multicast, svcfte_soundextended); - MSG_WriteByte (&sv.multicast, extfield_mask); + MSG_WriteByte (&sv.multicast, extfield_mask&0xff); + if (extfield_mask & FTESND_MOREFLAGS) + MSG_WriteByte (&sv.multicast, extfield_mask>>8); if (extfield_mask & NQSND_VOLUME) MSG_WriteByte (&sv.multicast, bound(0, volume, 255)); if (extfield_mask & NQSND_ATTENUATION) @@ -1174,10 +1200,18 @@ void SV_StartSound (int ent, vec3_t origin, int seenmask, int channel, const cha for (i=0 ; i<3 ; i++) MSG_WriteCoord (&sv.nqmulticast, origin[i]); #endif - if (use_phs) - SV_MulticastProtExt(origin, reliable ? MULTICAST_PHS_R : MULTICAST_PHS, seenmask, requiredextensions, 0); + + if (flags & CF_UNICAST) + { + SV_MulticastProtExt(origin, reliable ? MULTICAST_ONE_R : MULTICAST_ONE, seenmask, requiredextensions, 0); + } else - SV_MulticastProtExt(origin, reliable ? MULTICAST_ALL_R : MULTICAST_ALL, seenmask, requiredextensions, 0); + { + if (use_phs) + SV_MulticastProtExt(origin, reliable ? MULTICAST_PHS_R : MULTICAST_PHS, seenmask, requiredextensions, 0); + else + SV_MulticastProtExt(origin, reliable ? MULTICAST_ALL_R : MULTICAST_ALL, seenmask, requiredextensions, 0); + } } void SVQ1_StartSound (float *origin, wedict_t *wentity, int channel, const char *sample, int volume, float attenuation, int pitchadj, float timeofs, unsigned int flags) @@ -1253,6 +1287,8 @@ void SV_WriteEntityDataToMessage (client_t *client, sizebuf_t *msg, int pnum) float newa; ent = client->edict; + if (client->controller) + client = client->controller; if (!ent) return; @@ -1281,6 +1317,13 @@ void SV_WriteEntityDataToMessage (client_t *client, sizebuf_t *msg, int pnum) // a fixangle might get lost in a dropped packet. Oh well. if (ent->v->fixangle) { + if (!client->lockangles) + { + //try to keep them vaugely reliable. + if (client->netchan.message.cursize < client->netchan.message.maxsize/2) + msg = &client->netchan.message; + } + if (pnum) { MSG_WriteByte(msg, svcfte_choosesplitclient); @@ -1502,7 +1545,7 @@ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) if (ent->v->armorvalue) bits |= SU_ARMOR; - weaponmodelindex = SV_ModelIndex(ent->v->weaponmodel + svprogfuncs->stringtable); + weaponmodelindex = SV_ModelIndex(PR_GetString(svprogfuncs, ent->v->weaponmodel)); if (weaponmodelindex) bits |= SU_WEAPONMODEL; diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 28deeaf3c..39da32660 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -375,21 +375,27 @@ void SV_New_f (void) if (sv.state == ss_cinematic) playernum = -1; - if (ISQ2CLIENT(host_client)) - ClientReliableWrite_Short (host_client, playernum); - else - ClientReliableWrite_Byte (host_client, playernum); - split->state = cs_connected; split->connection_started = realtime; #ifdef SVRANKING split->stats_started = realtime; #endif splitnum++; + + if (ISQ2CLIENT(host_client)) + { + ClientReliableWrite_Short (host_client, playernum); + break; + } + else + ClientReliableWrite_Byte (host_client, playernum); } - if (host_client->fteprotocolextensions & PEXT_SPLITSCREEN) - ClientReliableWrite_Byte (host_client, 128); + if (!ISQ2CLIENT(host_client)) + { + if (host_client->fteprotocolextensions & PEXT_SPLITSCREEN) + ClientReliableWrite_Byte (host_client, 128); + } } // send full levelname @@ -640,7 +646,7 @@ void SVNQ_New_f (void) MSG_WriteByte (&host_client->netchan.message, 0); //fixme: don't send too many sounds. - for (i = 1; *sv.strings.sound_precache[i] ; i++) + for (i = 1; sv.strings.sound_precache[i] ; i++) MSG_WriteString (&host_client->netchan.message, sv.strings.sound_precache[i]); MSG_WriteByte (&host_client->netchan.message, 0); @@ -667,8 +673,8 @@ void SVNQ_New_f (void) #ifdef Q2SERVER void SVQ2_ConfigStrings_f (void) { - int start; - char *str; + unsigned int start; + const char *str; Con_DPrintf ("Configstrings() from %s\n", host_client->name); @@ -701,136 +707,50 @@ void SVQ2_ConfigStrings_f (void) && start < Q2MAX_CONFIGSTRINGS) { str = sv.strings.configstring[start]; - if (*str) + if (str && *str) { MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); MSG_WriteShort (&host_client->netchan.message, start); MSG_WriteString (&host_client->netchan.message, str); } - /* - //choose range to grab from. - if (start < Q2CS_CDTRACK) + start++; + } + + //model overflows + if (start == Q2MAX_CONFIGSTRINGS) + start = 0x8000; + while ( host_client->netchan.message.cursize < host_client->netchan.message.maxsize/2 + && start < 0x8000+MAX_PRECACHE_MODELS) + { + str = sv.strings.q2_extramodels[start-0x8000]; + if (str && *str) { MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, sv.name); + MSG_WriteString (&host_client->netchan.message, str); } - else if (start < Q2CS_SKY) + start++; + } + + //sound overflows + if (start == 0x8000+MAX_PRECACHE_MODELS) + start = 0xc000; + while ( host_client->netchan.message.cursize < host_client->netchan.message.maxsize/2 + && start < 0xc000+MAX_PRECACHE_SOUNDS) + { + str = sv.strings.q2_extrasounds[start-0xc000]; + if (str && *str) { MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); MSG_WriteShort (&host_client->netchan.message, start); - if (svprogfuncs) - MSG_WriteString (&host_client->netchan.message, va("%i", sv.edicts->v->sounds)); - else - MSG_WriteString (&host_client->netchan.message, "0"); + MSG_WriteString (&host_client->netchan.message, str); } - else if (start < Q2CS_SKYAXIS) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, "unit1_"); - } - else if (start < Q2CS_SKYROTATE) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, "0"); - } - else if (start < Q2CS_STATUSBAR) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, "0"); - } - else if (start < Q2CS_AIRACCEL) - {//show status bar - if (start == Q2CS_STATUSBAR) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, sv.statusbar); - } - } - else if (start < Q2CS_MAXCLIENTS) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, va("%i", (int)1)); - } - else if (start < Q2CS_MAPCHECKSUM) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, va("%i", (int)32)); - } - else if (start < Q2CS_MODELS) - { - extern int map_checksum; - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, va("%i", map_checksum)); - } - else if (start < Q2CS_SOUNDS) - { - if (*sv.model_precache[start-Q2CS_MODELS]) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, sv.model_precache[start-Q2CS_MODELS]); - } - } - else if (start < Q2CS_IMAGES) - { - if (*sv.sound_precache[start-Q2CS_SOUNDS]) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, sv.sound_precache[start-Q2CS_SOUNDS]); - } - } - else if (start < Q2CS_LIGHTS) - { - if (*sv.image_precache[start-Q2CS_IMAGES]) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, sv.image_precache[start-Q2CS_IMAGES]); - } - } - else if (start < Q2CS_ITEMS) - { - if (start-Q2CS_LIGHTS < MAX_LIGHTSTYLES && sv.lightstyles[start-Q2CS_LIGHTS]) - { - MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); - MSG_WriteShort (&host_client->netchan.message, start); - MSG_WriteString (&host_client->netchan.message, sv.lightstyles[start-Q2CS_LIGHTS]); - } - } - else if (start < Q2CS_PLAYERSKINS) - { -// MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); -// MSG_WriteShort (&host_client->netchan.message, start); -// MSG_WriteString (&host_client->netchan.message, sv.configstrings[start-Q2CS_ITEMS]); - } - else if (start < Q2CS_GENERAL) - { -// MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); -// MSG_WriteShort (&host_client->netchan.message, start); -// MSG_WriteString (&host_client->netchan.message, sv.configstrings[start]); - } - else - { -// MSG_WriteByte (&host_client->netchan.message, svcq2_configstring); -// MSG_WriteShort (&host_client->netchan.message, start); -// MSG_WriteString (&host_client->netchan.message, sv.configstrings[start]); - } - */ start++; } // send next command - if (start == Q2MAX_CONFIGSTRINGS) + if (start == 0xc000+MAX_PRECACHE_SOUNDS) { MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext); MSG_WriteString (&host_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) ); @@ -1055,9 +975,9 @@ void SV_SendClientPrespawnInfo(client_t *client) } client->prespawn_idx++; - if (client->prespawn_idx >= maxclientsupportedsounds || !*sv.strings.sound_precache[client->prespawn_idx]) + if (client->prespawn_idx >= maxclientsupportedsounds || !sv.strings.sound_precache[client->prespawn_idx]) { - if (*sv.strings.sound_precache[client->prespawn_idx] && !(client->plimitwarned & PLIMIT_SOUNDS)) + if (sv.strings.sound_precache[client->prespawn_idx] && !(client->plimitwarned & PLIMIT_SOUNDS)) { client->plimitwarned |= PLIMIT_SOUNDS; SV_ClientPrintf(client, PRINT_HIGH, "WARNING: Your client's network protocol only supports %i sounds. Please upgrade or enable extensions.\n", client->prespawn_idx); @@ -1205,7 +1125,7 @@ void SV_SendClientPrespawnInfo(client_t *client) break; } - if (*sv.strings.particle_precache[client->prespawn_idx]) + if (sv.strings.particle_precache[client->prespawn_idx]) { ClientReliableWrite_Begin (client, ISNQCLIENT(client)?svcdp_precache:svcfte_precache, 4 + strlen(sv.strings.particle_precache[client->prespawn_idx])); ClientReliableWrite_Short (client, client->prespawn_idx | PC_PARTICLE); @@ -1298,7 +1218,7 @@ void SV_SendClientPrespawnInfo(client_t *client) if (client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) { MSG_WriteByte(&client->netchan.message, svcfte_spawnstatic2); - SVFTE_EmitBaseline(state, false, &client->netchan.message, client); + SVFTE_EmitBaseline(state, false, &client->netchan.message, client->fteprotocolextensions2); continue; } if (client->fteprotocolextensions & PEXT_SPAWNSTATIC2) @@ -1402,7 +1322,7 @@ void SV_SendClientPrespawnInfo(client_t *client) else if (client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) { MSG_WriteByte(&client->netchan.message, svcfte_spawnbaseline2); - SVFTE_EmitBaseline(state, true, &client->netchan.message, client); + SVFTE_EmitBaseline(state, true, &client->netchan.message, client->fteprotocolextensions2); } else if (client->fteprotocolextensions & PEXT_SPAWNSTATIC2) { @@ -1625,15 +1545,15 @@ void SVQW_Spawn_f (void) if (!sv.strings.lightstyles[i]) continue; #ifdef PEXT_LIGHTSTYLECOL - if ((host_client->fteprotocolextensions & PEXT_LIGHTSTYLECOL) && (sv.strings.lightstylecolours[i][0]!=1||sv.strings.lightstylecolours[i][1]!=1||sv.strings.lightstylecolours[i][2]!=1) && sv.strings.lightstyles[i]) + if ((host_client->fteprotocolextensions & PEXT_LIGHTSTYLECOL) && (sv.lightstylecolours[i][0]!=1||sv.lightstylecolours[i][1]!=1||sv.lightstylecolours[i][2]!=1) && sv.strings.lightstyles[i]) { ClientReliableWrite_Begin (host_client, svcfte_lightstylecol, 10 + (sv.strings.lightstyles[i] ? strlen(sv.strings.lightstyles[i]) : 1)); ClientReliableWrite_Byte (host_client, (char)i); ClientReliableWrite_Char (host_client, 0x87); - ClientReliableWrite_Short (host_client, sv.strings.lightstylecolours[i][0]*1024); - ClientReliableWrite_Short (host_client, sv.strings.lightstylecolours[i][1]*1024); - ClientReliableWrite_Short (host_client, sv.strings.lightstylecolours[i][2]*1024); + ClientReliableWrite_Short (host_client, sv.lightstylecolours[i][0]*1024); + ClientReliableWrite_Short (host_client, sv.lightstylecolours[i][1]*1024); + ClientReliableWrite_Short (host_client, sv.lightstylecolours[i][2]*1024); ClientReliableWrite_String (host_client, sv.strings.lightstyles[i]); } else @@ -1889,6 +1809,7 @@ void SV_Begin_Core(client_t *split) split->edict->v->maxs[2] = 32; split->edict->v->movetype = MOVETYPE_NOCLIP; } + VectorCopy(split->edict->v->origin, split->edict->v->oldorigin); //make sure oldorigin isn't 0 0 0 or anything too clumsy like that. stuck somewhere killable is better than stuck outside the map. } oh = host_client; @@ -2830,14 +2751,6 @@ static int SV_LocateDownload(char *name, flocation_t *loc, char **replacementnam *p = (char)tolower((unsigned char)*p); } - - - if (!SV_AllowDownload(name)) - { - Sys_Printf ("%s denied download of %s due to path/name rules\n", host_client->name, name); - return -2; //not permitted (even if it exists). - } - //mvdsv demo downloading support demonum/ -> demos/XXXX (sets up the client paths) if (!strncmp(name, "demonum/", 8)) { @@ -2851,11 +2764,17 @@ static int SV_LocateDownload(char *name, flocation_t *loc, char **replacementnam Sys_Printf ("%s requested invalid demonum %s\n", host_client->name, name+8); return -1; //not found } - *replacementname = va("demos/%s\n", mvdname); - return -4; //redirect + name = *replacementname = va("demos/%s", mvdname); + return -4; } } + if (!SV_AllowDownload(name)) + { + Sys_Printf ("%s denied download of %s due to path/name rules\n", host_client->name, name); + return -2; //not permitted (even if it exists). + } + //mvdsv demo downloading support. demos/ -> demodir (sets up the server paths) if (!strncmp(name, "demos/", 6)) name = va("%s/%s", sv_demoDir.string, name+6); @@ -3073,12 +2992,27 @@ void SV_BeginDownload_f(void) if (result == -4) { #ifdef PEXT_CHUNKEDDOWNLOADS - if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS) + qboolean isezquake = !strncmp(Info_ValueForKey(host_client->userinfo, "*client"), "ezQuake", 7); + if ((host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS) && !isezquake) { //redirect the client (before the message saying download failed) - char *s = va("dlsize \"%s\" r \"%s\"\n", name, redirection); +// char *s = va("dlsize \"%s\" r \"%s\"\n", name, redirection); +// ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(s)); +// ClientReliableWrite_String (host_client, s); + + ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(name)); + ClientReliableWrite_Long (host_client, -1); + ClientReliableWrite_Long (host_client, result); + ClientReliableWrite_String (host_client, redirection); + return; + } + else + { //crappy hack for crappy clients. tell them to download the new file instead without telling them about any failure. + //this will seriously mess with any download queues or anything like that + char *s = va("download \"%s\"\n", redirection); ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(s)); ClientReliableWrite_String (host_client, s); + return; } #endif } @@ -3719,6 +3653,9 @@ static void SV_UpdateSeats(client_t *controller) curclients++; } + if (controller->protocol == SCP_QUAKE2) + return; //wait for the clientinfo stuff instead. + ClientReliableWrite_Begin(controller, svc_signonnum, 2+curclients); ClientReliableWrite_Byte(controller, curclients); for (curclients = 0, cl = controller; cl; cl = cl->controlled, curclients++) @@ -3977,6 +3914,13 @@ void SV_SetInfo_f (void) if (!strcmp(Info_ValueForKey(host_client->userinfo, key), oldval)) return; // key hasn't changed + if (svs.gametype == GT_QUAKE2) + { + ge->ClientUserinfoChanged (host_client->q2edict, host_client->userinfo); //tell the gamecode + SV_ExtractFromUserinfo(host_client, true); //let the server routines know + return; + } + // process any changed values SV_ExtractFromUserinfo (host_client, true); @@ -5614,8 +5558,6 @@ ucmd_t ucmdsq2[] = { {"baselines", SVQ2_BaseLines_f, true}, {"begin", SV_Begin_f, true}, -// {"setinfo", SV_SetInfo_f, true}, - {"serverinfo", SV_ShowServerinfo_f, true}, {"info", SV_ShowServerinfo_f, true}, @@ -5624,7 +5566,10 @@ ucmd_t ucmdsq2[] = { {"nextserver", SVQ2_NextServer_f, true}, - {"ftevote", SV_Vote_f, true}, + //fte stuff + {"setinfo", SV_SetInfo_f, true}, + {"ftevote", SV_Vote_f, true}, //voting... kinda messed up by 'vote' being common in mods + {"addseat", Cmd_AddSeat_f, true}, //for splitscreen //#ifdef SVRANKING // {"topten", Rank_ListTop10_f, true}, @@ -6647,8 +6592,9 @@ if (sv_player->v->health > 0 && before && !after ) { vec3_t delta; - VectorSubtract (pmove.angles, sv_player->v->v_angle, delta); - + delta[0] = pmove.angles[0] - SHORT2ANGLE(pmove.cmd.angles[0]); + delta[1] = pmove.angles[1] - SHORT2ANGLE(pmove.cmd.angles[1]); + delta[2] = pmove.angles[2] - SHORT2ANGLE(pmove.cmd.angles[2]); if (delta[0] || delta[1] || delta[2]) { if (host_client->fteprotocolextensions2 & PEXT2_SETANGLEDELTA) @@ -6700,7 +6646,22 @@ if (sv_player->v->health > 0 && before && !after ) VectorCopy (pmove.safeorigin, sv_player->v->oldorigin); VectorCopy (pmove.origin, sv_player->v->origin); - VectorCopy (pmove.angles, sv_player->v->v_angle); + if (!sv_player->v->fixangle) + { + VectorCopy (pmove.angles, sv_player->v->v_angle); + + //some clients (especially NQ ones) attempt to cheat. don't let it benefit them. + //some things would break though. + if ((!sv_player->xv->gravitydir[0] && !sv_player->xv->gravitydir[1] && !sv_player->xv->gravitydir[2]) && sv_player->v->movetype == MOVETYPE_WALK) + { + float minpitch = *sv_minpitch.string?sv_minpitch.value:-70; + float maxpitch = *sv_maxpitch.string?sv_maxpitch.value:80; + if (sv_player->v->v_angle[0] < minpitch) + sv_player->v->v_angle[0] = minpitch; + if (sv_player->v->v_angle[0] > maxpitch) + sv_player->v->v_angle[0] = maxpitch; + } + } // VectorCopy (pmove.gravitydir, sv_player->xv->gravitydir); if (pmove.gravitydir[0] || pmove.gravitydir[1] || (pmove.gravitydir[2] && pmove.gravitydir[2] != -1)) @@ -7346,11 +7307,12 @@ void SVQ2_ExecuteClientMessage (client_t *cl) char *s; usercmd_t oldest, oldcmd, newcmd; q2client_frame_t *frame; - qboolean move_issued = false; //only allow one move command + int move_issued = 0; //only allow one move command int checksumIndex; qbyte checksum, calculatedChecksum; int seq_hash; int lastframe; + client_t *split; if (!ge) { @@ -7432,75 +7394,88 @@ void SVQ2_ExecuteClientMessage (client_t *cl) break; case clcq2_move: - if (move_issued) + if (move_issued >= MAX_SPLITS) return; // someone is trying to cheat... - move_issued = true; + for (checksumIndex = 0, split = cl; split && checksumIndex < move_issued; checksumIndex++) + split = split->controlled; - checksumIndex = MSG_GetReadCount(); - checksum = (qbyte)MSG_ReadByte (); - - - lastframe = MSG_ReadLong(); - if (lastframe != host_client->delta_sequence) + if (move_issued) { - cl->delta_sequence = lastframe; + checksumIndex = -1; + checksum = 0; + } + else + { + checksumIndex = MSG_GetReadCount(); + checksum = (qbyte)MSG_ReadByte (); + + + lastframe = MSG_ReadLong(); + if (lastframe != split->delta_sequence) + { + split->delta_sequence = lastframe; + } } MSGQ2_ReadDeltaUsercmd (&nullcmd, &oldest); MSGQ2_ReadDeltaUsercmd (&oldest, &oldcmd); MSGQ2_ReadDeltaUsercmd (&oldcmd, &newcmd); - if ( cl->state != cs_spawned ) - break; - - // if the checksum fails, ignore the rest of the packet - calculatedChecksum = Q2COM_BlockSequenceCRCByte( - net_message.data + checksumIndex + 1, - MSG_GetReadCount() - checksumIndex - 1, - seq_hash); - - if (calculatedChecksum != checksum) + if ( split && split->state == cs_spawned ) { - Con_DPrintf ("Failed command checksum for %s(%d) (%d != %d)\n", - cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum); - return; - } - - if (cl->penalties & BAN_CRIPPLED) - { - cl->lastcmd.forwardmove = 0; //hmmm.... does this work well enough? - oldest.forwardmove = 0; - newcmd.forwardmove = 0; - - cl->lastcmd.sidemove = 0; - oldest.sidemove = 0; - newcmd.sidemove = 0; - - cl->lastcmd.upmove = 0; - oldest.upmove = 0; - newcmd.upmove = 0; - } - - host_client->q2edict->client->ping = SV_CalcPing (host_client, false); - if (!sv.paused) - { - if (net_drop < 20) + if (checksumIndex != -1) { - while (net_drop > 2) - { - ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&cl->lastcmd); - net_drop--; - } - if (net_drop > 1) - ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&oldest); - if (net_drop > 0) - ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&oldcmd); - } - ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&newcmd); - } + // if the checksum fails, ignore the rest of the packet + calculatedChecksum = Q2COM_BlockSequenceCRCByte( + net_message.data + checksumIndex + 1, + MSG_GetReadCount() - checksumIndex - 1, + seq_hash); - cl->lastcmd = newcmd; + if (calculatedChecksum != checksum) + { + Con_DPrintf ("Failed command checksum for %s(%d) (%d != %d)\n", + cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum); + return; + } + } + + if (split->penalties & BAN_CRIPPLED) + { + split->lastcmd.forwardmove = 0; //hmmm.... does this work well enough? + oldest.forwardmove = 0; + newcmd.forwardmove = 0; + + split->lastcmd.sidemove = 0; + oldest.sidemove = 0; + newcmd.sidemove = 0; + + split->lastcmd.upmove = 0; + oldest.upmove = 0; + newcmd.upmove = 0; + } + + split->q2edict->client->ping = SV_CalcPing (split, false); + if (!sv.paused) + { + if (net_drop < 20) + { + while (net_drop > 2) + { + ge->ClientThink (split->q2edict, (q2usercmd_t*)&split->lastcmd); + net_drop--; + } + if (net_drop > 1) + ge->ClientThink (split->q2edict, (q2usercmd_t*)&oldest); + if (net_drop > 0) + ge->ClientThink (split->q2edict, (q2usercmd_t*)&oldcmd); + } + ge->ClientThink (split->q2edict, (q2usercmd_t*)&newcmd); + } + + split->lastcmd = newcmd; + } + move_issued++; break; case clcq2_userinfo: diff --git a/engine/server/svq2_ents.c b/engine/server/svq2_ents.c index 91ea0958c..8e792f941 100644 --- a/engine/server/svq2_ents.c +++ b/engine/server/svq2_ents.c @@ -110,16 +110,36 @@ void MSGQ2_WriteDeltaEntity (q2entity_state_t *from, q2entity_state_t *to, sizeb bits |= Q2U_EVENT; if ( to->modelindex != from->modelindex ) + { bits |= Q2U_MODEL; + if (to->modelindex > 0xff) + bits |= Q2UX_INDEX16; + } if ( to->modelindex2 != from->modelindex2 ) + { bits |= Q2U_MODEL2; + if (to->modelindex2 > 0xff) + bits |= Q2UX_INDEX16; + } if ( to->modelindex3 != from->modelindex3 ) + { bits |= Q2U_MODEL3; + if (to->modelindex3 > 0xff) + bits |= Q2UX_INDEX16; + } if ( to->modelindex4 != from->modelindex4 ) + { bits |= Q2U_MODEL4; + if (to->modelindex4 > 0xff) + bits |= Q2UX_INDEX16; + } if ( to->sound != from->sound ) + { bits |= Q2U_SOUND; + if (to->sound > 0xff) + bits |= Q2UX_INDEX16; + } if (newentity || (to->renderfx & Q2RF_BEAM)) bits |= Q2U_OLDORIGIN; @@ -165,13 +185,33 @@ void MSGQ2_WriteDeltaEntity (q2entity_state_t *from, q2entity_state_t *to, sizeb MSG_WriteByte (msg, to->number); if (bits & Q2U_MODEL) - MSG_WriteByte (msg, to->modelindex); + { + if (bits & Q2UX_INDEX16) + MSG_WriteShort (msg, to->modelindex); + else + MSG_WriteByte (msg, to->modelindex); + } if (bits & Q2U_MODEL2) - MSG_WriteByte (msg, to->modelindex2); + { + if (bits & Q2UX_INDEX16) + MSG_WriteShort (msg, to->modelindex2); + else + MSG_WriteByte (msg, to->modelindex2); + } if (bits & Q2U_MODEL3) - MSG_WriteByte (msg, to->modelindex3); + { + if (bits & Q2UX_INDEX16) + MSG_WriteShort (msg, to->modelindex3); + else + MSG_WriteByte (msg, to->modelindex3); + } if (bits & Q2U_MODEL4) - MSG_WriteByte (msg, to->modelindex4); + { + if (bits & Q2UX_INDEX16) + MSG_WriteShort (msg, to->modelindex4); + else + MSG_WriteByte (msg, to->modelindex4); + } if (bits & Q2U_FRAME8) MSG_WriteByte (msg, to->frame); @@ -222,11 +262,22 @@ void MSGQ2_WriteDeltaEntity (q2entity_state_t *from, q2entity_state_t *to, sizeb } if (bits & Q2U_SOUND) - MSG_WriteByte (msg, to->sound); + { + if (bits & Q2UX_INDEX16) + MSG_WriteShort (msg, to->sound); + else + MSG_WriteByte (msg, to->sound); + } + if (bits & Q2U_EVENT) MSG_WriteByte (msg, to->event); if (bits & Q2U_SOLID) - MSG_WriteShort (msg, to->solid); + { + if (net_message.prim.q2flags & NPQ2_SOLID32) + MSG_WriteLong (msg, to->solid); + else + MSG_WriteShort (msg, to->solid); + } } @@ -346,7 +397,7 @@ SV_WritePlayerstateToClient ============= */ -void SVQ2_WritePlayerstateToClient (q2client_frame_t *from, q2client_frame_t *to, sizebuf_t *msg) +void SVQ2_WritePlayerstateToClient (unsigned int pext, int seat, int extflags, q2client_frame_t *from, q2client_frame_t *to, sizebuf_t *msg) { int i; int pflags; @@ -354,20 +405,24 @@ void SVQ2_WritePlayerstateToClient (q2client_frame_t *from, q2client_frame_t *to q2player_state_t dummy; int statbits; - ps = &to->ps; + ps = &to->ps[seat]; if (!from) { memset (&dummy, 0, sizeof(dummy)); ops = &dummy; } else - ops = &from->ps; + ops = &from->ps[seat]; // // determine what needs to be sent // pflags = 0; + if (pext & PEXT_SPLITSCREEN) + if (!from || from->clientnum[seat] != to->clientnum[seat]) + pflags |= Q2PS_CLIENTNUM; + if (ps->pmove.pm_type != ops->pmove.pm_type) pflags |= Q2PS_M_TYPE; @@ -426,13 +481,27 @@ void SVQ2_WritePlayerstateToClient (q2client_frame_t *from, q2client_frame_t *to if (ps->gunframe != ops->gunframe) pflags |= Q2PS_WEAPONFRAME; - pflags |= Q2PS_WEAPONINDEX; + if (ps->gunindex != ops->gunindex) + pflags |= Q2PS_WEAPONINDEX; + + if (pext & PEXT_MODELDBL) + { + if ((pflags & Q2PS_WEAPONINDEX) && ps->gunindex > 0xff) + pflags |= Q2PS_INDEX16; + if ((pflags & Q2PS_WEAPONFRAME) && ps->gunframe > 0xff) + pflags |= Q2PS_INDEX16; + } + + if (pflags > 0xffff) + pflags |= Q2PS_EXTRABITS; // // write it // MSG_WriteByte (msg, svcq2_playerinfo); - MSG_WriteShort (msg, pflags); + MSG_WriteShort (msg, pflags&0xffff); + if (pflags & Q2PS_EXTRABITS) + MSG_WriteByte (msg, pflags>>16); // // write the pmove_state_t @@ -497,12 +566,18 @@ void SVQ2_WritePlayerstateToClient (q2client_frame_t *from, q2client_frame_t *to if (pflags & Q2PS_WEAPONINDEX) { - MSG_WriteByte (msg, ps->gunindex); + if (pflags & Q2PS_INDEX16) + MSG_WriteShort(msg, ps->gunindex); + else + MSG_WriteByte (msg, ps->gunindex); } if (pflags & Q2PS_WEAPONFRAME) { - MSG_WriteByte (msg, ps->gunframe); + if (pflags & Q2PS_INDEX16) + MSG_WriteShort (msg, ps->gunframe); + else + MSG_WriteByte (msg, ps->gunframe); MSG_WriteChar (msg, ps->gunoffset[0]*4); MSG_WriteChar (msg, ps->gunoffset[1]*4); MSG_WriteChar (msg, ps->gunoffset[2]*4); @@ -533,6 +608,9 @@ void SVQ2_WritePlayerstateToClient (q2client_frame_t *from, q2client_frame_t *to for (i=0 ; istats[i]); + + if ((extflags & Q2PSX_CLIENTNUM) || (pflags & Q2PS_CLIENTNUM)) + MSG_WriteByte(msg, to->clientnum[seat]); } @@ -545,6 +623,9 @@ void SVQ2_WriteFrameToClient (client_t *client, sizebuf_t *msg) { q2client_frame_t *frame, *oldframe; int lastframe; + client_t *split; + int seat; + int extflags = 0; //Com_Printf ("%i -> %i\n", client->lastframe, sv.framenum); // this is the frame we are creating @@ -571,6 +652,7 @@ void SVQ2_WriteFrameToClient (client_t *client, sizebuf_t *msg) MSG_WriteLong (msg, sv.framenum); MSG_WriteLong (msg, lastframe); // what we are delta'ing from MSG_WriteByte (msg, client->chokecount&0xff); // rate dropped packets + extflags |= Q2PSX_OLD; client->chokecount = 0; // send over the areabits @@ -578,7 +660,8 @@ void SVQ2_WriteFrameToClient (client_t *client, sizebuf_t *msg) SZ_Write (msg, frame->areabits, frame->areabytes); // delta encode the playerstate - SVQ2_WritePlayerstateToClient (oldframe, frame, msg); + for (split = client, seat = 0; split; split = split->controlled, seat++) + SVQ2_WritePlayerstateToClient (client->fteprotocolextensions, seat, extflags, oldframe, frame, msg); // delta encode the entities SVQ2_EmitPacketEntities (oldframe, frame, msg); @@ -605,28 +688,25 @@ void SVQ2_Ents_Init(void); void SVQ2_BuildClientFrame (client_t *client) { int e, i; - vec3_t org; + vec3_t org[MAX_SPLITS]; + int clientarea[MAX_SPLITS]; + q2edict_t *clent[MAX_SPLITS]; + client_t *split; q2edict_t *ent; - q2edict_t *clent; q2client_frame_t *frame; q2entity_state_t *state; int l; - int clientarea, clientcluster; - int leafnum; + int seat; int c_fullsend; qbyte clientpvs[(MAX_MAP_LEAFS+7)>>3]; - qbyte *clientphs; + qbyte *clientphs = NULL; + int seats; if (client->state < cs_spawned) return; SVQ2_Ents_Init(); - clent = client->q2edict; - - if (!clent->client) - return; - #if 0 numprojs = 0; // no projectiles yet #endif @@ -639,26 +719,48 @@ void SVQ2_BuildClientFrame (client_t *client) // this is the frame we are creating frame = &client->frameunion.q2frames[sv.framenum & Q2UPDATE_MASK]; - // find the client's PVS - for (i=0 ; i<3 ; i++) - org[i] = clent->client->ps.pmove.origin[i]*0.125 + clent->client->ps.viewoffset[i]; - - leafnum = CM_PointLeafnum (sv.world.worldmodel, org); - clientarea = CM_LeafArea (sv.world.worldmodel, leafnum); - clientcluster = CM_LeafCluster (sv.world.worldmodel, leafnum); - - // calculate the visible areas - frame->areabytes = CM_WriteAreaBits (sv.world.worldmodel, frame->areabits, clientarea, false); - // grab the current player_state_t - frame->ps = clent->client->ps; + for (seat = 0, split = client; split; split = split->controlled, seat++) + { + int clientcluster; + int leafnum; - if (sv.paused) - frame->ps.pmove.pm_type = Q2PM_FREEZE; + clent[seat] = split->q2edict; + frame->clientnum[seat] = split - svs.clients; + if (!split->q2edict->client) + { //shouldn't happen + VectorClear(org[seat]); + clientarea[seat] = 0; + memset(&frame->ps[seat], 0, sizeof(frame->ps[seat])); + frame->ps[seat].pmove.pm_type = Q2PM_FREEZE; + continue; + } - sv.world.worldmodel->funcs.FatPVS(sv.world.worldmodel, org, clientpvs, sizeof(clientpvs), false); - clientphs = CM_ClusterPHS (sv.world.worldmodel, clientcluster); + // find the client's PVS + for (i=0 ; i<3 ; i++) + org[seat][i] = clent[seat]->client->ps.pmove.origin[i]*0.125 + clent[seat]->client->ps.viewoffset[i]; + + leafnum = CM_PointLeafnum (sv.world.worldmodel, org[seat]); + clientarea[seat] = CM_LeafArea (sv.world.worldmodel, leafnum); + clientcluster = CM_LeafCluster (sv.world.worldmodel, leafnum); + + // calculate the visible areas + frame->areabytes = CM_WriteAreaBits (sv.world.worldmodel, frame->areabits, clientarea[seat], seat != 0); + + sv.world.worldmodel->funcs.FatPVS(sv.world.worldmodel, org[seat], clientpvs, sizeof(clientpvs), seat!=0); + clientphs = CM_ClusterPHS (sv.world.worldmodel, clientcluster); + + frame->ps[seat] = clent[seat]->client->ps; + if (sv.paused) + frame->ps[seat].pmove.pm_type = Q2PM_FREEZE; + } + seats = seat; + for (; seat < MAX_SPLITS; seat++) + { + memset(&frame->ps[seat], 0, sizeof(frame->ps[seat])); + frame->clientnum[seat] = 0xff; //invalid + } // build up the list of visible entities frame->num_entities = 0; @@ -679,60 +781,67 @@ void SVQ2_BuildClientFrame (client_t *client) && !ent->s.event) continue; - // ignore if not touching a PV leaf - if (ent != clent) + for (seat = 0; seat < seats; seat++) { - // check area - if (!CM_AreasConnected (sv.world.worldmodel, clientarea, ent->areanum)) - { // doors can legally straddle two areas, so - // we may need to check another one - if (!ent->areanum2 - || !CM_AreasConnected (sv.world.worldmodel, clientarea, ent->areanum2)) - continue; // blocked by a door - } - - // beams just check one point for PHS - if (ent->s.renderfx & Q2RF_BEAM) + // ignore if not touching a PV leaf + if (ent != clent[seat]) { - l = ent->clusternums[0]; - if ( !(clientphs[l >> 3] & (1 << (l&7) )) ) - continue; - } - else - { - // FIXME: if an ent has a model and a sound, but isn't - // in the PVS, only the PHS, clear the model + // check area + if (!CM_AreasConnected (sv.world.worldmodel, clientarea[seat], ent->areanum)) + { // doors can legally straddle two areas, so + // we may need to check another one + if (!ent->areanum2 + || !CM_AreasConnected (sv.world.worldmodel, clientarea[seat], ent->areanum2)) + continue; // blocked by a door + } - if (ent->num_clusters == -1) - { // too many leafs for individual check, go by headnode - if (!CM_HeadnodeVisible (sv.world.worldmodel, ent->headnode, clientpvs)) + // beams just check one point for PHS + if (ent->s.renderfx & Q2RF_BEAM) + { + l = ent->clusternums[0]; + if ( !(clientphs[l >> 3] & (1 << (l&7) )) ) continue; - c_fullsend++; } else - { // check individual leafs - for (i=0 ; i < ent->num_clusters ; i++) - { - l = ent->clusternums[i]; - if (clientpvs[l >> 3] & (1 << (l&7) )) - break; + { + // FIXME: if an ent has a model and a sound, but isn't + // in the PVS, only the PHS, clear the model + + if (ent->num_clusters == -1) + { // too many leafs for individual check, go by headnode + if (!CM_HeadnodeVisible (sv.world.worldmodel, ent->headnode, clientpvs)) + continue; + c_fullsend++; + } + else + { // check individual leafs + for (i=0 ; i < ent->num_clusters ; i++) + { + l = ent->clusternums[i]; + if (clientpvs[l >> 3] & (1 << (l&7) )) + break; + } + if (i == ent->num_clusters) + continue; // not visible } - if (i == ent->num_clusters) - continue; // not visible - } - if (!ent->s.modelindex) - { // don't send sounds if they will be attenuated away - vec3_t delta; - float len; + if (!ent->s.modelindex) + { // don't send sounds if they will be attenuated away + vec3_t delta; + float len; - VectorSubtract (org, ent->s.origin, delta); - len = Length (delta); - if (len > 400) - continue; + VectorSubtract (org[seat], ent->s.origin, delta); + len = Length (delta); + if (len > 400) + continue; + } } } + break; } + if (seat == seats) + continue; //not visible to any seat + seat = 0; //FIXME // add it to the circular client_entities array state = &svs_client_entities[svs_next_client_entities%svs_num_client_entities]; @@ -744,7 +853,7 @@ void SVQ2_BuildClientFrame (client_t *client) *state = ent->s; // don't mark players missiles as solid - if (ent->owner == client->q2edict) + if (ent->owner == clent[seat]) state->solid = 0; svs_next_client_entities++; diff --git a/engine/server/svq2_game.c b/engine/server/svq2_game.c index 278356083..4668f01fb 100644 --- a/engine/server/svq2_game.c +++ b/engine/server/svq2_game.c @@ -126,6 +126,24 @@ static void VARGS PFQ2_Unicast (q2edict_t *ent, qboolean reliable) if (client->state < cs_connected) return; + if (client->controller) + { + client_t *peer; + for (p = 0, peer = client->controller; peer; peer = peer->controlled, p++) + { + if (peer == client) + break; + } + client = client->controller; + + //svcq2_playerinfo is not normally valid except within the svc_frame message. + //this means its 'free' to repurpose for things like splitscreen. woot. + MSG_WriteShort(&sv.q2multicast, 0); + memmove(sv.q2multicast.data+2, sv.q2multicast.data, sv.q2multicast.cursize-2); + sv.q2multicast.data[0] = svcq2_playerinfo; + sv.q2multicast.data[1] = p; + } + if (reliable) SZ_Write (&client->netchan.message, sv.q2multicast.data, sv.q2multicast.cursize); else @@ -142,7 +160,7 @@ PF_dprintf Debug print to server console =============== */ -static void VARGS PFQ2_dprintf (char *fmt, ...) +static void VARGS PFQ2_dprintf (const char *fmt, ...) { char msg[1024]; va_list argptr; @@ -162,7 +180,7 @@ PF_cprintf Print to a single client =============== */ -static void VARGS PFQ2_cprintf (q2edict_t *ent, int level, char *fmt, ...) +static void VARGS PFQ2_cprintf (q2edict_t *ent, int level, const char *fmt, ...) { char msg[1024]; va_list argptr; @@ -202,7 +220,7 @@ PF_centerprintf centerprint to a single client =============== */ -static void VARGS PFQ2_centerprintf (q2edict_t *ent, char *fmt, ...) +static void VARGS PFQ2_centerprintf (q2edict_t *ent, const char *fmt, ...) { char msg[1024]; va_list argptr; @@ -232,7 +250,7 @@ PF_error Abort the server with a game error =============== */ -static void VARGS PFQ2_error (char *fmt, ...) +static void VARGS PFQ2_error (const char *fmt, ...) { char msg[1024]; va_list argptr; @@ -250,7 +268,7 @@ PF_Configstring =============== */ -static void VARGS PFQ2_Configstring (int i, char *val) +static void VARGS PFQ2_Configstring (int i, const char *val) { if (i < 0 || i >= Q2MAX_CONFIGSTRINGS) Sys_Error ("configstring: bad index %i\n", i); @@ -258,53 +276,12 @@ static void VARGS PFQ2_Configstring (int i, char *val) if (!val) val = ""; - strcpy(sv.strings.configstring[i], val); + Z_Free((char*)sv.strings.configstring[i]); + sv.strings.configstring[i] = Z_StrDup(val); if (i == Q2CS_NAME) Q_strncpyz(sv.mapname, val, sizeof(sv.mapname)); -/* - //work out range - if (i >= Q2CS_LIGHTS && i < Q2CS_LIGHTS+Q2MAX_LIGHTSTYLES) - { - j = i - Q2CS_LIGHTS; - if (j < MAX_LIGHTSTYLES) - { - if (sv.lightstyles[j]) - Z_Free(sv.lightstyles[j]); - sv.lightstyles[j] = Z_Malloc(strlen(val)+1); - strcpy(sv.lightstyles[j], val); - } - } - else if (i >= Q2CS_MODELS && i < Q2CS_MODELS+Q2MAX_MODELS) - { - Q_strncpyS(sv.model_precache[i-Q2CS_MODELS], val, MAX_QPATH-1); - } - else if (i >= Q2CS_SOUNDS && i < Q2CS_SOUNDS+Q2MAX_SOUNDS) - { - Q_strncpyS(sv.sound_precache[i-Q2CS_SOUNDS], val, MAX_QPATH-1); - } - else if (i >= Q2CS_IMAGES && i < Q2CS_IMAGES+Q2MAX_IMAGES) - { - Q_strncpyS(sv.image_precache[i-Q2CS_IMAGES], val, MAX_QPATH-1); - } - else if (i == Q2CS_STATUSBAR) - { - if (sv.statusbar) - Z_Free(sv.statusbar); - sv.statusbar = Z_Malloc(strlen(val)+1); - strcpy(sv.statusbar, val); - } - else if (i == Q2CS_NAME) - { - Q_strncpyz(sv.mapname, val, sizeof(sv.name)); - } - else - { - Con_Printf("Ignoring configstring %i\n", i); - } -*/ - if (sv.state != ss_loading) { // send the update to everyone SZ_Clear (&sv.q2multicast); @@ -316,25 +293,69 @@ static void VARGS PFQ2_Configstring (int i, char *val) } } -static int SVQ2_FindIndex (char *name, int start, int max, qboolean create) +static int SVQ2_FindIndex (const char *name, int start, int max, int overflowtype) { int i; - int stringlength = MAX_QPATH; - char *strings = sv.strings.configstring[start]; - strings += stringlength; + const char *strings; if (!name || !name[0]) return 0; - for (i=1 ; i 1.0) Sys_Error ("SV_StartSound: volume = %f", volume); @@ -545,6 +559,11 @@ void VARGS SVQ2_StartSound (vec3_t origin, q2edict_t *entity, int channel, flags |= Q2SND_VOLUME; if (attenuation != Q2DEFAULT_SOUND_PACKET_ATTENUATION) flags |= Q2SND_ATTENUATION; + if (soundindex > 0xff) + { + flags |= Q2SND_LARGEIDX; + needext |= PEXT_SOUNDDBL; + } // the client doesn't know that bmodels have weird origins // the origin can also be explicitly set @@ -572,11 +591,25 @@ void VARGS SVQ2_StartSound (vec3_t origin, q2edict_t *entity, int channel, { VectorCopy (entity->s.origin, origin_v); } + + if (flags & Q2SND_POS) + { + for (i=0 ; i<3 ; i++) + if (-32768/8.0 > origin_v[i] || origin_v[i] > 32767/8.0) + { + flags |= Q2SND_LARGEPOS; + needext |= PEXT_FLOATCOORDS; + break; + } + } } MSG_WriteByte (&sv.q2multicast, svcq2_sound); MSG_WriteByte (&sv.q2multicast, flags); - MSG_WriteByte (&sv.q2multicast, soundindex); + if (flags & Q2SND_LARGEIDX) + MSG_WriteShort (&sv.q2multicast, soundindex); + else + MSG_WriteByte (&sv.q2multicast, soundindex); if (flags & Q2SND_VOLUME) MSG_WriteByte (&sv.q2multicast, volume*255); @@ -590,9 +623,18 @@ void VARGS SVQ2_StartSound (vec3_t origin, q2edict_t *entity, int channel, if (flags & Q2SND_POS) { - MSG_WriteCoord (&sv.q2multicast, origin[0]); - MSG_WriteCoord (&sv.q2multicast, origin[1]); - MSG_WriteCoord (&sv.q2multicast, origin[2]); + if (flags & Q2SND_LARGEPOS) + { + MSG_WriteFloat (&sv.q2multicast, origin[0]); + MSG_WriteFloat (&sv.q2multicast, origin[1]); + MSG_WriteFloat (&sv.q2multicast, origin[2]); + } + else + { + MSG_WriteCoord (&sv.q2multicast, origin[0]); + MSG_WriteCoord (&sv.q2multicast, origin[1]); + MSG_WriteCoord (&sv.q2multicast, origin[2]); + } } // if the sound doesn't attenuate,send it to everyone @@ -601,19 +643,9 @@ void VARGS SVQ2_StartSound (vec3_t origin, q2edict_t *entity, int channel, use_phs = false; if (channel & Q2CHAN_RELIABLE) - { - if (use_phs) - SV_Multicast (origin, MULTICAST_PHS_R); - else - SV_Multicast (origin, MULTICAST_ALL_R); - } + SV_MulticastProtExt(origin, use_phs?MULTICAST_PHS_R:MULTICAST_ALL_R, FULLDIMENSIONMASK, needext, 0); else - { - if (use_phs) - SV_Multicast (origin, MULTICAST_PHS); - else - SV_Multicast (origin, MULTICAST_ALL); - } + SV_MulticastProtExt(origin, use_phs?MULTICAST_PHS:MULTICAST_ALL, FULLDIMENSIONMASK, needext, 0); } static void VARGS PFQ2_StartSound (q2edict_t *entity, int channel, int sound_num, float volume, @@ -653,7 +685,7 @@ static int VARGS SVQ2_PointContents (vec3_t p) // return CM_PointContents(p, 0); } -static cvar_t *VARGS Q2Cvar_Get (char *var_name, char *value, int flags) +static cvar_t *VARGS Q2Cvar_Get (const char *var_name, const char *value, int flags) { cvar_t *var = Cvar_Get(var_name, value, flags, "Quake2 game variables"); if (!var) @@ -664,7 +696,7 @@ static cvar_t *VARGS Q2Cvar_Get (char *var_name, char *value, int flags) return var; } -cvar_t *VARGS Q2Cvar_Set (char *var_name, char *value) +cvar_t *VARGS Q2Cvar_Set (const char *var_name, const char *value) { cvar_t *var = Cvar_FindVar(var_name); if (!var) @@ -674,7 +706,7 @@ cvar_t *VARGS Q2Cvar_Set (char *var_name, char *value) } return Cvar_Set(var, value); } -cvar_t *VARGS Q2Cvar_ForceSet (char *var_name, char *value) +cvar_t *VARGS Q2Cvar_ForceSet (const char *var_name, const char *value) { cvar_t *var = Cvar_FindVar(var_name); if (!var) @@ -704,7 +736,7 @@ void VARGS SVQ2_ShutdownGameProgs (void) ge = NULL; } -static void VARGS AddCommandString(char *command) +static void VARGS AddCommandString(const char *command) { Cbuf_AddText(command, RESTRICT_LOCAL); } @@ -717,7 +749,7 @@ Init the game subsystem for a new map =============== */ -void VARGS Q2SCR_DebugGraph(float value, int color) +static void VARGS Q2SCR_DebugGraph(float value, int color) {return;} static void VARGS SVQ2_LinkEdict (q2edict_t *ent) diff --git a/engine/server/world.c b/engine/server/world.c index cd2b9d500..dab0516b1 100644 --- a/engine/server/world.c +++ b/engine/server/world.c @@ -2166,6 +2166,8 @@ static qboolean GenerateCollisionMesh_BSP(world_t *world, model_t *mod, wedict_t mesh_t *mesh; unsigned int numverts; unsigned int numindexes,i; + int *ptr_elements; + float *ptr_verts; numverts = 0; numindexes = 0; @@ -2192,8 +2194,8 @@ static qboolean GenerateCollisionMesh_BSP(world_t *world, model_t *mod, wedict_t Con_DPrintf("entity %i (classname %s) has no geometry\n", NUM_FOR_EDICT(world->progs, (edict_t*)ed), PR_GetString(world->progs, ed->v->classname)); return false; } - ed->ode.ode_element3i = (int*)BZ_Malloc(numindexes*sizeof(*ed->ode.ode_element3i)); - ed->ode.ode_vertex3f = (float*)BZ_Malloc(numverts*sizeof(vec3_t)); + ptr_elements = (int*)BZ_Malloc(numindexes*sizeof(*ptr_elements)); + ptr_verts = (float*)BZ_Malloc(numverts*sizeof(vec3_t)); numverts = 0; numindexes = 0; @@ -2207,13 +2209,13 @@ static qboolean GenerateCollisionMesh_BSP(world_t *world, model_t *mod, wedict_t { mesh = surf->mesh; for (i = 0; i < mesh->numvertexes; i++) - VectorSubtract(mesh->xyz_array[i], geomcenter, (ed->ode.ode_vertex3f + 3*(numverts+i))); + VectorSubtract(mesh->xyz_array[i], geomcenter, (ptr_verts + 3*(numverts+i))); for (i = 0; i < mesh->numindexes; i+=3) { //flip the triangles as we go - ed->ode.ode_element3i[numindexes+i+0] = numverts+mesh->indexes[i+2]; - ed->ode.ode_element3i[numindexes+i+1] = numverts+mesh->indexes[i+1]; - ed->ode.ode_element3i[numindexes+i+2] = numverts+mesh->indexes[i+0]; + ptr_elements[numindexes+i+0] = numverts+mesh->indexes[i+2]; + ptr_elements[numindexes+i+1] = numverts+mesh->indexes[i+1]; + ptr_elements[numindexes+i+2] = numverts+mesh->indexes[i+0]; } numverts += mesh->numvertexes; numindexes += i; @@ -2238,19 +2240,21 @@ static qboolean GenerateCollisionMesh_BSP(world_t *world, model_t *mod, wedict_t vec = mod->vertexes[edge->v[1]].position; } - VectorSubtract(vec, geomcenter, (ed->ode.ode_vertex3f + 3*(numverts+i))); + VectorSubtract(vec, geomcenter, (ptr_verts + 3*(numverts+i))); } for (i = 2; i < surf->numedges; i++) { //quake is backwards, not ode - ed->ode.ode_element3i[numindexes++] = numverts+i; - ed->ode.ode_element3i[numindexes++] = numverts+i-1; - ed->ode.ode_element3i[numindexes++] = numverts; + ptr_elements[numindexes++] = numverts+i; + ptr_elements[numindexes++] = numverts+i-1; + ptr_elements[numindexes++] = numverts; } numverts += surf->numedges; } } + ed->ode.ode_element3i = ptr_elements; + ed->ode.ode_vertex3f = ptr_verts; ed->ode.ode_numvertices = numverts; ed->ode.ode_numtriangles = numindexes/3; return true; @@ -2265,6 +2269,8 @@ static qboolean GenerateCollisionMesh_Alias(world_t *world, model_t *mod, wedict galiasinfo_t *inf; unsigned int surfnum = 0; entity_t re; + int *ptr_elements; + float *ptr_verts; numverts = 0; numindexes = 0; @@ -2287,8 +2293,8 @@ static qboolean GenerateCollisionMesh_Alias(world_t *world, model_t *mod, wedict Con_DPrintf("entity %i (classname %s) has no geometry\n", NUM_FOR_EDICT(world->progs, (edict_t*)ed), PR_GetString(world->progs, ed->v->classname)); return false; } - ed->ode.ode_element3i = (int*)BZ_Malloc(numindexes*sizeof(*ed->ode.ode_element3i)); - ed->ode.ode_vertex3f = (float*)BZ_Malloc(numverts*sizeof(vec3_t)); + ptr_elements = (int*)BZ_Malloc(numindexes*sizeof(*ptr_elements)); + ptr_verts = (float*)BZ_Malloc(numverts*sizeof(vec3_t)); numverts = 0; numindexes = 0; @@ -2298,13 +2304,13 @@ static qboolean GenerateCollisionMesh_Alias(world_t *world, model_t *mod, wedict { Alias_GAliasBuildMesh(&mesh, NULL, inf, surfnum++, &re, false); for (i = 0; i < mesh.numvertexes; i++) - VectorSubtract(mesh.xyz_array[i], geomcenter, (ed->ode.ode_vertex3f + 3*(numverts+i))); + VectorSubtract(mesh.xyz_array[i], geomcenter, (ptr_verts + 3*(numverts+i))); for (i = 0; i < mesh.numindexes; i+=3) { //flip the triangles as we go - ed->ode.ode_element3i[numindexes+i+0] = numverts+mesh.indexes[i+2]; - ed->ode.ode_element3i[numindexes+i+1] = numverts+mesh.indexes[i+1]; - ed->ode.ode_element3i[numindexes+i+2] = numverts+mesh.indexes[i+0]; + ptr_elements[numindexes+i+0] = numverts+mesh.indexes[i+2]; + ptr_elements[numindexes+i+1] = numverts+mesh.indexes[i+1]; + ptr_elements[numindexes+i+2] = numverts+mesh.indexes[i+0]; } numverts += inf->numverts; numindexes += inf->numindexes; @@ -2313,13 +2319,15 @@ static qboolean GenerateCollisionMesh_Alias(world_t *world, model_t *mod, wedict Alias_FlushCache(); //it got built using an entity on the stack, make sure other stuff doesn't get hurt. + ed->ode.ode_element3i = ptr_elements; + ed->ode.ode_vertex3f = ptr_verts; ed->ode.ode_numvertices = numverts; ed->ode.ode_numtriangles = numindexes/3; return true; } //Bullet has a fit if we have any degenerate triangles, so make sure we can determine some surface normal -static void World_Bullet_CleanupMesh(wedict_t *ed) +static void CollisionMesh_CleanupMesh(wedict_t *ed) { float *v1, *v2, *v3; vec3_t d1, d2, cr; @@ -2363,7 +2371,7 @@ qboolean QDECL World_GenerateCollisionMesh(world_t *world, model_t *mod, wedict_ if (result) { - World_Bullet_CleanupMesh(ed); + CollisionMesh_CleanupMesh(ed); if (ed->ode.ode_numtriangles > 0) return true; } diff --git a/engine/sw/sw.h b/engine/sw/sw.h index cf70e38f7..0cc11ff90 100644 --- a/engine/sw/sw.h +++ b/engine/sw/sw.h @@ -167,7 +167,7 @@ void SWBE_DrawMesh_List(shader_t *shader, int nummeshes, struct mesh_s **mesh, s void SWBE_DrawMesh_Single(shader_t *shader, struct mesh_s *meshchain, struct vbo_s *vbo, unsigned int be_flags); void SWBE_SubmitBatch(struct batch_s *batch); struct batch_s *SWBE_GetTempBatch(void); -void SWBE_DrawWorld(qboolean drawworld, qbyte *vis); +void SWBE_DrawWorld(batch_t **worldbatches, qbyte *vis); void SWBE_Init(void); void SWBE_GenBrushModelVBO(struct model_s *mod); void SWBE_ClearVBO(struct vbo_s *vbo); diff --git a/engine/sw/sw_backend.c b/engine/sw/sw_backend.c index a6c15c296..dd81cf080 100644 --- a/engine/sw/sw_backend.c +++ b/engine/sw/sw_backend.c @@ -6,6 +6,11 @@ vecV_t vertbuf[65535]; +swimage_t sw_nulltex = +{ + 1, 1, 0, 0, 0, 0 +}; + static struct { int foo; @@ -420,6 +425,7 @@ void SWBE_TransformVerticies(swvert_t *v, mesh_t *mesh) // v->colour[3] = mesh->colors4b_array[i][3]; } } + static void SWBE_DrawMesh_Internal(shader_t *shader, mesh_t *mesh, struct vbo_s *vbo, struct texnums_s *texnums, unsigned int be_flags) { wqcom_t *com; @@ -438,7 +444,10 @@ static void SWBE_DrawMesh_Internal(shader_t *shader, mesh_t *mesh, struct vbo_s { com = SWRast_BeginCommand(&commandqueue, WTC_TRIFAN, mesh->numvertexes*sizeof(swvert_t) + sizeof(com->trifan) - sizeof(com->trifan.verts)); - com->trifan.texture = texnums->base->ptr; + if (texnums->base) + com->trifan.texture = texnums->base->ptr; + else + com->trifan.texture = &sw_nulltex; com->trifan.numverts = mesh->numvertexes; SWBE_TransformVerticies(com->trifan.verts, mesh); @@ -449,7 +458,10 @@ static void SWBE_DrawMesh_Internal(shader_t *shader, mesh_t *mesh, struct vbo_s { com = SWRast_BeginCommand(&commandqueue, WTC_TRISOUP, (mesh->numvertexes*sizeof(swvert_t)) + sizeof(com->trisoup) - sizeof(com->trisoup.verts) + (sizeof(index_t)*mesh->numindexes)); - com->trisoup.texture = texnums->base->ptr; + if (texnums->base) + com->trisoup.texture = texnums->base->ptr; + else + com->trisoup.texture = &sw_nulltex; com->trisoup.numverts = mesh->numvertexes; com->trisoup.numidx = mesh->numindexes; @@ -530,19 +542,18 @@ static void SWBE_SubmitMeshesSortList(batch_t *sortlist) } } -void SWBE_SubmitMeshes (qboolean drawworld, batch_t **blist, int start, int stop) +void SWBE_SubmitMeshes (batch_t **worldbatches, batch_t **blist, int start, int stop) { - model_t *model = cl.worldmodel; int i; for (i = start; i <= stop; i++) { - if (drawworld) + if (worldbatches) { // if (i == SHADER_SORT_PORTAL && !r_noportals.ival && !r_refdef.recurse) -// SWBE_SubmitMeshesPortals(model->batches, blist[i]); +// SWBE_SubmitMeshesPortals(worldbatches, blist[i]); - SWBE_SubmitMeshesSortList(model->batches[i]); + SWBE_SubmitMeshesSortList(worldbatches[i]); } SWBE_SubmitMeshesSortList(blist[i]); } @@ -599,7 +610,7 @@ void SWBE_Set2D(void) SWBE_UpdateUniforms(); } -void SWBE_DrawWorld(qboolean drawworld, qbyte *vis) +void SWBE_DrawWorld(batch_t **worldbatches, qbyte *vis) { batch_t *batches[SHADER_SORT_COUNT]; @@ -621,7 +632,7 @@ void SWBE_DrawWorld(qboolean drawworld, qbyte *vis) shaderstate.curentity = NULL; SWBE_SelectEntity(&r_worldentity); - SWBE_SubmitMeshes(drawworld, batches, SHADER_SORT_PORTAL, SHADER_SORT_NEAREST); + SWBE_SubmitMeshes(worldbatches, batches, SHADER_SORT_PORTAL, SHADER_SORT_NEAREST); SWBE_Set2D(); } diff --git a/engine/sw/sw_image.c b/engine/sw/sw_image.c index e6dd30b74..b020a4320 100644 --- a/engine/sw/sw_image.c +++ b/engine/sw/sw_image.c @@ -103,11 +103,11 @@ qboolean SW_LoadTextureMips (texid_t tex, struct pendingtextureinfo *mips) imgdata[i+3] = 255; } - for (i = 0; i < mips->mipcount; i++) - if (mips->mip[i].needfree) - Z_Free(mips->mip[i].data); - if (mips->extrafree) - Z_Free(mips->extrafree); +// for (i = 0; i < mips->mipcount; i++) +// if (mips->mip[i].needfree) +// Z_Free(mips->mip[i].data); +// if (mips->extrafree) +// Z_Free(mips->extrafree); return true; } diff --git a/quakec/menusys/README.TXT b/quakec/menusys/README.TXT new file mode 100644 index 000000000..546dd3364 --- /dev/null +++ b/quakec/menusys/README.TXT @@ -0,0 +1,109 @@ +This directory contains my menusys mod (with menusys qc library). +This mod is provided to serve as a base mod for menuqc or menus within csqc. + +To add a new item onto the menus, you can open up the relavent menu/*.qc file, find an item of the same widget type, and dupe then edit that line for your new item, following the example given. +There's often a little extra logic used to center the menu vertically, so you may also need to tweak some numbers before anything is added. +You should NOT need to edit menusys/*.qc - you can if you want, but I wouldn't recommend it to beginners. +To add a new menu, open your .src file, find the concommandslist macro and add an extra line following the example given. This provides you with console command -> function mappings (as well as including the right qc file, if specified - hurrah for each menu being its own qc file). Then you can just copy+paste one of my menus and tweak it a little. + +Some more detailed info on menusys: +The menusys library is heirachical. It uses the concept of a 'desktop' object as the root node (in csqc, this would normally be expected to provide the draw calls for the actual game). +Then top-level windows/menus are embedded upon that desktop. And then sliders and buttons etc are normally embedded in those windows/menus. Note that you could directly embed your widgets on the desktop, but this can make using the mouse awkward as there's no easy way to release the mouse so you can interact with things. +Menusys also uses inheritance. Thus, you can easily create your own 'frame' object that inherits from mitem_frame, and thereby acts as a container for other widgets (for example). + + +DP compatibility: +This mod should also work in DP, however there are some exceptions. +Things not supported by DP obviously cannot be used with DP. This includes 3d models in menuqc, server browsers in csqc, etc. +My personal focus is on FTE, and thus you will likely want to research DP cvars in order to include any additional DP-only cvars that you may wish to include. +I've used a variable called 'dp_workarounds' in many places in order to work around the DP bugs / incompatibilities that are obvious enough for me to notice. +Feel free to provide diffs to fix anything that you notice (make sure they work in FTE too though). + + +MenuQC: +The presence of menu.dat causes the engine's built-in menus to become inaccessible. This means that any mod wishing to provide a single extra option on the menus needs to rewrite the entire thing from scratch... +And menuqc is somewhat mythical as a result - hopefully this mod will challenge that perception by making it slightly more accessible. + + +CSQC: +The CSQC version of this mod provides a 'full' set of the menus equivelent to the menuqc ones. This typically means that using both becomes redundant. Thus you may well wish to remove most of the menus from csqc so that you can use only the menuqc ones. If you want in-game menus like class customisation then you will likely want those menus in csqc in order to more easily communicate with your server. +Alternatively, if you set pr_csqcformenus to 1 in your default.cfg, then FTE will use your csprogs.dat instead of any menu.dat. This allows you to implement ALL your menus within a single module, which you may find to be a simplification. Note that this does not bypass cheat protections, and thus the csqc version will still generally not work on public servers. However, if you're making a stand-alone mod, this means that you can completely disregard menu.dat module (NOTE: this is only an option in FTE, not DP). +If your entire game is strictly single player, then look up my PureCSQC mod some time, which does not involve ssqc or even a server at all. Using PureCSQC as a base mod may help some headaches, however you'll need to merge the entry points yourself somehow (PureCSQC incorporates its own partial menu system more as an example of 2d input/drawing than actual menus). +When both modules are loaded, the csqc receives escape events and thus invokes its own menu. Because the menus are linked via console commands, you can easily implement the in-game menu in csqc, and all other menus in menuqc. + + +Your mod: +If you're creating a separate mod with its own special defaults then it is recommended to provide your own default.cfg +Your default.cfg should not only contain bind commands for your default key settings, and defaults for engine cvars, but you should also use the set or seta commands for your own cvars. +(note that if you use '-v -v' on fteqcc's commandline, it will generate a full autocvar listing from each module, including default values and some descriptions - just change any you wish to be flagged for archiving to use seta instead of set). + + +Keybinds: +One common thing modders want is new keybinds. +you can either open menus/options_keys.qc and edit the array there. +Alternatively, you can create a bindlist.lst file with quoted pairs on each line. If the command is a hyphen then its treated as a caption. +If there are too many keys, the frame menuitem thing will add a scrollbar for you, so go ahead and define a hundred different keys. + + +A Mod Options Menu: +The easy way to add a menu is to just copy+paste an existing one, like the video menu. +First, open up the appropriate src file and add a line like the following in the place that should be obvious: +cmd("m_mod", M_Options_Mod, menu/options_mod.qc) \ +That should create the console command for you, associate that with a function, AND include your menu's qc file into the progs. +Now copy menu/options_video.qc to the obvious name, and start editing it. +Okay, these lines: + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Video Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); +creates a fullscreen menu thing, they add it into the desktop, they give focus to it, and they make sure that it appears on top of everything else. +the spawn call is some special class stuff, with named field initialisations. The 'Video Options' thing isn't actually visible, but hey. The m_options string says which console command to issue when the menu is closed (by right-click or escape), this allows you to return to the previous menu. +And the following lines: + float h = 200 * 0.5; + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-160-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); +adds a static image. woo. +The RS_ constants are 'ReSize' constants. They say how to calculate the items top-left and bottom-right positions whenever the item is added or its parent is resized. The two vectors are additional offsets from the reference points that the RS flags give. +In this case, it just centers the image above some 200qu-high area in the middle of the screen. +And these lines are fun: + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); +they an invisible 'frame' that then holds the widgits that you're actually going to interact with. +While that sounds a little useless at first, this provides two things: 1) if there are two many items, the frame_hasscroll value causes it to include a scrollbar, allowing the items to be scrolled without affecting any of the artwork attached to the menu itself (like that title image). and 2) child items are clipped to this frame. +This line: + addmenuback(m); +quite simply just creates a widgit on the menu that darkens the background (insertion order affects draw order, this should be last-ish, so that it is drawn BEFORE any of the other widgits, such that it darkens everything other than the menu. +Lines like this: + fr.add(menuitemcombo_spawn(_("Video Width"), "vid_width", '280 8', _( + "0 \"Default\" " + "640 \"640\" " + "800 \"800\" " + "1024 \"1024\" " + "1280 \"1280\" " + "1920 \"1920\" " + )), fl, [0, pos], [0, 8]); pos += 8; +just adds a single widgit into the frame's area (its the same add method as before, with the same RS_ flags in the fl argument), so I only really need to explain menuitemcombo_spawn here. +menuitemcombo_spawn's first argument is the text to put next to it. The _("") weirdness provides some internationalisation thing that I'm far too lazy to go into right now (ask divverent or something for tools to use this properly). +The second argument is the 'cvar' that its attached to (actually, you can subclass the parent item and handle the get+set events yourself). +The third is the size of the widget. Generally they should be wide enough for both the text and the value. And about 8 vpixels high. +And finally, the fourth argument is the list of possible values, and the text that should be shown if that value is selected. Easy, right? +Alternatively, this line: + fr.add(menuitemslider_spawn(_("View Size"), "viewsize", '50 120 10', '280 8'), fl, [0, pos], [0, 8]); pos += 8; +spawns a 'slider' opject. Its basically the same as combos, except that the 3rd argument is the mix, max, and step-size values. Note that min+max can be inverted if you want (you should also negate the graduation in this case). +And this line: + fr.add(menuitemcheck_spawn(_("Show Framerate"), dp("showfps", "show_fps"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; +is how you add a checkbox. There is no 'third' argument in this case, just that fourth (okay, so it does have a third, but shush). +The 'dp' function you see there is part of how my code handles cvar differences between DP and other engines. If cvar_type reports that the first cvar exists, then it uses that. Otherwise it uses the second. Simple. +Unfortunately this only detects whether a cvar exists or not, and can't handle different values very well, which is what the vid_fullscreen hack is all about. Yes, that sort of thing can be really awkward... +And finally, in order to add a link to your new menu from the main or options menu or whereever, just open the qc file for that menu and copy one of the add+spawn lines to provide a textitem that invokes your menu's console command when clicked. +Easy. + + +Server browser: +QC-based server browsers have a significant limitation - they have no access to the system clipboard. You can't copy+paste ip numbers etc. +This is why I've decided to just utilise FTE's normal server browser even with menuqc active. This isn't an option in DP unfortunately, and your mod might be sufficiently different for the engine's to not work too well. Or you might want to exclude other servers... +If you want to tie your server browser to a specific gamedir, for instance, you can find the MOD_GAMEDIR define, set it to something, and enable it. +My code uses lots of preprocessor tricks too, sorry about that. You'll get used to them eventually, and hopefully understand why I used them too!... +You'll likely need to polish it quite a lot before its truely friendly. +If you do polish it, feel free to send me a patch (assuming it's not too specific to your mod). diff --git a/quakec/menusys/cs/entrypoints.qc b/quakec/menusys/cs/entrypoints.qc new file mode 100644 index 000000000..faad71512 --- /dev/null +++ b/quakec/menusys/cs/entrypoints.qc @@ -0,0 +1,112 @@ +mitem_desktop thedesktop; +void(mitem_desktop desktop) M_Pop = +{ + mitem it = desktop.item_kactivechild; + if (it) + it.item_remove(); +}; +void(mitem_desktop desktop) M_ToggleMenu = +{ + mitem it = desktop.item_kactivechild; + if (it) + it.item_remove(); + else + M_Main(desktop); +}; + + +float(string str) CSQC_ConsoleCommand = +{ + local float args; + args = tokenize(str); + + switch(argv(0)) + { +#define cmd(n,fnc,inc) case n: fnc(thedesktop); return TRUE; + concommandslist +#undef cmd + default: + return FALSE; + } + return TRUE; +}; + +float(float isnew) updateplayer = +{ + self.drawmask = 1; + if (self.entnum == player_localentnum) + self.renderflags = RF_EXTERNALMODEL; + else + self.renderflags = 0; + return TRUE; +}; +//void(float apilevel, string enginename, float engineversion) CSQC_Init = +//{ +// deltalisten("progs/player.mdl", updateplayer, 0); +//}; + +var float autocvar_dp_workarounds_allow = TRUE; +var float autocvar_dp_workarounds_force = FALSE; +void(float apilevel, string enginename, float engineversion) CSQC_Init = +{ + dprint(sprintf("CSQC: Running on \"%s\" ver=%g, api=%g\n", enginename, engineversion, apilevel)); + if (!apilevel) + dp_workarounds = autocvar_dp_workarounds_allow; + if (autocvar_dp_workarounds_force) + dp_workarounds = TRUE; + if (dp_workarounds) + print("DP-specific workarounds are enabled\n"); + + //make sure the engine knows what commands we want to handle +#define cmd(n,fnc,inc) registercommand(n); + concommandslist +#undef cmd + +// Hud_Init(); //make sure the hud images are precached properly, so there's no stalls. +}; + +float (float event, float parama, float paramb, float devid) CSQC_InputEvent = +{ + if (!thedesktop) + return event!=IE_KEYUP; + if (items_keypress(thedesktop, event, parama, paramb, devid)) + return TRUE; + + return FALSE; +}; + +/*The desktop object will not normally draw anything, but you can get the desktop object to do the drawing by overriding its 'drawgame' method. +The primary advantage of doing the drawing this way is that the menu system can properly handle mouse positions in 3d space with multiple views. The menu system also handles splitscreen efficiently. Note that the menu system will handle clearing the scene and adding entities before this function is called. +You could instead draw the game then draw the menusystem over the top, if you're more comfortable with that. +*/ +class mydesktop : mitem_desktop +{ + virtual void(float seat, vector minpos, vector size) drawgame = + { + setproperty(VF_DRAWENGINESBAR, TRUE); +/* + entity playerent = findfloat(world, entnum, player_localentnum); + if (playerent) + { + vector vorg = playerent.origin - playerent.gravitydir*getstat(STAT_VIEWHEIGHT); + vector vang = playerent.v_angle; + setproperty(VF_ORIGIN, vorg); + setproperty(VF_ANGLES, vang); + + makevectors(vang); + SetListener(vorg, v_forward, v_right, v_up, playerent.waterlevel==3); + } +*/ + + renderscene(); + }; +}; + +void(float width, float height, float do2d) CSQC_UpdateView = +{ + if (!thedesktop) + thedesktop = spawn(mydesktop); + items_draw(thedesktop); +}; +//void(float width, float height, float do2d) CSQC_UpdateView_Loading = CSQC_UpdateView; + \ No newline at end of file diff --git a/quakec/menusys/csprogs.src b/quakec/menusys/csprogs.src new file mode 100644 index 000000000..d97a5bd36 --- /dev/null +++ b/quakec/menusys/csprogs.src @@ -0,0 +1,47 @@ +#pragma progs_dat "../csprogs.dat" + +#define CSQC //select the module +//#pragma TARGET FTE + +#includelist +fteextensions.qc //also sets up system defs +menusys/mitems.qc //root item+basic types +menusys/mitem_desktop.qc //other sort of root item +menusys/mitem_exmenu.qc //fullscreen/exclusive menus +menusys/mitem_edittext.qc //simple text editor +menusys/mitem_tabs.qc //tabs +menusys/mitem_colours.qc //colour picker +menusys/mitem_checkbox.qc //checkbox (boolean thingies) +menusys/mitem_slider.qc //scrollbars +menusys/mitem_combo.qc //multiple-choice thingies +menusys/mitem_bind.qc //key binding thingie +menusys/mitem_spinnymodel.qc //rotating 3d models, used for art/theme. +#endlist + +//define the commands. +//cmd argments are: Name, Function, Sourcefile(may be empty) +//note that this list can be expanded in multiple places. +#define concommandslist \ + cmd("togglemenu", M_ToggleMenu, ) \ + cmd("m_main", M_Main, menu/main.qc) \ + cmd("m_pop", M_Pop, ) \ + cmd("m_options", M_Options, menu/options.qc) \ + cmd("m_keys", M_Options_Keys, menu/options_keys.qc) \ + cmd("m_basicopts", M_Options_Basic, menu/options_basic.qc) \ + cmd("m_video", M_Options_Video, menu/options_video.qc) \ + cmd("m_audio", M_Options_Audio, menu/options_audio.qc) \ + cmd("m_load", M_Load, menu/loadsave.qc) \ + cmd("m_save", M_Save, ) \ + cmd("m_quit", M_Quit, menu/quit.qc) \ + cmd("m_newgame", M_NewGame, menu/newgame.qc) \ + cmd("m_servers", M_Servers, menu/servers.qc) \ + cmd("m_preset", M_Preset, menu/presets.qc) + +//make sure all the right files are included +#define cmd(n,fnc,inc) inc +#includelist + concommandslist +#endlist +#undef cmd + +#include "cs/entrypoints.qc" diff --git a/quakec/menusys/fteextensions.qc b/quakec/menusys/fteextensions.qc new file mode 100644 index 000000000..f891ce313 --- /dev/null +++ b/quakec/menusys/fteextensions.qc @@ -0,0 +1,2783 @@ +/* +This file was automatically generated by FTE QuakeWorld v1.03 +This file can be regenerated by issuing the following command: +pr_dumpplatform +Available options: +-Ffte - target only FTE (optimations and additional extensions) +-Tnq - dump specifically NQ fields +-Tqw - dump specifically QW fields +-Tcs - dump specifically CSQC fields +-Tmenu - dump specifically menuqc fields +-Fdefines - generate #defines instead of constants +-Faccessors - use accessors instead of basic types via defines +-O - write to a different qc file +*/ +#pragma noref 1 +//#pragma flag enable logicops +#pragma warning error Q101 /*too many parms*/ +#pragma warning error Q105 /*too few parms*/ +#pragma warning error Q106 /*assignment to constant/lvalue*/ +#pragma warning error Q208 /*system crc unknown*/ +#pragma warning enable F301 /*non-utf-8 strings*/ +#pragma warning enable F302 /*uninitialised locals*/ +#if !defined(CSQC) && !defined(NQSSQC) && !defined(QWSSQC)&& !defined(MENU) +#ifdef QUAKEWORLD +#define QWSSQC +#else +#define NQSSQC +#endif +#endif +#if !defined(SSQC) && (defined(QWSSQC) || defined(NQSSQC)) +#define SSQC +#endif +#define FTE_PEXT_SETVIEW /* NQ's svc_setview works correctly even in quakeworld */ +#define DP_ENT_SCALE +#define FTE_PEXT_LIGHTSTYLECOL +#define DP_ENT_ALPHA +#define FTE_PEXT_VIEW2 +#define FTE_PEXT_ACURATETIMINGS +#define FTE_PEXT_SOUNDDBL +#define FTE_PEXT_FATNESS +#define DP_HALFLIFE_MAP +#define FTE_PEXT_TE_BULLET +#define FTE_PEXT_HULLSIZE +#define FTE_PEXT_MODELDBL +#define FTE_PEXT_ENTITYDBL +#define FTE_PEXT_ENTITYDBL2 +#define FTE_PEXT_FLOATCOORDS +#define FTE_PEXT_VWEAP +#define FTE_PEXT_Q2BSP +#define FTE_PEXT_Q3BSP +#define DP_ENT_COLORMOD +#define FTE_HEXEN2 +#define FTE_PEXT_SPAWNSTATIC +#define FTE_PEXT_CUSTOMTENTS +#define FTE_PEXT_256PACKETENTITIES +#define TEI_SHOWLMP2 +#define DP_GFX_QUAKE3MODELTAGS +#define FTE_PK3DOWNLOADS +#define PEXT_CHUNKEDDOWNLOADS +#define EXT_CSQC_SHARED +#define PEXT_DPFLAGS +#define EXT_CSQC +#define BX_COLOREDTEXT +#define DP_CON_SET /* The 'set' console command exists, and can be used to create/set cvars. */ +#define DP_CON_SETA /* The 'seta' console command exists, like the 'set' command, but also marks the cvar for archiving, allowing it to be written into the user's config. Use this command in your default.cfg file. */ +#define DP_EF_ADDITIVE +#define DP_EF_BLUE +#define DP_EF_FULLBRIGHT +#define DP_EF_NODEPTHTEST +#define DP_EF_NODRAW +#define DP_EF_NOSHADOW +#define DP_EF_RED +#define DP_ENT_CUSTOMCOLORMAP +#define DP_ENT_EXTERIORMODELTOCLIENT +#define DP_ENT_TRAILEFFECTNUM /* self.traileffectnum=particleeffectnum("myeffectname +"); can be used to attach a particle trail to the given server entity. This is equivelent to calling trailparticles each frame. */ +#define DP_ENT_VIEWMODEL +#define DP_GECKO_SUPPORT +#define DP_GFX_SKINFILES +#define DP_GFX_SKYBOX +#define DP_HALFLIFE_MAP_CVAR +#define DP_INPUTBUTTONS +#define DP_LIGHTSTYLE_STATICVALUE +#define DP_LITSUPPORT +#define DP_MD3_TAGSINFO +#define DP_MONSTERWALK /* MOVETYPE_WALK is valid on non-player entities. Note that only players receive acceleration etc in line with none/bounce/fly/noclip movetypes on the player, thus you will have to provide your own accelerations (incluing gravity) yourself. */ +#define DP_MOVETYPEBOUNCEMISSILE +#define DP_MOVETYPEFOLLOW +#define DP_QC_ASINACOSATANATAN2TAN +#define DP_QC_CHANGEPITCH +#define DP_QC_COPYENTITY +#define DP_QC_CRC16 +#define DP_QC_CVAR_DEFSTRING +#define DP_QC_CVAR_STRING +#define DP_QC_CVAR_TYPE +#define DP_QC_EDICT_NUM +#define DP_QC_ENTITYDATA +#define DP_QC_ETOS +#define DP_QC_FINDCHAIN +#define DP_QC_FINDCHAINFLOAT +#define DP_QC_FINDFLAGS +#define DP_QC_FINDCHAINFLAGS +#define DP_QC_FINDFLOAT +#define DP_QC_FS_SEARCH +#define DP_QC_GETSURFACE +#define DP_QC_GETSURFACEPOINTATTRIBUTE +#define DP_QC_MINMAXBOUND +#define DP_QC_MULTIPLETEMPSTRINGS /* Superseded by DP_QC_UNLIMITEDTEMPSTRINGS. Functions that return a temporary string will not overwrite/destroy previous temporary strings until at least 16 strings are returned (or control returns to the engine). */ +#define DP_QC_RANDOMVEC +#define DP_QC_RENDER_SCENE /* clearscene+addentity+setviewprop+renderscene+setmodel are available to menuqc. WARNING: DP advertises this extension without actually supporting it, FTE does actually support it. */ +#define DP_QC_SINCOSSQRTPOW +#define DP_QC_STRFTIME +#define DP_QC_STRING_CASE_FUNCTIONS +#define DP_QC_STRINGBUFFERS +#define DP_QC_STRINGCOLORFUNCTIONS +#define DP_QC_STRREPLACE +#define DP_QC_TOKENIZEBYSEPARATOR +#define DP_QC_TRACEBOX +#define DP_QC_TRACETOSS +#define DP_QC_TRACE_MOVETYPE_HITMODEL +#define DP_QC_TRACE_MOVETYPE_WORLDONLY +#define DP_QC_TRACE_MOVETYPES +#define DP_QC_UNLIMITEDTEMPSTRINGS /* Supersedes DP_QC_MULTIPLETEMPSTRINGS, superseded by FTE_QC_PERSISTENTTEMPSTRINGS. Specifies that all temp strings will be valid at least until the QCVM returns. */ +#define DP_QC_URI_ESCAPE +#define DP_QC_URI_GET +#define DP_QC_URI_POST +#define DP_QC_VECTOANGLES_WITH_ROLL +#define DP_QC_VECTORVECTORS +#define DP_QC_WHICHPACK +#define DP_QUAKE2_MODEL +#define DP_QUAKE2_SPRITE +#define DP_QUAKE3_MODEL +#define DP_REGISTERCVAR +#define DP_SND_STEREOWAV +#define DP_SND_OGGVORBIS +#define DP_SOLIDCORPSE +#define DP_SPRITE32 +#define DP_SV_BOTCLIENT +#define DP_SV_CLIENTCOLORS /* Provided only for compatibility with DP. */ +#define DP_SV_CLIENTNAME /* Provided only for compatibility with DP. */ +#define DP_SV_DRAWONLYTOCLIENT +#define DP_SV_DROPCLIENT /* Equivelent to quakeworld's stuffcmd(self,"disconnect\n"); hack */ +#define DP_SV_EFFECT +#define DP_SV_EXTERIORMODELFORCLIENT +#define DP_SV_NODRAWTOCLIENT +#define DP_SV_PLAYERPHYSICS /* Allows reworking parts of NQ player physics. USE AT OWN RISK - this necessitates NQ physics and is thus guarenteed to break prediction. */ +#define DP_SV_POINTSOUND +#define DP_SV_PRECACHEANYTIME /* Specifies that the various precache builtins can be called at any time. WARNING: precaches are sent reliably while sound events, modelindexes, and particle events are not. This can mean sounds and particles might not work the first time around, or models may take a while to appear (after the reliables are received and the model is loaded from disk). Always attempt to precache a little in advance in order to reduce these issues (preferably at the start of the map...) */ +#define DP_SV_SETCOLOR +#define DP_SV_SPAWNFUNC_PREFIX +#define DP_SV_WRITEPICTURE +#define DP_SV_WRITEUNTERMINATEDSTRING +#define DP_TE_BLOOD +#define DP_TE_CUSTOMFLASH +#define DP_TE_EXPLOSIONRGB +#define DP_TE_PARTICLECUBE +#define DP_TE_SMALLFLASH +#define DP_TE_SPARK +#define DP_TE_STANDARDEFFECTBUILTINS +#define DP_VIEWZOOM +#define EXT_BITSHIFT +#define EXT_DIMENSION_VISIBILITY +#define EXT_DIMENSION_PHYSICS +#define EXT_DIMENSION_GHOST +#define FRIK_FILE +#define FTE_CALLTIMEOFDAY +#define FTE_CSQC_ALTCONSOLES_WIP +#define FTE_CSQC_BASEFRAME /* Specifies that .basebone, .baseframe, .baselerpfrac, etc exist. These fields affect all bones in the entity's model with a lower index than the .basebone field, allowing you to give separate control to the legs of a skeletal model, without affecting the torso animations. */ +#define FTE_CSQC_SERVERBROWSER +#define FTE_CSQC_SKELETONOBJECTS +#define FTE_CSQC_RENDERTARGETS_WIP /* VF_DESTCOLOUR etc exist and are supported */ +#define FTE_ENT_SKIN_CONTENTS /* self.skin = CONTENTS_WATER; makes a brush entity into water. use -16 for a ladder. */ +#define FTE_ENT_UNIQUESPAWNID +#define FTE_EXTENDEDTEXTCODES +#define FTE_FORCESHADER +#define FTE_FORCEINFOKEY +#define FTE_GFX_QUAKE3SHADERS +#define FTE_ISBACKBUFFERED /* Allows you to check if a client has too many reliable messages pending. */ +#define FTE_MEMALLOC /* Allows dynamically allocating memory. Use pointers to access this memory. Memory will not be saved into saved games. */ +#define FTE_MEDIA_AVI /* playfilm command supports avi files. */ +#define FTE_MEDIA_CIN /* playfilm command supports q2 cin files. */ +#define FTE_MEDIA_ROQ /* playfilm command supports q3 roq files. */ +#define FTE_MULTIPROGS /* Multiple progs.dat files can be loaded inside the same qcvm. */ +#define FTE_MULTITHREADED +#define FTE_NPCCHAT +#define FTE_PART_SCRIPT /* Specifies that the r_particledesc cvar can be used to select a list of particle effects to load from particles/*.cfg, the format of which is documented elsewhere. */ +#define FTE_PART_NAMESPACES /* Specifies that the engine can use foo.bar to load effect foo from particle description bar. When used via ssqc, this should cause the client to download whatever effects as needed. */ +#define FTE_PART_NAMESPACE_EFFECTINFO /* Specifies that effectinfo.bar can load effects from effectinfo.txt for DP compatibility. */ +#define FTE_QC_CHECKCOMMAND /* Provides a way to test if a console command exists, and whether its a command/alias/cvar. Does not say anything about the expected meanings of any arguments or values. */ +#define FTE_QC_CHECKPVS +#define FTE_QC_HARDWARECURSORS /* setcursormode exists in both csqc+menuqc, and accepts additional arguments to specify a cursor image to use when this module has focus. If the image exceeds hardware limits, it will be emulated using regular draws - this at least still avoids conflicting cursors. */ +#define FTE_QC_HASHTABLES +#define FTE_QC_INTCONV +#define FTE_QC_MATCHCLIENTNAME +#define FTE_QC_PAUSED +#define FTE_QC_PERSISTENTTEMPSTRINGS /* Supersedes DP_QC_MULTIPLETEMPSTRINGS. Temp strings are garbage collected automatically, and do not expire while they're still in use. This makes strzone redundant. */ +#define FTE_QC_RAGDOLL_WIP +#define FTE_QC_SENDPACKET +#define FTE_QC_TRACETRIGGER +#define FTE_QUAKE2_CLIENT /* This engine is able to act as a quake2 client */ +#define FTE_QUAKE2_SERVER /* This engine is able to act as a quake2 server */ +#define FTE_QUAKE3_CLIENT /* This engine is able to act as a quake3 client */ +#define FTE_QUAKE3_SERVER /* This engine is able to act as a quake3 server */ +#define FTE_SOLID_LADDER +#define FTE_SQL +#define FTE_STRINGS +#define FTE_SV_POINTPARTICLES /* Specifies that particleeffectnum, pointparticles, and trailparticles exist in ssqc as well as csqc. particleeffectnum acts as a precache, allowing ssqc values to be networked up with csqc for use. Use in combination with FTE_PART_SCRIPT+FTE_PART_NAMESPACES to use custom effects. This extension is functionally identical to the DP version, but avoids any misplaced assumptions about the format of the client's particle descriptions. */ +#define FTE_SV_REENTER +#define FTE_TE_STANDARDEFFECTBUILTINS +#define FTE_TERRAIN_MAP /* This engine supports .hmp files, as well as terrain embedded within bsp files. */ +#define FTE_RAW_MAP /* This engine supports directly loading .map files, as well as realtime editing of the various brushes. */ +#define KRIMZON_SV_PARSECLIENTCOMMAND +#define NEH_CMD_PLAY2 +#define NEH_RESTOREGAME +#define QSG_CVARSTRING +#define QW_ENGINE +#define QWE_MVD_RECORD +#define TEI_MD3_MODEL +#define ZQ_MOVETYPE_FLY +#define ZQ_MOVETYPE_NOCLIP +#define ZQ_MOVETYPE_NONE +#define ZQ_VWEP +#define ZQ_QC_STRINGS +#define strbuf float +#define searchhandle float +#define hashtable float +#define infostring string +#define filestream float +entity self; /* The magic me */ +#if defined(CSQC) || defined(SSQC) +entity other; /* Valid in touch functions, this is the entity that we touched. */ +entity world; /* The null entity. Hurrah. Readonly after map spawn time. */ +float time; /* The current game time. Stops when paused. */ +#endif +#ifdef CSQC +float cltime; /* A local timer that ticks relative to local time regardless of latency, packetloss, or pause. */ +#endif +#if defined(CSQC) || defined(SSQC) +float frametime; /* The time since the last physics/render/input frame. */ +#endif +#ifdef CSQC +float player_localentnum; /* This is entity number the player is seeing from/spectating, or the player themself, can change mid-map. */ +float player_localnum; /* The 0-based player index, valid for getplayerkeyvalue calls. */ +float maxclients; /* Maximum number of player slots on the server. */ +float clientcommandframe; /* This is the input-frame sequence. frames < clientcommandframe have been sent to the server. frame==clientcommandframe is still being generated and can still change. */ +float servercommandframe; /* This is the input-frame that was last acknowledged by the server. Input frames greater than this should be applied to the player's entity. */ +#endif +#if defined(QWSSQC) +entity newmis; /* A named entity that should be run soon, to reduce the effects of latency. */ +#endif +#ifdef SSQC +float force_retouch; /* If positive, causes all entities to check for triggers. */ +#endif +#if defined(CSQC) || defined(SSQC) +string mapname; /* The short name of the map. */ +#endif +#if defined(NQSSQC) +float deathmatch; +float coop; +float teamplay; +#endif +#ifdef SSQC +float serverflags; +float total_secrets; +float total_monsters; +float found_secrets; +float killed_monsters; +float parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16; +#endif +#ifdef CSQC +float intermission; +#endif +#if defined(CSQC) || defined(SSQC) +vector v_forward, v_up, v_right; +#endif +#ifdef CSQC +vector view_angles; /* +x=DOWN */ +#endif +#if defined(CSQC) || defined(SSQC) +float trace_allsolid, trace_startsolid, trace_fraction; +vector trace_endpos, trace_plane_normal; +float trace_plane_dist; +entity trace_ent; +float trace_inopen; +float trace_inwater; +#endif +#ifdef CSQC +float input_timelength; +vector input_angles; /* +x=DOWN */ +vector input_movevalues; +float input_buttons; +float input_impulse; +#endif +#ifdef SSQC +entity msg_entity; +void() main; +void() StartFrame; +void() PlayerPreThink; +void() PlayerPostThink; +void() ClientKill; +void() ClientConnect; +void() PutClientInServer; +void() ClientDisconnect; +void() SetNewParms; +void() SetChangeParms; +#endif +void end_sys_globals; +#if defined(CSQC) || defined(SSQC) +.float modelindex; /* This is the model precache index for the model that was set on the entity, instead of having to look up the model according to the .model field. Use setmodel to change it. */ +.vector absmin; /* Set by the engine when the entity is relinked (by setorigin, setsize, or setmodel). This is in world coordinates. */ +.vector absmax; /* Set by the engine when the entity is relinked (by setorigin, setsize, or setmodel). This is in world coordinates. */ +#endif +#ifdef SSQC +.float ltime; /* On MOVETYPE_PUSH entities, this is used as an alternative to the 'time' global, and .nextthink is synced to this instead of time. This allows time to effectively freeze if the entity is blocked, ensuring the think happens when the entity reaches the target point instead of randomly. */ +#endif +#ifdef CSQC +.float entnum; /* The entity number as its known on the server. */ +.float drawmask; /* Acts as a filter in the addentities call. */ +.float() predraw; /* Called by addentities after the filter and before the entity is actually drawn. Do your interpolation and animation in here. Should return one of the PREDRAW_* constants. */ +#endif +#if defined(QWSSQC) +.float lastruntime; /* This field used to be used to avoid running an entity multiple times in a single frame due to quakeworld's out-of-order thinks. It is no longer used by FTE due to precision issues, but may still be updated for compatibility reasons. */ +#endif +#if defined(CSQC) || defined(SSQC) +.float movetype; /* Describes how the entity moves. One of the MOVETYPE_ constants. */ +.float solid; /* Describes whether the entity is solid or not, and any special properties infered by that. Must be one of the SOLID_ constants */ +.vector origin; /* The current location of the entity in world space. Inline bsp entities (ie: ones placed by a mapper) will typically have a value of '0 0 0' in their neutral pose, as the geometry is offset from that. It is the reference point of the entity rather than the center of its geometry, for non-bsp models, this is often not a significant distinction. */ +.vector oldorigin; /* This is often used on players to reset the player back to where they were last frame if they somehow got stuck inside something due to fpu precision. Never change a player's oldorigin field to inside a solid, because that might cause them to become pemanently stuck. */ +.vector velocity; /* The direction and speed that the entity is moving in world space. */ +.vector angles; /* The eular angles the entity is facing in, in pitch, yaw, roll order. Due to a legacy bug, mdl/iqm/etc formats use +x=UP, bsp/spr/etc formats use +x=DOWN. */ +.vector avelocity; /* The amount the entity's angles change by each frame. Note that this is direct eular angles, and thus the angular change is non-linear and often just looks buggy. */ +#endif +#ifdef CSQC +.float pmove_flags; +#endif +#if defined(NQSSQC) +.vector punchangle; +#endif +#if defined(CSQC) || defined(SSQC) +.string classname; /* Identifies the class/type of the entity. Useful for debugging, also used for loading, but its value is not otherwise significant to the engine, this leaves the mod free to set it to whatever it wants and randomly test strings for values in whatever inefficient way it chooses fit. */ +#endif +#ifdef CSQC +.float renderflags; +#endif +#if defined(CSQC) || defined(SSQC) +.string model; /* The model name that was set via setmodel, in theory. Often, this is cleared to null to prevent the engine from being seen by clients while not changing modelindex. This behaviour allows inline models to remain solid yet be invisible. */ +.float frame; /* The current frame the entity is meant to be displayed in. In CSQC, note the lerpfrac and frame2 fields as well. if it specifies a framegroup, the framegroup will autoanimate in ssqc, but not in csqc. */ +#endif +#ifdef CSQC +.float frame1time; /* The absolute time into the animation/framegroup specified by .frame. */ +.float frame2; /* The alternative frame. Visible only when lerpfrac is set to 1. */ +.float frame2time; /* The absolute time into the animation/framegroup specified by .frame2. */ +.float lerpfrac; /* If 0, use frame1 only. If 1, use frame2 only. Mix them together for values between. */ +#endif +#if defined(CSQC) || defined(SSQC) +.float skin; /* The skin index to use. on a bsp entity, setting this to 1 will switch to the 'activated' texture instead. A negative value will be understood as a replacement contents value, so setting it to CONTENTS_WATER will make a movable pool of water. */ +.float effects; /* Lots of random flags that change random effects. See EF_* constants. */ +.vector mins; /* The minimum extent of the model (ie: the bottom-left coordinate relative to the entity's origin). Change via setsize. May also be changed by setmodel. */ +.vector maxs; /* like mins, but in the other direction. */ +.vector size; /* maxs-mins. Updated when the entity is relinked (by setorigin, setsize, setmodel) */ +.void() touch; +#endif +#ifdef SSQC +.void() use; +#endif +#if defined(CSQC) || defined(SSQC) +.void() think; +.void() blocked; +.float nextthink; /* The time at which the entity is next scheduled to fire its think event. For MOVETYPE_PUSH entities, this is relative to that entity's ltime field, for all other entities it is relative to the time gloal. */ +#endif +#ifdef SSQC +.entity groundentity; +.float health; +.float frags; +.float weapon; +.string weaponmodel; +.float weaponframe; +.float currentammo; +.float ammo_shells; +.float ammo_nails; +.float ammo_rockets; +.float ammo_cells; +.float items; +.float takedamage; +#endif +#if defined(CSQC) || defined(SSQC) +.entity chain; +#endif +#ifdef SSQC +.float deadflag; +.vector view_ofs; +.float button0; +.float button1; +.float button2; +.float impulse; +.float fixangle; +.vector v_angle; /* The angles a player is viewing. +x is DOWN (pitch, yaw, roll) */ +#endif +#if defined(NQSSQC) +.float idealpitch; +#endif +#ifdef SSQC +.string netname; +#endif +#if defined(CSQC) || defined(SSQC) +.entity enemy; +.float flags; +.float colormap; +#endif +#ifdef SSQC +.float team; +.float max_health; +.float teleport_time; /* While active, prevents the player from using the +back command, also blocks waterjumping. */ +.float armortype; +.float armorvalue; +.float waterlevel; +.float watertype; +.float ideal_yaw; +.float yaw_speed; +.entity aiment; +.entity goalentity; +.float spawnflags; +.string target; +.string targetname; +.float dmg_take; +.float dmg_save; +.entity dmg_inflictor; +#endif +#if defined(CSQC) || defined(SSQC) +.entity owner; +#endif +#ifdef SSQC +.vector movedir; +.string message; +.float sounds; +.string noise; +.string noise1; +.string noise2; +.string noise3; +#endif +void end_sys_fields; +#ifdef MENU +float time; /* The current local time. Increases while paused. */ +#endif +#ifdef SSQC +float input_timelength; +vector input_angles; /* +x=DOWN */ +vector input_movevalues; +float input_buttons; +float input_impulse; +#endif +#ifdef CSQC +vector input_cursor_screen; +vector input_cursor_trace_start; +vector input_cursor_trace_endpos; +float input_cursor_trace_entnum; +#endif +#if defined(CSQC) || defined(SSQC) +int trace_brush_id; +int trace_brush_faceid; +vector global_gravitydir = '0 0 -1'; /* The direction gravity should act in if not otherwise specified per entity. */ +int serverid; /* The unique id of this server within the server cluster. */ +#endif +#if defined(NQSSQC) +.float lastruntime; /* This field used to be used to avoid running an entity multiple times in a single frame due to quakeworld's out-of-order thinks. It is no longer used by FTE due to precision issues, but may still be updated for compatibility reasons. */ +#endif +#if defined(CSQC) || defined(QWSSQC) +.vector punchangle; +#endif +#if defined(CSQC) || defined(SSQC) +.float gravity; +.float hull; /* Overrides the hull used by the entity for walkmove/movetogoal and not traceline/tracebox. */ +.entity movechain; /* This is a linked list of entities which will be moved whenever this entity moves, logically they are attached to this entity. */ +.void() chainmoved; /* Called when the entity is moved as a result of being part of another entity's .movechain */ +.void(float old, float new) contentstransition; /* This function is called when the entity moves between water and air. If specified, default splash sounds will be disabled allowing you to provide your own. */ +.float dimension_solid; /* This is the bitmask of dimensions which the entity is solid within. */ +.float dimension_hit; /* This is the bitmask of dimensions which the entity will be blocked by. If other.dimension_solid & self.dimension_hit, our traces will impact and not proceed. If its false, the traces will NOT impact, allowing self to pass straight through. */ +.float hitcontentsmask; +.float scale; /* Multiplier that resizes the entity. 1 is normal sized, 2 is double sized. scale 0 is remapped to 1. In SSQC, this is limited to 1/16th precision, with a maximum just shy of 16. */ +.float fatness; /* How many QuakeUnits to push the entity's verticies along their normals by. */ +.float alpha; /* The transparency of the entity. 1 means opaque, 0.0001 means virtually invisible. 0 is remapped to 1, for compatibility. */ +.float modelflags; /* Used to override the flags set in the entity's model. Should be set according to the MF_ constants. Use effects|=EF_NOMODELFLAGS to ignore the model's flags completely. The traileffectnum field is more versatile. */ +.void() customphysics; /* Called once each physics frame, overriding the entity's .movetype field and associated logic. You'll probably want to use tracebox to move it through the world. Be sure to call .think as appropriate. */ +.entity tag_entity; +.float tag_index; +.float skeletonindex; /* This object serves as a container for the skeletal bone states used to override the animation data. */ +.vector colormod; /* Provides a colour tint for the entity. */ +.vector glowmod; +.vector gravitydir; /* Specifies the direction in which gravity acts. Must be normalised. '0 0 0' also means down. Use '0 0 1' if you want the player to be able to run on ceilings. */ +.vector(vector org, vector ang) camera_transform; /* Provides portal transform information for portal surfaces attached to this entity. Also used to open up pvs in ssqc. */ +#endif +#ifdef SSQC +.float pmove_flags; +#endif +#if defined(CSQC) || defined(SSQC) +.float geomtype; +.float friction; +.float erp; +.float jointtype; +.float mass; +.float bouncefactor; +.float bouncestop; +#endif +#if defined(CSQC) || defined(QWSSQC) +.float idealpitch; +#endif +#if defined(CSQC) || defined(SSQC) +.float pitch_speed; +#endif +#ifdef SSQC +.float maxspeed; +.float items2; +.entity view2; +.vector movement; /* These are the directions that the player is currently trying to move in (ie: which +forward/+moveright/+moveup etc buttons they have held), expressed relative to that player's angles. Order is forward, right, up. */ +.float vw_index; +.entity nodrawtoclient; /* This entity will not be sent to the player named by this field. They will be invisible and not emit dlights/particles. Does not work in MVD-recorded game. */ +.entity drawonlytoclient; /* This entity will be sent *only* to the player named by this field. To other players they will be invisible and not emit dlights/particles. Does not work in MVD-recorded game. */ +.entity viewmodelforclient; /* This entity will be sent only to the player named by this field, and this entity will be attached to the player's view as an additional weapon model. */ +.entity exteriormodeltoclient; /* This entity will be invisible to the player named by this field, except in mirrors or mirror-like surfaces, where it will be visible as normal. It may still cast shadows as normal, and generate lights+particles, depending on client settings. Does not affect how other players see the entity. */ +.float button3; /* DP_INPUTBUTTONS (note in qw, we set 1 to equal 3, to match zquake/fuhquake/mvdsv) */ +.float button4; +.float button5; +.float button6; +.float button7; +.float button8; +.float viewzoom; +.float glow_size; +.float glow_color; +.float glow_trail; +.float traileffectnum; /* This should be set to the result of particleeffectnum, in order to attach a custom trail effect to an entity as it moves. */ +.vector color; /* This affects the colour of realtime lights that were enabled via the pflags field. */ +.float light_lev; /* This is the radius of an entity's light. This is not normally used by the engine, but is used for realtime lights (ones that are enabled with the pflags field). */ +.float style; /* Used by the light util to decide how an entity's light should animate. On an entity with pflags set, this also affects realtime lights. */ +.float pflags; /* Realtime lighting flags */ +.float clientcolors; +.float dimension_see; /* This is the dimension mask (bitfield) that the client is allowed to see. Entities and events not in this dimension mask will be invisible. */ +.float dimension_seen; /* This is the dimension mask (bitfield) that the client is visible within. Clients that cannot see this dimension mask will not see this entity. */ +.float dimension_ghost; /* If this entity is visible only within these dimensions, it will become transparent, as if a ghost. */ +.float dimension_ghost_alpha; /* If this entity is subject to dimension_ghost, this is the scaler for its alpha value. If 0, 0.5 will be used instead. */ +.float playerclass; +.float drawflags; /* Various flags that affect lighting values and scaling. Typically set to 96 in quake for proper compatibility with DP_QC_SCALE. */ +.float hasted; +.float light_level; /* Used by hexen2 to indicate the light level where the player is standing. */ +.float abslight; /* Allows overriding light levels. Use drawflags to state that this field should actually be used. */ +.float(entity playerent, float changedflags) SendEntity; /* Called by the engine whenever an entity needs to be (re)sent to a client's csprogs, either because SendFlags was set or because data was lost. Must write its data to the MSG_ENTITY buffer. Will be called at the engine's leasure. */ +.float SendFlags; /* Indicates that something in the entity has been changed, and that it needs to be updated to all players that can see it. The engine will clear it at some point, with the cleared bits appearing in the 'changedflags' argument of the SendEntity method. */ +.float Version; /* Obsolete, set a SendFlags bit instead. */ +.float pvsflags; /* Reconfigures when the entity is visible to clients */ +.float uniquespawnid; /* Incremented by 1 whenever the entity is respawned. Persists across remove calls, for when the two-second grace period is insufficient. */ +.float() customizeentityforclient; /* Called just before an entity is sent to a client (non-csqc protocol). This gives you a chance to tailor 'self' according to what 'other' should see. */ +#endif +#ifdef CSQC +.float frame3; /* Some people just don't understand how to use framegroups... */ +.float frame4; +.float lerpfrac3; +.float lerpfrac4; +.float forceshader; /* Contains a shader handle used to replace all surfaces upon the entity. */ +.float baseframe; /* See basebone */ +.float baseframe2; /* See basebone */ +.float baseframe1time; /* See basebone */ +.float baseframe2time; /* See basebone */ +.float baselerpfrac; /* See basebone */ +.float basebone; /* The base* frame animations are equivelent to their non-base versions, except that they only affect bone numbers below the 'basebone' value. This means that the base* animation can affect the legs of a skeletal model independantly of the normal animation fields affecting the torso area. For more complex animation than this, use skeletal objects. */ +#endif +void(float reqid, float responsecode, string resourcebody) URI_Get_Callback; /* Called as an eventual result of the uri_get builtin. */ +#ifdef SSQC +void() SpectatorConnect; /* Called when a spectator joins the game. */ +void() SpectatorDisconnect; /* Called when a spectator disconnects from the game. */ +void() SpectatorThink; /* Called each frame for each spectator. */ +void(string cmd) SV_ParseClientCommand; /* Provides QC with a way to intercept 'cmd foo' commands from the client. Very handy. Self will be set to the sending client, while the 'cmd' argument can be tokenize()d and each element retrieved via argv(argno). Unrecognised cmds MUST be passed on to the clientcommand builtin. */ +void(string dest, string from, string cmd, string info) SV_ParseClusterEvent; /* Part of cluster mode. Handles cross-node events that were sent via clusterevent, on behalf of the named client. */ +float(string sender, string body) SV_ParseConnectionlessPacket; /* Provides QC with a way to communicate between servers, or with client server browsers. Sender is the sender's ip. Body is the body of the message. You'll need to add your own password/etc support as required. Self is not valid. */ +void(float pauseduration) SV_PausedTic; /* For each frame that the server is paused, this function will be called to give the gamecode a chance to unpause the server again. the pauseduration argument says how long the server has been paused for (the time global is frozen and will not increment while paused). Self is not valid. */ +float(float newstatus) SV_ShouldPause; /* Called to give the qc a change to block pause/unpause requests. Return false for the pause request to be ignored. newstatus is 1 if the user is trying to pause the game. For the duration of the call, self will be set to the player who tried to pause, or to world if it was triggered by a server-side event. */ +void() SV_RunClientCommand; /* Called each time a player movement packet was received from a client. Self is set to the player entity which should be updated, while the input_* globals specify the various properties stored within the input packet. The contents of this function should be somewaht identical to the equivelent function in CSQC, or prediction misses will occur. If you're feeling lazy, you can simply call 'runstandardplayerphysics' after modifying the inputs. */ +void() SV_AddDebugPolygons; /* Called each video frame. This is the only place where ssqc is allowed to call the R_BeginPolygon/R_PolygonVertex/R_EndPolygon builtins. This is exclusively for debugging, and will break in anything but single player as it will not be called if the engine is not running both a client and a server. */ +void() SV_PlayerPhysics; /* Legacy method to tweak player input that does not reliably work with prediction (prediction WILL break). Mods that care about prediction should use SV_RunClientCommand instead. If pr_no_playerphysics is set to 1, this function will never be called, which will either fix prediction or completely break player movement depending on whether the feature was even useful. */ +void() EndFrame; /* Called after non-player entities have been run at the end of the physics frame. Player physics is performed out of order and can/will still occur between EndFrame and BeginFrame. */ +string(string addr, string uinfo, string features) SV_CheckRejectConnection; /* Called to give the mod a chance to ignore connection requests based upon client protocol support or other properties. Use infoget to read the uinfo and features arguments. */ +#endif +#ifdef CSQC +void(float apilevel, string enginename, float engineversion) CSQC_Init; /* Called at startup. enginename and engineversion are arbitary hints and can take any form. enginename should be consistant between revisions, but this cannot truely be relied upon. */ +void() CSQC_WorldLoaded; /* Called after model+sound precaches have been executed. Gives a chance for the qc to read the entity lump from the bsp. */ +void() CSQC_Shutdown; /* Specifies that the csqc is going down. Save your persistant settings here. */ +void(float vwidth, float vheight, float notmenu) CSQC_UpdateView; /* Called every single video frame. The CSQC is responsible for rendering the entire screen. */ +void(float vwidth, float vheight, float notmenu) CSQC_UpdateViewLoading; /* Alternative to CSQC_UpdateView, called when the engine thinks there should be a loading screen. If present, will inhibit the engine's normal loading screen, deferring to qc to draw it. */ +void(string msg) CSQC_Parse_StuffCmd; /* Gives the CSQC a chance to intercept stuffcmds. Use the tokenize builtin to parse the message. Unrecognised commands would normally be localcmded, but its probably better to drop unrecognised stuffcmds completely. */ +float(string msg) CSQC_Parse_CenterPrint; /* Gives the CSQC a chance to intercept centerprints. Return true if you wish the engine to otherwise ignore the centerprint. */ +float(float save, float take, vector inflictororg) CSQC_Parse_Damage; /* Called as a result of player.dmg_save or player.dmg_take being set on the server. +Return true to completely inhibit the engine's colour shift and damage rolls, allowing you to do your own thing. +You can use punch_roll += (normalize(inflictororg-player.origin)*v_right)*(take+save)*autocvar_v_kickroll; as a modifier for the roll angle should the player be hit from the side, and slowly fade it away over time. */ +void(string printmsg, float printlvl) CSQC_Parse_Print; /* Gives the CSQC a chance to intercept sprint/bprint builtin calls. CSQC should filter by the client's current msg setting and then pass the message on to the print command, or handle them itself. */ +void() CSQC_Parse_Event; /* Called when the client receives an SVC_CGAMEPACKET. The csqc should read the data or call the error builtin if it does not recognise the message. */ +float(float evtype, float scanx, float chary, float devid) CSQC_InputEvent; /* Called whenever a key is pressed, the mouse is moved, etc. evtype will be one of the IE_* constants. The other arguments vary depending on the evtype. Key presses are not guarenteed to have both scan and unichar values set at the same time. */ +__used void() CSQC_Input_Frame; /* Called just before each time clientcommandframe is updated. You can edit the input_* globals in order to apply your own player inputs within csqc, which may allow you a convienient way to pass certain info to ssqc. */ +void(void) CSQC_RendererRestarted; /* Called by the engine after the video was restarted. This serves to notify the CSQC that any render targets that it may have cached were purged, and will need to be regenerated. */ +float(string cmd) CSQC_ConsoleCommand; /* Called if the user uses any console command registed via registercommand. */ +float(string text, string info) CSQC_ConsoleLink; /* Called if the user clicks a ^[text\infokey\infovalue^] link. Use infoget to read/check each supported key. Return true if you wish the engine to not attempt to handle the link itself. */ +void(float isnew) CSQC_Ent_Update; +void() CSQC_Ent_Remove; +float(float entnum, float channel, string soundname, float vol, float attenuation, vector pos, float pitchmod, float flags) CSQC_Event_Sound; +float(string resname, string restype) CSQC_LoadResource; /* Called each time some resource is being loaded. CSQC can invoke various draw calls to provide a loading screen, until WorldLoaded is called. */ +float() CSQC_Parse_TempEntity; /* Please don't use this. Use CSQC_Parse_Event and multicasts instead. */ +#endif +#if defined(CSQC) || defined(MENU) +void(string cmdtext) GameCommand; +#endif +#if defined(CSQC) || defined(SSQC) +void(float prevprogs) init; /* Part of FTE_MULTIPROGS. Called as soon as a progs is loaded, called at a time when entities are not valid. This is the only time when it is safe to call addprogs without field assignment. As it is also called as part of addprogs, this also gives you a chance to hook functions in modules that are already loaded (via externget+externget). */ +void() initents; /* Part of FTE_MULTIPROGS. Called after fields have been finalized. This is the first point at which it is safe to call spawn(), and is called before any entity fields have been parsed. You can use this entrypoint to send notifications to other modules. */ +#endif +#ifdef MENU +void() m_init; +void() m_shutdown; +void() m_draw; +void(float scan, float chr) m_keydown; +void(float scan, float chr) m_keyup; +void(float wantmode) m_toggle; +#endif +#ifdef SSQC +float parm17, parm18, parm19, parm20, parm21, parm22, parm23, parm24, parm25, parm26, parm27, parm28, parm29, parm30, parm31, parm32; +float parm33, parm34, parm35, parm36, parm37, parm38, parm39, parm40, parm41, parm42, parm43, parm44, parm45, parm46, parm47, parm48; +float parm49, parm50, parm51, parm52, parm53, parm54, parm55, parm56, parm57, parm58, parm59, parm60, parm61, parm62, parm63, parm64; +var float dimension_send; /* Used by multicast functionality. Multicasts (and related builtins that multicast internally) will only be sent to players where (player.dimension_see & dimension_send) is non-zero. */ +//var float dimension_default = 255; +/* Default dimension bitmask */ +#endif +#if defined(CSQC) || defined(SSQC) +__used var float physics_mode = 2; /* 0: original csqc - physics are not run +1: DP-compat. Thinks occur, but not true movetypes. +2: movetypes occur just as they do in ssqc. */ +#endif +#ifdef CSQC +float gamespeed; /* Set by the engine, this is the value of the sv_gamespeed cvar */ +float numclientseats; /* This is the number of splitscreen clients currently running on this client. */ +#endif +#if defined(CSQC) || defined(MENU) +var vector drawfontscale = '1 1 0'; /* Specifies a scaler for all text rendering. There are other ways to implement this. */ +float drawfont; /* Allows you to choose exactly which font is to be used to draw text. Fonts can be registered/allocated with the loadfont builtin. */ +const float FONT_DEFAULT = 0; +#endif +const float TRUE = 1; +const float FALSE = 0; /* File not found... */ +const float M_PI = 3.14159; +#if defined(CSQC) || defined(SSQC) +const float MOVETYPE_NONE = 0; +const float MOVETYPE_WALK = 3; +const float MOVETYPE_STEP = 4; +const float MOVETYPE_FLY = 5; +const float MOVETYPE_TOSS = 6; +const float MOVETYPE_PUSH = 7; +const float MOVETYPE_NOCLIP = 8; +const float MOVETYPE_FLYMISSILE = 9; +const float MOVETYPE_BOUNCE = 10; +const float MOVETYPE_BOUNCEMISSILE = 11; +const float MOVETYPE_FOLLOW = 12; +const float MOVETYPE_6DOF = 30; /* A glorified MOVETYPE_FLY. Players using this movetype will get some flightsim-like physics, with fully independant rotations (order-dependant transforms). */ +const float MOVETYPE_WALLWALK = 31; /* Players using this movetype will be able to orient themselves to walls, and then run up them. */ +const float MOVETYPE_PHYSICS = 32; /* Enable the use of ODE physics upon this entity. */ +const float SOLID_NOT = 0; +const float SOLID_TRIGGER = 1; +const float SOLID_BBOX = 2; +const float SOLID_SLIDEBOX = 3; +const float SOLID_BSP = 4; /* Does not collide against other SOLID_BSP entities. Normally paired with MOVETYPE_PUSH. */ +const float SOLID_CORPSE = 5; /* Non-solid to SOLID_SLIDEBOX or other SOLID_CORPSE entities. For hitscan weapons to hit corpses, change the player's .solid value to SOLID_BBOX or so, perform the traceline, then revert the player's .solid value. */ +const float SOLID_LADDER = 20; /* Obsolete and may be removed at some point. Use skin=CONTENT_LADDER and solid_bsp or solid_trigger instead. */ +const float SOLID_PORTAL = 21; /* CSG subtraction volume combined with entity transformations on impact. */ +const float SOLID_PHYSICS_BOX = 32; +const float SOLID_PHYSICS_SPHERE = 33; +const float SOLID_PHYSICS_CAPSULE = 34; +const float SOLID_PHYSICS_TRIMESH = 35; +const float SOLID_PHYSICS_CYLINDER = 36; +const float GEOMTYPE_NONE = -1; +const float GEOMTYPE_SOLID = 0; +const float GEOMTYPE_BOX = 1; +const float GEOMTYPE_SPHERE = 2; +const float GEOMTYPE_CAPSULE = 3; +const float GEOMTYPE_TRIMESH = 4; +const float GEOMTYPE_CYLINDER = 5; +const float GEOMTYPE_CAPSULE_X = 6; +const float GEOMTYPE_CAPSULE_Y = 7; +const float GEOMTYPE_CAPSULE_Z = 8; +const float GEOMTYPE_CYLINDER_X = 9; +const float GEOMTYPE_CYLINDER_Y = 10; +const float GEOMTYPE_CYLINDER_Z = 11; +const float JOINTTYPE_FIXED = -1; +const float JOINTTYPE_POINT = 1; +const float JOINTTYPE_HINGE = 2; +const float JOINTTYPE_SLIDER = 3; +const float JOINTTYPE_UNIVERSAL = 4; +const float JOINTTYPE_HINGE2 = 5; +#endif +#ifdef CSQC +const float GE_MAXENTS = -1; /* Valid for getentity, ignores the entity argument. Returns the maximum number of entities which may be valid, to avoid having to poll 65k when only 100 are used. */ +const float GE_ACTIVE = 0; /* Valid for getentity. Returns whether this entity is known to the client or not. */ +const float GE_ORIGIN = 1; /* Valid for getentity. Returns the interpolated .origin. */ +const float GE_FORWARD = 2; /* Valid for getentity. Returns the interpolated forward vector. */ +const float GE_RIGHT = 3; /* Valid for getentity. Returns the entity's right vector. */ +const float GE_UP = 4; /* Valid for getentity. Returns the entity's up vector. */ +const float GE_SCALE = 5; /* Valid for getentity. Returns the entity .scale. */ +const float GE_ORIGINANDVECTORS = 6; /* Valid for getentity. Returns interpolated .origin, but also sets v_forward, v_right, and v_up accordingly. Use vectoangles(v_forward,v_up) to determine the angles. */ +const float GE_ALPHA = 7; /* Valid for getentity. Returns the entity alpha. */ +const float GE_COLORMOD = 8; /* Valid for getentity. Returns the colormod vector. */ +const float GE_PANTSCOLOR = 9; /* Valid for getentity. Returns the entity's lower color (from .colormap), as a palette range value. */ +const float GE_SHIRTCOLOR = 10; /* Valid for getentity. Returns the entity's lower color (from .colormap), as a palette range value. */ +const float GE_SKIN = 11; /* Valid for getentity. Returns the entity's .skin index. */ +const float GE_MINS = 12; /* Valid for getentity. Guesses the entity's .min vector. */ +const float GE_MAXS = 13; /* Valid for getentity. Guesses the entity's .max vector. */ +const float GE_ABSMIN = 14; /* Valid for getentity. Guesses the entity's .absmin vector. */ +const float GE_ABSMAX = 15; /* Valid for getentity. Guesses the entity's .absmax vector. */ +const float GE_MODELINDEX = 200; /* Valid for getentity. Guesses the entity's .modelindex float. */ +const float GE_MODELINDEX2 = 201; /* Valid for getentity. Guesses the entity's .vw_index float. */ +const float GE_EFFECTS = 202; /* Valid for getentity. Guesses the entity's .effects float. */ +const float GE_FRAME = 203; /* Valid for getentity. Guesses the entity's .frame float. */ +const float GE_ANGLES = 204; /* Valid for getentity. Guesses the entity's .angles vector. */ +const float GE_FATNESS = 205; /* Valid for getentity. Guesses the entity's .fatness float. */ +const float GE_DRAWFLAGS = 206; /* Valid for getentity. Guesses the entity's .drawflags float. */ +const float GE_ABSLIGHT = 207; /* Valid for getentity. Guesses the entity's .abslight float. */ +const float GE_GLOWMOD = 208; /* Valid for getentity. Guesses the entity's .glowmod vector. */ +const float GE_GLOWSIZE = 209; /* Valid for getentity. Guesses the entity's .glowsize float. */ +const float GE_GLOWCOLOUR = 210; /* Valid for getentity. Guesses the entity's .glowcolor float. */ +const float GE_RTSTYLE = 211; /* Valid for getentity. Guesses the entity's .style float. */ +const float GE_RTPFLAGS = 212; /* Valid for getentity. Guesses the entity's .pflags float. */ +const float GE_RTCOLOUR = 213; /* Valid for getentity. Guesses the entity's .color vector. */ +const float GE_RTRADIUS = 214; /* Valid for getentity. Guesses the entity's .light_lev float. */ +const float GE_TAGENTITY = 215; /* Valid for getentity. Guesses the entity's .tag_entity float. */ +const float GE_TAGINDEX = 216; /* Valid for getentity. Guesses the entity's .tag_index float. */ +const float GE_GRAVITYDIR = 217; /* Valid for getentity. Guesses the entity's .gravitydir vector. */ +const float GE_TRAILEFFECTNUM = 218; /* Valid for getentity. Guesses the entity's .traileffectnum float. */ +#endif +#ifdef SSQC +const float DAMAGE_NO = 0; +const float DAMAGE_YES = 1; +const float DAMAGE_AIM = 2; +#endif +#if defined(CSQC) || defined(SSQC) +const float CONTENT_EMPTY = -1; +const float CONTENT_SOLID = -2; +const float CONTENT_WATER = -3; +const float CONTENT_SLIME = -4; +const float CONTENT_LAVA = -5; +const float CONTENT_SKY = -6; +const float CONTENT_LADDER = -16; /* If this value is assigned to a solid_bsp's .skin field, the entity will become a ladder volume. */ +const float CHAN_AUTO = 0; /* The automatic channel, play as many sounds on this channel as you want, and they'll all play, however the other channels will replace each other. */ +const float CHAN_WEAPON = 1; +const float CHAN_VOICE = 2; +const float CHAN_ITEM = 3; +const float CHAN_BODY = 4; +#endif +#if defined(QWSSQC) +const float CHANF_RELIABLE = 8; /* Only valid if the flags argument is not specified. The sound will be sent reliably, which is important if it is intended to replace looping sounds on doors etc. */ +#endif +#ifdef SSQC +const float SOUNDFLAG_RELIABLE = 1; /* The sound will be sent reliably, and without regard to phs. */ +#endif +#ifdef CSQC +const float SOUNDFLAG_ABSVOLUME = 16; /* The sample's volume is not scaled by the volume cvar. Use with caution */ +#endif +#if defined(CSQC) || defined(SSQC) +const float SOUNDFLAG_FORCELOOP = 2; /* The sound will restart once it reaches the end of the sample. */ +#endif +#ifdef CSQC +const float SOUNDFLAG_NOSPACIALISE = 4; /* The different audio channels are played at the same volume regardless of which way the player is facing, without needing to use 0 attenuation. */ +#endif +#ifdef SSQC +const float SOUNDFLAG_UNICAST = 256; /* The sound will be heard only by the player specified by msg_entity. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float ATTN_NONE = 0; /* Sounds with this attenuation can be heard throughout the map */ +const float ATTN_NORM = 1; /* Standard attenuation */ +const float ATTN_IDLE = 2; /* Extra attenuation so that sounds don't travel too far. */ +const float ATTN_STATIC = 3; /* Even more attenuation to avoid torches drowing out everything else throughout the map. */ +#endif +#ifdef SSQC +const float SVC_CGAMEPACKET = 83; /* Direct ssqc->csqc message. Must only be multicast. The data triggers a CSQC_Parse_Event call in the csqc for the csqc to read the contents. The server *may* insert length information for clients connected via proxies which are not able to cope with custom csqc payloads. This should only ever be used in conjunction with the MSG_MULTICAST destination. */ +const float MSG_BROADCAST = 0; /* The byte(s) will be unreliably sent to all players. MSG_ constants are valid arguments to the Write* builtin family. */ +const float MSG_ONE = 1; /* The byte(s) will be reliably sent to the player specified in the msg_entity global. WARNING: in quakeworld servers without network preparsing enabled, this can result in illegible server messages (due to individual reliable messages being split between multiple backbuffers/packets). NQ has larger reliable buffers which avoids this issue, but still kicks the client. */ +const float MSG_ALL = 2; /* The byte(s) will be reliably sent to all players. */ +const float MSG_INIT = 3; /* The byte(s) will be written into the signon buffer. Clients will see these messages when they connect later. This buffer is only flushed on map changes, so spamming it _WILL_ result in overflows. */ +const float MSG_MULTICAST = 4; /* The byte(s) will be written into the multicast buffer for more selective sending. Messages sent this way will never be split across packets, and using this for csqc-only messages will not break protocol translation. */ +const float MSG_ENTITY = 5; /* The byte(s) will be written into the entity buffer. This is a special value used only inside 'SendEntity' functions. */ +const float MULTICAST_ALL = 0; /* The multicast message is unreliably sent to all players. MULTICAST_ constants are valid arguments for the multicast builtin, which ignores the specified origin when given this constant. */ +const float MULTICAST_PHS = 1; /* The multicast message is unreliably sent to only players that can potentially hear the specified origin. Its quite loose. */ +const float MULTICAST_PVS = 2; /* The multicast message is unreliably sent to only players that can potentially see the specified origin. */ +const float MULTICAST_ONE = 6; /* The multicast message is unreliably sent to the player specified in the msg_entity global. The specified origin is ignored. */ +const float MULTICAST_ALL_R = 3; /* The multicast message is reliably sent to all players. The specified origin is ignored. */ +const float MULTICAST_PHS_R = 4; /* The multicast message is reliably sent to only players that can potentially hear the specified origin. Players might still not receive it if they are out of range. */ +const float MULTICAST_PVS_R = 5; /* The multicast message is reliably sent to only players that can potentially see the specified origin. Players might still not receive it if they cannot see the event. */ +const float MULTICAST_ONE_R = 7; /* The multicast message is reliably sent to the player specified in the msg_entity global. The specified origin is ignored */ +#endif +#if defined(QWSSQC) +const float PRINT_LOW = 0; +const float PRINT_MEDIUM = 1; +const float PRINT_HIGH = 2; +const float PRINT_CHAT = 3; +#endif +#ifdef SSQC +const float PVSF_NORMALPVS = 0; /* Filter first by PVS, then filter this entity using tracelines if sv_cullentities is enabled. */ +const float PVSF_NOTRACECHECK = 1; /* Filter strictly by PVS. */ +const float PVSF_USEPHS = 2; /* Send if we're close enough to be able to hear this entity. */ +const float PVSF_IGNOREPVS = 3; /* Ignores pvs. This entity is visible whereever you are on the map. */ +const float PVSF_NOREMOVE = 128; /* Once visible to a client, this entity will remain visible. This can be useful for csqc and corpses. */ +const string INFOKEY_P_IP = "ip"; /* The apparent ip address of the client. This may be a proxy's ip address. */ +const string INFOKEY_P_REALIP = "realip"; /* If sv_getrealip is set, this gives the ip as determine using that algorithm. */ +const string INFOKEY_P_CSQCACTIVE = "csqcactive"; /* Client has csqc enabled. CSQC ents etc will be sent to this player. */ +const string INFOKEY_P_SVPING = "svping"; +const string INFOKEY_P_GUID = "guid"; /* Some hash string which should be reasonably unique to this player's quake installation. */ +const string INFOKEY_P_CHALLENGE = "challenge"; +const string INFOKEY_P_USERID = "*userid"; +const string INFOKEY_P_DOWNLOADPCT = "download"; /* The client's download percentage for the current file. Additional files are not known. */ +const string INFOKEY_P_TRUSTLEVEL = "trustlevel"; +const string INFOKEY_P_PROTOCOL = "protocol"; /* The network protocol the client is using to connect to the server. */ +const string INFOKEY_P_VIP = "*VIP"; /* 1 if the player has the VIP 'penalty'. */ +const string INFOKEY_P_ISMUTED = "*ismuted"; /* 1 if the player has the 'mute' penalty and is not allowed to use the say/say_team commands. */ +const string INFOKEY_P_ISDEAF = "*isdeaf"; /* 1 if the player has the 'deaf' penalty and cannot see other people's say/say_team commands. */ +const string INFOKEY_P_ISCRIPPLED = "*ismuted"; /* 1 if the player has the cripple penalty, and their movement values are ignored (.movement is locked to 0). */ +const string INFOKEY_P_ISCUFFED = "*ismuted"; /* 1 if the player has the cuff penalty, and is unable to attack or use impulses(.button0 and .impulse fields are locked to 0). */ +const string INFOKEY_P_ISLAGGED = "*ismuted"; /* 1 if the player has the fakelag penalty and has an extra 200ms of lag. */ +#endif +#if defined(CSQC) || defined(SSQC) +const string INFOKEY_P_PING = "ping"; /* The player's ping time, in milliseconds. */ +const string INFOKEY_P_NAME = "name"; /* The player's name. */ +const string INFOKEY_P_TOPCOLOR = "topcolor"; /* The player's upper/shirt colour (palette index). */ +const string INFOKEY_P_BOTTOMCOLOR = "bottomcolor"; /* The player's lower/pants/trouser colour (palette index). */ +#endif +#ifdef CSQC +const string INFOKEY_P_TOPCOLOR_RGB = "topcolor_rgb"; /* The player's upper/shirt colour as an rgb value in a format usable with stov. */ +const string INFOKEY_P_BOTTOMCOLOR_RGB = "bottomcolor_rgb"; /* The player's lower/pants/trouser colour as an rgb value in a format usable with stov. */ +const string INFOKEY_P_MUTED = "ignored"; /* 0: we can see the result of the player's say/say_team commands. 1: we see no say/say_team messages from this player. Use the ignore command to toggle this value. */ +const string INFOKEY_P_VOIP_MUTED = "vignored"; /* 0: we can hear this player when they speak (assuming voip is generally enabled). 1: we ignore everything this player says. Use cl_voip_mute to change the values. */ +const string INFOKEY_P_ENTERTIME = "entertime"; /* Reads the timestamp at which the player entered the game, in terms of csqc's time global. */ +const string INFOKEY_P_FRAGS = "frags"; /* Reads a player's frag count. */ +const string INFOKEY_P_PACKETLOSS = "pl"; /* Reads a player's packetloss, as a percentage. */ +const string INFOKEY_P_VOIPSPEAKING = "voipspeaking"; /* Boolean value that says whether the given player is currently sending voice information. */ +const string INFOKEY_P_VOIPLOUDNESS = "voiploudness"; /* Only valid for the local player. Gives a value between 0 and 1 to indicate to the user how loud their mic is. */ +const string SERVERKEY_IP = "ip"; /* The address of the server we connected to. */ +const string SERVERKEY_SERVERNAME = "servername"; /* The hostname that was last passed to the connect command. */ +const string SERVERKEY_CONSTATE = "constate"; /* The current connection state. Will be set to one of: disconnected (menu-only mode), active (gamestate received and loaded), connecting(connecting, downloading, or precaching content, aka: loading screen). */ +const string SERVERKEY_TRANSFERRING = "transferring"; /* Set to the hostname of the server that we are attempting to connect or transfer to. */ +const string SERVERKEY_LOADSTATE = "loadstate"; /* loadstage, loading image name, current step, max steps +Stages are: 1=connecting, 2=serverside, 3=clientside +Key will be empty if we are not loading. */ +const string SERVERKEY_PAUSESTATE = "pausestate"; /* 1 if the server claimed to be paused. 0 otherwise */ +const string SERVERKEY_DLSTATE = "dlstate"; /* The progress of any current downloads. Empty string if no download is active, otherwise a tokenizable string containing this info: +files-remaining, total-size, unknown-sizes-flag, file-localname, file-remotename, file-percent, file-rate, file-received-bytes, file-total-bytes +If the current file info is omitted, then we are waiting for a download to start. */ +const string SERVERKEY_PROTOCOL = "protocol"; /* The protocol we are connected to the server with. */ +const string SERVERKEY_MAXPLAYERS = "maxplayers"; /* The protocol we are connected to the server with. */ +#endif +#ifdef SSQC +const float STUFFCMD_IGNOREINDEMO = 1; /* The protocol we are connected to the server with. */ +const float STUFFCMD_DEMOONLY = 2; /* The protocol we are connected to the server with. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float FL_FLY = 1; +const float FL_SWIM = 2; +const float FL_CLIENT = 8; +const float FL_INWATER = 16; +const float FL_MONSTER = 32; +#endif +#ifdef SSQC +const float FL_GODMODE = 64; +const float FL_NOTARGET = 128; +#endif +#if defined(CSQC) || defined(SSQC) +const float FL_ITEM = 256; +const float FL_ONGROUND = 512; +const float FL_PARTIALGROUND = 1024; +const float FL_WATERJUMP = 2048; +#endif +#if defined(CSQC) || defined(NQSSQC) +const float FL_JUMPRELEASED = 4096; +#endif +#if defined(CSQC) || defined(SSQC) +const float FL_FINDABLE_NONSOLID = 16384; /* Allows this entity to be found with findradius */ +#endif +#ifdef SSQC +const float FL_LAGGEDMOVE = 65536; /* Enables anti-lag on rockets etc. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float MOVE_NORMAL = 0; +const float MOVE_NOMONSTERS = 1; /* The trace will ignore all non-solid_bsp entities. */ +const float MOVE_MISSILE = 2; /* The trace will use a bbox size of +/- 15 against entities with FL_MONSTER set. */ +const float MOVE_HITMODEL = 4; /* Traces will impact the actual mesh of the model instead of merely their bounding box. Should generally only be used for tracelines. Note that this flag is unreliable as an object can animate through projectiles. The bounding box MUST be set to completely encompass the entity or those extra areas will be non-solid (leaving a hole for things to go through). */ +const float MOVE_TRIGGERS = 16; /* This trace type will impact only triggers. It will ignore non-solid entities. */ +const float MOVE_EVERYTHING = 32; /* This type of trace will hit solids and triggers alike. Even non-solid entities. */ +#endif +#ifdef SSQC +const float MOVE_LAGGED = 64; /* Will use antilag based upon the player's latency. Traces will be performed against old positions for entities instead of their current origin. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float MOVE_ENTCHAIN = 128; /* Returns a list of entities impacted via the trace_ent.chain field */ +#endif +const float RESTYPE_MODEL = 0; /* RESTYPE_* constants are used as arguments with the resourcestatus builtin. */ +const float RESTYPE_SOUND = 1; /* precache_sound */ +const float RESTYPE_PARTICLE = 2; /* particleeffectnum */ +#if defined(CSQC) || defined(MENU) +const float RESTYPE_PIC = 3; /* precache_pic. Status results are an amalgomation of the textures used by the named shader. */ +const float RESTYPE_SKIN = 4; /* setcustomskin */ +const float RESTYPE_TEXTURE = 5; /* Individual textures within shaders. These are not directly usable, but may be named as part of a skin file, or a shader. */ +#endif +const float RESSTATE_NOTKNOWN = 0; /* RESSTATE_* constants are return values from the resourcestatus builtin. The engine doesn't know about the resource if it is in this state. This means you will need to precache it. Attempting to use it anyway may result in warnings, errors, or silently succeed, depending on engine version and resource type. */ +const float RESSTATE_NOTLOADED = 1; /* The resource was precached, but has been flushed and there has not been an attempt to reload it. If you use the resource normally, chances are it'll be loaded but at the cost of a stall. */ +const float RESSTATE_LOADING = 2; /* Resources in this this state are queued for loading, and will be loaded at the engine's convienience. If you attempt to query the resource now, the engine will stall until the result is available. sounds in this state may be delayed, while models/pics/shaders may be invisible. */ +const float RESSTATE_FAILED = 3; /* Resources in this state are unusable/could not be loaded. You will get placeholders or dummy results. Queries will not stall the engine. The engine may display placeholder content. */ +const float RESSTATE_LOADED = 4; /* Resources in this state are finally usable, everything will work okay. Hurrah. Queries will not stall the engine. */ +#if defined(CSQC) || defined(SSQC) +const float EF_BRIGHTFIELD = 1; +#endif +#if defined(CSQC) || defined(NQSSQC) +const float EF_MUZZLEFLASH = 2; +#endif +#if defined(CSQC) || defined(SSQC) +const float EF_BRIGHTLIGHT = 4; +const float EF_DIMLIGHT = 8; +#endif +#if defined(QWSSQC) +const float EF_FLAG1 = 16; +const float EF_FLAG2 = 32; +#endif +#if defined(CSQC) || defined(NQSSQC) +const float EF_ADDITIVE = 32; +#endif +#if defined(CSQC) || defined(SSQC) +const float EF_BLUE = 64; +const float EF_RED = 128; +const float EF_GREEN = 262144; +const float EF_FULLBRIGHT = 512; +const float EF_NOSHADOW = 4096; +const float EF_NODEPTHTEST = 8192; +#endif +#ifdef SSQC +const float EF_NOMODELFLAGS = 8388608; /* Surpresses the normal flags specified in the model. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float MF_ROCKET = 1; +const float MF_GRENADE = 2; +const float MF_GIB = 4; +const float MF_ROTATE = 8; +const float MF_TRACER = 16; +const float MF_ZOMGIB = 32; +const float MF_TRACER2 = 64; +const float MF_TRACER3 = 128; +#endif +#ifdef SSQC +const float SL_ORG_TL = 20; +const float SL_ORG_TR = 21; +const float SL_ORG_BL = 22; +const float SL_ORG_BR = 23; +const float SL_ORG_MM = 24; +const float SL_ORG_TM = 25; +const float SL_ORG_BM = 26; +const float SL_ORG_ML = 27; +const float SL_ORG_MR = 28; +#endif +#if defined(CSQC) || defined(SSQC) +const float PFLAGS_NOSHADOW = 1; /* Associated RT lights attached will not cast shadows, making them significantly faster to draw. */ +const float PFLAGS_CORONA = 2; /* Enables support of coronas on the associated rtlights. */ +#endif +#ifdef SSQC +const float PFLAGS_FULLDYNAMIC = 128; /* When set in self.pflags, enables fully-customised dynamic lights. Custom rtlight information is not otherwise used. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float EV_STRING = 1; +const float EV_FLOAT = 2; +const float EV_VECTOR = 3; +const float EV_ENTITY = 4; +const float EV_FIELD = 5; +const float EV_FUNCTION = 6; +const float EV_POINTER = 7; +const float EV_INTEGER = 8; +const float EV_VARIANT = 9; +#endif +hashtable gamestate; /* Special hash table index for hash_add and hash_get. Entries in this table will persist over map changes (and doesn't need to be created/deleted). */ +const float HASH_REPLACE = 256; /* Used with hash_add. Attempts to remove the old value instead of adding two values for a single key. */ +const float HASH_ADD = 512; /* Used with hash_add. The new entry will be inserted in addition to the existing entry. */ +#ifdef CSQC +const float STAT_HEALTH = 0; /* Player's health. */ +const float STAT_WEAPONMODELI = 2; /* This is the modelindex of the current viewmodel (renamed from the original name 'STAT_WEAPON' due to confusions). */ +const float STAT_AMMO = 3; /* player.currentammo */ +const float STAT_ARMOR = 4; +const float STAT_WEAPONFRAME = 5; +const float STAT_SHELLS = 6; +const float STAT_NAILS = 7; +const float STAT_ROCKETS = 8; +const float STAT_CELLS = 9; +const float STAT_ACTIVEWEAPON = 10; /* player.weapon */ +const float STAT_TOTALSECRETS = 11; +const float STAT_TOTALMONSTERS = 12; +const float STAT_FOUNDSECRETS = 13; +const float STAT_KILLEDMONSTERS = 14; +const float STAT_ITEMS = 15; /* self.items | (self.items2<<23). In order to decode this stat properly, you need to use getstatbits(STAT_ITEMS,0,23) to read self.items, and getstatbits(STAT_ITEMS,23,11) to read self.items2 or getstatbits(STAT_ITEMS,28,4) to read the visible part of serverflags, whichever is applicable. */ +const float STAT_VIEWHEIGHT = 16; /* player.view_ofs_z */ +const float STAT_VIEW2 = 20; /* This stat contains the number of the entity in the server's .view2 field. */ +const float STAT_VIEWZOOM = 21; /* Scales fov and sensitiity. Part of DP_VIEWZOOM. */ +#endif +#if defined(CSQC) || defined(SSQC) +const float STAT_USER = 32; /* Custom user stats start here (lower values are reserved for engine use). */ +#endif +#if defined(CSQC) || defined(MENU) +const float VF_MIN = 1; /* The top-left of the 3d viewport in screenspace. The VF_ values are used via the setviewprop/getviewprop builtins. */ +const float VF_MIN_X = 2; +const float VF_MIN_Y = 3; +const float VF_SIZE = 4; /* The width+height of the 3d viewport in screenspace. */ +const float VF_SIZE_X = 5; +const float VF_SIZE_Y = 6; +const float VF_VIEWPORT = 7; /* vector+vector. Two argument shortcut for VF_MIN and VF_SIZE */ +const float VF_FOV = 8; /* sets both fovx and fovy. consider using afov instead. */ +const float VF_FOVX = 9; /* horizontal field of view. does not consider aspect at all. */ +const float VF_FOVY = 10; /* vertical field of view. does not consider aspect at all. */ +const float VF_ORIGIN = 11; /* The origin of the view. Not of the player. */ +const float VF_ORIGIN_X = 12; +const float VF_ORIGIN_Y = 13; +const float VF_ORIGIN_Z = 14; +const float VF_ANGLES = 15; /* The angles the view will be drawn at. Not the angle the client reports to the server. */ +const float VF_ANGLES_X = 16; +const float VF_ANGLES_Y = 17; +const float VF_ANGLES_Z = 18; +#endif +#ifdef CSQC +const float VF_DRAWWORLD = 19; /* boolean. If set to 1, the engine will draw the world and static/persistant rtlights. If 0, the world will be skipped and everything will be fullbright. */ +const float VF_DRAWENGINESBAR = 20; /* boolean. If set to 1, the sbar will be drawn, and viewsize will be honoured automatically. */ +const float VF_DRAWCROSSHAIR = 21; /* boolean. If set to 1, the engine will draw its default crosshair. */ +const float VF_CL_VIEWANGLES = 33; +const float VF_CL_VIEWANGLES_X = 34; +const float VF_CL_VIEWANGLES_Y = 35; +const float VF_CL_VIEWANGLES_Z = 36; +#endif +#if defined(CSQC) || defined(MENU) +const float VF_PERSPECTIVE = 200; /* 1: regular rendering. Fov specifies the angle. 0: isometric-style. Fov specifies the number of Quake Units each side of the viewport. */ +#endif +#ifdef CSQC +#define VF_LPLAYER VF_ACTIVESEAT +const float VF_ACTIVESEAT = 202; /* The 'seat' number, used when running splitscreen. */ +#endif +#if defined(CSQC) || defined(MENU) +const float VF_AFOV = 203; /* Aproximate fov. Matches the 'fov' cvar. The engine handles the aspect ratio for you. */ +const float VF_SCREENVSIZE = 204; /* Provides a reliable way to retrieve the current virtual screen size (even if the screen is automatically scaled to retain aspect). */ +const float VF_SCREENPSIZE = 205; /* Provides a reliable way to retrieve the current physical screen size (cvars need vid_restart for them to take effect). */ +#endif +#ifdef CSQC +const float VF_VIEWENTITY = 206; /* Changes the RF_EXTERNALMODEL flag on entities to match the new selection, and removes entities flaged with RF_VIEWENTITY. Requires cunning use of .entnum and typically requires calling addentities(MASK_VIEWMODEL) too. */ +#endif +#if defined(CSQC) || defined(MENU) +const float VF_RT_DESTCOLOUR = 212; /* The texture name to write colour info into, this includes both 3d and 2d drawing. +Additional arguments are: format (rgba8=1,rgba16f=2,rgba32f=3), sizexy. +Written to by both 3d and 2d rendering. +Note that any rendertarget textures may be destroyed on video mode changes or so. Shaders can name render targets by prefixing texture names with '$rt:', or $sourcecolour. */ +const float VF_RT_SOURCECOLOUR = 209; /* The texture name to use with shaders that specify a $sourcecolour map. */ +const float VF_RT_DEPTH = 210; /* The texture name to use as a depth buffer. Also used for shaders that specify $sourcedepth. 1-based. Additional arguments are: format (16bit=4,24bit=5,32bit=6), sizexy. */ +const float VF_RT_RIPPLE = 211; /* The texture name to use as a ripplemap (target for shaders with 'sort ripple'). Also used for shaders that specify $ripplemap. 1-based. Additional arguments are: format, sizexy. */ +#endif +#ifdef CSQC +const float RF_VIEWMODEL = 1; /* Specifies that the entity is a view model, and that its origin is relative to the current view position. These entities are also subject to viewweapon bob. */ +const float RF_EXTERNALMODEL = 2; /* Specifies that this entity should be displayed in mirrors (and may still cast shadows), but will not otherwise be visible. */ +#endif +#if defined(CSQC) || defined(MENU) +const float RF_DEPTHHACK = 4; /* Hacks the depth values such that the entity uses depth values as if it were closer to the screen. This is useful when combined with viewmodels to avoid weapons poking in to walls. */ +const float RF_ADDITIVE = 8; /* Shaders from this entity will temporarily be hacked to use an additive blend mode instead of their normal blend mode. */ +#endif +#ifdef CSQC +const float RF_USEAXIS = 16; /* The entity will be oriented according to the current v_forward+v_right+v_up vector values instead of the entity's .angles field. */ +const float RF_NOSHADOW = 32; /* This entity will not cast shadows. Often useful on view models. */ +const float RF_FRAMETIMESARESTARTTIMES = 64; /* Specifies that the frame1time, frame2time field are timestamps (denoting the start of the animation) rather than time into the animation. */ +const float IE_KEYDOWN = 0; /* Specifies that a key was pressed. Second argument is the scan code. Third argument is the unicode (printable) char value. Fourth argument denotes which keyboard(or mouse, if its a mouse 'scan' key) the event came from. Note that some systems may completely separate scan codes and unicode values, with a 0 value for the unspecified argument. */ +const float IE_KEYUP = 1; /* Specifies that a key was released. Arguments are the same as IE_KEYDOWN. On some systems, this may be fired instantly after IE_KEYDOWN was fired. */ +const float IE_MOUSEDELTA = 2; /* Specifies that a mouse was moved (touch screens and tablets typically give IE_MOUSEABS events instead, use _windowed_mouse 0 to test code to cope with either). Second argument is the X displacement, third argument is the Y displacement. Fourth argument is which mouse or touch event triggered the event. */ +const float IE_MOUSEABS = 3; /* Specifies that a mouse cursor or touch event was moved to a specific location relative to the virtual screen space. Second argument is the new X position, third argument is the new Y position. Fourth argument is which mouse or touch event triggered the event. */ +const float IE_ACCELEROMETER = 4; +const float IE_FOCUS = 5; /* Specifies that input focus was given. parama says mouse focus, paramb says keyboard focus. If either are -1, then it is unchanged. */ +const float IE_JOYAXIS = 6; /* Specifies that what value a joystick/controller axis currently specifies. x=axis, y=value. Will be called multiple times, once for each axis of each active controller. */ +#endif +#ifdef SSQC +const float CLIENTTYPE_DISCONNECTED = 0; /* Return value from clienttype() builtin. This entity is a player slot that is currently empty. */ +const float CLIENTTYPE_REAL = 1; /* This is a real player, and not a bot. */ +const float CLIENTTYPE_BOT = 2; /* This player slot does not correlate to a real player, any messages sent to this client will be ignored. */ +const float CLIENTTYPE_NOTACLIENT = 3; /* This entity is not even a player slot. This is typically an error condition. */ +#endif +const float FILE_READ = 0; /* The file may be read via fgets to read a single line at a time. */ +const float FILE_APPEND = 1; /* Like FILE_WRITE, but writing starts at the end of the file. */ +const float FILE_WRITE = 2; /* fputs will be used to write to the file. */ +#if defined(CSQC) || defined(SSQC) +const float FILE_READNL = 4; /* Like FILE_READ, except newlines are not special. fgets reads the entire file into a tempstring. */ +const float FILE_MMAP_READ = 5; /* The file will be loaded into memory. fgets returns a pointer to the first byte (and will always return the same value for this file). Cast this to your datatype. */ +const float FILE_MMAP_RW = 6; /* Like FILE_MMAP_READ, except any changes to the data will be written back to disk once the file is closed. */ +#endif +#ifdef CSQC +const float MASK_ENGINE = 1; /* Valid as an argument for addentities. If specified, all non-csqc entities will be added to the scene. */ +const float MASK_VIEWMODEL = 2; /* Valid as an argument for addentities. If specified, the regular engine viewmodel will be added to the scene. */ +const float PREDRAW_AUTOADD = 0; /* Valid as a return value from the predraw function. Returning this will cause the engine to automatically invoke addentity(self) for you. */ +const float PREDRAW_NEXT = 1; /* Valid as a return value from the predraw function. Returning this will simply move on to the next entity without the autoadd behaviour, so can be used for particle/invisible/special entites, or entities that were explicitly drawn with addentity. */ +const float LFIELD_ORIGIN = 0; +const float LFIELD_COLOUR = 1; +const float LFIELD_RADIUS = 2; +const float LFIELD_FLAGS = 3; +const float LFIELD_STYLE = 4; +const float LFIELD_ANGLES = 5; +const float LFIELD_FOV = 6; +const float LFIELD_CORONA = 7; +const float LFIELD_CORONASCALE = 8; +const float LFIELD_CUBEMAPNAME = 9; +const float LFIELD_AMBIENTSCALE = 10; +const float LFIELD_DIFFUSESCALE = 11; +const float LFIELD_SPECULARSCALE = 12; +const float LFIELD_ROTATION = 13; +const float LFIELD_DIETIME = 14; +const float LFIELD_RGBDECAY = 15; +const float LFIELD_RADIUSDECAY = 16; +const float LFLAG_NORMALMODE = 1; +const float LFLAG_REALTIMEMODE = 2; +const float LFLAG_LIGHTMAP = 4; +const float LFLAG_FLASHBLEND = 8; +const float LFLAG_NOSHADOWS = 256; +const float LFLAG_SHADOWMAP = 512; +const float LFLAG_CREPUSCULAR = 1024; +const float TEREDIT_RELOAD = 0; +const float TEREDIT_SAVE = 1; +const float TEREDIT_SETHOLE = 2; +const float TEREDIT_HEIGHT_SET = 3; +const float TEREDIT_HEIGHT_SMOOTH = 4; +const float TEREDIT_HEIGHT_SPREAD = 5; +const float TEREDIT_HEIGHT_RAISE = 6; +const float TEREDIT_HEIGHT_FLATTEN = 18; +const float TEREDIT_HEIGHT_LOWER = 7; +const float TEREDIT_TEX_KILL = 8; +const float TEREDIT_TEX_GET = 9; +const float TEREDIT_TEX_BLEND = 10; +const float TEREDIT_TEX_UNIFY = 11; +const float TEREDIT_TEX_NOISE = 12; +const float TEREDIT_TEX_BLUR = 13; +const float TEREDIT_TEX_REPLACE = 19; +const float TEREDIT_TEX_SETMASK = 25; +const float TEREDIT_WATER_SET = 14; +const float TEREDIT_MESH_ADD = 15; +const float TEREDIT_MESH_KILL = 16; +const float TEREDIT_TINT = 17; +const float TEREDIT_RESET_SECT = 20; +const float TEREDIT_RELOAD_SECT = 21; +const float TEREDIT_ENTS_WIPE = 22; +const float TEREDIT_ENTS_CONCAT = 23; +const float TEREDIT_ENTS_GET = 24; +#endif +#if defined(CSQC) || defined(MENU) +const float SLIST_HOSTCACHEVIEWCOUNT = 0; +const float SLIST_HOSTCACHETOTALCOUNT = 1; +const float SLIST_MASTERQUERYCOUNT = 2; +const float SLIST_MASTERREPLYCOUNT = 3; +const float SLIST_SERVERQUERYCOUNT = 4; +const float SLIST_SERVERREPLYCOUNT = 5; +const float SLIST_SORTFIELD = 6; +const float SLIST_SORTDESCENDING = 7; +const float SLIST_TEST_CONTAINS = 0; +const float SLIST_TEST_NOTCONTAIN = 1; +const float SLIST_TEST_LESSEQUAL = 2; +const float SLIST_TEST_LESS = 3; +const float SLIST_TEST_EQUAL = 4; +const float SLIST_TEST_GREATER = 5; +const float SLIST_TEST_GREATEREQUAL = 6; +const float SLIST_TEST_NOTEQUAL = 7; +const float SLIST_TEST_STARTSWITH = 8; +const float SLIST_TEST_NOTSTARTSWITH = 9; +#endif +#ifdef MENU +float(string ext) checkextension = #1; /* + Checks if the named extension is supported by the running engine. */ + +void(string err,...) error = #2; +void(string err,...) objerror = #3; +void(string text,...) print = #4; +void(string text,...) bprint = #5; +void(float clientnum, string text,...) msprint = #6; +void(string text,...) cprint = #7; +vector(vector) normalize = #8; +float(vector) vlen = #9; +float(vector) vectoyaw = #10; +vector(vector) vectoangles = #11; +float() random = #12; +void(string) localcmd = #13; +float(string name) cvar = #14; +void(string name, string value) cvar_set = #15; +void(string text) dprint = #16; +string(float) ftos = #17; +float(float) fabs = #18; +string(vector) vtos = #19; +string(entity) etos = #20; /* Part of DP_QC_ETOS*/ +float(string) stof = #21; /* Part of FRIK_FILE, FTE_STRINGS, QW_ENGINE, ZQ_QC_STRINGS*/ +entity() spawn = #22; +void(entity) remove = #23; +entity(entity start, .string field, string match) find = #24; +entity(entity start, .__variant field, __variant match) findfloat = #25; /* Part of DP_QC_FINDFLOAT*/ +entity(.string field, string match) findchain = #26; /* Part of DP_QC_FINDCHAIN*/ +entity(.__variant field, __variant match) findchainfloat = #27; /* Part of DP_QC_FINDCHAINFLOAT*/ +string(string file) precache_file = #28; +string(string sample) precache_sound = #29; +void() coredump = #30; +void() traceon = #31; +void() traceoff = #32; +void(entity) eprint = #33; +float(float) rint = #34; +float(float) floor = #35; +float(float) ceil = #36; +entity(entity) nextent = #37; +float(float) sin = #38; /* Part of DP_QC_SINCOSSQRTPOW*/ +float(float) cos = #39; /* Part of DP_QC_SINCOSSQRTPOW*/ +float(float) sqrt = #40; /* Part of DP_QC_SINCOSSQRTPOW*/ +vector() randomvector = #41; +float(string name, string value, float flags) registercvar = #42; /* Part of DP_REGISTERCVAR*/ +float(float,...) min = #43; /* Part of DP_QC_MINMAXBOUND*/ +float(float,...) max = #44; /* Part of DP_QC_MINMAXBOUND*/ +float(float min,float value,float max) bound = #45; /* Part of DP_QC_MINMAXBOUND*/ +float(float,float) pow = #46; /* Part of DP_QC_SINCOSSQRTPOW*/ +void(entity src, entity dst) copyentity = #47; /* Part of DP_QC_COPYENTITY*/ +filestream(string filename, float mode) fopen = #48; /* Part of FRIK_FILE*/ +void(filestream fhandle) fclose = #49; /* Part of FRIK_FILE*/ +string(filestream fhandle) fgets = #50; /* Part of FRIK_FILE*/ +void(filestream fhandle, string s) fputs = #51; /* Part of FRIK_FILE*/ +float(string) strlen = #52; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string, optional string, optional string, optional string, optional string, optional string, optional string, optional string) strcat = #53; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s, float start, float length) substring = #54; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +vector(string) stov = #55; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string) strzone = #56; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +void(string) strunzone = #57; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +float(string) tokenize = #58; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +string(float) argv = #59; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +float() isserver = #60; +float() clientcount = #61; +float() clientstate = #62; +void(string map) changelevel = #64; +void(string sample) localsound = #65; +vector() getmousepos = #66; +float(optional float timetype) gettime = #67; +void(string data) loadfromdata = #68; +void(string data) loadfromfile = #69; +float(float val, float m) mod = #70; +string(string name) cvar_string = #71; /* Part of DP_QC_CVAR_STRING*/ +void() crash = #72; +void() stackdump = #73; +searchhandle(string pattern, float caseinsensitive, float quiet) search_begin = #74; /* Part of DP_QC_FS_SEARCH*/ +void(searchhandle handle) search_end = #75; /* Part of DP_QC_FS_SEARCH*/ +float(searchhandle handle) search_getsize = #76; /* Part of DP_QC_FS_SEARCH*/ +string(searchhandle handle, float num) search_getfilename = #77; /* Part of DP_QC_FS_SEARCH*/ +float(entity) etof = #79; +entity(float) ftoe = #80; +float(string) validstring = #81; +float(string str) altstr_count = #82; +string(string str) altstr_prepare = #83; +string(string str, float num) altstr_get = #84; +string(string str, float num, string set) altstr_set = #85; +entity(entity start, .float field, float match) findflags = #87; /* Part of DP_QC_FINDFLAGS*/ +entity(.float field, float match) findchainflags = #88; /* Part of DP_QC_FINDCHAINFLAGS*/ +void(entity ent, string mname) setmodel = #90; /* + Menuqc-specific version. */ + +void(string mname) precache_model = #91; /* + Menuqc-specific version. */ + +void(entity ent, vector neworg) setorigin = #92; /* + Menuqc-specific version. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector vang) makevectors = #1; /* + Takes an angle vector (pitch,yaw,roll) (+x=DOWN). Writes its results into v_forward, v_right, v_up vectors. */ + +void(entity e, vector o) setorigin = #2; /* + Changes e's origin to be equal to o. Also relinks collision state (as well as setting absmin+absmax), which is required after changing .solid */ + +void(entity e, string m) setmodel = #3; /* + Looks up m in the model precache list, and sets both e.model and e.modelindex to match. BSP models will set e.mins and e.maxs accordingly, other models depend upon the value of sv_gameplayfix_setmodelrealbox - for compatibility you should always call setsize after all pickups or non-bsp models. Also relinks collision state. */ + +void(entity e, vector min, vector max) setsize = #4; /* + Sets the e's mins and maxs fields. Also relinks collision state, which sets absmin and absmax too. */ + +#endif +#ifdef SSQC +void() breakpoint = #6; /* + Trigger a debugging event. FTE will break into the qc debugger. Other engines may crash with a debug execption. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float() random = #7; /* + Returns a random value between 0 and 1. Be warned, this builtin can return 1 in most engines, which can break arrays. */ + +void(entity e, float chan, string samp, float vol, float atten, optional float speedpct, optional float flags, optional float timeofs) sound = #8; /* + Starts a sound centered upon the given entity. + chan is the entity sound channel to use, channel 0 will allow you to mix many samples at once, others will replace the old sample + 'samp' must have been precached first + if specified, 'speedpct' should normally be around 100 (or =0), 200 for double speed or 50 for half speed. + If flags is specified, the reliable flag in the channels argument is used for additional channels. Flags should be made from SOUNDFLAG_* constants + timeofs should be negative in order to provide a delay before the sound actually starts. */ + +vector(vector v) normalize = #9; /* + Shorten or lengthen a direction vector such that it is only one quake unit long. */ + +void(string e) error = #10; /* + Ends the game with an easily readable error message. */ + +void(string e) objerror = #11; /* + Displays a non-fatal easily readable error message concerning the self entity, including a field dump. self will be removed! */ + +float(vector v) vlen = #12; /* + Returns the square root of the dotproduct of a vector with itself. Or in other words the length of a distance vector, in quake units. */ + +float(vector v, optional entity reference) vectoyaw = #13; /* + Given a direction vector, returns the yaw angle in which that direction vector points. If an entity is passed, the yaw angle will be relative to that entity's gravity direction. */ + +entity() spawn = #14; /* + Adds a brand new entity into the world! Hurrah, you're now a parent! */ + +void(entity e) remove = #15; /* + Destroys the given entity and clears some limited fields (including model, modelindex, solid, classname). Any references to the entity following the call are an error. After two seconds, the entity will be reused, in the interim you can unfortunatly still read its fields to see if the reference is no longer valid. */ + +void(vector v1, vector v2, float flags, entity ent) traceline = #16; /* + Traces an infinitely thin line through the world from v1 towards v2. + Will not collide with ent, ent.owner, or any entity who's owner field refers to ent. + There are no side effects beyond the trace_* globals being written. + flags&MOVE_NOMONSTERS will not impact on non-bsp entities. + flags&MOVE_MISSILE will impact with increased size. + flags&MOVE_HITMODEL will impact upon model meshes, instead of their bounding boxes. + flags&MOVE_TRIGGERS will also stop on triggers + flags&MOVE_EVERYTHING will stop if it hits anything, even non-solid entities. + flags&MOVE_LAGGED will backdate entity positions for the purposes of this builtin according to the indicated player ent's latency, to provide lag compensation. */ + +#endif +#ifdef SSQC +entity() checkclient = #17; /* + Returns one of the player entities. The returned player will change periodically. */ + +#endif +#if defined(CSQC) || defined(SSQC) +entity(entity start, .string fld, string match) find = #18; /* + Scan for the next entity with a given field set to the given 'match' value. start should be either world, or the previous entity that was found. Returns world on failure/if there are no more. */ + +string(string s) precache_sound = #19; /* + Precaches a sound, making it known to clients and loading it from disk. This builtin (strongly) should be called during spawn functions. This builtin must be called for the sound before the sound builtin is called, or it might not even be heard. */ + +string(string s) precache_model = #20; /* + Precaches a model, making it known to clients and loading it from disk if it has a .bsp extension. This builtin (strongly) should be called during spawn functions. This must be called for each model name before setmodel may use that model name. + Modelindicies precached in SSQC will always be positive. CSQC precaches will be negative if they are not also on the server. */ + +#endif +#ifdef SSQC +void(entity client, string s) stuffcmd = #21; /* + Sends a console command (or cvar) to the client, where it will be executed. Different clients support different commands. Do NOT forget the final \n. + This builtin is generally considered evil. */ + +void(entity client, float flags, string s) stuffcmdflags = #0/*:stuffcmdflags*/; /* + Sends a console command (or cvar) to the client, where it will be executed. Different clients support different commands. Do NOT forget the final \n. + This (just as evil) variant allows specifying some flags too. See the STUFFCMD_* constants. */ + +#endif +#if defined(CSQC) || defined(SSQC) +entity(vector org, float rad) findradius = #22; /* + Finds all entities within a distance of the 'org' specified. One entity is returned directly, while other entities are returned via that entity's .chain field. */ + +#endif +#if defined(NQSSQC) +void(string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8) bprint = #23; /* + NQ: Concatenates all arguments, and prints the messsage on the console of all connected clients. */ + +#endif +#if defined(QWSSQC) +void(float msglvl, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) bprint = #23; /* + QW: Concatenates all string arguments, and prints the messsage on the console of only all clients who's 'msg' infokey is set lower or equal to the supplied 'msglvl' argument. */ + +#endif +#if defined(NQSSQC) +void(entity client, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) sprint = #24; /* + NQ: Concatenates all string arguments, and prints the messsage on the named client's console */ + +#endif +#if defined(QWSSQC) +void(entity client, float msglvl, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6) sprint = #24; /* + QW: Concatenates all string arguments, and prints the messsage on the named client's console, but only if that client's 'msg' infokey is set lower or equal to the supplied 'msglvl' argument. */ + +#endif +#if defined(CSQC) || defined(NQSSQC) +void(string s, ...) dprint = #25; /* + NQ: Prints the given message on the server's console, but only if the developer cvar is set. Arguments will be concatenated into a single message. */ + +#endif +#if defined(CSQC) || defined(QWSSQC) +void(string s, ...) dprint = #25; /* + QW: Unconditionally prints the given message on the server's console. Arguments will be concatenated into a single message. */ + +#endif +#if defined(CSQC) || defined(SSQC) +string(float val) ftos = #26; /* + Returns a tempstring containing a representation of the given float. Precision depends upon engine. */ + +string(vector val) vtos = #27; /* + Returns a tempstring containing a representation of the given vector. Precision depends upon engine. */ + +void() coredump = #28; /* + Writes out a coredump. This contains stack, globals, and field info for all ents. This can be handy for debugging. */ + +void() traceon = #29; /* + Enables tracing. This may be spammy, slow, and stuff. Set debugger 1 in order to use fte's qc debugger. */ + +void() traceoff = #30; /* + Disables tracing again. */ + +void(entity e) eprint = #31; /* + Debugging builtin that prints all fields of the given entity to the console. */ + +float(float yaw, float dist, optional float settraceglobals) walkmove = #32; /* + Attempt to walk the entity at a given angle for a given distance. + if settraceglobals is set, the trace_* globals will be set, showing the results of the movement. + This function will trigger touch events. */ + +float() droptofloor = #34; /* + Instantly moves the entity downwards until it hits the ground. If the entity is in solid or would need to drop more than 'pr_droptofloorunits' quake units, its position will be considered invalid and the builtin will abort, returning FALSE, otherwise TRUE. */ + +void(float lightstyle, string stylestring, optional vector rgb) lightstyle = #35; /* + Specifies an auto-animating string that specifies the light intensity for entities using that lightstyle. + a is off, z is fully lit. Should be lower case only. + rgb will recolour all lights using that lightstyle. */ + +float(float) rint = #36; /* + Rounds the given float up or down to the closest integeral value. X.5 rounds away from 0 */ + +float(float) floor = #37; /* + Rounds the given float downwards, even when negative. */ + +float(float) ceil = #38; /* + Rounds the given float upwards, even when negative. */ + +float(entity ent) checkbottom = #40; /* + Expensive checks to ensure that the entity is actually sitting on something solid, returns true if it is. */ + +float(vector pos) pointcontents = #41; /* + Checks the given point to see what is there. Returns one of the SOLID_* constants. Just because a spot is empty does not mean that the player can stand there due to the size of the player - use tracebox for such tests. */ + +float(float) fabs = #43; /* + Removes the sign of the float, making it positive if it is negative. */ + +#endif +#ifdef SSQC +vector(entity player, float missilespeed) aim = #44; /* + Returns a direction vector (specifically v_forward on error). This builtin attempts to guess what pitch angle to fire projectiles at for people that don't know about mouselook. Does not affect yaw angles. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(string) cvar = #45; /* + Returns the numeric value of the named cvar */ + +void(string, ...) localcmd = #46; /* + Adds the string to the console command queue. Commands will not be executed immediately, but rather at the start of the following frame. */ + +entity(entity) nextent = #47; /* + Returns the following entity. Skips over removed entities. Returns world when passed the last valid entity. */ + +void(vector pos, vector dir, float colour, float count) particle = #48; /* + Spawn 'count' particles around 'pos' moving in the direction 'dir', with a palette colour index between 'colour' and 'colour+8'. */ + +#define ChangeYaw changeyaw +void() changeyaw = #49; /* + Changes the self.angles_y field towards self.ideal_yaw by up to self.yaw_speed. */ + +vector(vector fwd, optional vector up) vectoangles = #51; /* + Returns the angles (+x=UP) required to orient an entity to look in the given direction. The 'up' argument is required if you wish to set a roll angle, otherwise it will be limited to just monster-style turning. */ + +#endif +#ifdef SSQC +void(float to, float val) WriteByte = #52; /* + Writes a single byte into a network message buffer. Typically you will find a more correct alternative to writing arbitary data. 'to' should be one of the MSG_* constants. MSG_ONE must have msg_entity set first. */ + +void(float to, float val) WriteChar = #53; +void(float to, float val) WriteShort = #54; +void(float to, float val) WriteLong = #55; +void(float to, float val) WriteCoord = #56; +void(float to, float val) WriteAngle = #57; +void(float to, string val) WriteString = #58; +void(float to, entity val) WriteEntity = #59; +#endif +#if defined(CSQC) || defined(SSQC) +float(float angle) sin = #60; /* Part of DP_QC_SINCOSSQRTPOW + Forgive me father, for I have trigonometry homework. */ + +float(float angle) cos = #61; /* Part of DP_QC_SINCOSSQRTPOW*/ +float(float value) sqrt = #62; /* Part of DP_QC_SINCOSSQRTPOW*/ +#endif +#ifdef SSQC +float(float a, float n) modulo = #0/*:modulo*/; +#endif +#if defined(CSQC) || defined(SSQC) +void(entity ent) changepitch = #63; /* Part of DP_QC_CHANGEPITCH*/ +void(entity ent, entity ignore) tracetoss = #64; +string(entity ent) etos = #65; /* Part of DP_QC_ETOS*/ +void(float step) movetogoal = #67; /* + Runs lots and lots of fancy logic in order to try to step the entity the specified distance towards its goalentity. */ + +string(string s) precache_file = #68; /* + This builtin does nothing. It was used only as a hint for pak generation. */ + +void(entity e) makestatic = #69; /* + Sends a copy of the entity's renderable fields to all clients, and REMOVES the entity, preventing further changes. This means it will be unmutable and non-solid. */ + +#endif +#ifdef SSQC +void(string mapname, optional string newmapstartspot) changelevel = #70; /* + Attempts to change the map to the named map. If 'newmapstartspot' is specified, the state of the current map will be preserved, and the argument will be passed to the next map in the 'startspot' global, and the next map will be loaded from archived state if it was previously visited. If not specified, all archived map states will be purged. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(string cvarname, string valuetoset) cvar_set = #72; /* + Instantly sets a cvar to the given string value. */ + +#endif +#ifdef SSQC +void(entity ent, string text, optional string text2, optional string text3, optional string text4, optional string text5, optional string text6, optional string text7) centerprint = #73; +#endif +#if defined(CSQC) || defined(SSQC) +void (vector pos, string samp, float vol, float atten) ambientsound = #74; +string(string str) precache_model2 = #75; +string(string str) precache_sound2 = #76; +string(string str) precache_file2 = #77; +#endif +#ifdef SSQC +void(entity player) setspawnparms = #78; +void(entity killer, entity killee) logfrag = #79; /* Part of QW_ENGINE*/ +string(entity e, string key) infokey = #80; /* Part of QW_ENGINE + If e is world, returns the field 'key' from either the serverinfo or the localinfo. If e is a player, returns the value of 'key' from the player's userinfo string. There are a few special exceptions, like 'ip' which is not technically part of the userinfo. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(string) stof = #81; /* Part of FRIK_FILE, FTE_STRINGS, QW_ENGINE, ZQ_QC_STRINGS*/ +#endif +#ifdef SSQC +#define unicast(pl,reli) do{msg_entity = pl; multicast('0 0 0', reli?MULITCAST_ONE_R:MULTICAST_ONE);}while(0) +void(vector where, float set) multicast = #82; /* + Once the MSG_MULTICAST network message buffer has been filled with data, this builtin is used to dispatch it to the given target, filtering by pvs for reduced network bandwidth. */ + +void(float style, float val, optional vector rgb) lightstylestatic = #5; /* + Sets the lightstyle to an explicit numerical level. From Hexen2. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent) tracebox = #90; /* Part of DP_QC_TRACEBOX + Exactly like traceline, but a box instead of a uselessly thin point. Acceptable sizes are limited by bsp format, q1bsp has strict acceptable size values. */ + +vector() randomvec = #91; /* Part of DP_QC_RANDOMVEC + Returns a vector with random values. Each axis is independantly a value between -1 and 1 inclusive. */ + +vector(vector org) getlight = #92; +float(string cvarname, string defaultvalue) registercvar = #93; /* Part of DP_REGISTERCVAR + Creates a new cvar on the fly. If it does not already exist, it will be given the specified value. If it does exist, this is a no-op. + This builtin has the limitation that it does not apply to configs or commandlines. Such configs will need to use the set or seta command causing this builtin to be a noop. + In engines that support it, you will generally find the autocvar feature easier and more efficient to use. */ + +float(float a, float b, ...) min = #94; /* Part of DP_QC_MINMAXBOUND + Returns the lowest value of its arguments. */ + +float(float a, float b, ...) max = #95; /* Part of DP_QC_MINMAXBOUND + Returns the highest value of its arguments. */ + +float(float minimum, float val, float maximum) bound = #96; /* Part of DP_QC_MINMAXBOUND + Returns val, unless minimum is higher, or maximum is less. */ + +float(float value, float exp) pow = #97; /* Part of DP_QC_SINCOSSQRTPOW*/ +#endif +float(float v, optional float base) logarithm = #0/*:logarithm*/; /* + Determines the logarithm of the input value according to the specified base. This can be used to calculate how much something was shifted by. */ + +#if defined(CSQC) || defined(SSQC) +#define findentity findfloat +entity(entity start, .__variant fld, __variant match) findfloat = #98; /* Part of DP_QC_FINDFLOAT + Equivelent to the find builtin, but instead of comparing strings contents, this builtin compares the raw values. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value. + world is returned when there are no more entities. */ + +float(string extname) checkextension = #99; /* + Checks for an extension by its name (eg: checkextension("FRIK_FILE") says that its okay to go ahead and use strcat). + Use cvar("pr_checkextension") to see if this builtin exists. */ + +#endif +float(__variant funcref) checkbuiltin = #0/*:checkbuiltin*/; /* + Checks to see if the specified builtin is supported/mapped. This is intended as a way to check for #0 functions, allowing for simple single-builtin functions. */ + +#ifdef SSQC +float(string builtinname) builtin_find = #100; /* + Looks to see if the named builtin is valid, and returns the builtin number it exists at. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(float value) anglemod = #102; +#endif +#ifdef SSQC +void(string slot, string picname, float x, float y, float zone, optional entity player) showpic = #104; /* Part of TEI_SHOWLMP2*/ +void(string slot, optional entity player) hidepic = #105; /* Part of TEI_SHOWLMP2*/ +void(string slot, float x, float y, float zone, optional entity player) movepic = #106; /* Part of TEI_SHOWLMP2*/ +void(string slot, string picname, optional entity player) changepic = #107; /* Part of TEI_SHOWLMP2*/ +#endif +#if defined(CSQC) || defined(SSQC) +filestream(string filename, float mode, optional float mmapminsize) fopen = #110; /* Part of FRIK_FILE*/ +void(filestream fhandle) fclose = #111; /* Part of FRIK_FILE*/ +string(filestream fhandle) fgets = #112; /* Part of FRIK_FILE*/ +void(filestream fhandle, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) fputs = #113; /* Part of FRIK_FILE*/ +float(string s) strlen = #114; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s1, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8) strcat = #115; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s, float start, float length) substring = #116; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +vector(string s) stov = #117; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS*/ +string(string s, ...) strzone = #118; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS + Create a semi-permanent copy of a string that only becomes invalid once strunzone is called on the string (instead of when the engine assumes your string has left scope). This builtin has become redundant in FTEQW due to the FTE_QC_PERSISTENTTEMPSTRINGS extension and is now functionally identical to strcat for compatibility with old engines+mods. */ + +void(string s) strunzone = #119; /* Part of FRIK_FILE, FTE_STRINGS, ZQ_QC_STRINGS + Destroys a string that was allocated by strunzone. Further references to the string MAY crash the game. In FTE, this function became redundant and now does nothing. */ + +#endif +#ifdef SSQC +void(string cvar, float val) cvar_setf = #176; +#endif +#if defined(CSQC) || defined(SSQC) +float(string modelname, optional float queryonly) getmodelindex = #200; /* + Acts as an alternative to precache_model(foo);setmodel(bar, foo); return bar.modelindex; + If queryonly is set and the model was not previously precached, the builtin will return 0 without needlessly precaching the model. */ + +__variant(float prnum, string funcname, ...) externcall = #201; /* Part of FTE_MULTIPROGS + Directly call a function in a different/same progs by its name. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +float(string progsname) addprogs = #202; /* Part of FTE_MULTIPROGS + Loads an additional .dat file into the current qcvm. The returned handle can be used with any of the externcall/externset/externvalue builtins. + There are cvars that allow progs to be loaded automatically. */ + +__variant(float prnum, string varname) externvalue = #203; /* Part of FTE_MULTIPROGS + Reads a global in the named progs by the name of that global. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +void(float prnum, __variant newval, string varname) externset = #204; /* Part of FTE_MULTIPROGS + Sets a global in the named progs by name. + prnum=0 is the 'default' or 'main' progs. + prnum=-1 means current progs. + prnum=-2 will scan through the active progs and will use the first it finds. */ + +void(entity portal, float state) openportal = #207; /* + Opens or closes the portals associated with a door or some such on q2 or q3 maps. On Q2BSPs, the entity should be the 'func_areaportal' entity - its style field will say which portal to open. On Q3BSPs, the entity is the door itself, the portal will be determined by the two areas found from a preceding setorigin call. */ + +#endif +#ifdef SSQC +float(float attributes, string effectname, ...) RegisterTempEnt = #208; /* Part of FTE_PEXT_CUSTOMTENTS*/ +void(float type, vector pos, ...) CustomTempEnt = #209; /* Part of FTE_PEXT_CUSTOMTENTS*/ +float(optional float sleeptime) fork = #210; /* Part of FTE_MULTITHREADED + When called, this builtin simply returns. Twice. + The current 'thread' will return instantly with a return value of 0. The new 'thread' will return after sleeptime seconds with a return value of 1. See documentation for the 'sleep' builtin for limitations/requirements concerning the new thread. Note that QC should probably call abort in the new thread, as otherwise the function will return to the calling qc function twice also. */ + +#endif +void(optional __variant ret) abort = #211; /* Part of FTE_MULTITHREADED + QC execution is aborted. Parent QC functions on the stack will be skipped, effectively this forces all QC functions to 'return ret' until execution returns to the engine. If ret is ommited, it is assumed to be 0. */ + +#ifdef SSQC +void(float sleeptime) sleep = #212; /* Part of FTE_MULTITHREADED + Suspends the current QC execution thread for 'sleeptime' seconds. + Other QC functions can and will be executed in the interim, including changing globals and field state (but not simultaneously). + The self and other globals will be restored when the thread wakes up (or set to world if they were removed since the thread started sleeping). Locals will be preserved, but will not be protected from remove calls. + If the engine is expecting the QC to return a value (even in the parent/root function), the value 0 shall be used instead of waiting for the qc to resume. */ + +void(entity player, string key, string value) forceinfokey = #213; /* Part of FTE_FORCEINFOKEY + Directly changes a user's info without pinging off the client. Also allows explicitly setting * keys, including *spectator. Does not affect the user's config or other servers. */ + +void(string filename, float starttag, entity edict) chat = #214; /* Part of FTE_NPCCHAT*/ +#endif +#if defined(CSQC) || defined(SSQC) +void(vector org, vector dmin, vector dmax, float colour, float effect, float count) particle2 = #215; /* Part of FTE_HEXEN2*/ +void(vector org, vector box, float colour, float effect, float count) particle3 = #216; /* Part of FTE_HEXEN2*/ +void(vector org, float radius, float colour, float effect, float count) particle4 = #217; /* Part of FTE_HEXEN2*/ +float(float number, float quantity) bitshift = #218; /* Part of EXT_BITSHIFT*/ +void(vector pos) te_lightningblood = #219; /* Part of FTE_TE_STANDARDEFFECTBUILTINS*/ +#endif +float(string s1, string sub, optional float startidx) strstrofs = #221; /* Part of FTE_STRINGS + Returns the 0-based offset of sub within the s1 string, or -1 if sub is not in s1. + If startidx is set, this builtin will ignore matches before that 0-based offset. */ + +float(string str, float index) str2chr = #222; /* Part of FTE_STRINGS + Retrieves the character value at offset 'index'. */ + +string(float chr, ...) chr2str = #223; /* Part of FTE_STRINGS + The input floats are considered character values, and are concatenated. */ + +string(float ccase, float redalpha, float redchars, string str, ...) strconv = #224; /* Part of FTE_STRINGS + Converts quake chars in the input string amongst different representations. + ccase specifies the new case for letters. + 0: not changed. + 1: forced to lower case. + 2: forced to upper case. + redalpha and redchars switch between colour ranges. + 0: no change. + 1: Forced white. + 2: Forced red. + 3: Forced gold(low) (numbers only). + 4: Forced gold (high) (numbers only). + 5+6: Forced to white and red alternately. + You should not use this builtin in combination with UTF-8. */ + +string(float pad, string str1, ...) strpad = #225; /* Part of FTE_STRINGS + Pads the string with spaces, to ensure its a specific length (so long as a fixed-width font is used, anyway). If pad is negative, the spaces are added on the left. If positive the padding is on the right. */ + +string(infostring old, string key, string value) infoadd = #226; /* Part of FTE_STRINGS + Returns a new tempstring infostring with the named value changed (or added if it was previously unspecified). Key and value may not contain the \ character. */ + +string(infostring info, string key) infoget = #227; /* Part of FTE_STRINGS + Reads a named value from an infostring. The returned value is a tempstring */ + +#define strcmp strncmp +float(string s1, string s2, optional float len, optional float s1ofs, optional float s2ofs) strncmp = #228; /* Part of FTE_STRINGS + Compares up to 'len' chars in the two strings. s1ofs allows you to treat s2 as a substring to compare against, or should be 0. + Returns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher. */ + +float(string s1, string s2) strcasecmp = #229; /* Part of FTE_STRINGS + Compares the two strings without case sensitivity. + Returns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon. */ + +float(string s1, string s2, float len, optional float s1ofs, optional float s2ofs) strncasecmp = #230; /* Part of FTE_STRINGS + Compares up to 'len' chars in the two strings without case sensitivity. s1ofs allows you to treat s2 as a substring to compare against, or should be 0. + Returns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon. */ + +string(string s) strtrim = #0/*:strtrim*/; /* + Trims the whitespace from the start+end of the string. */ + +#if defined(CSQC) || defined(SSQC) +void() calltimeofday = #231; /* Part of FTE_CALLTIMEOFDAY + Asks the engine to instantly call the qc's 'timeofday' function, before returning. For compatibility with mvdsv. + timeofday should have the prototype: void(float secs, float mins, float hour, float day, float mon, float year, string strvalue) + The strftime builtin is more versatile and less weird. */ + +#endif +#ifdef SSQC +void(float num, float type, .__variant fld) clientstat = #232; /* + Specifies what data to use in order to send various stats, in a client-specific way. + 'num' should be a value between 32 and 127, other values are reserved. + 'type' must be set to one of the EV_* constants, one of EV_FLOAT, EV_STRING, EV_INTEGER, EV_ENTITY. + fld must be a reference to the field used, each player will be sent only their own copy of these fields. */ + +void(float num, float type, string name) globalstat = #233; /* + Specifies what data to use in order to send various stats, in a non-client-specific way. num and type are as in clientstat, name however, is the name of the global to read in the form of a string (pass "foo"). */ + +void(float num, float type, __variant *address) pointerstat = #0/*:pointerstat*/; /* + Specifies what data to use in order to send various stats, in a non-client-specific way. num and type are as in clientstat, address however, is the address of the variable you would like to use (pass &foo). */ + +float(entity player) isbackbuffered = #234; /* Part of FTE_ISBACKBUFFERED + Returns if the given player's network buffer will take multiple network frames in order to clear. If this builtin returns non-zero, you should delay or reduce the amount of reliable (and also unreliable) data that you are sending to that client. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector angle) rotatevectorsbyangle = #235; +void(vector fwd, vector right, vector up) rotatevectorsbyvectors = #236; +float(float mdlindex, string skinname) skinforname = #237; +#endif +#if defined(CSQC) || defined(MENU) +float(string shadername, optional string defaultshader, ...) shaderforname = #238; /* Part of FTE_FORCESHADER + Caches the named shader and returns a handle to it. + If the shader could not be loaded from disk (missing file or ruleset_allow_shaders 0), it will be created from the 'defaultshader' string if specified, or a 'skin shader' default will be used. + defaultshader if not empty should include the outer {} that you would ordinarily find in a shader. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(vector org, optional float count) te_bloodqw = #239; /* Part of FTE_TE_STANDARDEFFECTBUILTINS*/ +#endif +#ifdef SSQC +void(entity ent) te_muzzleflash = #0/*:te_muzzleflash*/; +#endif +#if defined(CSQC) || defined(SSQC) +float(vector viewpos, entity entity) checkpvs = #240; /* Part of FTE_QC_CHECKPVS*/ +#endif +#ifdef SSQC +entity(string match, optional float matchnum) matchclientname = #241; /* Part of FTE_QC_MATCHCLIENTNAME*/ +void(string dest, string content) sendpacket = #242; /* Part of FTE_QC_SENDPACKET*/ +#endif +#ifdef CSQC +vector(entity ent, float tagnum) rotatevectorsbytag = #244; +#endif +#ifdef SSQC +float(float dividend, float divisor) mod = #245; +float(optional string host, optional string user, optional string pass, optional string defaultdb, optional string driver) sqlconnect = #250; /* Part of FTE_SQL*/ +void(float serveridx) sqldisconnect = #251; /* Part of FTE_SQL*/ +float(float serveridx, void(float serveridx, float queryidx, float rows, float columns, float eof, float firstrow) callback, float querytype, string query) sqlopenquery = #252; /* Part of FTE_SQL*/ +void(float serveridx, float queryidx) sqlclosequery = #253; /* Part of FTE_SQL*/ +string(float serveridx, float queryidx, float row, float column) sqlreadfield = #254; /* Part of FTE_SQL*/ +string(float serveridx, optional float queryidx) sqlerror = #255; /* Part of FTE_SQL*/ +string(float serveridx, string data) sqlescape = #256; /* Part of FTE_SQL*/ +string(float serveridx) sqlversion = #257; /* Part of FTE_SQL*/ +float(float serveridx, float queryidx, float row, float column) sqlreadfloat = #258; /* Part of FTE_SQL*/ +int(float serveridx, float queryidx, float row, float column, _variant *ptr, int maxsize) sqlreadblob = #0/*:sqlreadblob*/; +string(float serveridx, _variant *ptr, int maxsize) sqlescapeblob = #0/*:sqlescapeblob*/; +#endif +#if defined(CSQC) || defined(SSQC) +int(string) stoi = #259; /* Part of FTE_QC_INTCONV + Converts the given string into a true integer. Base 8, 10, or 16 is determined based upon the format of the string. */ + +string(int) itos = #260; /* Part of FTE_QC_INTCONV + Converts the passed true integer into a base10 string. */ + +int(string) stoh = #261; /* Part of FTE_QC_INTCONV + Reads a base-16 string (with or without 0x prefix) as an integer. Bugs out if given a base 8 or base 10 string. :P */ + +string(int) htos = #262; /* Part of FTE_QC_INTCONV + Formats an integer as a base16 string, with leading 0s and no prefix. Always returns 8 characters. */ + +#endif +int(float) ftoi = #0/*:ftoi*/; /* + Converts the given float into a true integer without depending on extended qcvm instructions. */ + +float(int) itof = #0/*:itof*/; /* + Converts the given true integer into a float without depending on extended qcvm instructions. */ + +#if defined(CSQC) || defined(SSQC) +float(float modlindex, optional float useabstransforms) skel_create = #263; /* Part of FTE_CSQC_SKELETONOBJECTS + Allocates a new uninitiaised skeletal object, with enough bone info to animate the given model. + eg: self.skeletonobject = skel_create(self.modelindex); */ + +float(float skel, entity ent, float modelindex, float retainfrac, float firstbone, float lastbone, optional float addfrac) skel_build = #264; /* Part of FTE_CSQC_SKELETONOBJECTS + Animation data (according to the entity's frame info) is pulled from the specified model and blended into the specified skeletal object. + If retainfrac is set to 0 on the first call and 1 on the others, you can blend multiple animations together according to the addfrac value. The final weight should be 1. Other values will result in scaling and/or other weirdness. You can use firstbone and lastbone to update only part of the skeletal object, to allow legs to animate separately from torso, use 0 for both arguments to specify all, as bones are 1-based. */ + +float(float skel) skel_get_numbones = #265; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrives the number of bones in the model. The valid range is 1<=bone<=numbones. */ + +string(float skel, float bonenum) skel_get_bonename = #266; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves the name of the specified bone. Mostly only for debugging. */ + +float(float skel, float bonenum) skel_get_boneparent = #267; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves which bone this bone's position is relative to. Bone 0 refers to the entity's position rather than an actual bone */ + +float(float skel, string tagname) skel_find_bone = #268; /* Part of FTE_CSQC_SKELETONOBJECTS + Finds a bone by its name, from the model that was used to create the skeletal object. */ + +vector(float skel, float bonenum) skel_get_bonerel = #269; /* Part of FTE_CSQC_SKELETONOBJECTS + Gets the bone position and orientation relative to the bone's parent. Return value is the offset, and v_forward, v_right, v_up contain the orientation. */ + +vector(float skel, float bonenum) skel_get_boneabs = #270; /* Part of FTE_CSQC_SKELETONOBJECTS + Gets the bone position and orientation relative to the entity. Return value is the offset, and v_forward, v_right, v_up contain the orientation. + Use gettaginfo for world coord+orientation. */ + +void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_set_bone = #271; /* Part of FTE_CSQC_SKELETONOBJECTS + Sets a bone position relative to its parent. If the orientation arguments are not specified, v_forward+v_right+v_up are used instead. */ + +void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_mul_bone = #272; /* Part of FTE_CSQC_SKELETONOBJECTS + Transforms a single bone by a matrix. You can use makevectors to generate a rotation matrix from an angle. */ + +void(float skel, float startbone, float endbone, vector org, optional vector fwd, optional vector right, optional vector up) skel_mul_bones = #273; /* Part of FTE_CSQC_SKELETONOBJECTS + Transforms an entire consecutive range of bones by a matrix. You can use makevectors to generate a rotation matrix from an angle, but you'll probably want to divide the angle by the number of bones. */ + +void(float skeldst, float skelsrc, float startbone, float entbone) skel_copybones = #274; /* Part of FTE_CSQC_SKELETONOBJECTS + Copy bone data from one skeleton directly into another. */ + +void(float skel) skel_delete = #275; /* Part of FTE_CSQC_SKELETONOBJECTS + Deletes a skeletal object. The actual delete is delayed, allowing the skeletal object to be deleted in an entity's predraw function yet still be valid by the time the addentity+renderscene builtins need it. Also uninstanciates any ragdoll currently in effect on the skeletal object. */ + +float(float modidx, string framename) frameforname = #276; /* Part of FTE_CSQC_SKELETONOBJECTS + Looks up a framegroup from a model by name, avoiding the need for hardcoding. Returns -1 on error. */ + +float(float modidx, float framenum) frameduration = #277; /* Part of FTE_CSQC_SKELETONOBJECTS + Retrieves the duration (in seconds) of the specified framegroup. */ + +void(float action, optional vector pos, optional float radius, optional float quant, ...) terrain_edit = #278; /* + Realtime terrain editing. Actions are the TEREDIT_ constants. */ + +typedef struct +{ + string shadername; + vector planenormal; + float planedist; + vector sdir; + float sbias; + vector tdir; + float tbias; +} brushface_t; +int(float modelidx, int brushid, brushface_t *out_faces, int maxfaces, int *out_contents) brush_get = #0/*:brush_get*/; /* + Queries a brush's information. You must pre-allocate the face array for the builtin to write to. Return value is the number of faces retrieved, 0 on error. */ + +int(float modelidx, brushface_t *in_faces, int numfaces, int contents) brush_create = #0/*:brush_create*/; /* + Inserts a new brush into the model. Return value is the new brush's id. */ + +void(float modelidx, int brushid) brush_delete = #0/*:brush_delete*/; /* + Destroys the specified brush. */ + +float(float modelid, int brushid, int faceid, float selectedstate) brush_selected = #0/*:brush_selected*/; /* + Allows you to easily set transient visual properties of a brush. returns old value. selectedstate=-1 changes nothing (called for its return value). */ + +int(float modelid, int brushid, int faceid, vector *points, int maxpoints) brush_getfacepoints = #0/*:brush_getfacepoints*/; /* + Returns the list of verticies surrounding the given face. If face is 0, returns the center of the brush (if space for 1 point) or the mins+maxs (if space for 2 points). */ + +int(int faceid, brushface_t *in_faces, int numfaces, vector *points, int maxpoints) brush_calcfacepoints = #0/*:brush_calcfacepoints*/; /* + Determines the points of the specified face, if the specified brush were to actually be created. */ + +int(float modelid, vector *planes, float *dists, int numplanes, int *out_brushes, int *out_faces, int maxresults) brush_findinvolume = #0/*:brush_findinvolume*/; /* + Allows you to easily obtain a list of brushes+faces within the given bounding region. If out_faces is not null, the same brush might be listed twice. */ + +void(optional entity ent, optional vector neworigin) touchtriggers = #279; /* + Triggers a touch events between self and every SOLID_TRIGGER entity that it is in contact with. This should typically just be the triggers touch functions. Also optionally updates the origin of the moved entity. */ + +#endif +#ifdef SSQC +void(float buf, float fl) WriteFloat = #280; +#endif +#if defined(CSQC) || defined(SSQC) +float(entity skelent, string dollcmd, float animskel) skel_ragupdate = #281; /* + Updates the skeletal object attached to the entity according to its origin and other properties. + if animskel is non-zero, the ragdoll will animate towards the bone state in the animskel skeletal object, otherwise they will pick up the model's base pose which may not give nice results. + If dollcmd is not set, the ragdoll will update (this should be done each frame). + If the doll is updated without having a valid doll, the model's default .doll will be instanciated. + commands: + doll foo.doll : sets up the entity to use the named doll file + dollstring TEXT : uses the doll file directly embedded within qc, with that extra prefix. + cleardoll : uninstanciates the doll without destroying the skeletal object. + animate 0.5 : specifies the strength of the ragdoll as a whole + animatebody somebody 0.5 : specifies the strength of the ragdoll on a specific body (0 will disable ragdoll animations on that body). + enablejoint somejoint 1 : enables (or disables) a joint. Disabling joints will allow the doll to shatter. */ + +float*(float skel) skel_mmap = #282; /* + Map the bones in VM memory. They can then be accessed via pointers. Each bone is 12 floats, the four vectors interleaved (sadly). */ + +void(entity ent, float bonenum, vector org, optional vector angorfwd, optional vector right, optional vector up) skel_set_bone_world = #283; /* + Sets the world position of a bone within the given entity's attached skeletal object. The world position is dependant upon the owning entity's position. If no orientation argument is specified, v_forward+v_right+v_up are used for the orientation instead. If 1 is specified, it is understood as angles. If 3 are specified, they are the forawrd/right/up vectors to use. */ + +string(float modidx, float framenum) frametoname = #284; +string(float modidx, float skin) skintoname = #285; +float(float resourcetype, float tryload, string resourcename) resourcestatus = #286; /* + resourcetype must be one of the RESTYPE_ constants. Returns one of the RESSTATE_ constants. Tryload 0 is a query only. Tryload 1 will attempt to reload the content if it was flushed. */ + +#endif +hashtable(float tabsize, optional float defaulttype) hash_createtab = #287; /* Part of FTE_QC_HASHTABLES + Creates a hash table object with at least 'tabsize' slots. hash table with index 0 is a game-persistant table and will NEVER be returned by this builtin (except as an error return). */ + +void(hashtable table) hash_destroytab = #288; /* Part of FTE_QC_HASHTABLES + Destroys a hash table object. */ + +void(hashtable table, string name, __variant value, optional float typeandflags) hash_add = #289; /* Part of FTE_QC_HASHTABLES + Adds the given key with the given value to the table. + If flags&HASH_REPLACE, the old value will be removed, if not set then multiple values may be added for a single key, they won't overwrite. + The type argument describes how the value should be stored and saved to files. While you can claim that all variables are just vectors, being more precise can result in less issues with tempstrings or saved games. */ + +__variant(hashtable table, string name, optional __variant deflt, optional float requiretype, optional float index) hash_get = #290; /* Part of FTE_QC_HASHTABLES + looks up the specified key name in the hash table. returns deflt if key was not found. If stringsonly=1, the return value will be in the form of a tempstring, otherwise it'll be the original value argument exactly as it was. If requiretype is specified, then values not of the specified type will be ignored. Hurrah for multiple types with the same name. */ + +__variant(hashtable table, string name) hash_delete = #291; /* Part of FTE_QC_HASHTABLES + removes the named key. returns the value of the object that was destroyed, or 0 on error. */ + +string(hashtable table, float idx) hash_getkey = #292; /* Part of FTE_QC_HASHTABLES + gets some random key name. add+delete can change return values of this, so don't blindly increment the key index if you're removing all. */ + +float(string name) checkcommand = #294; /* Part of FTE_QC_CHECKCOMMAND + Checks to see if the supplied name is a valid command, cvar, or alias. Returns 0 if it does not exist. */ + +string(string s) argescape = #295; /* + Marks up a string so that it can be reliably tokenized as a single argument later. */ + +#ifdef SSQC +void(string dest, string from, string cmd, string info) clusterevent = #0/*:clusterevent*/; /* + Only functions in mapcluster mode. Sends an event to whichever server the named player is on. The destination server can then dispatch the event to the client or handle it itself via the SV_ParseClusterEvent entrypoint. If dest is empty, the event is broadcast to ALL servers. If the named player can't be found, the event will be returned to this server with the cmd prefixed with 'error:'. */ + +string(entity player, optional string newnode) clustertransfer = #0/*:clustertransfer*/; /* + Only functions in mapcluster mode. Initiate transfer of the player to a different node. Can take some time. If dest is specified, returns null on error. Otherwise returns the current/new target node (or null if not transferring). */ + +#endif +#if defined(CSQC) || defined(MENU) +void() clearscene = #300; /* + Forgets all rentities, polygons, and temporary dlights. Resets all view properties to their default values. */ + +#endif +#ifdef CSQC +void(float mask) addentities = #301; /* + Walks through all entities effectively doing this: + if (ent.drawmask&mask){ ent.predaw(); if (wasremoved(ent)||(ent.renderflags&RF_NOAUTOADD))continue; addentity(ent); } + If mask&MASK_DELTA, non-csqc entities, particles, and related effects will also be added to the rentity list. + If mask&MASK_STDVIEWMODEL then the default view model will also be added. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(entity ent) addentity = #302; /* + Copies the entity fields into a new rentity for later rendering via addscene. */ + +#define setviewprop setproperty +float(float property, ...) setproperty = #303; /* + Allows you to override default view properties like viewport, fov, and whether the engine hud will be drawn. Different VF_ values have slightly different arguments, some are vectors, some floats. */ + +void() renderscene = #304; /* + Draws all entities, polygons, and particles on the rentity list (which were added via addentities or addentity), using the various view properties set via setproperty. There is no ordering dependancy. + The scene must generally be cleared again before more entities are added, as entities will persist even over to the next frame. + You may call this builtin multiple times per frame, but should only be called from CSQC_UpdateView. */ + +#endif +#ifdef CSQC +float(vector org, float radius, vector lightcolours, optional float style, optional string cubemapname, optional float pflags) dynamiclight_add = #305; /* + Adds a temporary dlight, ready to be drawn via addscene. Cubemap orientation will be read from v_forward/v_right/v_up. */ + +#endif +void(string texturename, optional float flags, optional float is2d) R_BeginPolygon = #306; /* + Specifies the shader to use for the following polygons, along with optional flags. + If is2d, the polygon will be drawn as soon as the EndPolygon call is made, rather than waiting for renderscene. This allows complex 2d effects. */ + +void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex = #307; /* + Specifies a polygon vertex with its various properties. */ + +void() R_EndPolygon = #308; /* + Ends the current polygon. At least 3 verticies must have been specified. You do not need to call beginpolygon if you wish to draw another polygon with the same shader. */ + +#if defined(CSQC) || defined(MENU) +#define getviewprop getproperty +__variant(float property) getproperty = #309; /* + Retrieve a currently-set (typically view) property, allowing you to read the current viewport or other things. Due to cheat protection, certain values may be unretrievable. */ + +#endif +#ifdef CSQC +vector (vector v) unproject = #310; /* + Transform a 2d screen-space point (with depth) into a 3d world-space point, according the various origin+angle+fov etc settings set via setproperty. */ + +vector (vector v) project = #311; /* + Transform a 3d world-space point into a 2d screen-space point, according the various origin+angle+fov etc settings set via setproperty. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(vector pos, vector size, float alignflags, string text) drawtextfield = #0/*:drawtextfield*/; /* + Draws a multi-line block of text, including word wrapping and alignment. alignflags bits are RTLB, typically 3. */ + +#endif +#ifdef CSQC +void(float width, vector pos1, vector pos2, vector rgb, float alpha, optional float drawflag) drawline = #315; /* + Draws a 2d line between the two 2d points. */ + +float(string name) iscachedpic = #316; /* + Checks to see if the image is currently loaded. Engines might lie, or cache between maps. */ + +string(string name, optional float trywad) precache_pic = #317; /* + Forces the engine to load the named image. If trywad is specified, the specified name must any lack path and extension. */ + +#define draw_getimagesize drawgetimagesize +vector(string picname) drawgetimagesize = #318; /* + Returns the dimensions of the named image. Images specified with .lmp should give the original .lmp's dimensions even if texture replacements use a different resolution. */ + +void(string name) freepic = #319; /* + Tells the engine that the image is no longer needed. The image will appear to be new the next time its needed. */ + +float(vector position, float character, vector size, vector rgb, float alpha, optional float drawflag) drawcharacter = #320; /* + Draw the given quake character at the given position. + If flag&4, the function will consider the char to be a unicode char instead (or display as a ? if outside the 32-127 range). + size should normally be something like '8 8 0'. + rgb should normally be '1 1 1' + alpha normally 1. + Software engines may assume the named defaults. + Note that ALL text may be rescaled on the X axis due to variable width fonts. The X axis may even be ignored completely. */ + +float(vector position, string text, vector size, vector rgb, float alpha, optional float drawflag) drawrawstring = #321; /* + Draws the specified string without using any markup at all, even in engines that support it. + If UTF-8 is globally enabled in the engine, then that encoding is used (without additional markup), otherwise it is raw quake chars. + Software engines may assume a size of '8 8 0', rgb='1 1 1', alpha=1, flag&3=0, but it is not an error to draw out of the screen. */ + +float(vector position, string pic, vector size, vector rgb, float alpha, optional float drawflag) drawpic = #322; /* + Draws an shader within the given 2d screen box. Software engines may omit support for rgb+alpha, but must support rescaling, and must clip to the screen without crashing. */ + +float(vector position, vector size, vector rgb, float alpha, optional float drawflag) drawfill = #323; /* + Draws a solid block over the given 2d box, with given colour, alpha, and blend mode (specified via flags). + flags&3=0 simple blend. + flags&3=1 additive blend */ + +void(float x, float y, float width, float height) drawsetcliparea = #324; /* + Specifies a 2d clipping region (aka: scissor test). 2d draw calls will all be clipped to this 2d box, the area outside will not be modified by any 2d draw call (even 2d polygons). */ + +void(void) drawresetcliparea = #325; /* + Reverts the scissor/clip area to the whole screen. */ + +float(vector position, string text, vector size, vector rgb, float alpha, float drawflag) drawstring = #326; /* + Draws a string, interpreting markup and recolouring as appropriate. */ + +float(string text, float usecolours, optional vector fontsize) stringwidth = #327; /* + Calculates the width of the screen in virtual pixels. If usecolours is 1, markup that does not affect the string width will be ignored. Will always be decoded as UTF-8 if UTF-8 is globally enabled. + If the char size is not specified, '8 8 0' will be assumed. */ + +void(vector pos, vector sz, string pic, vector srcpos, vector srcsz, vector rgb, float alpha, optional float drawflag) drawsubpic = #328; /* + Draws a rescaled subsection of an image to the screen. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(vector pivot, vector mins, vector maxs, string pic, vector rgb, float alpha, float angle) drawrotpic = #0/*:drawrotpic*/; /* + Draws an image rotating at the pivot. To rotate in the center, use mins+maxs of half the size with mins negated. Angle is in degrees. */ + +void(vector pivot, vector mins, vector maxs, string pic, vector txmin, vector txsize, vector rgb, vector alphaandangles) drawrotsubpic = #0/*:drawrotsubpic*/; /* + Overcomplicated draw function for over complicated people. Positions follow drawrotpic, while texture coords follow drawsubpic. Due to argument count limitations in builtins, the alpha value and angles are combined into separate fields of a vector (tip: use fteqcc's [alpha, angle] feature. */ + +#endif +#ifdef CSQC +float(float stnum) getstati = #330; /* + Retrieves the numerical value of the given EV_INTEGER or EV_ENTITY stat (converted to a float). */ + +#define getstatbits getstatf +float(float stnum, optional float firstbit, optional float bitcount) getstatf = #331; /* + Retrieves the numerical value of the given EV_FLOAT stat. If firstbit and bitcount are specified, retrieves the upper bits of the STAT_ITEMS stat. */ + +string(float stnum) getstats = #332; /* + Retrieves the value of the given EV_STRING stat, as a tempstring. + Older engines may use 4 consecutive integer stats, with a limit of 15 chars (yes, really. 15.), but FTE QuakeWorld uses a separate namespace for string stats and has a much higher length limit. */ + +__variant(float playernum, float statnum, float stattype) getplayerstat = #0/*:getplayerstat*/; /* + Retrieves a specific player's stat, matching the type specified on the server. This builtin is primarily intended for mvd playback where ALL players are known. For EV_ENTITY, world will be returned if the entity is not in the pvs, use type-punning with EV_INTEGER to get the entity number if you just want to see if its set. STAT_ITEMS should be queried as an EV_INTEGER on account of runes and items2 being packed into the upper bits. */ + +void(entity e, float mdlindex) setmodelindex = #333; /* + Sets a model by precache index instead of by name. Otherwise identical to setmodel. */ + +string(float mdlindex) modelnameforindex = #334; /* + Retrieves the name of the model based upon a precache index. This can be used to reduce csqc network traffic by enabling model matching. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(string effectname) particleeffectnum = #335; /* Part of DP_ENT_TRAILEFFECTNUM, FTE_SV_POINTPARTICLES + Precaches the named particle effect. If your effect name is of the form 'foo.bar' then particles/foo.cfg will be loaded by the client if foo.bar was not already defined. + Different engines will have different particle systems, this specifies the QC API only. */ + +void(float effectnum, entity ent, vector start, vector end) trailparticles = #336; /* Part of FTE_SV_POINTPARTICLES + Draws the given effect between the two named points. If ent is not world, distances will be cached in the entity in order to avoid framerate dependancies. The entity is not otherwise used. */ + +void(float effectnum, vector origin, optional vector dir, optional float count) pointparticles = #337; /* Part of FTE_SV_POINTPARTICLES + Spawn a load of particles from the given effect at the given point traveling or aiming along the direction specified. The number of particles are scaled by the count argument. */ + +#endif +#ifdef CSQC +void(string s, ...) cprint = #338; /* + Print into the center of the screen just as ssqc's centerprint would appear. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(string s, ...) print = #339; /* + Unconditionally print on the local system's console, even in ssqc (doesn't care about the value of the developer cvar). */ + +#endif +#ifdef CSQC +string(float keynum) keynumtostring = #340; /* + Returns a hunam-readable name for the given keycode, as a tempstring. */ + +#endif +#ifdef MENU +string(float keynum) keynumtostring_csqc = #340; /* + Returns a hunam-readable name for the given keycode, as a tempstring. */ + +#endif +#ifdef CSQC +float(string keyname) stringtokeynum = #341; /* + Looks up the key name in the same way that the bind command would, returning the keycode for that key. */ + +#endif +#ifdef MENU +float(string keyname) stringtokeynum_csqc = #341; /* + Looks up the key name in the same way that the bind command would, returning the keycode for that key. */ + +#endif +#if defined(CSQC) || defined(MENU) +string(float keynum) getkeybind = #342; /* + Returns the current binding for the given key (returning only the command executed when no modifiers are pressed). */ + +void(float usecursor, optional string cursorimage, optional vector hotspot, optional float scale) setcursormode = #343; /* + Pass TRUE if you want the engine to release the mouse cursor (absolute input events + touchscreen mode). Pass FALSE if you want the engine to grab the cursor (relative input events + standard looking). If the image name is specified, the engine will use that image for a cursor (use an empty string to clear it again), in a way that will not conflict with the console. Images specified this way will be hardware accelerated, if supported by the platform/port. */ + +float(float effective) getcursormode = #0/*:getcursormode*/; /* + Reports the cursor mode this module previously attempted to use. If 'effective' is true, reports the cursor mode currently active (if was overriden by a different module which has precidence, for instance, or if there is only a touchscreen and no mouse). */ + +#endif +#ifdef CSQC +vector() getmousepos = #344; /* + Nasty convoluted DP extension. Typically returns deltas instead of positions. Use CSQC_InputEvent for such things in csqc mods. */ + +float(float inputsequencenum) getinputstate = #345; /* + Looks up an input frame from the log, setting the input_* globals accordingly. + The sequence number range used for prediction should normally be servercommandframe < sequence <= clientcommandframe. + The sequence equal to clientcommandframe will change between input frames. */ + +void(float sens) setsensitivityscaler = #346; /* + Temporarily scales the player's mouse sensitivity based upon something like zoom, avoiding potential cvar saving and thus corruption. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(entity ent) runstandardplayerphysics = #347; /* + Perform the engine's standard player movement prediction upon the given entity using the input_* globals to describe movement. */ + +#endif +#ifdef CSQC +string(float playernum, string keyname) getplayerkeyvalue = #348; /* + Look up a player's userinfo, to discover things like their name, topcolor, bottomcolor, skin, team, *ver. + Also includes scoreboard info like frags, ping, pl, userid, entertime, as well as voipspeaking and voiploudness. */ + +#endif +#if defined(CSQC) || defined(MENU) +float() isdemo = #349; /* + Returns if the client is currently playing a demo or not */ + +#endif +#ifdef CSQC +float() isserver = #350; /* + Returns if the client is acting as the server (aka: listen server) */ + +void(vector origin, vector forward, vector right, vector up, optional float inwater) SetListener = #351; /* + Sets the position of the view, as far as the audio subsystem is concerned. This should be called once per CSQC_UpdateView as it will otherwise revert to default. */ + +void(string cmdname) registercommand = #352; /* + Register the given console command, for easy console use. + Console commands that are later used will invoke CSQC_ConsoleCommand. */ + +#endif +#if defined(CSQC) || defined(SSQC) +float(entity ent) wasfreed = #353; /* + Quickly check to see if the entity is currently free. This function is only valid during the two-second non-reuse window, after that it may give bad results. Try one second to make it more robust. */ + +#endif +#ifdef CSQC +string(string key) serverkey = #354; /* + Look up a key in the server's public serverinfo string */ + +string(optional string resetstring) getentitytoken = #355; /* + Grab the next token in the map's entity lump. + If resetstring is not specified, the next token will be returned with no other sideeffects. + If empty, will reset from the map before returning the first token, probably {. + If not empty, will tokenize from that string instead. + Always returns tempstrings. */ + +#endif +#if defined(CSQC) || defined(MENU) +float(string s) findfont = #356; /* + Looks up a named font slot. Matches the actual font name as a last resort. */ + +float(string fontname, string fontmaps, string sizes, float slot, optional float fix_scale, optional float fix_voffset) loadfont = #357; /* + too convoluted for me to even try to explain correct usage. Try drawfont = loadfont("foo", "cour", "16", 0, 0, 0); to switch to the courier font, if you have the freetype2 library in windows.. */ + +#endif +#ifdef CSQC +void(string evname, string evargs, ...) sendevent = #359; /* + Invoke Cmd_evname_evargs in ssqc. evargs must be a string of initials refering to the types of the arguments to pass. v=vector, e=entity(.entnum field is sent), f=float, i=int. 6 arguments max - you can get more if you pack your floats into vectors. */ + +float() readbyte = #360; +float() readchar = #361; +float() readshort = #362; +float() readlong = #363; +float() readcoord = #364; +float() readangle = #365; +string() readstring = #366; +float() readfloat = #367; +float() readentitynum = #368; +float(string modelname, float(float isnew) updatecallback, float flags) deltalisten = #371; /* + Specifies a per-modelindex callback to listen for engine-networking entity updates. Such entities are automatically interpolated by the engine (unless flags specifies not to). + The various standard entity fields will be overwritten each frame before the updatecallback function is called. */ + +__variant(float lno, float fld) dynamiclight_get = #372; /* + Retrieves a property from the given dynamic/rt light. Return type depends upon the light field requested. */ + +void(float lno, float fld, __variant value) dynamiclight_set = #373; /* + Changes a property on the given dynamic/rt light. Value type depends upon the light field to be changed. */ + +string(float efnum, float body) particleeffectquery = #374; /* + Retrieves either the name or the body of the effect with the given number. The effect body is regenerated from internal state, and can be changed before being reapplied via the localcmd builtin. */ + +void(string shadername, vector origin, vector up, vector side, vector rgb, float alpha) adddecal = #375; /* + Adds a temporary clipped decal shader to the scene, centered at the given point with given orientation. Will be drawn by the next renderscene call, and freed by the next clearscene call. */ + +#endif +#if defined(CSQC) || defined(MENU) +void(entity e, string skinfilename, optional string skindata) setcustomskin = #376; /* + Sets an entity's skin overrides. These are custom per-entity surface->shader lookups. The skinfilename/data should be in .skin format: + surfacename,shadername - makes the named surface use the named shader + replace "surfacename" "shadername" - same. + qwskin "foo" - use an unmodified quakeworld player skin (including crop+repalette rules) + q1lower 0xff0000 - specify an override for the entity's lower colour, in this case to red + q1upper 0x0000ff - specify an override for the entity's lower colour, in this case to blue + compose "surfacename" "shader" "imagename@x,y:w,h?r,g,b,a" - compose a skin texture from multiple images. + The texture is determined to be sufficient to hold the first named image, additional images can be named as extra tokens on the same line. + Use a + at the end of the line to continue reading image tokens from the next line also, the named shader must use 'map $diffuse' to read the composed texture (compatible with the defaultskin shader). */ + +#endif +__variant*(int size) memalloc = #384; /* Part of FTE_MEMALLOC + Allocate an arbitary block of memory */ + +void(__variant *ptr) memfree = #385; /* Part of FTE_MEMALLOC + Frees a block of memory that was allocated with memfree */ + +void(__variant *dst, __variant *src, int size) memcpy = #386; /* Part of FTE_MEMALLOC + Copys memory from one location to another */ + +void(__variant *dst, int val, int size) memfill8 = #387; /* Part of FTE_MEMALLOC + Sets an entire block of memory to a specified value. Pretty much always 0. */ + +__variant(__variant *dst, float ofs) memgetval = #388; /* + Looks up the 32bit value stored at a pointer-with-offset. */ + +void(__variant *dst, float ofs, __variant val) memsetval = #389; /* + Changes the 32bit value stored at the specified pointer-with-offset. */ + +__variant*(__variant *base, float ofs) memptradd = #390; /* + Perform some pointer maths. Woo. */ + +float(string s) memstrsize = #0/*:memstrsize*/; /* + strlen, except ignores utf-8 */ + +#if defined(CSQC) || defined(MENU) +string(string conname, string field, optional string newvalue) con_getset = #391; /* Part of FTE_CSQC_ALTCONSOLES_WIP + Reads or sets a property from a console object. The old value is returned. Iterrate through consoles with the 'next' field. Valid properties: title, name, next, unseen, markup, forceutf8, close, clear, hidden, linecount */ + +void(string conname, string messagefmt, ...) con_printf = #392; /* Part of FTE_CSQC_ALTCONSOLES_WIP + Prints onto a named console. */ + +void(string conname, vector pos, vector size, float fontsize) con_draw = #393; /* Part of FTE_CSQC_ALTCONSOLES_WIP + Draws the named console. */ + +float(string conname, float inevtype, float parama, float paramb, float paramc) con_input = #394; /* Part of FTE_CSQC_ALTCONSOLES_WIP + Forwards input events to the named console. Mouse updates should be absolute only. */ + +float() cvars_haveunsaved = #0/*:cvars_haveunsaved*/; /* + Returns true if any archived cvar has an unsaved value. */ + +#endif +float(entity e, float nowreadonly) entityprotection = #0/*:entityprotection*/; /* + Changes the protection on the specified entity to protect it from further edits from QC. The return value is the previous setting. Note that this can be used to unprotect the world, but doing so long term is not advised as you will no longer be able to detect invalid entity references. Also, world is not networked, so results might not be seen by clients (or in other words, world.avelocity_y=64 is a bad idea). */ + +#if defined(CSQC) || defined(SSQC) +entity(entity from, optional entity to) copyentity = #400; /* Part of DP_QC_COPYENTITY + Copies all fields from one entity to another. */ + +#endif +#ifdef SSQC +void(entity ent, float colours) setcolors = #401; /* + Changes a player's colours. The bits 0-3 are the lower/trouser colour, bits 4-7 are the upper/shirt colours. */ + +#endif +#if defined(CSQC) || defined(SSQC) +entity(.string field, string match) findchain = #402; /* Part of DP_QC_FINDCHAIN*/ +entity(.float fld, float match) findchainfloat = #403; /* Part of DP_QC_FINDCHAINFLOAT*/ +void(vector org, string modelname, float startframe, float endframe, float framerate) effect = #404; /* Part of DP_SV_EFFECT + Spawns a self-animating sprite */ + +void(vector org, vector dir, float count) te_blood = #405; /* Part of DP_TE_BLOOD*/ +void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower = #406; /* Part of _DP_TE_BLOODSHOWER*/ +void(vector org, vector color) te_explosionrgb = #407; /* Part of DP_TE_EXPLOSIONRGB*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube = #408; /* Part of DP_TE_PARTICLECUBE*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain = #409; /* Part of _DP_TE_PARTICLERAIN*/ +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow = #410; /* Part of _DP_TE_PARTICLESNOW*/ +void(vector org, vector vel, float howmany) te_spark = #411; /* Part of DP_TE_SPARK*/ +void(vector org) te_gunshotquad = #412; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_spikequad = #413; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_superspikequad = #414; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_explosionquad = #415; /* Part of _DP_TE_QUADEFFECTS1*/ +void(vector org) te_smallflash = #416; /* Part of DP_TE_SMALLFLASH*/ +void(vector org, float radius, float lifetime, vector color) te_customflash = #417; /* Part of DP_TE_CUSTOMFLASH*/ +void(vector org, optional float count) te_gunshot = #418; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_spike = #419; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_superspike = #420; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_explosion = #421; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_tarexplosion = #422; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_wizspike = #423; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_knightspike = #424; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_lavasplash = #425; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org) te_teleport = #426; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(vector org, float color, float colorlength) te_explosion2 = #427; /* Part of DP_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning1 = #428; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning2 = #429; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_lightning3 = #430; /* Part of DP_TE_STANDARDEFFECTBUILTINS, FTE_TE_STANDARDEFFECTBUILTINS*/ +void(entity own, vector start, vector end) te_beam = #431; /* Part of DP_TE_STANDARDEFFECTBUILTINS*/ +void(vector dir) vectorvectors = #432; /* Part of DP_QC_VECTORVECTORS*/ +void(vector org) te_plasmaburn = #433; /* Part of _DP_TE_PLASMABURN*/ +float(entity e, float s) getsurfacenumpoints = #434; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s, float n) getsurfacepoint = #435; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s) getsurfacenormal = #436; /* Part of DP_QC_GETSURFACE*/ +string(entity e, float s) getsurfacetexture = #437; /* Part of DP_QC_GETSURFACE*/ +float(entity e, vector p) getsurfacenearpoint = #438; /* Part of DP_QC_GETSURFACE*/ +vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; /* Part of DP_QC_GETSURFACE*/ +#endif +#ifdef MENU +strbuf() buf_create = #440; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle) buf_del = #441; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle) buf_getsize = #442; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle_from, float bufhandle_to) buf_copy = #443; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float sortprefixlen, float backward) buf_sort = #444; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, string glue) buf_implode = #445; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, float string_index) bufstr_get = #446; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index, string str) bufstr_set = #447; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle, string str, float order) bufstr_add = #448; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index) bufstr_free = #449; /* Part of DP_QC_STRINGBUFFERS*/ +float(string name) iscachedpic = #451; +string(string name, optional float trywad) precache_pic = #452; +float(vector position, float character, vector scale, vector rgb, float alpha, optional float flag) drawcharacter = #454; +float(vector position, string text, vector scale, vector rgb, float alpha, optional float flag) drawrawstring = #455; +float(vector position, string pic, vector size, vector rgb, float alpha, optional float flag) drawpic = #456; +float(vector position, vector size, vector rgb, float alpha, optional float flag) drawfill = #457; +void(float x, float y, float width, float height) drawsetcliparea = #458; +void(void) drawresetcliparea = #459; +vector(string picname) drawgetimagesize = #460; +float(vector position, string text, vector scale, vector rgb, float alpha, float flag) drawstring = #467; +float(string text, float usecolours, optional vector fontsize) stringwidth = #468; +void(vector pos, vector sz, string pic, vector srcpos, vector srcsz, vector rgb, float alpha, float flag) drawsubpic = #469; +#endif +#ifdef SSQC +void(entity e, string s) clientcommand = #440; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +#endif +#if defined(CSQC) || defined(SSQC) +float(string s) tokenize = #441; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +string(float n) argv = #442; /* Part of KRIMZON_SV_PARSECLIENTCOMMAND*/ +void(entity e, entity tagentity, string tagname) setattachment = #443; /* Part of DP_GFX_QUAKE3MODELTAGS*/ +searchhandle(string pattern, float caseinsensitive, float quiet) search_begin = #444; /* Part of DP_QC_FS_SEARCH + initiate a filesystem scan based upon filenames. Be sure to call search_end on the returned handle. */ + +void(searchhandle handle) search_end = #445; /* Part of DP_QC_FS_SEARCH*/ +float(searchhandle handle) search_getsize = #446; /* Part of DP_QC_FS_SEARCH + Retrieves the number of files that were found. */ + +string(searchhandle handle, float num) search_getfilename = #447; /* Part of DP_QC_FS_SEARCH + Retrieves name of one of the files that was found by the initial search. */ + +#endif +float(searchhandle handle, float num) search_getfilesize = #0/*:search_getfilesize*/; /* + Retrieves the size of one of the files that was found by the initial search. */ + +string(searchhandle handle, float num) search_getfilemtime = #0/*:search_getfilemtime*/; /* + Retrieves modification time of one of the files. */ + +#if defined(CSQC) || defined(SSQC) +string(string cvarname) cvar_string = #448; /* Part of DP_QC_CVAR_STRING*/ +entity(entity start, .float fld, float match) findflags = #449; /* Part of DP_QC_FINDFLAGS*/ +entity(.float fld, float match) findchainflags = #450; /* Part of DP_QC_FINDCHAINFLAGS*/ +float(entity ent, string tagname) gettagindex = #451; /* Part of DP_MD3_TAGSINFO*/ +vector(entity ent, float tagindex) gettaginfo = #452; /* Part of DP_MD3_TAGSINFO + Obtains the current worldspace position+orientation of the bone or tag from the given entity. The return value is the world coord, v_forward, v_right, v_up are also set according to the bone/tag's orientation. */ + +#endif +#ifdef SSQC +void(entity player) dropclient = #453; /* Part of DP_SV_DROPCLIENT*/ +entity() spawnclient = #454; /* Part of DP_SV_BOTCLIENT*/ +float(entity client) clienttype = #455; /* Part of DP_SV_BOTCLIENT*/ +void(float target, string str) WriteUnterminatedString = #456; /* Part of DP_SV_WRITEUNTERMINATEDSTRING*/ +#endif +#if defined(CSQC) || defined(SSQC) +entity(float entnum) edict_num = #459; /* Part of DP_QC_EDICT_NUM*/ +strbuf() buf_create = #460; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle) buf_del = #461; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle) buf_getsize = #462; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle_from, strbuf bufhandle_to) buf_copy = #463; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float sortprefixlen, float backward) buf_sort = #464; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, string glue) buf_implode = #465; /* Part of DP_QC_STRINGBUFFERS*/ +string(strbuf bufhandle, float string_index) bufstr_get = #466; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index, string str) bufstr_set = #467; /* Part of DP_QC_STRINGBUFFERS*/ +float(strbuf bufhandle, string str, float order) bufstr_add = #468; /* Part of DP_QC_STRINGBUFFERS*/ +void(strbuf bufhandle, float string_index) bufstr_free = #469; /* Part of DP_QC_STRINGBUFFERS*/ +#endif +float(float s) asin = #471; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float c) acos = #472; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float t) atan = #473; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float c, float s) atan2 = #474; /* Part of DP_QC_ASINACOSATANATAN2TAN*/ +float(float a) tan = #475; /* Part of DP_QC_ASINACOSATANATAN2TAN + Forgive me father, for I have a sunbed and I'm not afraid to use it. */ + +float(string s) strlennocol = #476; /* Part of DP_QC_STRINGCOLORFUNCTIONS + Returns the number of characters in the string after any colour codes or other markup has been parsed. */ + +string(string s) strdecolorize = #477; /* Part of DP_QC_STRINGCOLORFUNCTIONS + Flattens any markup/colours, removing them from the string. */ + +string(float uselocaltime, string format, ...) strftime = #478; /* Part of DP_QC_STRFTIME*/ +float(string s, string separator1, ...) tokenizebyseparator = #479; /* Part of DP_QC_TOKENIZEBYSEPARATOR*/ +string(string s) strtolower = #480; /* Part of DP_QC_STRING_CASE_FUNCTIONS*/ +string(string s) strtoupper = #481; /* Part of DP_QC_STRING_CASE_FUNCTIONS*/ +string(string s) cvar_defstring = #482; /* Part of DP_QC_CVAR_DEFSTRING*/ +#if defined(CSQC) || defined(SSQC) +void(vector origin, string sample, float volume, float attenuation) pointsound = #483; /* Part of DP_SV_POINTSOUND*/ +#endif +string(string search, string replace, string subject) strreplace = #484; /* Part of DP_QC_STRREPLACE*/ +string(string search, string replace, string subject) strireplace = #485; /* Part of DP_QC_STRREPLACE*/ +#if defined(CSQC) || defined(SSQC) +vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; /* Part of DP_QC_GETSURFACEPOINTATTRIBUTE*/ +#endif +#if defined(CSQC) || defined(MENU) +float(string name) gecko_create = #487; /* Part of DP_GECKO_SUPPORT + Create a new 'browser tab' shader with the specified name that can then be drawn via drawpic. In order to function correctly, this builtin depends upon external plugins being available. Use gecko_navigate to navigate it to a page of your choosing. */ + +void(string name) gecko_destroy = #488; /* Part of DP_GECKO_SUPPORT + Destroy a shader. */ + +void(string name, string URI) gecko_navigate = #489; /* Part of DP_GECKO_SUPPORT + Sends a command to the media decoder attached to the specified shader. In the case of a browser decoder, this changes the url that the browser displays. 'cmd:[un]focus' will tell the decoder that it has focus. */ + +float(string name, float key, float eventtype) gecko_keyevent = #490; /* Part of DP_GECKO_SUPPORT + Send a key event to a media decoder. This applies only to interactive decoders like browsers. */ + +void(string name, float x, float y) gecko_mousemove = #491; /* Part of DP_GECKO_SUPPORT + Sets a media decoder shader's mouse position. Values should be 0-1. */ + +void(string name, float w, float h) gecko_resize = #492; /* Part of DP_GECKO_SUPPORT + Request to resize a media decoder. */ + +vector(string name) gecko_get_texture_extent = #493; /* Part of DP_GECKO_SUPPORT + Query a media decoder for its current pixel size. */ + +#endif +float(float caseinsensitive, string s, ...) crc16 = #494; /* Part of DP_QC_CRC16*/ +float(string name) cvar_type = #495; /* Part of DP_QC_CVAR_TYPE*/ +float() numentityfields = #496; /* Part of DP_QC_ENTITYDATA + Gives the number of named entity fields. Note that this is not the size of an entity, but rather just the number of unique names (ie: vectors use 4 names rather than 3). */ + +float(string fieldname) findentityfield = #0/*:findentityfield*/; /* + Find a field index by name. */ + +typedef .__variant field_t; +field_t(float fieldnum) entityfieldref = #0/*:entityfieldref*/; /* + Returns a field value that can be directly used to read entity fields. Be sure to validate the type with entityfieldtype before using. */ + +string(float fieldnum) entityfieldname = #497; /* Part of DP_QC_ENTITYDATA + Retrieves the name of the given entity field. */ + +float(float fieldnum) entityfieldtype = #498; /* Part of DP_QC_ENTITYDATA + Provides information about the type of the field specified by the field num. Returns one of the EV_ values. */ + +string(float fieldnum, entity ent) getentityfieldstring = #499; /* Part of DP_QC_ENTITYDATA*/ +float(float fieldnum, entity ent, string s) putentityfieldstring = #500; /* Part of DP_QC_ENTITYDATA*/ +#ifdef CSQC +void(float effectindex, entity own, vector org_from, vector org_to, vector dir_from, vector dir_to, float countmultiplier, optional float flags) boxparticles = #502; +#endif +string(string filename, optional float makereferenced) whichpack = #503; /* Part of DP_QC_WHICHPACK + Returns the pak file name that contains the file specified. progs/player.mdl will generally return something like 'pak0.pak'. If makereferenced is true, clients will automatically be told that the returned package should be pre-downloaded and used, even if allow_download_refpackages is not set. */ + +#ifdef CSQC +__variant(float entnum, float fieldnum) getentity = #504; /* + Looks up fields from non-csqc-visible entities. The entity will need to be within the player's pvs. fieldnum should be one of the GE_ constants. */ + +#endif +string(string in) uri_escape = #510; /* Part of DP_QC_URI_ESCAPE*/ +string(string in) uri_unescape = #511; /* Part of DP_QC_URI_ESCAPE*/ +float(entity ent) num_for_edict = #512; +#define uri_post uri_get +float(string uril, float id, optional string postmimetype, optional string postdata) uri_get = #513; /* Part of DP_QC_URI_GET, DP_QC_URI_POST + uri_get() gets content from an URL and calls a callback "uri_get_callback" with it set as string; an unique ID of the transfer is returned + returns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string + For a POST request, you will typically want the postmimetype set to application/x-www-form-urlencoded. + For a GET request, omit the mime+data entirely. + Consult your webserver/php/etc documentation for best-practise. */ + +float(string str) tokenize_console = #514; /* + Tokenize a string exactly as the console's tokenizer would do so. The regular tokenize builtin became bastardized for convienient string parsing, which resulted in a large disparity that can be exploited to bypass checks implemented in a naive SV_ParseClientCommand function, therefore you can use this builtin to make sure it exactly matches. */ + +float(float idx) argv_start_index = #515; /* + Returns the character index that the tokenized arg started at. */ + +float(float idx) argv_end_index = #516; /* + Returns the character index that the tokenized arg stopped at. */ + +void(strbuf strbuf, string pattern, string antipattern) buf_cvarlist = #517; +string(string cvarname) cvar_description = #518; /* + Retrieves the description of a cvar, which might be useful for tooltips or help files. This may still not be useful. */ + +#if defined(CSQC) || defined(SSQC) +float(optional float timetype) gettime = #519; +#endif +#ifdef CSQC +string(float keynum) keynumtostring_omgwtf = #520; +string(string command, optional float bindmap) findkeysforcommand = #521; /* + Returns a list of keycodes that perform the given console command in a format that can only be parsed via tokenize (NOT tokenize_console). This only and always returns two values - if only one key is actually bound, -1 will be returned. The bindmap argument is listed for compatibility with dp-specific defs, but is ignored in FTE. */ + +string(string command, optional float bindmap) findkeysforcommandex = #0/*:findkeysforcommandex*/; /* + Returns a list of key bindings in keyname format instead of keynums. Use tokenize to parse. This list may contain modifiers. May return large numbers of keys. */ + +#endif +#if defined(CSQC) || defined(SSQC) +void(string s) loadfromdata = #529; /* + Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. */ + +void(string s) loadfromfile = #530; /* + Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead. */ + +#endif +#ifdef SSQC +float(string mname) precache_vwep_model = #532; /* Part of ZQ_VWEP*/ +#endif +#ifdef CSQC +float(entity e, float channel, string newsample, float volume, float attenuation, float pitchpct, float flags, float timeoffset) soundupdate = #0/*:soundupdate*/; /* + Changes the properties of the current sound being played on the given entity channel. newsample may be empty, and will be ignored in this case. timeoffset is relative to the current position (subtract the result of getsoundtime for absolute positions). Negative volume can be used to stop the sound. Return value is a fractional value based upon the number of audio devices that could be updated - test against TRUE rather than non-zero. */ + +float(entity e, float channel) getsoundtime = #533; /* + Returns the current playback time of the sample on the given entity's channel. Beware CHAN_AUTO (in csqc, channels are not limited by network protocol). */ + +#endif +#if defined(CSQC) || defined(MENU) +float(string sample) soundlength = #534; /* + Provides a way to query the duration of a sound sample, allowing you to set up a timer to chain samples. */ + +#endif +float(string filename, strbuf bufhandle) buf_loadfile = #535; /* + Appends the named file into a string buffer (which must have been created in advance). The return value merely says whether the file was readable. */ + +float(filestream filehandle, strbuf bufhandle, optional float startpos, optional float numstrings) buf_writefile = #536; /* + Writes the contents of a string buffer onto the end of the supplied filehandle (you must have already used fopen). Additional optional arguments permit you to constrain the writes to a subsection of the stringbuffer. */ + +#if defined(CSQC) || defined(SSQC) +void(entity e, float physics_enabled) physics_enable = #540; /* + Enable or disable the physics attached to a MOVETYPE_PHYSICS entity. Entities which have been disabled in this way will stop taking so much cpu time. */ + +void(entity e, vector force, vector relative_ofs) physics_addforce = #541; /* + Apply some impulse directional force upon a MOVETYPE_PHYSICS entity. */ + +void(entity e, vector torque) physics_addtorque = #542; /* + Apply some impulse rotational force upon a MOVETYPE_PHYSICS entity. */ + +#endif +#ifdef MENU +void(float dest) setkeydest = #601; +float() getkeydest = #602; +#endif +#if defined(CSQC) || defined(MENU) +void(float trg) setmousetarget = #603; +float() getmousetarget = #604; +#endif +void(.../*, string funcname*/) callfunction = #605; /* + Invokes the named function. The function name is always passed as the last parameter and must always be present. The others are passed to the named function as-is */ + +void(filestream fh, entity e) writetofile = #606; /* + Writes an entity's fields to the named frik_file file handle. */ + +float(string s) isfunction = #607; /* + Returns true if the named function exists and can be called with the callfunction builtin. */ + +#if defined(CSQC) || defined(MENU) +vector(float vidmode, optional float forfullscreen) getresolution = #608; /* + Supposed to query the driver for supported video modes. FTE does not query drivers in this way, nor would it trust drivers anyway. */ + +#endif +#ifdef CSQC +string(float keynum) keynumtostring_menu = #609; +#endif +#ifdef MENU +string(float keynum) keynumtostring = #609; /* + Converts a qscancode key number into a mostly-human-readable name, matching the bind command. */ + +string(string command, optional float bindmap) findkeysforcommand = #610; +#endif +#if defined(CSQC) || defined(MENU) +float(float type) gethostcachevalue = #611; /* Part of FTE_CSQC_SERVERBROWSER*/ +string(float type, float hostnr) gethostcachestring = #612; /* Part of FTE_CSQC_SERVERBROWSER*/ +#endif +void(entity e, string s) parseentitydata = #613; /* + Reads a single entity's fields into an already-spawned entity. s should contain field pairs like in a saved game: {"foo1" "bar" "foo2" "5"} */ + +#ifdef MENU +float(string key) stringtokeynum = #614; /* + Returns the qscancode of a key from its name. Names are identical to the bind command. ctrl/shift/alt modifiers are ignored. */ + +#endif +#ifdef CSQC +float(string key) stringtokeynum_menu = #614; +#endif +#if defined(CSQC) || defined(MENU) +void() resethostcachemasks = #615; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float mask, float fld, string str, float op) sethostcachemaskstring = #616; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float mask, float fld, float num, float op) sethostcachemasknumber = #617; /* Part of FTE_CSQC_SERVERBROWSER*/ +void() resorthostcache = #618; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(float fld, float descending) sethostcachesort = #619; /* Part of FTE_CSQC_SERVERBROWSER*/ +void() refreshhostcache = #620; /* Part of FTE_CSQC_SERVERBROWSER*/ +float(float fld, float hostnr) gethostcachenumber = #621; /* Part of FTE_CSQC_SERVERBROWSER*/ +float(string key) gethostcacheindexforkey = #622; /* Part of FTE_CSQC_SERVERBROWSER*/ +void(string key) addwantedhostcachekey = #623; /* Part of FTE_CSQC_SERVERBROWSER*/ +string() getextresponse = #624; /* Part of FTE_CSQC_SERVERBROWSER*/ +#endif +string(string dnsname, optional float defport) netaddress_resolve = #625; +string(string fmt, ...) sprintf = #627; +#if defined(CSQC) || defined(SSQC) +float(entity e, float s) getsurfacenumtriangles = #628; +vector(entity e, float s, float n) getsurfacetriangle = #629; +#endif +#if defined(CSQC) || defined(MENU) +vector() getbindmaps = #631; +float(vector bm) setbindmaps = #632; +#endif +string(string digest, string data, ...) digest_hex = #639; +#if defined(CSQC) || defined(MENU) +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 +#define K_LALT 132 +#define K_RALT -245 +#define K_LCTRL 133 +#define K_RCTRL -246 +#define K_LSHIFT 134 +#define K_RSHIFT -247 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 +#define K_KP_HOME 164 +#define K_KP_UPARROW 165 +#define K_KP_PGUP 166 +#define K_KP_LEFTARROW 161 +#define K_KP_5 162 +#define K_KP_RIGHTARROW 163 +#define K_KP_END 158 +#define K_KP_DOWNARROW 159 +#define K_KP_PGDN 160 +#define K_KP_ENTER 172 +#define K_KP_INS 157 +#define K_KP_DEL 167 +#define K_KP_SLASH 168 +#define K_KP_MINUS 170 +#define K_KP_PLUS 171 +#define K_KP_NUMLOCK 154 +#define K_KP_STAR 169 +#define K_KP_EQUALS 173 +#define K_MOUSE1 512 +#define K_MOUSE2 513 +#define K_MOUSE3 514 +#define K_MOUSE4 517 +#define K_MOUSE5 518 +#define K_MOUSE6 519 +#define K_MOUSE7 520 +#define K_MOUSE8 521 +#define K_MOUSE9 522 +#define K_MOUSE10 523 +#define K_LWIN 239 +#define K_RWIN 240 +#define K_APP -241 +#define K_SEARCH -242 +#define K_POWER 130 +#define K_VOLUP -243 +#define K_VOLDOWN -244 +#define K_JOY1 768 +#define K_JOY2 769 +#define K_JOY3 770 +#define K_JOY4 771 +#define K_AUX1 784 +#define K_AUX2 785 +#define K_AUX3 786 +#define K_AUX4 787 +#define K_AUX5 788 +#define K_AUX6 789 +#define K_AUX7 790 +#define K_AUX8 791 +#define K_AUX9 792 +#define K_AUX10 793 +#define K_AUX11 794 +#define K_AUX12 795 +#define K_AUX13 796 +#define K_AUX14 797 +#define K_AUX15 798 +#define K_AUX16 799 +#define K_AUX17 800 +#define K_AUX18 801 +#define K_AUX19 802 +#define K_AUX20 803 +#define K_AUX21 804 +#define K_AUX22 805 +#define K_AUX23 806 +#define K_AUX24 807 +#define K_AUX25 808 +#define K_AUX26 809 +#define K_AUX27 810 +#define K_AUX28 811 +#define K_AUX29 812 +#define K_AUX30 813 +#define K_AUX31 814 +#define K_AUX32 815 +#define K_PAUSE 153 +#define K_MWHEELUP 515 +#define K_MWHEELDOWN 516 +#define K_PRINTSCREEN 174 +#define K_CAPSLOCK 155 +#define K_SCROLLLOCK 156 +#define K_SEMICOLON 59 +#define K_PLUS 43 +#define K_TILDE 126 +#define K_BACKQUOTE 96 +#define K_BACKSLASH 92 +#endif +#pragma noref 0 diff --git a/quakec/menusys/menu.src b/quakec/menusys/menu.src new file mode 100644 index 000000000..e8c10e8b2 --- /dev/null +++ b/quakec/menusys/menu.src @@ -0,0 +1,133 @@ +#pragma progs_dat "../menu.dat" + +//#pragma target fte + +#define MENU //select the module + +#includelist +fteextensions.qc //also sets up system defs + +menusys/mitems.qc //root item type +menusys/mitems_common.qc //basic types +menusys/mitem_desktop.qc //other sort of root item +menusys/mitem_exmenu.qc //fullscreen/exclusive menus +menusys/mitem_edittext.qc //simple text editor +menusys/mitem_tabs.qc //tabs +menusys/mitem_colours.qc //colour picker +menusys/mitem_checkbox.qc //checkbox (boolean thingies) +menusys/mitem_slider.qc //scrollbars +menusys/mitem_combo.qc //multiple-choice thingies +menusys/mitem_bind.qc //key binding thingie +menusys/mitem_spinnymodel.qc //menu art +#endlist + + + +//might as well put this here. + +void(mitem_desktop desktop) M_Pop = +{ + mitem it = desktop.item_kactivechild; + if (it) + it.item_remove(); +}; + +//define the commands. +//cmd argments are: Name, Function, Sourcefile(may be empty) +#define concommandslist \ + cmd("m_main", M_Main, menu/main.qc) \ + cmd("m_pop", M_Pop, ) \ + cmd("m_options", M_Options, menu/options.qc) \ + cmd("m_keys", M_Options_Keys, menu/options_keys.qc) \ + cmd("m_basicopts", M_Options_Basic, menu/options_basic.qc) \ + cmd("m_video", M_Options_Video, menu/options_video.qc) \ + cmd("m_effects", M_Options_Effects, menu/options_effects.qc) \ + cmd("m_audio", M_Options_Audio, menu/options_audio.qc) \ + cmd("m_particles", M_Options_Particles, menu/options_particles.qc) \ + cmd("m_hud", M_Options_Hud, menu/options_hud.qc) \ + cmd("m_load", M_Load, menu/loadsave.qc) \ + cmd("m_save", M_Save, ) \ + cmd("m_quit", M_Quit, menu/quit.qc) \ + cmd("m_newgame", M_NewGame, menu/newgame.qc) \ + cmd("m_servers", M_Servers, menu/servers.qc) \ + cmd("m_configs", M_Configs, menu/options_configs.qc) \ + cmd("m_reset", M_Reset, ) \ + cmd("m_preset", M_Preset, menu/presets.qc) + + +#if 0 +#append concommandslist cmd("m_servers", M_Servers, menu/servers.qc) +#define serverbrowser "m_servers" +#else +#define serverbrowser "menu_servers" +#endif + +//make sure all the right files are included +#define cmd(n,fnc,inc) inc +#includelist + concommandslist +#endlist +#undef cmd + +mitem_desktop desktop; +void() m_shutdown = {}; +void() m_draw = {items_draw(desktop);}; +void(float scan, float chr) m_keydown = {items_keypress(desktop, scan, chr, TRUE);}; +void(float scan, float chr) m_keyup = {items_keypress(desktop, scan, chr, FALSE);}; +void(float mode) m_toggle = +{ + if (mode) + M_Main(desktop); + + items_updategrabs(); +}; + +var float autocvar_dp_workarounds_allow = TRUE; +var float autocvar_dp_workarounds_force = FALSE; +void() m_init = +{ + desktop = spawn(mitem_desktop); + + //register the console commands via the alias command. +#define cmd(n,f) localcmd("alias " n " \"menu_cmd " n " $*\"\n"); + concommandslist +#undef cmd + + //work around some dp differences/bugs. + //this check identifies one significant bug in DP. + //if anyone actually cares to fix DP, then there is no reason they cannot do so by just removing DP_QC_RENDERSCENE and then fixing anything else that arises. + if (checkextension("DP_QC_RENDER_SCENE") && !checkextension("DP_CON_SET")) + dp_workarounds = autocvar(dp_workarounds_allow, TRUE); + if (autocvar(dp_workarounds_force, FALSE)) + dp_workarounds = TRUE; + + if (dp_workarounds) + print("^1WORKING AROUND DP BUGS\n"); + + //for compat with DP, 'none' is the default cursor in menuqc. + //naturally this is not ideal. + if (checkextension("FTE_QC_HARDWARECURSORS")) + setcursormode(TRUE, ""); + else + print("No hardware cursors\n"); + + if (clientstate() == 1) //disconnected==1, supposedly + m_toggle(1); +}; +void(string cstr) GameCommand = +{ + tokenize(cstr); + string cmd = argv(0); + + switch(cmd) + { +//switch on the known commands. +#define cmd(n,f) case n: f(desktop); break; + concommandslist +#undef cmd + default: + print("unknown command ", cmd, "\n"); + break; + } + items_updategrabs(); +}; diff --git a/quakec/menusys/menu/loadsave.qc b/quakec/menusys/menu/loadsave.qc new file mode 100644 index 000000000..6c06296d5 --- /dev/null +++ b/quakec/menusys/menu/loadsave.qc @@ -0,0 +1,123 @@ +#ifndef LOADSAVE_QC +#define LOADSAVE_QC + +//I'm feeling lazy, so I'm going to only provide X slots, like quake's menu. +#define NUMSAVESLOTS 10 +float selectedsaveslot; + +/* +class mitem_savescreeny : mitem +{ + virtual void(vector pos) item_draw = + { + string s = sprintf("saves/s%g/screeny.png", selectedsaveslot); + if not(whichpack(s)) + if (drawgetimagesize(s) != '0 0 0') + ui.drawpic(pos, s, item_size, item_rgb, item_alpha, 0); + }; +}; +*/ +class mitem_saveoption : mitem_text +{ + float slot; + float mode; + + virtual void(vector pos) item_draw = + { + //some sort of pulsing if its active. + if (selectedsaveslot == slot) + ui.drawfill(pos, item_size, '1 0 0', sin(cltime)*0.125+0.150, 0); + float w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); + ui.drawstring(pos + [(item_size_x-w)/2, 0], item_text, '1 1 0' * item_scale, menuitem_textcolour(this), item_alpha, 0); + }; + + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (!down) + return FALSE; + if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) + { + if (selectedsaveslot == slot) + { + switch(mode) + { + case 0: + break; //can't load a slot which is empty. + case 1: + localcmd(sprintf("m_pop;load s%g\n", slot)); + break; + case 2: + //FTE has a savegame_legacy command if you want compatibility with other engines. + localcmd(sprintf("m_pop;wait;echo \"%s\";save s%g\n", _("Saving Game"), slot)); + //localcmd(sprintf("m_pop;wait;screenshot saves/s%g/screeny.png;echo \"%s\";save s%g\n", slot, _("Saving Game"), slot)); + break; + } + } + selectedsaveslot = slot; + return TRUE; + } + return FALSE; + }; +}; + +static string(string savename) scansave = +{ + string l; + float f = fopen(sprintf("saves/%s/info.fsv", savename), FILE_READ); + if (f < 0) + f = fopen(sprintf("%s.sav", savename), FILE_READ); + if (f < 0) + return __NULL__; //weird + + fgets(f); //should be the version + l = fgets(f); //description + if (l) + l = strreplace("_", " ", l); + fclose(f); + return l; +}; + +void(mitem_desktop desktop, float mode) M_LoadSave = +{ + mitem_exmenu m = spawn(mitem_exmenu, item_text:"Load/Save", item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + string l; + float i; + float smode; + float pos = NUMSAVESLOTS*16/-2; + + mitem_pic banner = spawn(mitem_pic, item_text:((mode==2)?"gfx/p_save.lmp":"gfx/p_load.lmp"), item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [(banner.item_size_x)*-0.5, pos-32], [(banner.item_size_x)*0.5, pos-8]); + + for (i = 0; i < NUMSAVESLOTS; i++) + { + l = scansave(sprintf("s%g", i)); + smode = mode; + if (l=="") + { + l = "Empty Slot"; + if (mode==1) + smode = 0; + } + m.addm(spawn (mitem_saveoption, item_scale:16, slot:i, mode:smode, item_text:l), [-320, pos+i*16], [320, pos+(i+1)*16]); + } + +// m.addm(spawn (mitem_savescreeny), [-320, -240], [320, 240]); + addmenuback(m); +}; + +void(mitem_desktop desktop) M_Load = +{ + M_LoadSave(desktop, 1); +}; +void(mitem_desktop desktop) M_Save = +{ + if (!(isserver() || dp_workarounds)) + M_Main(desktop); //can't save when not connected. this should be rare, but can if you use the console or the main menu options are stale. + else + M_LoadSave(desktop, 2); +}; +#endif diff --git a/quakec/menusys/menu/main.qc b/quakec/menusys/menu/main.qc new file mode 100644 index 000000000..1211ba03c --- /dev/null +++ b/quakec/menusys/menu/main.qc @@ -0,0 +1,102 @@ +/* +The main / root menu. +Just a load of text with console commands attached. +Choice of buttons available is somewhat dynamic. + +There's also some generic kludge crap in here, like menu background tints +*/ + +/* +Adds a background tint to a (typically) exmenu parent. +In FTE, we use built-in stuff to give a sepia effect. +In DP, we just tint it black. +*/ +nonstatic void(mitem_frame m) addmenuback = +{ + if (iscachedpic("menutint")) //fte internal hacks! meh, admit it. its cool. + m.add(spawn (mitem_pic, item_text:"menutint", item_alpha:0.5), + RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [0, 0], [0, 0]); + else + m.add(spawn (mitem_fill, item_rgb:'0 0 0.01', item_alpha:0.5), + RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [0, 0], [0, 0]); +}; + +/* +helper functions to avoid blowing up in older clients. + +*/ +#define dp(dpc,qwc) (cvar_type(dpc)?dpc:qwc) +float(string cmd) assumetruecheckcommand = +{ + if (!checkextension("FTE_QC_CHECKCOMMAND")) + return TRUE; + return checkcommand(cmd); +}; +float(string cmd) assumefalsecheckcommand = +{ + if (!checkextension("FTE_QC_CHECKCOMMAND")) + return FALSE; + return checkcommand(cmd); +}; + + + +nonstatic void(mitem_desktop desktop) M_Main = +{ + local float y; + local mitem_exmenu m; + + //no dupes please. + m = (mitem_exmenu)desktop.findchild(_("Main Menu")); + if (m) + { + m.totop(); + return; + } + + //create a fullscreen frame + m = spawn(mitem_exmenu, item_text:_("Main Menu"), item_flags:IF_SELECTABLE); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + +// m.item_flags |= IF_NOKILL; +// m.adda(menuitempic_spawn ("gfx/qplaque.lmp", '32 144'), '16 4'); + + y = 7*-16/2; + + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_main.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [(160-banner.item_size_x)*0.5, y-32], [(160+banner.item_size_x)*0.5, y-8]); + + +//a macro, in a desperate attempt at readability +#define menuitemtext_cladd16(m,t,c,y) m.addm(spawn(mitem_text, item_text:t, item_command:c, item_scale:16, item_flags:IF_CENTERALIGN), [0, y], [160, y+16]) + +#ifdef CSQC + if (serverkey("constate") != "disconnected") {menuitemtext_cladd16(m, _("Return To Game"), "m_pop", y); y += 16;} + if (serverkey("constate") != "disconnected") {menuitemtext_cladd16(m, isserver?_("End Game"):_("Disconnect"), "m_pop;disconnect", y); y += 16;} else +#endif +#ifdef CSQC + if (checkextension("FTE_CSQC_SERVERBROWSER")) +#endif + {menuitemtext_cladd16(m, _("Join Server"), "m_pop;m_servers", y); y += 16;} + if (assumetruecheckcommand("map")) {menuitemtext_cladd16(m, _("New Game"), "m_pop;m_newgame", y); y += 16;} + if (assumetruecheckcommand("save") && (isserver()||dp_workarounds)) {menuitemtext_cladd16(m, _("Save"), "m_pop;m_save", y); y += 16;} + if (assumetruecheckcommand("load")) {menuitemtext_cladd16(m, _("Load"), "m_pop;m_load", y); y += 16;} + if (assumefalsecheckcommand("xmpp")) {menuitemtext_cladd16(m, _("Social"), "m_pop;xmpp", y); y += 16;} + {menuitemtext_cladd16(m, _("Options"), "m_pop;m_options", y); y += 16;} + {menuitemtext_cladd16(m, _("Quit"), "m_pop;m_quit", y); y += 16;} + +#if 1//def CSQC + //spinny quad/pent, for the luls + local string it = (random()<0.9)?"progs/quaddama.mdl":"progs/invulner.mdl"; + m.add(spawn (mitem_spinnymodel, item_text: it), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, 12*-16/2], [0, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(-160-plaque.item_size_x)*0.5, 12*-16/2], [(-160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + + addmenuback(m); +}; diff --git a/quakec/menusys/menu/newgame.qc b/quakec/menusys/menu/newgame.qc new file mode 100644 index 000000000..4714fca36 --- /dev/null +++ b/quakec/menusys/menu/newgame.qc @@ -0,0 +1,238 @@ +static string newgameinfo; +class mitem_newgame : mitem_exmenu +{ + virtual string(string key) get = + { + return infoget(newgameinfo, key); + }; + virtual void(string key, string newval) set = + { + string old = newgameinfo; + newgameinfo = strzone(infoadd(newgameinfo, key, newval)); + if (old) + strunzone(old); + }; +}; + +nonstatic void(mitem_desktop desktop) M_NewGame = +{ + mitem_pic banner; + string gametype = argv(1); + local float pos; + mitem_exmenu m; + if (gametype == "sp") + { + //single player has no options. the start map itself gives skill+episode options. + localcmd("\ndeathmatch 0; coop 0; maxplayers 0; timelimit 0; fraglimit 0; teamplay 0; map start\n"); + return; + } + if (gametype == "begin") + { + cvar_set("hostname", infoget(newgameinfo, "hostname")); + cvar_set("deathmatch", infoget(newgameinfo, "deathmatch")); + cvar_set("coop", infoget(newgameinfo, "coop")); + cvar_set("teamplay", infoget(newgameinfo, "teamplay")); + cvar_set("sv_public", infoget(newgameinfo, "sv_public")); + cvar_set("maxclients", infoget(newgameinfo, "maxclients")); + cvar_set("timelimit", infoget(newgameinfo, "timelimit")); + cvar_set("fraglimit", infoget(newgameinfo, "fraglimit")); + string map = infoget(newgameinfo, "map"); + if (map == "") + map = sprintf("dm%g", floor(random(1, 6))); + localcmd(sprintf("\nmap \"%s\"\n", map)); + return; + } + + if (newgameinfo) + strunzone(newgameinfo); + newgameinfo = ""; + newgameinfo = infoadd(newgameinfo, "hostname", cvar_string("hostname")); + newgameinfo = infoadd(newgameinfo, "deathmatch", cvar_string("deathmatch")); + newgameinfo = infoadd(newgameinfo, "teamplay", cvar_string("teamplay")); + newgameinfo = infoadd(newgameinfo, "sv_public", cvar_string("sv_public")); + newgameinfo = infoadd(newgameinfo, "maxclients", cvar_string("maxclients")); + newgameinfo = infoadd(newgameinfo, "timelimit", cvar_string("timelimit")); + newgameinfo = infoadd(newgameinfo, "fraglimit", cvar_string("fraglimit")); + newgameinfo = strzone(newgameinfo); + + m = spawn(mitem_newgame, item_text:_("New Game"), item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + switch(gametype) + { + case "tdm": + case "dm": + case "coop": + case "sp": + break; + default: + //show game type selection + pos = (16/-2)*(4); + banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.addm(banner, [(160-banner.item_size_x)*0.5, pos-32], [(160+banner.item_size_x)*0.5, pos-8]); + m.addm(spawn(mitem_text, item_text:"Single Player", item_command:"m_pop;m_newgame sp", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16; + m.addm(spawn(mitem_text, item_text:"Cooperative", item_command:"m_pop;m_newgame coop", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16; + m.addm(spawn(mitem_text, item_text:"Deathmatch", item_command:"m_pop;m_newgame dm", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16; + m.addm(spawn(mitem_text, item_text:"Team Deathmatch", item_command:"m_pop;m_newgame tdm", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16; + +#if 1//def CSQC + m.add(spawn (mitem_spinnymodel, item_text: "progs/soldier.mdl",firstframe:73, framecount:8, shootframe:81, shootframes:9), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, 12*-16/2], [0, 12*16/2]); +#else + //need some art for menuqc +#endif + return; + } + + pos = (16/-2)*(4); + + banner = spawn(mitem_pic, item_text:"gfx/p_multi.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.addm(banner, [(160-banner.item_size_x)*0.5, pos-32], [(160+banner.item_size_x)*0.5, pos-8]); + + m.addm(menuitemeditt_spawn(_("Hostname"), "hostname", '280 8'), [-160, pos], [160, pos+8]); pos += 8; + m.addm(menuitemcheck_spawn(_("Public"), "sv_public", '280 8'), [-160, pos], [160, pos+8]); pos += 8; + m.addm(menuitemcombo_spawn(_("Max Clients"), "maxclients", '280 8', _( + "2 \"Two\" " + "3 \"Three\" " + "4 \"Four\" " + "8 \"Eight\" " + "16 \"Sixteen\" " + "32 \"Thirty Two\" " + )), [-160, pos], [160, pos+8]); pos += 8; + + if (gametype == "dm" || gametype == "tdm") + { + if (m.get("deathmatch") == "0") + { + m.set("deathmatch", "1"); + m.set("coop", "0"); + } + m.addm(menuitemcombo_spawn(_("Deathmatch Mode"), "deathmatch", '280 8', _( + "1 \"Weapons Respawn\" " + "2 \"Weapons Stay\" " + "3 \"Powerups Respawn\" " + "4 \"Start With Weapons\" " + )), [-160, pos], [160, pos+8]); pos += 8; + } + else + { + if (m.get("coop") == "0") + { + m.set("deathmatch", "0"); + m.set("coop", "1"); + } + } + if (gametype == "tdm") + { + if (m.get("teamplay") == "0") + m.set("teamplay", "1"); + } + if (gametype == "dm") + { + if (m.get("teamplay") != "0") + m.set("teamplay", "0"); + } + if (gametype == "coop") + m.addm(menuitemcheck_spawn(_("No Friendly Fire"), "teamplay", '280 8'), [-160, pos], [160, pos+8]); pos += 8; +// if (gametype == "dm" || gametype == "tdm") + + if (gametype == "coop") + m.set("map", "start"); + else + { + m.addm(menuitemcombo_spawn(_("Time Limit"), "timelimit", '280 8', _( + "0 \"No Limit\" " + "5 \"5 minutes\" " + "10 \"10 minutes\" " + "15 \"15 minutes\" " + "20 \"20 minutes\" " + "25 \"25 minutes\" " + "30 \"30 minutes\" " + "35 \"35 minutes\" " + "40 \"40 minutes\" " + "45 \"45 minutes\" " + "50 \"50 minutes\" " + "55 \"55 minutes\" " + "60 \"1 hour\" " + )), [-160, pos], [160, pos+8]); pos += 8; + m.addm(menuitemcombo_spawn(_("Frag Limit"), "fraglimit", '280 8', _( + "0 \"No Limit\" " + "10 \"10 frags\" " + "20 \"20 frags\" " + "30 \"30 frags\" " + "40 \"40 frags\" " + "50 \"50 frags\" " + "60 \"60 frags\" " + "70 \"70 frags\" " + "80 \"80 frags\" " + "90 \"90 frags\" " + "100 \"100 frags\" " + )), [-160, pos], [160, pos+8]); pos += 8; + + m.set("map", ""); + m.addm(menuitemcombo_spawn(_("Initial Map"), "map", '280 8', _( + "dm1 \"DM1 (dm1)\" " + "dm2 \"DM2 (dm2)\" " + "dm3 \"DM3 (dm3)\" " + "dm4 \"DM4 (dm4)\" " + "dm5 \"DM5 (dm5)\" " + "dm6 \"DM6 (dm6)\" " + "start \"Start (Introduction)\" " + "e1m1 \"E1M1 (The Slipgate Complex)\" " + "e1m2 \"E1M2 (Castle Of The Damned)\" " + "e1m3 \"E1M3 (The Necropolis)\" " + "e1m4 \"E1M4 (The Grisly Grotto)\" " + "e1m5 \"E1M5 (Gloom Keep)\" " + "e1m6 \"E1M6 (The Door To Chthon)\" " + "e1m7 \"E1M7 (The House Of Chthon)\" " + "e1m8 \"E1M8 (Ziggarat Vertigo)\" " + "e2m1 \"E2M1 (The Installation)\" " + "e2m2 \"E2M2 (The Ogre Citadel)\" " + "e2m3 \"E2M3 (The Crypt Of Decay)\" " + "e2m4 \"E2M4 (The Ebon Fortress)\" " + "e2m5 \"E2M5 (The Wizard's Manse)\" " + "e2m6 \"E2M6 (The Dismal Oubliette\" " + "e2m7 \"E2M7 (The Underearth)\" " + "e3m1 \"E3M1 (Termination Central)\" " + "e3m2 \"E3M2 (The Vaults Of Zin)\" " + "e3m3 \"E3M3 (The Tomb Of Terror)\" " + "e3m4 \"E3M4 (Satan's Dark Delight)\" " + "e3m5 \"E3M5 (The Wind Tunnels)\" " + "e3m6 \"E3M6 (Chambers Of Torment)\" " + "e3m7 \"E3M7 (Tha Haunted Halls)\" " + "e4m1 \"E4M1 (The Sewage System)\" " + "e4m2 \"E4M2 (The Tower Of Despair)\" " + "e4m3 \"E4M3 (The Elder God Shrine)\" " + "e4m4 \"E4M4 (The Palace Of Hate)\" " + "e4m5 \"E4M5 (Hell's Atrium)\" " + "e4m6 \"E4M6 (The Pain Maze)\" " + "e4m7 \"E4M7 (Azure Agony)\" " + "e4m8 \"E4M8 (The Nameless City)\" " + "end \"End (Shub-Niggurath's Pit)\" " + )), [-160, pos], [160, pos+8]); pos += 8; + } + + m.addm(spawn(mitem_text, item_text:"BEGIN!", item_command:"m_pop;m_newgame begin", item_scale:16, item_flags:IF_CENTERALIGN), [-160, pos], [160, pos+16]); + + if (gametype == "coop") + { + //random art for style +#if 1//def CSQC + m.add(spawn (mitem_spinnymodel, item_text: "progs/soldier.mdl", firstframe:73, framecount:8, shootframe:81, shootframes:9), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, -240/2], [0, 240/2]); +#else + //need some art for menuqc +#endif + } + else + { + //random art for style +#if 1//def CSQC + m.add(spawn (mitem_spinnymodel, item_text: "progs/player.mdl", firstframe:0, framecount:6, shootframe:119, shootframes:6), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, 12*-16/2], [0, 12*16/2]); +#else + //need some art for menuqc +#endif + } + + addmenuback(m); +}; diff --git a/quakec/menusys/menu/options.qc b/quakec/menusys/menu/options.qc new file mode 100644 index 000000000..d651755bc --- /dev/null +++ b/quakec/menusys/menu/options.qc @@ -0,0 +1,102 @@ +/*************************************************************************** +Options menu. +just a simple list. +*/ +nonstatic void(mitem_desktop desktop) M_Options = +{ + local float pos; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + +/* //center the actual items + pos = (16/-2)*(9); + + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.addm(banner, [(-160-banner.item_size_x)*0.5, pos-32], [(-160+banner.item_size_x)*0.5, pos-8]); +*/ + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-80-banner.item_size_x*0.5, -h-32], [-80+banner.item_size_x*0.5, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160-160, -h], [0+160, h*2]); + + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + pos = 0; + + + + //and show the options. + fr.add(spawn(mitem_text, item_text:"Graphical Presets", item_command:"m_pop;m_preset", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Game Presets", item_command:"m_pop;m_configs", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Basic Setup", item_command:"m_pop;m_basicopts", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Audio", item_command:"m_pop;m_audio", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Video", item_command:"m_pop;m_video", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + fr.add(spawn(mitem_text, item_text:"Effects", item_command:"m_pop;m_effects", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + if (assumefalsecheckcommand("r_particledesc")) + {fr.add(spawn(mitem_text, item_text:"Particles", item_command:"m_pop;m_particles", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16;} + if (assumefalsecheckcommand("ezhud_nquake")) + {fr.add(spawn(mitem_text, item_text:"Hud", item_command:"m_pop;m_hud", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16;} + fr.add(spawn(mitem_text, item_text:"Keys", item_command:"m_pop;m_keys", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16; + if (assumefalsecheckcommand("cfg_save")) + {fr.add(spawn(mitem_text, item_text:"Save Settings", item_command:"cfg_save", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16;} + if (assumefalsecheckcommand("cvarreset")) + {fr.add(spawn(mitem_text, item_text:"Reset to Defaults", item_command:"m_reset", item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [0, 16]); pos += 16;} + + //random art for style +#if 1//def CSQC + m.addm(spawn (mitem_spinnymodel, item_text: "progs/suit.mdl"), [0, 12*-16/2], [160, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + addmenuback(m); +}; + + + +static void(mitem_desktop desktop, string question, string affirmitive, string affirmitiveaction, string negative, string negativeaction) M_SimplePrompt = +{ + local float pos; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:""); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_exclusive = m; + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + //center the actual items + pos = (16/-2)*(2); + + //draw title art above the options +// mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); +// m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [(-160-banner.item_size_x)*0.5, pos-32], [(-160+banner.item_size_x)*0.5, pos-8]); + + m.add(spawn(mitem_text, item_text:question, item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos-(8+16)], [0, pos-8]); pos += 16; + + m.add(spawn(mitem_text, item_text:affirmitive, item_command:affirmitiveaction, item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + m.add(spawn(mitem_text, item_text:negative, item_command:negativeaction, item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + + //random art for style +#if 1//def CSQC + m.add(spawn (mitem_spinnymodel, item_text: "progs/suit.mdl"), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [0, 12*-16/2], [160, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + addmenuback(m); +}; + +nonstatic void(mitem_desktop desktop) M_Reset = +{ + M_SimplePrompt(desktop, "Really Reset All Settings?", "Yes!", "m_pop;cvarreset *;exec default.cfg", "NOOOO! MY PRECIOUS!!!", "m_pop"); +}; diff --git a/quakec/menusys/menu/options_audio.qc b/quakec/menusys/menu/options_audio.qc new file mode 100644 index 000000000..96082a031 --- /dev/null +++ b/quakec/menusys/menu/options_audio.qc @@ -0,0 +1,75 @@ +nonstatic void(mitem_desktop desktop) M_Options_Audio = +{ + local float pos; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Audio Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [banner.item_size_x*-0.5, -h-32], [banner.item_size_x*0.5, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); + + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + pos = 0; + + //add the options + fr.add(spawn(mitem_text, item_text:_("Restart Sound"), item_command:"snd_restart", item_scale:8, item_flags:IF_RIGHTALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [0, pos], [-8, 8]); pos += 8; + pos += 8; + fr.add(menuitemcombo_spawn(_("Sound Device"), "s_device", '280 8', cvar_string("_s_device_opts")), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Master Volume"), "volume", '0.0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Ambient Volume"),"s_ambientlevel", '0 0.5 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Self Volume"), "s_localvolume", '0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Music Volume"), "musicvolume", '0 0.5 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Channels"), "s_numspeakers", '1 6 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Audio Quality"), "s_khz", '280 8', _( + "11025 \"11025hz (vanilla quake)\" " + "22050 \"22050hz\" " + "44100 \"44100hz (cd quality)\" " + "48000 \"48000hz (dvd quality)\" " + "96000 \"96000hz\" " + "192000 \"192000hz\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("8bit audio"), "s_loadas8bit", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Swap Speakers"), "s_swapstereo", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Latency"), "s_mixahead", '0.1 0.3 0.01', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Disable Sound"), "nosound", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + //ambient fade + fr.add(menuitemcheck_spawn(_("Static Sounds"), "cl_staticsounds", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Mix in Background"),"s_inactive", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + if (assumefalsecheckcommand("+voip")) + { + pos += 8; + fr.add(menuitemcombo_spawn(_("Microphone Device"), "cl_voip_capturedevice", '280 8', cvar_string("_cl_voip_capturedevice_opts")), + fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("VOIP Playback Vol"),"cl_voip_play", '0 2 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("VOIP Test"), "cl_voip_test", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("VOIP Record Vol"), "cl_voip_micamp", '0 4 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("VOIP Mode"), "cl_voip_send", '280 8', _( + "0 \"Push-To-Talk\" 1 " + "\"Voice Activation\" " + "2 \"Continuous\"" + )), fl, [0, pos], [0, 8]); pos += 8; + //VAD threshhold + //ducking + //noise cancelation + fr.add(menuitemcombo_spawn(_("VOIP Codec"), "cl_voip_codec", '280 8',_( + "0 \"speex (narrow 11khz)\" " + //"1 \"raw (wasteful)\" " + "2 \"opus\" " + "3 \"speex (narrow 8khz)\" " + "4 \"speex (wide 16khz)\" " + "5 \"speex (ultrawide 32khz)\" " + )), fl, [0, pos], [0, 8]); pos += 8; + + fr.add(menuitemslider_spawn(_("Opus bitrate"), "cl_voip_bitrate", '0.5 128 0.5','280 8'), fl, [0, pos], [0, 8]); pos += 8; + } + + addmenuback(m); +}; \ No newline at end of file diff --git a/quakec/menusys/menu/options_basic.qc b/quakec/menusys/menu/options_basic.qc new file mode 100644 index 000000000..49710f798 --- /dev/null +++ b/quakec/menusys/menu/options_basic.qc @@ -0,0 +1,71 @@ + +.float skinobject; +class mitem_playerpreview : mitem_spinnymodel +{ + virtual void(vector pos) item_draw = + { + //if you wanted to get more advanced, you could use q3 skins here. + setcustomskin(self, "", sprintf("q1upper \"%s\"\nq1lower \"%s\"\nqwskin \"%s\"\n", cvar_string("topcolor"), cvar_string("bottomcolor"), cvar_string("skin"))); + + super::item_draw(pos); + }; +}; + +static string() skinopts = +{ + string opts = ""; + float s = search_begin("skins/*.pcx", TRUE, TRUE); + if (s < 0) + return opts; + float n = search_getsize(s); + for (float i = 0; i < n; i++) + { + string f = substring(search_getfilename(s, i), 6, -5); + opts = strcat(opts, "\"", f, "\" \"", f, "\" "); + } + return opts; +}; + +nonstatic void(mitem_desktop desktop) M_Options_Basic = +{ + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Basic Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_cstm.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-160-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + float pos = 0; + + + fr.add(menuitemeditt_spawn(_("Player Name"), dp("_cl_name", "name"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemeditt_spawn(_("Player Team"), "team", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Player Skin"), "skin", '280 8', skinopts()), fl, [0, pos], [0, 8]); pos += 8; + + + if (assumefalsecheckcommand("topcolor")) + fr.add(menuitemcolour_spawn(_("Upper Colour"), "topcolor", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + if (assumefalsecheckcommand("bottomcolor")) + fr.add(menuitemcolour_spawn(_("Lower Colour"), "bottomcolor", '280 8'), fl, [0, pos], [0, 8]); pos += 8; /*aka: arse colour*/ + pos += 8; +// it = spawn(mitem_check, item_text:_("Invert Mouse"), item_command:"m_pitch", item_scale:8, item_size:'280 8'); +// it.item_dynamicvalue = idv_cvar_invert; +// fr.add(it, fr, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Sensitivity"), "sensitivity", '3 20 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Fov"), "fov", '80 130 5', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Gamma"), dp("v_gamma", "gamma"), '0.4 1.3 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Contrast"), dp("v_contrast", "contrast"), '0.8 1.8 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Brightness"), dp("v_brightness", "brightness"),'0.0 0.5 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Crosshair"), "crosshair", '0.0 19 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + + m.add(spawn (mitem_playerpreview, item_text: "progs/player.mdl", firstframe:0, framecount:6, shootframe:119, shootframes:6), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-200, 12*-16/2], [-40, 12*16/2]); + + addmenuback(m); +}; diff --git a/quakec/menusys/menu/options_configs.qc b/quakec/menusys/menu/options_configs.qc new file mode 100644 index 000000000..d9a7dc464 --- /dev/null +++ b/quakec/menusys/menu/options_configs.qc @@ -0,0 +1,63 @@ +/*************************************************************************** +Uses the engine command to apply the preset, and tries an exec instead if a config file exists. +Doesn't track the current one or anything. +Really simple and stupid menu. +no background tint, so the game is still visible so you can preview it. + +you should probably remove the fps_preset part. I left it in so that I don't have to bother with providing cfg files. note that this isn't a valid solution for dp, but whatever. +the great thing about providing source is that you can change it. +you can instead just shove your preset settings directly into the quotes instead of exec. +*/ + +struct +{ + string name; + string preset; + string config; +} configs[] = +{ +// {"Simple Deathmatch", "simple", "configs/game_simple.cfg"}, + {"Deathmatch", "deathmatch", "configs/game_deathmatch.cfg"}, + {"Mod Compatibility", "compat", "configs/game_compat.cfg"}, + {"Standard", "normal", "configs/game_normal.cfg"} +}; + +nonstatic void(mitem_desktop desktop) M_Configs = +{ + local float i; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Game Presets / Configs"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + + float h = 200*0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-60-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, -h], [0, h*2]); + + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + + //add the options + for (i = 0; i < configs.length; i++) + { + fr.add(spawn(mitem_text, item_text:configs[i].name, item_command:sprintf("exec \"%s\"", configs[i].config), item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, i*16], [100, 16]); + } + + //random art for style +#if 1//def CSQC + m.addm(spawn (mitem_spinnymodel, item_text: "progs/g_rock2.mdl", zbias:-16), [-160-60, 12*-16/2], [-60, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(-160-plaque.item_size_x)*0.5, 12*-16/2], [(-160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + + addmenuback(m); +}; + diff --git a/quakec/menusys/menu/options_effects.qc b/quakec/menusys/menu/options_effects.qc new file mode 100644 index 000000000..32c5cd157 --- /dev/null +++ b/quakec/menusys/menu/options_effects.qc @@ -0,0 +1,53 @@ +nonstatic void(mitem_desktop desktop) M_Options_Effects = +{ + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Effects Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-160-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + float pos = 0; + + fr.add(menuitemcheck_spawn(_("Bloom"), "r_bloom", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("HDR"), "r_hdr_irisadaptation", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Coronas"), "r_coronas", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Relief Mapping"), "r_glsl_offsetmapping", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Realtime World Lighting"), "r_shadow_realtime_world", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Realtime Dynamic Lights"), "r_shadow_realtime_dlight", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("High Res Textures"), "gl_load24bit", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + + if (assumefalsecheckcommand("r_particledesc")) + fr.add(spawn(mitem_text, item_text:_("Particle Sets"), item_command:"m_particles", item_scale:8, item_flags:IF_RIGHTALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [0, pos], [-8, 8]); pos += 8; +// fr.add(menuitemcombo_spawn(_("Particles"), "r_particledesc", '280 8', _( +// "\"classic\" \"Classic Effects\" " +// "\"spikeset tsshaft\" \"Some lame set\" " +// "\"high tsshaft\" \"High\" " +// )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Particle Density"), "r_part_density", '0.25 4 0.25', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + + fr.add(menuitemcombo_spawn(_("Water Effects"), "r_waterstyle", '280 8', _( + "1 \"Classic\" " + "2 \"Ripples\" " + "3 \"Reflections\" " + )), fl, [0, pos], [0, 8]); pos += 8; + + fr.add(menuitemcombo_spawn(_("Fish Eye"), "ffov", '280 8', _( + "0 \"Off\" " + "180 \"180 degrees\" " + "360 \"360 degrees\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Show Framerate"), dp("showfps", "show_fps"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; + + fr.add(spawn(mitem_text, item_text:_("Apply"), item_command:"vid_restart", item_scale:8, item_flags:IF_RIGHTALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [0, pos], [-8, 8]); pos += 8; + + addmenuback(m); +}; + diff --git a/quakec/menusys/menu/options_hud.qc b/quakec/menusys/menu/options_hud.qc new file mode 100644 index 000000000..a51733164 --- /dev/null +++ b/quakec/menusys/menu/options_hud.qc @@ -0,0 +1,52 @@ +nonstatic void(mitem_desktop desktop) M_Options_Hud = +{ + local float i; + local float h; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Hud Configurations"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + h = 200; + h *= 0.5; //and halve it + + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [banner.item_size_x*-0.5, -h-32], [banner.item_size_x*0.5, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, -h], [0, h*2]); + + float fs = search_begin("configs/hud_*.cfg", TRUE, TRUE); + float fc = search_getsize(fs); + + //add the options + for (i = 0, float y = 0; i < fc; i++) + { + string fname = search_getfilename(fs, i); + string iname = substring(fname, 12, -5); + string dname = GetFirstLineComment(fname, iname); + if (dname) + { + fr.add(spawn(mitem_text, item_text:dname, item_command:sprintf("exec \"%s\"", fname), item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, y], [0, 16]); + y += 16; + } + } + search_end(fs); + + if (!i) + fr.add(spawn(mitem_text, item_text:"NO HUDS FOUND", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, 0], [0, 16]); + + //random art for style +#if 1//def CSQC + m.addm(spawn (mitem_spinnymodel, item_text: "progs/g_nail2.mdl", zbias:-16), [-160-60, 12*-16/2], [-60, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(-160-plaque.item_size_x)*0.5, 12*-16/2], [(-160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + + addmenuback(m); +}; diff --git a/quakec/menusys/menu/options_keys.qc b/quakec/menusys/menu/options_keys.qc new file mode 100644 index 000000000..5b0142657 --- /dev/null +++ b/quakec/menusys/menu/options_keys.qc @@ -0,0 +1,109 @@ +const static struct +{ + string name; + string cmd; +} binds[] = +{ + {_("Forwards"), "+forward"}, + {_("Back"), "+back"}, + {_("Move Left"), "+moveleft"}, + {_("Move Right"), "+moveright"}, + {_("Turn Left"), "+left"}, + {_("Turn Right"), "+right"}, + {_("Look Up"), "+lookup"}, + {_("Look Down"), "+lookdown"}, + {_("Swim Up"), "+moveup"}, + {_("Swim Down"), "+movedown"}, + {_("Center view"), "centerview"}, + {_("Jump"), "+jump"}, + {_("Attack"), "+attack"}, + {_("Next Weapon"), "impulse 10"}, + {_("Prev Weapon"), "impulse 12"}, + {_("Scores"), "+showscores"}, + {_("Server Chat"), "messagemode"}, + {_("Team Chat"), "messagemode2"}, + {_("Voice Chat"), "+voip"}, +// {_("Mouse Look"), "+mlook"}, + {_("Keyboard Look"), "+klook"}, + {_("Strafe"), "+strafe"}, + {_("Run"), "+speed"}, + {0, 0}, + {_("Axe"), "impulse 1"}, + {_("Shotgun"), "impulse 2"}, + {_("Super Shotgun"), "impulse 3"}, + {_("Nailgun"), "impulse 4"}, + {_("Super Nailgun"), "impulse 5"}, + {_("Grenade Launcher"), "impulse 6"}, + {_("Rocket Launcher"), "impulse 7"}, + {_("Lightning Gun"), "impulse 8"}, +// {_("Railgun"), "impulse 9"}, +}; +void(mitem_desktop desktop) M_Options_Keys = +{ + float i; + float h; + + //create the menu, give it focus, and make sure its displayed over everything else. + mitem_exmenu m = spawn(mitem_exmenu, item_text:_("Key Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + //figure out the size of the stuff +// h = sizeof(binds) / sizeof(binds[0]); +// h *= 8; + h = 200; + h *= 0.5; //and halve it + + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_cstm.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [banner.item_size_x*-0.5, -h-32], [banner.item_size_x*0.5, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_OWN_MIN, [-140, -h], [280, h*2]); + + float f = fopen("bindlist.lst", FILE_READ); + if (f >= 0) + { + //throw a load of bind options onto it by reading from the array. + for (i = 0; ; ) + { + string line = fgets(f); + if not (line) + break; //eof + float args = tokenize(line); + if (!args) + continue; //blank line + string c = argv(0); + string n = argv(1); + string t = argv(2); + if (c == "-") //command only + { + if (n != "") + { + mitem it = menuitemtext_spawn(n, "", 8); + it.item_flags &= ~IF_SELECTABLE; + fr.add(it, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN|RS_Y_MAX_OWN_MIN, [-it.item_size_x/2, i], it.item_size); + } + } + else + fr.add(menuitembind_spawn(n, c, '280 8'), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, i], '0 8'); + i += 8; + } + fclose(f); + } + else + { + //throw a load of bind options onto it by reading from the array. + for (i = 0; i < sizeof(binds) / sizeof(binds[0]); i++) + { + if (binds[i].name == "") //no name is a spacer + continue; + fr.add(menuitembind_spawn(binds[i].name, binds[i].cmd, '280 8'), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, (i*8)], '0 8'); + } + } + + //and give us a suitable menu tint too, just because. + addmenuback(m); +}; diff --git a/quakec/menusys/menu/options_particles.qc b/quakec/menusys/menu/options_particles.qc new file mode 100644 index 000000000..3cfa6de86 --- /dev/null +++ b/quakec/menusys/menu/options_particles.qc @@ -0,0 +1,146 @@ +//menu that configues r_particledesc according to the files available in the particles/ subdir, including the (known) internal effects. + +class particlesmenu : mitem_frame +{ + string thelist; + void() particlesmenu = + { + thelist = cvar_string("r_particledesc"); + }; + virtual void() item_remove = + { + cvar_set("r_particledesc", thelist); + }; + + //to reconfigure colours for when something changees. + nonvirtual void() UpdateSelections = + { + float sc = tokenize(thelist); + for (mitem ch = item_children; ch; ch = ch.item_next) + { + ch.item_rgb = '1 0.5 0.5'; + for (float i = 0; i < sc; i++) + { + if (!strcasecmp(argv(i), ch.item_command)) + { + ch.item_rgb = '1 1 1'; + break; + } + } + } + }; + virtual void(mitem fromitem, string cmd) item_execcommand = + { + float sc = tokenize(thelist); + for (float i = 0; i < sc; i++) + { + if (!strcasecmp(argv(i), cmd)) + { + thelist = strtrim(strcat(strtrim(substring(thelist, 0, argv_start_index(i))), " ", strtrim(substring(thelist, argv_end_index(i), -1)))); + UpdateSelections(); + return; + } + } + + if (strstrofs(cmd, "\t") >= 0 || strstrofs(cmd, " ") >= 0 || strstrofs(cmd, "\"") >= 0 || strstrofs(cmd, ";") >= 0 || strstrofs(cmd, "\n") >= 0) + thelist = strtrim(strcat(thelist, " \"", cmd, "\"")); + else + thelist = strtrim(strcat(thelist, " ", cmd)); + UpdateSelections(); + }; + //to avoid dupes + nonvirtual float(string n) HasChild = + { + for (mitem ch = item_children; ch; ch = ch.item_next) + { + if (!strcasecmp(n, ch.item_command)) + return TRUE; + } + return FALSE; + }; +}; + +string(string fname, string dflt) GetFirstLineComment = +{ + float f = fopen(fname, FILE_READ); + if (f < 0) + return 0; + string l = strtrim(fgets(f)); + fclose(f); + if (!strcasecmp(substring(l, 0, 9), "//private")) + return 0; + if (!strcasecmp(substring(l, 0, 8), "//hidden")) + return 0; + if (!strcasecmp(substring(l, 0, 7), "//desc:")) + return strtrim(substring(l, 7, -1)); +// if (substring(l, 0, 1) == "#") +// return strtrim(substring(l, 1, -1)); + return dflt; +}; + +nonstatic void(mitem_desktop desktop) M_Options_Particles = +{ + float y = -8; + float h; + + //create the menu, give it focus, and make sure its displayed over everything else. + mitem_exmenu m = spawn(mitem_exmenu, item_text:_("Particles Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + //figure out the size of the stuff +// h = sizeof(binds) / sizeof(binds[0]); +// h *= 8; + h = 200; + h *= 0.5; //and halve it + + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_cstm.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [banner.item_size_x*-0.5, -h-32], [banner.item_size_x*0.5, -h-8]); + + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + particlesmenu fr = spawn(particlesmenu, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, -h], [0, h*2]); + + //FIXME: this stuff should be listed in order of selection, to reflect the priorities given to the various effects. + + //FIXME: these should not be listed if its a no-compat/no-legacy build. this'll do for now, but its a bit wrong + if (checkextension("FTE_PART_NAMESPACE_EFFECTINFO")) + { + fr.add(spawn(mitem_text, item_text:"Classic Particles", item_command:"classic", item_scale:8, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, y+=8], '0 8'); + fr.add(spawn(mitem_text, item_text:"High Quality", item_command:"high", item_scale:8, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, y+=8], '0 8'); + fr.add(spawn(mitem_text, item_text:"TimeServ's Shaft", item_command:"tsshaft", item_scale:8, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, y+=8], '0 8'); + + string efi = GetFirstLineComment("effectinfo.txt", "Effectinfo"); + if (efi) + fr.add(spawn(mitem_text, item_text:efi, item_command:"effectinfo", item_scale:8, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, y+=8], '0 8'); + } + + float fs; + fs = search_begin("particles/*.cfg", TRUE, TRUE); + for (float c = search_getsize(fs), float i = 0; i < c; i++) + { + string fname = search_getfilename(fs, i); + string iname = substring(fname, 10, -5); + string dname = GetFirstLineComment(fname, iname); + if (dname && !fr.HasChild(iname)) + fr.add(spawn(mitem_text, item_text:dname, item_command:iname, item_scale:8, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, y+=8], '0 8'); + } + search_end(fs); + fs = search_begin("particles/*/*.cfg", TRUE, TRUE); + for (float c = search_getsize(fs), float i = 0; i < c; i++) + { + string fname = search_getfilename(fs, i); + string iname = substring(fname, 10, -5); + string dname = GetFirstLineComment(fname, iname); + if (dname && !fr.HasChild(iname)) + fr.add(spawn(mitem_text, item_text:dname, item_command:iname, item_scale:8, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, y+=8], '0 8'); + } + search_end(fs); + + fr.UpdateSelections(); + + //and give us a suitable menu tint too, just because. + addmenuback(m); +}; diff --git a/quakec/menusys/menu/options_video.qc b/quakec/menusys/menu/options_video.qc new file mode 100644 index 000000000..92806e5ec --- /dev/null +++ b/quakec/menusys/menu/options_video.qc @@ -0,0 +1,86 @@ +nonstatic void(mitem_desktop desktop) M_Options_Video = +{ + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Video Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-160-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]); + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + float pos = 0; + + pos += 8; + fr.add(spawn(mitem_text, item_text:_("Apply / Restart"), item_command:"vid_restart", item_scale:8, item_flags:IF_RIGHTALIGN), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [0, pos], [-8, 8]); pos += 8; + pos += 8; + + //add the options + if (!dp_workarounds) + { + fr.add(menuitemcombo_spawn(_("Display Mode"), "vid_fullscreen", '280 8', + "0 \"Windowed\" " + "1 \"Fullscreen\" " + "2 \"Borderless Windowed\" " + ), fl, [0, pos], [0, 8]); pos += 8; + } + else + { + fr.add(menuitemcheck_spawn(_("Fullscreen"), "vid_fullscreen", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + } + if (cvar_type("vid_resizable")) fr.add(menuitemcheck_spawn(_("Resizable"), "vid_resizable", '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Anti-Aliasing"), dp("vid_samples", "vid_multisample"), '280 8', + "0 \"Off\" " + "2 \"2x\" " + "4 \"4x\" " + ), fl, [0, pos], [0, 8]); pos += 8; + + //as far as video mode selections go, this is shite. + //should probably have an aspect+modes option instead, but that makes the combo really messy. especially as that would be two cvars. + fr.add(menuitemcombo_spawn(_("Video Width"), "vid_width", '280 8', _( + "0 \"Default\" " + "640 \"640\" " + "800 \"800\" " + "1024 \"1024\" " + "1280 \"1280\" " + "1920 \"1920\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Video Height"), "vid_height", '280 8', _( + "0 \"Default\" " + "480 \"480\" " + "600 \"600\" " + "768 \"768\" " + "720 \"720\" " + "1080 \"1080\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Video Zoom"), "vid_conautoscale", '280 8', _( + "0 \"Default\" " + "1.5 \"x1.5\" " + "2 \"x2\" " + "4 \"x4\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Colour Depth"), dp("vid_bitsperpixel", "vid_bpp"), '280 8', _( + "16 \"16bit\" " + "32 \"24bit\" " //alpha doesn't count. + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcombo_spawn(_("Refresh Rate"), "vid_displayfrequency", '280 8', _( + "0 \"Default\" " + // "60 \"60\" " + // "75 \"75\" " + )), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("View Size"), "viewsize", '50 120 10', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Field Of View"), "fov", '50 140 5', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Gamma"), "gamma", '1.3 0.5 -0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Contrast"), "contrast", '0.7 2 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemslider_spawn(_("Brightness"), "brightness", '0 0.4 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("VSync"), dp("vid_vsync", "vid_wait"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; + fr.add(menuitemcheck_spawn(_("Show Framerate"), dp("showfps", "show_fps"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; + + addmenuback(m); +}; + diff --git a/quakec/menusys/menu/presets.qc b/quakec/menusys/menu/presets.qc new file mode 100644 index 000000000..3feca52be --- /dev/null +++ b/quakec/menusys/menu/presets.qc @@ -0,0 +1,86 @@ +/*************************************************************************** +Uses the engine command to apply the preset, and tries an exec instead if a config file exists. +Doesn't track the current one or anything. +Really simple and stupid menu. +no background tint, so the game is still visible so you can preview it. + +you should probably remove the fps_preset part. I left it in so that I don't have to bother with providing cfg files. note that this isn't a valid solution for dp, but whatever. +the great thing about providing source is that you can change it. +you can instead just shove your preset settings directly into the quotes instead of exec. +*/ + +struct +{ + string name; + string preset; + string config; +} presets[] = +{ + {"Simple", "286", "configs/preset_simple.cfg"}, + {"Fast", "fast", "configs/preset_fast.cfg"}, + {"Regression", "vanilla", "configs/preset_vanilla.cfg"}, + {"Faithful", "normal", "configs/preset_faithful.cfg"}, + {"Nice", "nice", "configs/preset_nice.cfg"}, + {"Realtime", "realtime", "configs/preset_realtime.cfg"} +}; +nonstatic float(string fname) checkfileexists = +{ + if (dp_workarounds) + { + float sh = search_begin(fname, FALSE, TRUE); + float result = FALSE; + if (sh >= 0) + { + result = !!search_getsize(sh); + search_end(sh); + } + return result; + } + else + { + //FTE's whichpack returns empty if its not in a pack, and null if its not found anywhere. + if (whichpack(fname)) + return TRUE; + return FALSE; + } +}; +nonstatic void(mitem_desktop desktop) M_Preset = +{ + local float i; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Graphical Presets"), item_flags:IF_SELECTABLE, item_command:"m_options"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + + float h = 200 * 0.5; + //draw title art above the options + mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); + m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_OWN_MIN|RS_Y_MAX_PARENT_MID, [(160-60-banner.item_size_x)*0.5, -h-32], [banner.item_size_x, -h-8]); + //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. + mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); + m.add(fr, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN, [0, -h], [0, h*2]); + float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; + + //add the options + for (i = 0, float pos = 0; i < presets.length; i++) + { + if (checkfileexists(presets[i].config)) //file exists, try to exec it + fr.add(spawn(mitem_text, item_text:presets[i].name, item_command:sprintf("exec \"%s\";vid_reload", presets[i].config), item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [100, 16]); + else //okay, no file exists, depend upon the engine for it. + fr.add(spawn(mitem_text, item_text:presets[i].name, item_command:sprintf("fps_preset %s", presets[i].preset), item_scale:16, item_flags:IF_CENTERALIGN), fl, [0, pos], [100, 16]); + pos += 16; + } + + //random art for style +#if 1//def CSQC + m.addm(spawn (mitem_spinnymodel, item_text: "progs/suit.mdl"), [-160, 12*-16/2], [0, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(-160-plaque.item_size_x)*0.5, 12*-16/2], [(-160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + + addmenuback(m); +}; diff --git a/quakec/menusys/menu/quit.qc b/quakec/menusys/menu/quit.qc new file mode 100644 index 000000000..f8eb7b13c --- /dev/null +++ b/quakec/menusys/menu/quit.qc @@ -0,0 +1,46 @@ +/*************************************************************************** +Quit menu. Quite lame for now. +*/ +float() cvars_haveunsaved = #0; +nonstatic void(mitem_desktop desktop) M_Quit = +{ + local float pos; + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); + + //center the actual items + pos = (16/-2)*(2); + + //draw title art above the options +// mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); +// m.add(banner, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [(-160-banner.item_size_x)*0.5, pos-32], [(-160+banner.item_size_x)*0.5, pos-8]); + + if (cvars_haveunsaved()) + { + m.add(spawn(mitem_text, item_text:"Save configuration?", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos-(8+16)], [0, pos-8]); pos += 16; + + m.add(spawn(mitem_text, item_text:"Save and quit", item_command:"m_pop;cfg_save;quit", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + m.add(spawn(mitem_text, item_text:"Quit and discard settings", item_command:"m_pop;quit", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + m.add(spawn(mitem_text, item_text:"Keep playing.", item_command:"m_pop;m_main", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + } + else + { + m.add(spawn(mitem_text, item_text:"Really Quit?", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos-(8+16)], [0, pos-8]); pos += 16; + + m.add(spawn(mitem_text, item_text:"Yes, I'm late for work.", item_command:"m_pop;quit", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + m.add(spawn(mitem_text, item_text:"No, keep playing!", item_command:"m_pop;m_main", item_scale:16, item_flags:IF_CENTERALIGN), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, pos], [0, pos+16]); pos += 16; + } + + //random art for style +#if 1//def CSQC + m.add(spawn (mitem_spinnymodel, item_text: "progs/suit.mdl"), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [0, 12*-16/2], [160, 12*16/2]); +#else + //menuqc doesn't support entities. shove some random crappy static image there instead. + local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); + m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); +#endif + addmenuback(m); +}; \ No newline at end of file diff --git a/quakec/menusys/menu/servers.qc b/quakec/menusys/menu/servers.qc new file mode 100644 index 000000000..e879f9191 --- /dev/null +++ b/quakec/menusys/menu/servers.qc @@ -0,0 +1,411 @@ +//very very basic server browser. +//this was written as an example for the api, rather than a truely usable server browser. + +//WARNING: make sure you only create one server browser widgit otherwise they'll fight each other + +#ifndef MOD_GAMEDIR +//look up what its meant to be. +//#define MOD_GAMEDIR cvar_string("game") +#endif + +#include "../menusys/mitem_menu.qc" //subwindow + +var float autocvar_sb_shownumplayers = 1; +var float autocvar_sb_showmaxplayers = 0; +var float autocvar_sb_showfraglimit = 0; +var float autocvar_sb_showtimelimit = 0; +var float autocvar_sb_showping = 1; +var float autocvar_sb_showgamedir = 0; +var float autocvar_sb_showaddress = 0; +var float autocvar_sb_showmap = 1; +var float autocvar_sb_showname = 1; + +//#define COLUMN(width, sortname, title, draw) +#define COLUMN_NUMPLAYERS COLUMN(2*8, numplayers, "Pl", ui.drawstring(pos, sprintf("%-2g", gethostcachenumber(field_numplayers, sv)), '8 8', col, 1, 0);) +#define COLUMN_MAXPLAYERS COLUMN(2*8, maxplayers, "MP", ui.drawstring(pos, sprintf("%-2g", gethostcachenumber(field_maxplayers, sv)), '8 8', col, 1, 0);) +#define COLUMN_PING COLUMN(4*8, ping, "Ping", ui.drawstring(pos, sprintf("%-4g", gethostcachenumber(field_ping, sv)), '8 8', col, 1, 0);) +#define COLUMN_FRAGLIMIT COLUMN(4*8, fraglimit, "FL", ui.drawstring(pos, sprintf("%-3g", gethostcachenumber(field_fraglimit, sv)), '8 8', col, 1, 0);) +#define COLUMN_TIMELIMIT COLUMN(4*8, timelimit, "TL", ui.drawstring(pos, sprintf("%-3g", gethostcachenumber(field_timelimit, sv)), '8 8', col, 1, 0);) +#define COLUMN_GAMEDIR COLUMN(8*8, gamedir, "Gamedir", ui.drawstring(pos, sprintf("%-.8s", gethostcachestring(field_gamedir, sv)), '8 8', col, 1, 0);) +#define COLUMN_ADDRESS COLUMN(16*8, address, "Address", ui.drawstring(pos, sprintf("%-.16s", gethostcachestring(field_address, sv)), '8 8', col, 1, 0);) +#define COLUMN_MAP COLUMN(8*8, map, "Map", ui.drawstring(pos, sprintf("%-.8s", gethostcachestring(field_map, sv)), '8 8', col, 1, 0);) +#define COLUMN_HOSTNAME COLUMN(64*8, name, "Name", ui.drawstring(pos, sprintf("%s", gethostcachestring(field_name, sv)), '8 8', col, 1, 0);) +//FIXME: add a little * icon before the hostname for favourites or something + +#define COLUMNS COLUMN_NUMPLAYERS COLUMN_MAXPLAYERS COLUMN_PING COLUMN_FRAGLIMIT COLUMN_TIMELIMIT COLUMN_GAMEDIR COLUMN_ADDRESS COLUMN_MAP COLUMN_HOSTNAME + +class mitem_servers : mitem +{ + float server_selected; + mitem_vslider slider; + float dbltime; + float dobound; + + void() mitem_servers = + { + dbltime = cltime - 10; + this.item_flags |= IF_SELECTABLE; + server_selected = -1; + + //clear the filter + resethostcachemasks(); +#ifdef MOD_GAMEDIR + if (MOD_GAMEDIR != "") + { + //constrain the list to only servers with the right gamedir. + sethostcachemaskstring(0, gethostcacheindexforkey("gamedir"), MOD_GAMEDIR, SLIST_TEST_EQUAL); + } +#endif + //sort by ping by default + sethostcachesort(gethostcacheindexforkey("ping"), FALSE); + //(re)query the servers. + refreshhostcache(); + + resorthostcache(); + }; + + virtual void(vector pos) item_draw = + { + local float sv, maxsv = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); + local float maxy = self.item_size_y; + float left = pos_x; + + local vector omin = ui.drawrectmin, omax = ui.drawrectmax; + local vector cmin = pos + '0 8', cmax = pos + item_size; + + cmin_x = max(omin_x, cmin_x); + cmin_y = max(omin_y, cmin_y); + cmax_x = min(omax_x, cmax_x); + cmax_y = min(omax_y, cmax_y); + + slider.maxv = maxsv - (floor(maxy/8) - 1); + + //constrain the view the lazy way. + if (dobound) + { + dobound = FALSE; + if (slider.val < 0) + slider.val = 0; + while (slider.val + floor(maxy/8) - 1 <= server_selected) + slider.val+=1; + while (server_selected < slider.val && slider.val > 0) + slider.val-=1; + } + + float field_name = gethostcacheindexforkey("name"); + float field_ping = gethostcacheindexforkey("ping"); + float field_numplayers = gethostcacheindexforkey("numhumans"); + if (field_numplayers < 0) + field_numplayers = gethostcacheindexforkey("numplayers"); + float field_maxplayers = gethostcacheindexforkey("maxplayers"); + float field_gamedir = gethostcacheindexforkey("gamedir"); + if (field_gamedir < 0) + field_gamedir = gethostcacheindexforkey("mod"); + float field_address = gethostcacheindexforkey("cname"); + float field_map = gethostcacheindexforkey("map"); + float field_timelimit = gethostcacheindexforkey("timelimit"); + float field_fraglimit = gethostcacheindexforkey("fraglimit"); + + maxy = ceil(maxy/8) - 1; + if (maxsv > slider.val + maxy) + maxsv = slider.val + maxy; + + float sort = gethostcachevalue(SLIST_SORTFIELD); + string colkey; +#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {if (ui.mousepos[0] > pos_x && ui.mousepos[1] < pos_y+8) colkey = #sortname; pos_x += width+8;} + COLUMNS +#undef COLUMN + + vector col; + pos_x = left; +#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {col = '1 1 1'; if (sort == field_##sortname) col_z = 0; if (colkey == #sortname) col_x = 0; ui.drawstring(pos, title, '8 8', col, 1, 0); pos_x += width+8;} + COLUMNS +#undef COLUMN + + //make sure things get cut off if we have too many rows (or fractional start) + ui.setcliparea(cmin[0], cmin[1], cmax[0] - cmin[0], cmax[1] - cmin[1]); + + pos_y += 8 * (1-(slider.val-floor(slider.val))); + for (float y=pos_y, sv = max(0, floor(slider.val)); sv < maxsv; sv+=1) + { + col = (sv&1)?'0.1 0.1 0.1':'0.15 0.1 0.05'; + drawfill([left, y], [item_size_x,8], col, 0.8, 0); + y += 8; + } + for ( sv = max(0, floor(slider.val)); sv < maxsv; sv+=1) + { + col = ((server_selected==sv)?'1 1 0':'1 1 1'); + + //if isproxy + //if islocal + //if isfavorite + + pos_x = left; +#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {draw pos_x += width+8; } + COLUMNS +#undef COLUMN + pos_y += 8; + } + + ui.setcliparea(omin_x, omin_y, omax_x - omin_x, omax_y - omin_y); + }; + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + float displaysize; + string addr; + /*just sink all inputs*/ + if (!down) + return FALSE; + if (scan != K_MOUSE1) + dbltime = cltime - 10; + if (scan == K_MOUSE1) + { + float news = ui.mousepos[1] - (pos_y+8); + news = floor(news / 8); + if (news == -1) + { + string colkey = ""; +#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {if (ui.mousepos[0] > pos_x) colkey = #sortname; pos_x += width+8;} + COLUMNS +#undef COLUMN + if (colkey != "") + { + float fld = gethostcacheindexforkey(colkey); + if (colkey == "numplayers") + { + //favour descending order + sethostcachesort(fld, (gethostcachevalue(SLIST_SORTFIELD) != fld) || !gethostcachevalue(SLIST_SORTDESCENDING)); + } + else + { + //favour ascending order + sethostcachesort(fld, (gethostcachevalue(SLIST_SORTFIELD) == fld) && !gethostcachevalue(SLIST_SORTDESCENDING)); + } + resorthostcache(); //tell the engine that its okay to resort everything. + dobound = TRUE; + } + } + if (news < 0 || news >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) + return FALSE; + + news += floor(slider.val); + + if (server_selected == news && dbltime > cltime) + { + //connect on double clicks. because we can. + addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected); + if (addr) + localcmd(sprintf("m_pop;connect \"%s\"\n", addr)); + } + else + server_selected = news; + dobound = TRUE; + + dbltime = cltime + 0.5; + } + else if (scan == K_ENTER) + { //connect normally + addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected); + if (addr) + localcmd(sprintf("m_pop;connect \"%s\"\n", addr)); + } + else if (scan == 's') + { //s = join as a spectator + addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected); + if (addr) + localcmd(sprintf("m_pop;observe \"%s\"\n", addr)); + } + else if (scan == 'j') + { //s = join as a spectator + addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected); + if (addr) + localcmd(sprintf("m_pop;join \"%s\"\n", addr)); + } + else if (scan == K_UPARROW || scan == K_MWHEELUP) + { + this.server_selected -= 1; + if (this.server_selected < 0) + { + this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); + if (this.server_selected) + this.server_selected -= 1; + } + dobound = TRUE; + } + else if (scan == K_DOWNARROW || scan == K_MWHEELDOWN) + { + this.server_selected += 1; + if (this.server_selected >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) + this.server_selected = 0; + dobound = TRUE; + } + else if (scan == K_PGUP) + { + displaysize = (item_size[1]-8)/(8); //this many rows + displaysize = floor(displaysize*0.5); + if (displaysize < 1) + displaysize = 1; + this.server_selected -= displaysize ; + if (this.server_selected < 0) + this.server_selected = 0; + dobound = TRUE; + } + else if (scan == K_PGDN) + { + displaysize = (item_size[1]-8)/(8); //this many rows + displaysize = floor(displaysize*0.5); + if (displaysize < 1) + displaysize = 1; + this.server_selected += displaysize; + if (this.server_selected >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) + this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)-1; + dobound = TRUE; + } + else if (scan == K_HOME) + { + this.server_selected = 0; + dobound = TRUE; + } + else if (scan == K_END) + { + this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)-1; + if (this.server_selected < 0) + this.server_selected = 0; + dobound = TRUE; + } + else if (char == 'r' || char == 'R' || scan == K_F5) + { + refreshhostcache(); + resorthostcache(); + } + else + return FALSE; + return TRUE; + }; +}; + +class mitem_servers_players : mitem +{ + mitem_servers listing; + + void() mitem_servers_players = + { +// this.item_flags |= IF_SELECTABLE; + }; + + virtual void(vector pos) item_draw = + { + float player; + float y; + float m; + vector opos = pos; + if (listing.server_selected < 0) + return; + for (player = 0, y = 0; player < 256; player++) + { + string playerinfo = gethostcachestring(gethostcacheindexforkey(sprintf("player%g", player)), listing.server_selected); + if (!playerinfo) + break; + tokenize(playerinfo); + float userid = stof(argv(0)); + float frags = stof(argv(1)); + float ontime = stof(argv(2)); + float ping = stof(argv(3)); + string name = argv(4); + string skin = argv(5); + vector top = stov(argv(6)); + vector bot = stov(argv(7)); + + drawfill(pos, '16 4', top, 1, 0); + drawfill(pos+'0 4 0', '16 4', bot, 1, 0); + drawstring(pos, sprintf("%g", frags), '8 8', '1 1 1', 1, 0); + drawstring(pos+'20 0', name, '8 8', '1 1 1', 1, 0); + pos_y += 8; + if (++y == 8) + { + y-= 8; + pos_y = opos_y; + pos_x += 16*8; + } + } + + if (y) + { + pos_y = opos_y; + pos_x += 16*6; + y = 0; + } +// drawtextfield(opos, item_size, 3, gethostcachestring(gethostcacheindexforkey("serverinfo"), listing.server_selected)); + m = tokenizebyseparator(gethostcachestring(gethostcacheindexforkey("serverinfo"), listing.server_selected), "\\"); + for(player = 1; player <= m; player += 2) + { + drawtextfield(pos, '64 8', 6, argv(player)); + drawtextfield(pos+'68 0', [32*8-40, 8], 3, argv(player+1)); + + pos_y += 8; + if (++y == 8) + { + y-= 8; + pos_y -= 8*8; + pos_x += 32*8; + } + } + }; +}; + + +nonstatic void(mitem_desktop desktop) M_Servers = +{ + mitem_menu o; + + if (assumefalsecheckcommand("menu_servers") && argv(1) != "force") + { + localcmd("menu_servers\n"); + return; + } + +#ifdef CSQC + if not (checkextension("FTE_CSQC_SERVERBROWSER")) + { + print(_("Sorry, your client does not support FTE_CSQC_SERVERBROWSER\n")); + return; + } +#endif + + o = (mitem_menu)desktop.findchild(_("Servers List")); + if (o) + o.totop(); + else + { +#if 1 + mitem_exmenu m; + m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main"); + desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); +#else + mitem_menu m; + m = menu_spawn(desktop, _("Servers List"), '320 200'); + m.item_command = "m_main"; + m.item_flags |= IF_RESIZABLE; + desktop.item_focuschange(m, IF_KFOCUSED); + m.totop(); +#endif + + mitem_vslider sl = spawn(mitem_vslider, stride:4, item_flags:IF_SELECTABLE); + mitem_servers ls = spawn(mitem_servers, slider:sl); + + m.add(ls, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '-16 -68'); + m.add(spawn(mitem_servers_players, listing:ls), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [8*20, -8*8], [0, -8*0]); + m.add(sl, RS_X_MIN_PARENT_MAX|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [-16, 8], [0, -68]); + + m.add(menuitemcheck_spawn(_("Ping"), "sb_showping", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*8], [8*20, -8*7]); + m.add(menuitemcheck_spawn(_("Address"), "sb_showaddress", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*7], [8*20, -8*6]); + m.add(menuitemcheck_spawn(_("Map"), "sb_showmap", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*6], [8*20, -8*5]); + m.add(menuitemcheck_spawn(_("Gamedir"), "sb_showgamedir", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*5], [8*20, -8*4]); + m.add(menuitemcheck_spawn(_("NumPlayers"), "sb_shownumplayers", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*4], [8*20, -8*3]); + m.add(menuitemcheck_spawn(_("MaxPlayers"), "sb_showmaxplayers", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*3], [8*20, -8*2]); + m.add(menuitemcheck_spawn(_("Fraglimit"), "sb_showfraglimit", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*2], [8*20, -8*1]); + m.add(menuitemcheck_spawn(_("Timelimit"), "sb_showtimelimit", [8*8, 8]), RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*1], [8*20, -8*0]); + } +}; \ No newline at end of file diff --git a/quakec/menusys/menusys/mitem_bind.qc b/quakec/menusys/menusys/mitem_bind.qc new file mode 100644 index 000000000..13c1849b1 --- /dev/null +++ b/quakec/menusys/menusys/mitem_bind.qc @@ -0,0 +1,100 @@ +/*************************************************************************** +key binding item. +interactable - queries binds for a command, and accepts new scan codes to bind for its given command. +*/ +class mitem_bind : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float flag) item_focuschange; + + void() mitem_bind = + { + item_scale = item_size[1]; + + item_flags |= IF_SELECTABLE; + }; +}; +#define menuitembind_spawn(text,command,sz) \ + spawn(mitem_bind, \ + item_text: text, \ + item_command: command, \ + item_size: sz \ + ) + +void(vector pos) mitem_bind::item_draw = +{ + /*this is not my API...*/ + tokenize(findkeysforcommand(self.item_command)); + string key1 = argv(0); + string key2 = argv(1); + if (key1 != "") key1 = (key1=="-1")?"":keynumtostring(stof(key1)); + if (key2 != "") key2 = (key2=="-1")?"":keynumtostring(stof(key2)); + + super::item_draw(pos); + pos_x += self.item_size_x / 2; + + if (self.item_flags & IF_INTERACT) + { + ui.drawstring(pos, "Please press a key", '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); + } + else + { + ui.drawstring(pos, key1, '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); + pos_x += stringwidth(key1, TRUE, '1 1 0'*self.item_scale); + + if (key2 != "") + { + ui.drawstring(pos, " or ", '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); + pos_x += stringwidth(" or ", TRUE, '1 1 0'*self.item_scale); + + ui.drawstring(pos, key2, '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); +// pos_x += stringwidth(key2, TRUE, '1 1 0'*self.item_scale); + } + } +}; +float(vector pos, float scan, float char, float down) mitem_bind::item_keypress = +{ + if (!down) + return FALSE; + + if (self.item_flags & IF_INTERACT) + { + if (scan == K_ESCAPE) + { + } + else if (scan) + localcmd(sprintf("bind \"%s\" \"%s\"\n", keynumtostring(scan), self.item_command)); + else + return FALSE; + self.item_flags -= IF_INTERACT; + return TRUE; + } + else + { + if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, self.item_size))) + { + self.item_flags |= IF_INTERACT; + return TRUE; + } + if (scan == K_DEL || scan == K_BACKSPACE) + { + /*again, this is not my API...*/ + tokenize(findkeysforcommand(self.item_command)); + string key1 = argv(0); + string key2 = argv(1); + if (key1 != "") key1 = (key1=="-1")?"":keynumtostring(stof(key1)); + if (key2 != "") key2 = (key2=="-1")?"":keynumtostring(stof(key2)); + if (key1 != "") localcmd(sprintf("bind \"%s\" \"\"\n", key1)); + if (key2 != "") localcmd(sprintf("bind \"%s\" \"\"\n", key2)); + return TRUE; + } + return FALSE; + } +}; +void(mitem newfocus, float flag) mitem_bind::item_focuschange = +{ + if (!(self.item_flags & IF_KFOCUSED)) + self.item_flags = self.item_flags - (self.item_flags & IF_INTERACT); +}; + diff --git a/quakec/menusys/menusys/mitem_checkbox.qc b/quakec/menusys/menusys/mitem_checkbox.qc new file mode 100644 index 000000000..1ce98715e --- /dev/null +++ b/quakec/menusys/menusys/mitem_checkbox.qc @@ -0,0 +1,62 @@ +/*************************************************************************** +checkbox, directly linked to a cvar. +*/ +class mitem_check : mitem +{ + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (!down) + return FALSE; + + if (scan == K_ENTER || scan == K_SPACE || scan == K_LEFTARROW || scan == K_RIGHTARROW || scan == K_MOUSE1) + { + pos_x += this.item_size_x / 2; +// if (ui.mousepos[0] > pos_x || scan != K_MOUSE1) //don't do anything if they clicked the bit on the left to select it + set(item_command, ftos(!stof(get(item_command)))); + return TRUE; + } + else if (scan == K_DEL && down && cvar_type(item_command)) + set(item_command, cvar_defstring(item_command)); + return FALSE; + }; + virtual void(vector pos) item_draw = + { + local float truth = stof(get(item_command)); + + super::item_draw(pos); + pos_x += item_size_x / 2; + + if (dp_workarounds) + { //lame, but whatever + ui.drawstring(pos, chr2str(0xe080, 0xe082), '1 1 0' * this.item_scale, this.item_rgb, this.item_alpha, 0); + if (truth) + ui.drawstring(pos + '4 0', chr2str(0xe083), '1 1 0' * this.item_scale, this.item_rgb, this.item_alpha, 0); + } + else + { + ui.drawstring(pos, "^{e080}^{e082}", '1 1 0' * this.item_scale, this.item_rgb, this.item_alpha, 0); + if (truth) + ui.drawstring(pos + '4 0', "^{e083}", '1 1 0' * this.item_scale, this.item_rgb, this.item_alpha, 0); + } + }; + void() mitem_check = + { + if (!item_scale) + item_scale = 8; + if (!item_size_y) + item_size_y = item_scale; + item_flags |= IF_SELECTABLE; + }; +}; + +//optional, can spawn direcly +mitem_check(string text, string command, vector sz) menuitemcheck_spawn = +{ + mitem_check n = spawn(mitem_check); + n.item_scale = sz_y; + n.item_text = text; + n.item_size = sz; + + n.item_command = command; + return n; +}; diff --git a/quakec/menusys/menusys/mitem_colours.qc b/quakec/menusys/menusys/mitem_colours.qc new file mode 100644 index 000000000..2d5a58568 --- /dev/null +++ b/quakec/menusys/menusys/mitem_colours.qc @@ -0,0 +1,212 @@ +/*************************************************************************** +hue selection thing, directly linked to a cvar. +We hard code 1 in the saturation+value arguments for the selection + +Screw Quake colours, they're too brown! +*/ +class mitem_colours : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; +}; + +//http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c +static vector(vector rgb) rgbtohsv = +{ + float r = rgb_x, g = rgb_y, b = rgb_z; + float maxc = max(r, g, b), minc = min(r, g, b); + float h, s, l = (maxc + minc) / 2; + + local float d = maxc - minc; + if (maxc) + s = d / maxc; + else + s = 0; + + if(maxc == minc) + { + h = 0; // achromatic + } + else + { + if (maxc == r) + h = (g - b) / d + ((g < b) ? 6 : 0); + else if (maxc == g) + h = (b - r) / d + 2; + else + h = (r - g) / d + 4; + h /= 6; + } + + return [h, s, l]; +}; + +//http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c +static vector(vector hsv) hsvtorgb = +{ + float h = hsv_x, s = hsv_y, v = hsv_z; + float r=0, g=1, b=0; + + while(h < 0) + h+=1; + while(h >= 1) + h-=1; + + float i = floor(h * 6); + float f = h * 6 - i; + float p = v * (1 - s); + float q = v * (1 - f * s); + float t = v * (1 - (1 - f) * s); + switch(i) + { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + + return [r, g, b]; +}; + +float(float chr) nibbletofloat = +{ + chr = chr&0xff; + if (chr >= '0' && chr <= '9') + return chr - '0'; + if (chr >= 'a' && chr <= 'f') + return chr - 'a' + 10; + if (chr >= 'A' && chr <= 'F') + return chr - 'A' + 10; + return 0; +}; + +static vector(string v) hextorgb = +{ + if (!strncmp(v, "0x", 2)) + { + vector r; + r_x = (nibbletofloat(str2chr(v, 2))*16 + nibbletofloat(str2chr(v, 3)))/255; + r_y = (nibbletofloat(str2chr(v, 4))*16 + nibbletofloat(str2chr(v, 5)))/255; + r_z = (nibbletofloat(str2chr(v, 6))*16 + nibbletofloat(str2chr(v, 7)))/255; + return r; + } + else + return '0 0 0'; +}; +static string(vector v) rgbtohex = +{ + v *= 255; + return sprintf("0x%02x%02x%02x", v_x, v_y, v_z); +}; + +void(vector pos) mitem_colours::item_draw = +{ + local float step; + local float stride; + local string curval; + local vector rgb; + + super::item_draw(pos); + + //calculate the rgb from hue at each step across the colour block +#define STEPS 32 + pos_x += item_size_x / 2; + + if (ui.mgrabs == this) + { + float frac; + //if we're sliding it, update the value + frac = (ui.mousepos[1] - pos_x-(item_size_y+4)) / (item_size_x / 2 - (item_size_y+4)); + if (frac >= 0 && frac <= 1) + { + set(item_command, rgbtohex(hsvtorgb([frac, 1, 1]))); + } + } + curval = get(item_command); + + stride = (item_size_x / 2 - (item_size_y+4)) / STEPS; + + ui.drawfill(pos, [item_size_y, item_size_y], hextorgb(curval), item_alpha, 0); + pos_x += item_size_y+4; + + pos_y += 1; +#if defined(MENU) || 1 + for (step = 0; step < STEPS; step += 1, pos_x += stride) + { + rgb = hsvtorgb([step/STEPS, 1, 1]); + ui.drawfill(pos, [stride, item_size_y-2], rgb, item_alpha, 0); + } +#else +//FIXME: WTF is going on here? it comes out as black? wtf? + //draw quads (we should probably not use an internal-to-engine shader here...) + R_BeginPolygon("fill_opaque", 4); //outside so we can skip it for faster reuse by avoiding lookups + rgb = hsvtorgb([0, 1, 1]); + for (step = 0; step < STEPS;) + { + R_PolygonVertex([pos_x, pos_y+item_size_y-2], '0 1', rgb, item_alpha); + R_PolygonVertex([pos_x, pos_y], '0 0', rgb, 1); + + pos_x += stride; + step += 1; + rgb = hsvtorgb([step/STEPS, 1, 1]); + + R_PolygonVertex([pos_x, pos_y], '1 0', rgb, 1); + R_PolygonVertex([pos_x, pos_y+item_size_y-2], '1 1', rgb, item_alpha); + + R_EndPolygon(); + } +#endif +#undef STEPS +}; + +float(vector pos, float scan, float char, float down) mitem_colours::item_keypress = +{ + if (!down) + { + if (ui.mgrabs == this) + ui.mgrabs = __NULL__; + return FALSE; + } + local float curval = rgbtohsv(hextorgb(get(item_command)))[0]; + if (scan == K_MWHEELUP || scan == K_MWHEELDOWN) + { + if (mouseinbox(pos, item_size)) + scan = ((scan == K_MWHEELDOWN)?K_LEFTARROW:K_RIGHTARROW); + } + if (scan == K_MOUSE1) + { + pos_x += item_size_x / 2; + pos_x += item_size_y+4; + curval = (ui.mousepos[0] - pos_x) / (item_size_x / 2 - item_size_y+4); + if (curval < 0 || curval > 1) + return FALSE; + curval = curval; + set(item_command, rgbtohex(hsvtorgb([(curval), 1, 1]))); + ui.mgrabs = this; + } + else if (scan == K_LEFTARROW || scan == K_SPACE) + { + set(item_command, rgbtohex(hsvtorgb([curval - (1/64.0), 1, 1]))); //yay autorepeat + } + else if (scan == K_RIGHTARROW || scan == K_ENTER) + { + set(item_command, rgbtohex(hsvtorgb([curval + (1/64.0), 1, 1]))); + } + else + return FALSE; + return TRUE; +}; +mitem_colours(string text, string command, vector sz) menuitemcolour_spawn = +{ + mitem_colours n = spawn(mitem_colours); + n.item_scale = sz_y; + n.item_text = text; + n.item_size = sz; + + n.item_command = command; + n.item_flags |= IF_SELECTABLE; + return n; +}; + diff --git a/quakec/menusys/menusys/mitem_combo.qc b/quakec/menusys/menusys/mitem_combo.qc new file mode 100644 index 000000000..3a8d3927d --- /dev/null +++ b/quakec/menusys/menusys/mitem_combo.qc @@ -0,0 +1,258 @@ +/*************************************************************************** +combo item. +No longer using pointers, now using a tokenized string. Less efficient, but saves creating lots of different arrays, just pass a string. +The string list is doubled, first is the actual value, second is the friendly text. +Will show actual value when focused, and will show readable value when not. +The possible values is a separate popup. +*/ + +class mitem_combo; +class mitem_combo_popup; + +class mitem_combo : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float changedflag) item_focuschange; + + mitem_combo_popup cfriend; + string mstrlist; + float firstrow; + float visrows; + + virtual void() item_remove = + { + strunzone(mstrlist); + super::item_remove(); + }; +}; + +class mitem_combo_popup : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float changedflag) item_focuschange; + + mitem_combo pfriend; +}; + +void(vector pos) mitem_combo::item_draw = +{ + local float i, v; + local string curval = get(item_command); + + if (cfriend) + cfriend.item_position = pos + [item_size_x / 2, item_size_y]; + + super::item_draw(pos); + + v = tokenize(mstrlist); + + //display the friendly string if the current value matches +// if (!(item_flags & IF_KFOCUSED) && (!cfriend || !(cfriend.item_flags & IF_KFOCUSED))) + { + for (i = 0; i < v; i+=2) + { + if (argv(i) == curval) + { + curval = argv(i+1); + break; + } + } + } + + pos_x += item_size_x / 2; + + ui.drawfill(pos, [item_size_x/2, 1], TD_BOT, item_alpha, 0); + ui.drawfill(pos, [1, item_size_y - 1], TD_RGT, item_alpha, 0); + ui.drawfill(pos + [item_size_x/2-1, 1], [1, item_size_y - 1], TD_LFT, item_alpha, 0); + ui.drawfill(pos + [0, item_size_y-1], [item_size_x/2, 1], TD_TOP, item_alpha, 0); + + //silly strings need to get cut off properly. + ui.setcliparea(pos[0], pos[1], item_size_x/2, item_size_y); + pos_y += (item_size_y - item_scale)*0.5; + pos_x += 1; + ui.drawstring(pos, curval, '1 1 0' * item_scale, item_rgb, item_alpha, 0); + ui.setcliparea(ui.drawrectmin[0], ui.drawrectmin[1], ui.drawrectmax[0] - ui.drawrectmin[0], ui.drawrectmax[1] - ui.drawrectmin[1]); +}; +void(mitem newfocus, float flag) mitem_combo::item_focuschange = +{ + if (!cfriend || !(flag & IF_KFOCUSED)) + return; //don't care + if (newfocus != (mitem)this && newfocus != (mitem)cfriend) + { + cfriend.item_size = cfriend.maxs = '0 0'; + cfriend.item_flags &~= IF_SELECTABLE; + } +}; +void(mitem newfocus, float flag) mitem_combo_popup::item_focuschange = +{ + pfriend.item_focuschange(newfocus, flag); +}; +void(vector pos) mitem_combo_popup::item_draw = +{ + vector col; + if (item_size_y < 1) + return; + + local mitem_combo f = pfriend; + item_command = f.item_command; + local string curval = f.get(f.item_command); + local float i, m, c, v; + + if (!((f.item_flags | item_flags) & IF_KFOCUSED)) + { + item_size = maxs = '0 0'; + item_flags &~= IF_SELECTABLE; + return; + } + + ui.drawfill(pos, item_size, item_rgb, item_alpha, 0); + + ui.drawfill(pos, [item_size_x, 1], TD_BOT, item_alpha, 0); + ui.drawfill(pos, [1, item_size_y - 1], TD_RGT, item_alpha, 0); + ui.drawfill(pos + [item_size_x-1, 1], [1, item_size_y - 1], TD_LFT, item_alpha, 0); + ui.drawfill(pos + [0, item_size_y-1], [item_size_x, 1], TD_TOP, item_alpha, 0); + pos_x += 1; + + v = tokenize(f.mstrlist); + for (c = 0; c < v; c += 2) + if (argv(c) == curval) + break; + if (c >= v) + c = 0; + + i = f.firstrow; + i = i*2; + if (!f.visrows) + i = 0; + else + { + //bound the displayed position + if (c < i) + i = c; + if (i < c - (f.visrows-1)*2) + i = c - (f.visrows-1)*2; + } + m = i + f.visrows*2; + f.firstrow = floor(i*0.5); + + //constrain the drawing so it doesn't overflow the combo + ui.setcliparea(pos[0], pos[1], item_size[0], item_size[1]); + + for (; i < m && i < v ; i+=2) + { + col = f.item_rgb; + if (item_flags & IF_MFOCUSED) + if (mouseinbox(pos, [item_size_x, item_scale])) + col_z = 0; + if (c == i) + col_x = 0; + + ui.drawstring(pos, argv(i+1), '1 1 0' * item_scale, col, f.item_alpha, 0); + pos_y += item_scale; + } + + //reset the clip area + ui.setcliparea(ui.drawrectmin[0], ui.drawrectmin[1], ui.drawrectmax[0] - ui.drawrectmin[0], ui.drawrectmax[1] - ui.drawrectmin[1]); +}; +float(vector pos, float scan, float char, float down) mitem_combo_popup::item_keypress = +{ + return pfriend.item_keypress(pos - [0, pfriend.item_size_y], scan, char, down); +}; +float(vector pos, float scan, float char, float down) mitem_combo::item_keypress = +{ + if (!down) + return FALSE; + + local string curval = get(item_command); + local float i, c; + local float f; + c = tokenize(mstrlist); + + //find which one is active + for (i = 0; i < c; i+=2) + { + if (argv(i) == curval) + { + break; + } + } + + if (scan == K_ESCAPE) + return FALSE; + else if (scan == K_MWHEELUP || (scan == K_UPARROW && cfriend)) + { + i -= 2; + if (i < 0) + i = c - 2; + curval = argv(i); + } + else if (scan == K_MWHEELDOWN || (scan == K_DOWNARROW && cfriend)) + { + i += 2; + if (i >= c) + i = 0; + curval = argv(i); + } + else if (scan == K_MOUSE1) + { + visrows = ((c>18)?18/2:c/2); + if (!cfriend) + { + cfriend = spawn(mitem_combo_popup); + cfriend.pfriend = this; + cfriend.item_scale = 8; + cfriend.item_rgb = MENUBACK_RGB; + cfriend.item_alpha = MENUBACK_ALPHA; + pos = item_position; + mitem_frame fr = item_parent; + while (fr.item_parent) + { //try to inject the combo thingie into the desktop item. this is to avoid scissoring. + pos += fr.item_position; + fr = fr.item_parent; + } + fr.addr(cfriend, RS_X_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN, pos + [self.item_size_x / 2, self.item_size_y], [item_size_x*0.5, item_size_y*visrows]); + } + cfriend.item_size = cfriend.maxs = [item_size_x*0.5, item_size_y*visrows]; + cfriend.item_flags |= IF_SELECTABLE; + cfriend.totop(); + + if (cfriend.item_flags & IF_MFOCUSED) + { + //if they clicked inside the popup, change the selected item. + f = ui.mousepos[1] - (pos_y + item_size_y); + f /= cfriend.item_scale; + f += firstrow; + i = floor(f) * 2; + if (i < c) + { + curval = argv(i); + cfriend.item_flags &~= IF_SELECTABLE; + cfriend.item_size = cfriend.maxs = '0 0'; + item_parent.item_focuschange(this, IF_MFOCUSED|IF_KFOCUSED); + } + } + } + else if (scan == K_BACKSPACE || scan == K_DEL) + curval = substring(curval, 0, -2); + else if (char >= ' ') + curval = strcat(curval, chr2str(char)); + else + return FALSE; + + set(item_command, curval); + return TRUE; +}; +mitem_combo(string text, string command, vector sz, string valuelist) menuitemcombo_spawn = +{ + mitem_combo n = spawn(mitem_combo); + n.item_scale = sz_y; + n.item_text = text; + n.item_size = sz; + n.mstrlist = strzone(valuelist); + + n.item_command = command; + n.item_flags |= IF_SELECTABLE; + return n; +}; diff --git a/quakec/menusys/menusys/mitem_console.qc b/quakec/menusys/menusys/mitem_console.qc new file mode 100644 index 000000000..30d9b3816 --- /dev/null +++ b/quakec/menusys/menusys/mitem_console.qc @@ -0,0 +1,58 @@ +/*************************************************************************** +Embed a (sub) console in a menu item thing. + +you can print to these consoles with con_print("consolename", "text to be printed\n"); +they can be detached from the system console with con_getset("consolename", "hidden", "1"). be sure to getset(con, "close", "1") or unhide afterwards. +you can enumerate the active consoles with con="";while((con=con_getset(con,"next"))!=""){print(con_getset(con,"title"));} this is useful for finding xmpp conversations. you might then want to hide them afterwards. +*/ + +class mitem_console : mitem +{ + virtual void() item_remove = + { + strunzone(item_command); + super::item_remove(); + }; + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + local float ret; + if (scan == K_ESCAPE) //let the owning menu take it. + return FALSE; + if (down) + { + ret = con_input(item_command, IE_KEYDOWN, scan, char, 0); + if (scan == K_MOUSE1 || scan == K_MOUSE2) //grab mouse, so we still receive mouse up events. + { + ui.mgrabs = this; + ret = TRUE; + local mitem_frame p = item_parent; + p.item_focuschange(this, IF_KFOCUSED); + } + } + else + { + ret = con_input(item_command, IE_KEYUP, scan, char, 0); //note the engine never tries to cancel key up events anyway. + if (ui.mgrabs == this) + ui.mgrabs = __NULL__; + } + return ret; + }; + virtual void(mitem newfocus, float flag) item_focuschange = + { + con_input(item_command, IE_FOCUS, !!(item_flags&IF_MFOCUSED), !!(item_flags&IF_KFOCUSED), 0); + }; + virtual void(vector pos) item_draw = + { + con_input(item_command, IE_MOUSEABS, ui.mousepos[0] - pos_x, ui.mousepos[1] - pos_y, 0); + con_draw(item_command, pos, item_size, 12); //use a 12-point font, if we can. + }; + void() mitem_console = + { + item_flags |= IF_SELECTABLE; + item_command = strzone(item_command); + }; +}; + +#define menuitemconsole_spawn(conname) spawn(mitem_console, item_command:conname) + + diff --git a/quakec/menusys/menusys/mitem_desktop.qc b/quakec/menusys/menusys/mitem_desktop.qc new file mode 100644 index 000000000..cc634f131 --- /dev/null +++ b/quakec/menusys/menusys/mitem_desktop.qc @@ -0,0 +1,451 @@ +/*************************************************************************** +desktop and top-level functions. +the 'tabs' widgit is simply a tab-selection control. horizontal multiple choice. it draws only its currently active child. +the 'tab' widgit is merely a container of other widgits, no different from a standard menu, except not exclusive or anything. +*/ + +#ifdef USEPOINTERS +typedef mitem mitem_desktop; +#else +class mitem_desktop : mitem_frame +{ + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float flag) item_focuschange; + virtual void(vector pos) item_draw; //draws the game, then calls mitem_frame::item_draw for children. +#ifndef MENU + virtual void(float seat, vector minpos, vector size) drawgame = __NULL__; //if overridden, should call renderscene and then draw whatever hud it needs to. this will do splitscreen efficiently and automatically. +#endif + void() mitem_desktop; //the constructor. uninteresting. just ensures the size defaults to fullscreen. +}; +#endif + +#ifndef MENU +float sb_showscores; +#endif + +void() mitem_desktop::mitem_desktop = +{ +#define menu_font_win autocvar(menu_font_win, "cour") +#define menu_font autocvar(menu_font, "") + queryscreensize(); + + //make sure we have a font that can cope with slightly up-scaled stuff. + if (menu_font_win != "" && !strncmp(cvar_string("sys_platform"), "Win", 3)) + drawfont = loadfont("", menu_font_win, "8 12 16", 0); + else + drawfont = loadfont("", menu_font, "8 12 16", 0); + + item_text = "desktop"; + if (!item_flags) + { + item_size = ui.screensize; + item_flags = IF_SELECTABLE | IF_MFOCUSED | IF_KFOCUSED | IF_RESIZABLE | IF_NOCURSOR; + } + + if (!ui.kgrabs && !ui.mgrabs && (item_flags & IF_NOCURSOR)) + { + ui.kgrabs = this; + ui.mgrabs = this; + } +}; + +void(mitem newfocus, float flag) mitem_desktop::item_focuschange = +{ + super::item_focuschange(newfocus, flag); + + if (flag & IF_KFOCUSED) + { + //if we're deselecting the current one, reenable grabs + if (newfocus == __NULL__) + { + if(item_flags & IF_NOCURSOR) + { + ui.kgrabs = this; + ui.mgrabs = this; + } + } + else + { + if (ui.kgrabs == this) + { + ui.kgrabs = __NULL__; + ui.mgrabs = __NULL__; + } + } + } +}; + +//the interact flag says that the mouse is held down on the desktop +float(vector pos, float scan, float char, float down) mitem_desktop::item_keypress = +{ + float oldfl = item_flags; + + if (down & 2) + { + down &= 1; + //if we're grabbing, then cancel if they press escape, otherwise block other items from taking the keys. + if (scan >= K_MOUSE1 && scan <= K_MOUSE5) + return 2; //block other wigits, don't cancel the event so the engine still does its thing + else + { + if (scan == K_ESCAPE && down) + { + local mitem sc; + //block the keyevent if we already have menus loaded but not focused (select one if we do). + for (sc = item_children; sc; sc = sc.item_next) + { + if (sc.item_flags & IF_SELECTABLE) + { + if (!item_kactivechild) + item_focuschange(sc, IF_KFOCUSED); + return 3; + } + } + //make sure our code takes it, instead of showing the engine menu... +#ifdef MENU + m_toggle(0); + return 3; +#else + return 2|CSQC_ConsoleCommand("togglemenu"); +#endif + } + return 2; + } + } + + if (mitem_frame::item_keypress(pos, scan, char, down)) + return TRUE; + + if (scan == K_MOUSE1 && down) + { +#ifdef CSQC + if (numclientseats) + localcmd("in_forcesplitclient 0\n"); +#endif + if (item_flags & IF_NOCURSOR) + { + ui.kgrabs = this; + ui.mgrabs = this; + } + return TRUE; + } + if (scan == K_MOUSE2 && down) + { +#ifdef CSQC + if (numclientseats > 3) + localcmd("in_forcesplitclient ", ftos(1 + ((ui.mousepos[0]>ui.screensize[0]/2)?1:0) + ((ui.mousepos[1]>ui.screensize[1]/2)?2:0)), "\n"); + else if (numclientseats > 1) + localcmd("in_forcesplitclient ", ftos(1 + floor(ui.mousepos[1]*numclientseats/ui.screensize[1])), "\n"); + else if (numclientseats) + localcmd("in_forcesplitclient 0\n"); +#endif + if (item_flags & IF_NOCURSOR) + { + ui.kgrabs = this; + ui.mgrabs = this; + } + return TRUE; + } + +#ifdef CSQC + //catch otherwise unhangled escape presses, just to be sure we can use escape to toggle the menu + if (scan == K_ESCAPE && down) + { + return CSQC_ConsoleCommand("togglemenu"); + } +#endif + return FALSE; +}; + +#ifndef MENU +static vector(vector v) vtodpp = +{ + //so fucking disgustingly ugly. + if (dp_workarounds) + { + v_x *= cvar("vid_width") / cvar("vid_conwidth"); + v_y *= cvar("vid_height") / cvar("vid_conheight"); + } + return v; +}; + +//vector pmove_org; +void(float seat, vector minpos, vector size) mitem_desktop::drawgame_helper = +{ + if not(seat) + { + clearscene(); + addentities(MASK_ENGINE|MASK_VIEWMODEL); + } + else + { + setviewprop(VF_LPLAYER, seat); + setproperty(VF_VIEWENTITY, player_localentnum); + addentities(MASK_VIEWMODEL); //don't do mask_engine because that's already done + } +// if (dp_workarounds) +// setproperty(VF_ORIGIN, pmove_org); + setproperty(VF_MIN, minpos); + setproperty(VF_SIZE, vtodpp(size)); + + setproperty(VF_DRAWCROSSHAIR, (ui.mgrabs == this)); + + drawgame(seat, minpos, size); + if (mouseinbox(minpos, size)) + { + ui.havemouseworld = TRUE; + ui.mouseworldnear = unproject([ui.mousepos[0], ui.mousepos[1], 0]); + ui.mouseworldfar = unproject([ui.mousepos[0], ui.mousepos[1], 100000]); + } +}; +#endif + +void(vector pos) mitem_desktop::item_draw = +{ +#ifndef MENU +//menuqc picks up the game/console as a background + string constate = serverkey("constate"); + if (constate != "" && constate != "active") //allow empty, so things still kinda work with dp too. + { + drawfill(pos, ui.screensize, '0 0 0', 1, 0); + } + else if (this.drawgame != __NULL__) + { + if (numclientseats > 3) + { + drawgame_helper(0, [0, 0], 0.5*ui.screensize); + drawgame_helper(1, [ui.screensize[0]*0.5, 0], 0.5*ui.screensize); + drawgame_helper(2, [0, ui.screensize[1]*0.5], 0.5*ui.screensize); + drawgame_helper(3, [ui.screensize[0]*0.5, ui.screensize[1]*0.5], 0.5*ui.screensize); + setviewprop(VF_LPLAYER, 0); + } + else if (numclientseats > 2) + { + drawgame_helper(0, [0, 0], [ui.screensize[0], ui.screensize[1]*0.333]); + drawgame_helper(1, [0, ui.screensize[1]*0.333], [ui.screensize[0], ui.screensize[1]*0.333]); + drawgame_helper(2, [0, ui.screensize[1]*0.666], [ui.screensize[0], ui.screensize[1]*0.333]); + setviewprop(VF_LPLAYER, 0); + } + else if (numclientseats > 1) + { + drawgame(0, [0, 0], [ui.screensize[0], ui.screensize[1]*0.5]); + drawgame(1, [0, ui.screensize[1]*0.5], [ui.screensize[0], ui.screensize[1]*0.5]); + setviewprop(VF_LPLAYER, 0); + } + else + { + drawgame_helper(0, '0 0', ui.screensize); + } + } +#endif + super::item_draw(pos); + + if (ui.kgrabs == this) + { +#ifndef MENU + if (sb_showscores && ui.mgrabs == this) + ui.mgrabs = __NULL__; + else +#endif + if (!ui.mgrabs) + ui.mgrabs = this; + } +}; + + +var string autocvar_cl_cursor = "gfx/cursor.lmp"; +var float autocvar_cl_cursorsize = 32; +var float autocvar_cl_cursorbias = 4; + +var static float oldgrabstate; //to work around a DP bug (as well as unnecessary spam) + +void() items_updategrabs = +{ + if (!ui.mgrabs || !(ui.mgrabs.item_flags & IF_NOCURSOR)) + { + if (!oldgrabstate) + { + oldgrabstate = TRUE; +#ifdef MENU + setkeydest(2); + setmousetarget(2); +#else + setcursormode(TRUE); + //setcursormode(TRUE, autocvar_cl_cursor, autocvar_cl_cursorbias*'1 1', autocvar_cl_cursorscale); +#endif + } + } + else if (oldgrabstate) + { + oldgrabstate = FALSE; +#ifdef MENU + setkeydest(0); + setmousetarget(1); +#else + setcursormode(FALSE); +#endif + } +}; + +void(mitem_desktop desktop) items_draw = +{ + queryscreensize(); + +#ifdef MENU + ui.mousepos = getmousepos(); +#else + if (ui.havemouseworld) + ui.havemouseworld = 2; //stale, but not too stale +#endif + + if (desktop.item_size != ui.screensize) + { + desktop.item_size = ui.screensize; + desktop.item_resized(); + } + ui.drawrectmax = ui.screensize; + + desktop.item_draw(desktop.item_position); + drawresetcliparea(); + + items_updategrabs(); + if (dp_workarounds && oldgrabstate) + { + if (drawgetimagesize(autocvar_cl_cursor) == '0 0') + ui.drawcharacter(ui.mousepos - [stringwidth("+", TRUE, '4 4')*0.5, 4], '+', '8 8', '1 1 1', 1, 0); + else + ui.drawpic(ui.mousepos - autocvar_cl_cursorbias*'1 1', autocvar_cl_cursor, autocvar_cl_cursorsize*'1 1', '1 1 1', 1, 0); + } + +#ifndef MENU + if (ui.havemouseworld == 2) //if its still stale then its totally invalid. + ui.havemouseworld = FALSE; +#endif + ui.oldmousepos = ui.mousepos; +}; + +//items_keypress has quite strong dimorphism. These are meant to tailored to the target's available event notifications, rather than being really rather annoying. +#ifdef CSQC +float(mitem_desktop desktop, float evtype, float scanx, float chary, float devid) items_keypress = +{ + local float result = FALSE; + vector pos; + mitem p; + switch(evtype) + { + case IE_KEYDOWN: + case IE_KEYUP: +#ifdef HEIRACHYDEBUG + if (scanx == K_F1 && evtype == IE_KEYDOWN) + { + mitem_printtree(desktop, "items_keypress", __LINE__); + return TRUE; + } +#endif + if (scanx >= K_MOUSE1 && scanx <= K_MOUSE5) + { + if (ui.mgrabs) + { + pos = '0 0 0'; + for (p = ui.mgrabs; p; p = p.item_parent) + pos += p.item_position; + result = ui.mgrabs.item_keypress(pos, scanx, chary, (evtype == IE_KEYDOWN)|2); + if (result & 2) + { + ui.mousedown = 0; + return result & 1; + } + } + } + else + { + if (ui.kgrabs) + { + pos = '0 0 0'; + for (p = ui.kgrabs; p; p = p.item_parent) + pos += p.item_position; + result = ui.kgrabs.item_keypress(pos, scanx, chary, (evtype == IE_KEYDOWN)|2); + if (result & 2) + return result & 1; + } + } + if (desktop && desktop.item_keypress) + result = desktop.item_keypress(desktop.item_position, scanx, chary, evtype == IE_KEYDOWN); + if (scanx >= K_MOUSE1 && scanx <= K_MOUSE5) + { + if (evtype == IE_KEYDOWN) + ui.mousedown |= pow(1, scanx-K_MOUSE1); + else + ui.mousedown &~= pow(1, scanx-K_MOUSE1); + } + result = result & 1; + break; + case IE_MOUSEDELTA: + result = !ui.mgrabs || !(ui.mgrabs.item_flags & IF_NOCURSOR); + if (result) + { + queryscreensize(); + ui.mousepos[0] = bound(0, ui.mousepos[0]+scanx, ui.screensize[0]); + ui.mousepos[1] = bound(0, ui.mousepos[1]+chary, ui.screensize[1]); + } + break; + case IE_MOUSEABS: + ui.mousepos[0] = scanx; + ui.mousepos[1] = chary; + result = !ui.mgrabs || !(ui.mgrabs.item_flags & IF_NOCURSOR); + break; + } + return result; +}; +#endif +#ifdef MENU +float(mitem_desktop desktop, float scan, float char, float down) items_keypress = +{ + local float result = FALSE; + local vector pos; + local mitem p; + ui.mousepos = getmousepos(); + queryscreensize(); +#ifdef HEIRACHYDEBUG + if (scan == K_F1 && down) + { + mitem_printtree(desktop, "items_keypress", __LINE__); + return TRUE; + } +#endif + + if (scan >= K_MOUSE1 && scan <= K_MOUSE5) + { + if (ui.mgrabs) + { + pos = '0 0 0'; + for (p = ui.mgrabs; p; p = p.item_parent) + pos += p.item_position; + result = ui.mgrabs.item_keypress(pos, scan, char, (down)|2); + if (result & 2) + { + ui.mousedown = 0; + return result & 1; + } + } + } + else + { + if (ui.kgrabs) + { + pos = '0 0 0'; + for (p = ui.kgrabs; p; p = p.item_parent) + pos += p.item_position; + result = ui.kgrabs.item_keypress(pos, scan, char, (down)|2); + if (result & 2) + return result & 1; + } + } + + if (desktop && desktop.item_keypress) + result = desktop.item_keypress(desktop.item_position, scan, char, down); + + return result; +}; +#endif + diff --git a/quakec/menusys/menusys/mitem_edittext.qc b/quakec/menusys/menusys/mitem_edittext.qc new file mode 100644 index 000000000..14024a8bb --- /dev/null +++ b/quakec/menusys/menusys/mitem_edittext.qc @@ -0,0 +1,89 @@ +/*************************************************************************** +editable text, directly linked to a cvar. +FIXME: This can only edit the end of the string. +*/ +class mitem_edit : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void() item_remove; + float spos; +}; + +void() mitem_edit::item_remove = +{ + strunzone(item_text); + strunzone(item_command); + super::item_remove(); +}; + +void(vector pos) mitem_edit::item_draw = +{ + local string curval = get(item_command); + + super::item_draw(pos); + + pos_x += item_size_x / 2; + ui.drawfill(pos, [item_size_x/2, 1], TD_BOT, item_alpha, 0); + ui.drawfill(pos, [1, self.item_size_y - 1], TD_RGT, item_alpha, 0); + ui.drawfill(pos + [item_size_x/2-1, 1], [1, item_size_y - 1], TD_LFT, item_alpha, 0); + ui.drawfill(pos + [0, item_size_y-1], [item_size_x/2, 1], TD_TOP, item_alpha, 0); + pos_y += (item_size_y - item_scale)*0.5; + pos_x += 1; + + spos = min(spos, strlen(curval)); + if (((cltime*4)&1) && (item_flags & IF_KFOCUSED)) + curval = strcat(substring(curval, 0, spos), chr2str(0xe00b), substring(curval, spos+1, -1)); //replace the char with a box... ugly, whatever + ui.drawstring(pos, curval, '1 1 0' * item_scale, item_rgb, item_alpha, 0); +}; +float(vector pos, float scan, float char, float down) mitem_edit::item_keypress = +{ + if (!down) + return FALSE; + + local string curval = get(item_command); + spos = min(spos, strlen(curval)); + + if (scan == K_ESCAPE) + return FALSE; + else if (scan == K_LEFTARROW) + spos = max(spos-1, 0); + else if (scan == K_RIGHTARROW) + spos+=1; +/* else if (scan == K_MOUSE1) + { + //FIXME: figure out the spos for the cursor + return TRUE; + }*/ + else if (scan == K_BACKSPACE || scan == K_DEL) + { + if (spos) + { + curval = strcat(substring(curval, 0, spos-1), substring(curval, spos, -1)); + spos -= 1; + } + } + else if (char >= ' ') + { + curval = strcat(substring(curval, 0, spos), chr2str(char), substring(curval, spos, -1)); + spos += strlen(chr2str(char)); + } + else + return FALSE; + + set(item_command, curval); + return TRUE; +}; +mitem_edit(string text, string command, vector sz) menuitemeditt_spawn = +{ + mitem_edit n = spawn(mitem_edit); + n.item_scale = sz_y; + n.item_text = strzone(text); + n.item_size = sz; + n.spos = 100000; //will be clipped so meh + + n.item_command = strzone(command); + n.item_flags |= IF_SELECTABLE; + return n; +}; + diff --git a/quakec/menusys/menusys/mitem_exmenu.qc b/quakec/menusys/menusys/mitem_exmenu.qc new file mode 100644 index 000000000..58bac5563 --- /dev/null +++ b/quakec/menusys/menusys/mitem_exmenu.qc @@ -0,0 +1,58 @@ +/*************************************************************************** +fullscreen exclusive menu +you should only have ONE of these visible at once. +interactable - basically just a container for the items in the menu, but also handles killing them+itself when the user presses escape. +will keep stealing focus from the desktop, so you won't be able to play while one of these is active. +will not steal focus from siblings. this means console slideouts or whatever are still usable. + +these items will automatically be added to the desktop/workspace thing +Call it.item_remove() to pop the menu. +Regular items can be added to the menu by first spawning them, then calling menu_additem to actually add it to the desired menu in the right place. +*/ + +class mitem_exmenu : mitem_frame +{ + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + local float ret = super::item_keypress(pos, scan, char, down); + if (!ret && down) + { + ret = TRUE; + if (scan == K_MOUSE2 || scan == K_ESCAPE) + { + localcmd(strcat(item_command, "\n")); //console command to exec if someone clicks the close button. + item_remove(); + } + else if (scan == K_UPARROW && down) + this.item_focuschange(menu_nextitem(this, item_kactivechild?item_kactivechild:item_mactivechild), IF_KFOCUSED); + else if (scan == K_DOWNARROW && down) + this.item_focuschange(menu_previousitem(this, item_kactivechild?item_kactivechild:item_mactivechild), IF_KFOCUSED); + else if (scan >= K_F1 && scan <= K_F12) //allow f1-f12 to work, but every other button event gets canceled. + ret = FALSE; + } + return ret; + }; + virtual void(vector pos) item_draw = + { + if (ui.mgrabs == item_parent || ui.kgrabs == item_parent) //if our parent has grabs, steal it instead, this means you can select the console, but the game never gets focus + { + if (item_flags & IF_SELECTABLE) + { + ui.mgrabs = __NULL__; + ui.kgrabs = __NULL__; + item_parent.item_focuschange(this, IF_KFOCUSED); + } + } + super::item_draw(pos); + }; + + //same as mitem, but does not propogate to parent. + virtual string(string key) get = + { + return cvar_string(key); + }; + virtual void(string key, string newval) set = + { + cvar_set(key, newval); + }; +}; \ No newline at end of file diff --git a/quakec/menusys/menusys/mitem_frame.qc b/quakec/menusys/menusys/mitem_frame.qc new file mode 100644 index 000000000..51a981e6b --- /dev/null +++ b/quakec/menusys/menusys/mitem_frame.qc @@ -0,0 +1,589 @@ +/*************************************************************************** +Basic frame, containing sub-items. +Logically the parent of menu objects. Also used for tabs. +*/ + +#ifndef MITEM_FRAME_QC__ +#define MITEM_FRAME_QC__ + +class mitem_vslider : mitem +{ + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(vector pos) item_draw; + void() mitem_vslider; + float val; + float minv; + float maxv; + float stride; //size of one 'notch' +}; + +class mitem_frame : mitem +{ + virtual void(mitem newfocus, float changedflag) item_focuschange; + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void() item_resized; + virtual void(vector pos) item_draw; + virtual void() item_remove; + virtual void(mitem fromitem, string cmd) item_execcommand; + + mitem item_children; + mitem item_mactivechild; + mitem item_kactivechild; + mitem item_exclusive; + + mitem_vslider vslider; //displayed if any client item's max[y] > our item_size[y] + vector item_framesize; //x=sides, y=top, z=bottom + float frame_hasscroll; + + static mitem(string title) findchild; + static void(mitem newitem, float originflags, vector originmin, vector originmax) add; + static void(mitem newitem, vector pos) adda; + static void(mitem newitem, vector originmin, vector originmax) addm; + static void(mitem newitem, float originflags, vector originmin, vector originmax) addr; + static void(mitem newitem, float ypos) addc; + + void() mitem_frame = + { + item_flags |= IF_ISFRAME; + }; +}; + +void(mitem fromitem, string cmd) mitem_frame::item_execcommand = +{ + if (item_parent) + item_parent.item_execcommand(fromitem, cmd); + else + localcmd(strcat(cmd, "\n")); +}; + +mitem(string title) mitem_frame::findchild = +{ + mitem ch; + for (ch = item_children; ch; ch = ch.item_next) + { + if (ch.item_text == title) + return ch; + } + return __NULL__; +}; + +//adds an item with the desired origin settings +void(mitem newitem, float originflags, vector originmin, vector originmax) mitem_frame::add = +{ + newitem.item_next = item_children; + item_children = newitem; + newitem.item_parent = this; + + //set up position and resize + newitem.resizeflags = originflags; + newitem.mins = originmin; + newitem.maxs = originmax; + mitem_parentresized(newitem, [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]); + newitem.item_resized(); + + item_flags |= IF_CLIENTMOVED; //make sure it happens. + +// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE)) +// item_focuschange(newitem, IF_KFOCUSED); +}; + +//adds an item to the parent with an absolute position relative to the parent's top-left. objects are expected to already have a size specified. +void(mitem newitem, vector pos) mitem_frame::adda = +{ + local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]; + newitem.item_next = item_children; + item_children = newitem; + newitem.item_parent = this; + + newitem.resizeflags = RS_X_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN; + newitem.mins = pos; + newitem.maxs = newitem.item_size; + + mitem_parentresized(newitem, parentsize); + newitem.item_resized(); + + item_flags |= IF_CLIENTMOVED; //make sure it happens. + +// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE)) +// item_focuschange(newitem, IF_KFOCUSED); +}; + +//adds an item to the parent in reverse order (ie: at the tail, so the actual order in code) +void(mitem newitem, float originflags, vector originmin, vector originmax) mitem_frame::addr = +{ + local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]; + if (item_children) + { + local mitem prev; + for (prev = item_children; prev.item_next; prev = prev.item_next) + ; + prev.item_next = newitem; + newitem.item_next = __NULL__; + } + else + { + newitem.item_next = item_children; + item_children = newitem; + } + newitem.item_parent = this; + + newitem.resizeflags = originflags; + newitem.mins = originmin; + newitem.maxs = originmax; + mitem_parentresized(newitem, parentsize); + newitem.item_resized(); + + item_flags |= IF_CLIENTMOVED; //make sure it happens. + +// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE)) +// item_focuschange(newitem, IF_KFOCUSED); +}; + +//adds an item on the parent with the x coord centered, and absolute y. +//if multiple items are at the same ypos, it will recenter all with respect to the others +void(mitem newitem, float ypos) mitem_frame::addc = +{ + float w, c; + local mitem prev; + local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]; + + newitem.item_next = item_children; + item_children = newitem; + newitem.item_position[1] = ypos; + newitem.item_position[0] = (parentsize[0] - newitem.item_size[0])*0.5; + newitem.item_parent = this; + + + newitem.resizeflags = RS_X_MIN_PARENT_MID | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN; + newitem.mins[0] = 0; + newitem.mins[1] = ypos; + newitem.maxs = newitem.item_size; + + //count the width of the other items at this height. + w = 0; + c = 0; + for (prev = item_children; prev; prev = prev.item_next) + if (prev.resizeflags == newitem.resizeflags && prev.mins[1] == ypos && prev.maxs[1] == newitem.maxs[1]) + { + w += prev.maxs[0]; + c += 1; + } + + //distribute them evenly (from the right, because its add-at-head) + w += (c-1)*16; //this much gap space + for (prev = item_children; prev; prev = prev.item_next) + { + if (prev.resizeflags == newitem.resizeflags && prev.mins[1] == ypos && prev.maxs[1] == newitem.maxs[1]) + { + w -= prev.maxs[0]; + prev.mins[0] = (w+prev.maxs[0])/-2; + mitem_parentresized(prev, parentsize); + w -= 16; + } + } + newitem.item_resized(); + + item_flags |= IF_CLIENTMOVED; //make sure it happens. + +// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE)) +// item_focuschange(newitem, IF_KFOCUSED); +}; + +//Adds the item in the exact middle of the parent, in both axis +void(mitem newitem, vector min, vector max) mitem_frame::addm = +{ + this.add(newitem, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, min, max); +}; + +//updates this.item_activechild, and calls focus notifications to ensure things get the message. +//flag should be IF_MFOCUSED or IF_KFOCUSED +void(mitem newfocus, float flag) mitem_frame::item_focuschange = +{ + local mitem it; + + if (newfocus == this) + { + //our focus didn't change, but the parent is telling us that *it* got changed while we're the focus + if (flag & IF_MFOCUSED) + { + if (item_parent.item_flags & IF_MFOCUSED && item_parent.item_mactivechild == this) + item_flags |= IF_MFOCUSED; + else + item_flags = item_flags - (item_flags&IF_MFOCUSED); + //and tell the child + it = item_mactivechild; + if (it) + if (it.item_focuschange) + it.item_focuschange(it, IF_MFOCUSED); + } + if (flag & IF_KFOCUSED) + { + if (item_parent.item_flags & IF_KFOCUSED && item_parent.item_kactivechild == this) + item_flags |= IF_KFOCUSED; + else + item_flags = item_flags - (item_flags&IF_KFOCUSED); + //and tell the child + it = item_kactivechild; + if (it) + if (it.item_focuschange) + it.item_focuschange(it, IF_KFOCUSED); + } + return; + } + + if ((flag & IF_MFOCUSED) && item_mactivechild != newfocus) + { + //make key focus follow the mouse cursor. this should probably only apply to button menus or something? :s + if (newfocus && (item_flags & IF_FOCUSFOLLOWSMOUSE)) + flag |= IF_KFOCUSED; + + it = item_mactivechild; + item_mactivechild = newfocus; + if (it) + { + it.item_flags = it.item_flags - (it.item_flags&IF_MFOCUSED); + if (it.item_focuschange) + it.item_focuschange(it, IF_MFOCUSED); + } + it = item_mactivechild; + if (it) + { + it.item_flags = it.item_flags | (item_flags & IF_MFOCUSED); + if (it.item_focuschange) + it.item_focuschange(it, IF_MFOCUSED); + } + } + + if ((flag & IF_KFOCUSED) && item_kactivechild != newfocus) + { + it = item_kactivechild; + item_kactivechild = newfocus; + if (it) + { + it.item_flags = it.item_flags - (it.item_flags&IF_KFOCUSED); + if (it.item_focuschange) + it.item_focuschange(it, IF_KFOCUSED); + } + it = item_kactivechild; + if (it) + { + it.item_flags = it.item_flags | (item_flags & IF_KFOCUSED); + if (it.item_focuschange) + it.item_focuschange(it, IF_KFOCUSED); + } + } +}; + + +float(vector pos, float scan, float char, float down) mitem_vslider::item_keypress = +{ + if (down != 1) + { + if (scan == K_MOUSE1 && ui.mgrabs == this) + ui.mgrabs = __NULL__; + return FALSE; + } + if (scan == K_MWHEELDOWN) + val = bound(minv, val+stride, maxv); + else if (scan == K_MWHEELUP) + val = bound(minv, val-stride, maxv); + else if (scan == K_MOUSE1) + ui.mgrabs = this; + else + return FALSE; + return TRUE; +}; +void(vector pos) mitem_vslider::item_draw = +{ + float f; + float w = item_size[0]; + float h = item_size[1]; + float isize = w; + + vector v = drawgetimagesize("scrollbars/slider.tga"); + + if (v != '0 0 0') + { + ui.drawpic(pos, "scrollbars/arrow_up.tga", [w, w], '1 1 1', item_alpha, 0); //top + pos_y += w; + h -= w*2; + ui.drawpic(pos + [0, h], "scrollbars/arrow_down.tga", [w, w], '1 1 1', item_alpha, 0); //bottom + ui.drawpic(pos, "scrollbars/slidebg.tga", [w, h], '1 1 1', item_alpha, 0); //back-middle + + isize = (v_y * w) / (v_x); + if (isize > h/2) + isize = h/2; + } + else + { +// ui.drawfill(pos, [w, w], '1 1 1', item_alpha, 0); //top +// pos_y += w; +// h -= w*2; +// ui.drawfill(pos + [0, h], [w, w], '1 1 1', item_alpha, 0); //bottom + ui.drawfill(pos, [w, h], '0.5 0.5 0.5', item_alpha, 0); //back-middle + } + + if (ui.mgrabs == this) + { + f = (ui.mousepos[1] - pos_y - (isize/2))/(h - isize); + f = bound(0, f, 1); + val = minv + (f * (maxv - minv)); + } + else + f = bound(0, (val - minv) / (maxv - minv), 1); + + h -= isize; + if (v != '0 0 0') + ui.drawpic(pos + [0, h*f], "scrollbars/slider.tga", [w, isize], '1 1 1', item_alpha, 0); //back-middle + else + ui.drawfill(pos + [0, h*f], [w, isize], '1 1 1', item_alpha, 0); //back-middle +}; +void() mitem_vslider::mitem_vslider = +{ + item_size[0] = 16; + item_size[1] = 128; +}; + + +mitem(mitem_frame menu, mitem item) menu_previousitem = +{ + if (!item) + item = menu.item_children; + local mitem stop = item; + local mitem prev; + if (!stop) + return stop; + while(1) + { + for(prev = menu.item_children; prev.item_next; prev = prev.item_next) + { + if (prev.item_next == item) + break; + } + if (prev == stop || (prev.item_flags & IF_SELECTABLE)) + return prev; + item = prev; + } +}; +mitem(mitem_frame menu, mitem item) menu_nextitem = +{ + if (!item) + item = menu.item_children; + local mitem stop = item; + if (!stop) + return stop; + while(1) + { + item = item.item_next; + if (!item) + item = menu.item_children; + + if (item == stop || (item.item_flags & IF_SELECTABLE)) + return item; + } +}; + +float(vector pos, float scan, float char, float down) mitem_frame::item_keypress = +{ + mitem ch; + float handled = FALSE; + + //compensate for the frame + pos[0] += item_framesize[0]; + pos[1] += item_framesize[1]; + + if (scan >= K_MOUSE1 && scan <= K_MOUSE5 && scan != K_MWHEELUP && scan != K_MWHEELDOWN) + { + if (item_exclusive) + ch = item_exclusive; + else + ch = item_mactivechild; + if (down) //keyboard focus follows on mouse click. + { + item_focuschange(ch, IF_KFOCUSED); + } + } + else + { + ch = item_kactivechild; + if (!ch && down) + { + //if there's no key child active, then go and pick one so you can just start using keyboard access etc. + if (item_exclusive) + ch = item_exclusive; + else if (item_mactivechild) + ch = item_mactivechild; + else + { + mitem c; + for (c = item_children; c; c = c.item_next) + { + if (c.item_flags & IF_SELECTABLE) + ch = c; + } + } + if (ch) + item_focuschange(ch, IF_KFOCUSED); + ch = item_kactivechild; + } + } + + if (vslider) + pos[1] -= vslider.val; + + if (ch) + { + if (ch.item_keypress) + handled = ch.item_keypress(pos + ch.item_position, scan, char, down); + } + if (vslider && !handled && (scan == K_MWHEELUP || scan == K_MWHEELDOWN)) //give mwheel to the slider if its visible. + handled = vslider.item_keypress(pos + vslider.item_position, scan, char, down); + + return handled; +}; +void() mitem_frame::item_remove = +{ + local mitem ch; + while (this.item_children) + { + ch = this.item_children; + this.item_children = ch.item_next; + ch.item_remove(); + } + super::item_remove(); +}; +void() mitem_frame::item_resized = +{ + mitem ch; + vector framemax = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])]; + for (ch = this.item_children; ch; ch = ch.item_next) + { + vector os = ch.item_size; + mitem_parentresized(ch, framemax); + if (ch.item_resized && ch.item_size != os) + ch.item_resized(); + } + item_flags |= IF_CLIENTMOVED; //make sure it happens. + + super::item_resized(); +}; +void(vector pos) mitem_frame::item_draw = +{ + local vector omin = ui.drawrectmin, omax = ui.drawrectmax; + local mitem ch; + local vector clientpos; + local vector clientsize; + + if (frame_hasscroll && (item_flags & IF_CLIENTMOVED)) + { + //if a client object moved, make sure the scrollbar is updated + item_flags &~= IF_CLIENTMOVED; + clientsize= '0 0'; + for(ch = item_children; ch; ch = ch.item_next) + { + if (clientsize[0] < ch.item_position[0] + ch.item_size[0]) + clientsize[0] = ch.item_position[0] + ch.item_size[0]; + if (clientsize[1] < ch.item_position[1] + ch.item_size[1]) + clientsize[1] = ch.item_position[1] + ch.item_size[1]; + } +// if (clientsize[0] > item_size[0] - item_framesize[0]*2) +// //add hscroll + if (clientsize[1] > item_size[1] - (item_framesize[1]+item_framesize[2])) + { + if (!vslider) + { + local mitem_vslider tmp; + tmp = spawn(mitem_vslider, val:0, stride:8); + vslider = tmp; + } + vslider.maxv = clientsize[1] - (item_size[1] - (item_framesize[1]+item_framesize[2])); + } + else if (vslider) + { + vslider.item_remove(); + vslider = (mitem_vslider)__NULL__; + } + } + else if (!frame_hasscroll && this.vslider) + { + vslider.item_remove(); + vslider = (mitem_vslider)__NULL__; + } + + + //compensate for the frame + pos[0] += item_framesize[0]; + pos[1] += item_framesize[1]; + + clientpos = pos; + clientsize = this.item_size; + clientsize[0] -= item_framesize[0]*2; + clientsize[1] -= (item_framesize[1]+item_framesize[2]); + + if (vslider) + { + //scroll+shrink the client area to fit the slider on it. + clientpos[1] -= vslider.val; + clientsize[0] -= vslider.item_size[0]; + } + + + if (ui.mousepos != ui.oldmousepos) + { + local mitem newfocus = __NULL__; + //if the mouse moved, select the new item + if (item_exclusive) + newfocus = item_exclusive; + else + { + for(ch = item_children; ch; ch = ch.item_next) + { + if (ch.item_flags & IF_SELECTABLE) + if (mouseinbox(clientpos + ch.item_position, ch.item_size)) + { + newfocus = ch; + } + } + } + if (vslider) + if (mouseinbox(pos + [clientsize[0], 0], vslider.item_size)) + newfocus = vslider; + this.item_focuschange(newfocus, IF_MFOCUSED); + } + + //clip the draw rect to our area, so children don't draw outside it. don't draw if its inverted. + if (pos_x > ui.drawrectmin[0]) + ui.drawrectmin[0] = pos_x; + if (pos_y > ui.drawrectmin[1]) + ui.drawrectmin[1] = pos_y; + if (pos_x+clientsize_x < ui.drawrectmax[0]) + ui.drawrectmax[0] = pos_x+clientsize_x; + if (pos_y+clientsize_y < ui.drawrectmax[1]) + ui.drawrectmax[1] = pos_y+clientsize[1]; + if (ui.drawrectmax[0] > ui.drawrectmin[0] && ui.drawrectmax[1] > ui.drawrectmin[1]) + { + ui.setcliparea(ui.drawrectmin[0], ui.drawrectmin[1], ui.drawrectmax[0] - ui.drawrectmin[0], ui.drawrectmax[1] - ui.drawrectmin[1]); + if (item_exclusive) + item_exclusive.item_draw(clientpos + ch.item_position); + else + { + for(ch = item_children; ch; ch = ch.item_next) + { + if not (ch.item_flags & IF_INVISIBLE) + ch.item_draw(clientpos + ch.item_position); + } + } + ui.setcliparea(omin_x, omin_y, omax_x - omin_x, omax_y - omin_y); + + if (vslider) + { + vslider.item_size[1] = clientsize[1]; + vslider.item_draw(pos + [clientsize[0], 0]); + } + } + ui.drawrectmin = omin; + ui.drawrectmax = omax; +}; +#define menuitemframe_spawn(sz) spawn(mitem_frame, item_size:sz) + +#endif //MITEM_FRAME_QC__ diff --git a/quakec/menusys/menusys/mitem_menu.qc b/quakec/menusys/menusys/mitem_menu.qc new file mode 100644 index 000000000..ad133b299 --- /dev/null +++ b/quakec/menusys/menusys/mitem_menu.qc @@ -0,0 +1,332 @@ +/*************************************************************************** +window-like menu. +interactable - basically just a container for the items in the menu, but also handles killing them+itself when the user presses escape. + +these items will automatically be added to the desktop/workspace thing +Call it.item_remove() to pop the menu. +Regular items can be added to the menu by first spawning them, then calling menu_additem to actually add it to the desired menu in the right place. +*/ + +class mitem_menu : mitem_frame +{ + vector draginfo; //x, y, resizeflags={0:none, 1:left, 2:bottom, 4:right, 8:top, 16:move} + + virtual float(vector pos, float scan, float char, float down) item_keypress; + virtual void(mitem newfocus, float flag) item_focuschange; + virtual void(vector pos) item_draw; + + virtual void() item_remove = + { + strunzone(item_text); + super::item_remove(); + }; + + nonvirtual float(vector pos) whichgrabhandle; + nonvirtual void(vector pos, float handle) drawgrabhandle; + void() mitem_menu = + { + item_text = strzone(item_text); + item_scale = 8; + item_rgb = MENUBACK_RGB; + item_alpha = MENUBACK_ALPHA; + + if (item_framesize == '0 0 0') + item_framesize = '4 16 4'; + + item_size[0] += item_framesize[0]; + item_size[1] += item_framesize[1]+item_framesize[2]; + + item_flags |= IF_SELECTABLE; + +#ifdef CSQC + cprint(""); +#endif + }; + + //same as mitem, but does not propogate to parent. + virtual string(string key) get = + { + return cvar_string(key); + }; + virtual void(string key, string newval) set = + { + cvar_set(key, newval); + }; +}; + +void(mitem newfocus, float flag) mitem_menu::item_focuschange = +{ + if (flag & IF_KFOCUSED) + { + //move the window to the top of the z-order when it gets focus. + if (item_flags & IF_KFOCUSED) + totop(); + //release grabs if someone else took focus. + else if (ui.mgrabs == this) + { + ui.mgrabs = __NULL__; + draginfo[2] = 0; + } + } + + super::item_focuschange(newfocus, flag); +}; + +void(vector pos, float handle) mitem_menu::drawgrabhandle = +{ + if ((handle & 3) == 3) + { + //bottom left + drawfill(pos + [0, item_size[1]-item_framesize[2]], [item_framesize[0], item_framesize[2]], '1 1 0', 1, 0); + handle &~= 3; + } + if ((handle & 6) == 6) + { + //bottom right + drawfill(pos + [item_size[0]-item_framesize[0], item_size[1]-item_framesize[2]], [item_framesize[0], item_framesize[2]], '1 1 0', 1, 0); + handle &~= 6; + } + if (handle & 1) + { + //left + drawfill(pos + [0, 0], [item_framesize[0], item_size[1]], '1 1 0', 1, 0); + } + if (handle & 2) + { + //bottom + drawfill(pos + [0, item_size[1]-item_framesize[2]], [item_size[0], item_framesize[2]], '1 1 0', 1, 0); + } + if (handle & 4) + { + //right + drawfill(pos + [item_size[0]-item_framesize[0], 0], [item_framesize[0], item_size[1]], '1 1 0', 1, 0); + } +}; + +float(vector pos) mitem_menu::whichgrabhandle = +{ + //handle dragging and resizing. + if (mouseinbox(pos, [item_size[0]-item_framesize[1], item_framesize[1]])) + { + //drag + return 16; + } + else if (this.item_flags & IF_RESIZABLE) + { + if (mouseinbox(pos + [0, item_size[1]-item_framesize[2]], [item_framesize[0], item_framesize[2]])) + { + //bottom-left + return 3; + } + else if (mouseinbox(pos + [item_size[0]-item_framesize[0], item_size[1]-item_framesize[2]], [item_framesize[0], item_framesize[2]])) + { + //bottom-right + return 6; + } + else if (mouseinbox(pos + [0, 0], [item_framesize[0], item_size[1]])) + { + //left + return 1; + } + else if (mouseinbox(pos + [0, item_size[1]-item_framesize[2]], [item_size[0], item_framesize[2]])) + { + //bottom + return 2; + } + else if (mouseinbox(pos + [item_size[0]-item_framesize[0], 0], [item_framesize[0], item_size[1]])) + { + //right + return 4; + } + } + return 0; +}; + +float(vector pos, float scan, float char, float down) mitem_menu::item_keypress = +{ + if (scan == K_MOUSE1 && (down&1)) + { + if (!(item_flags & IF_NOKILL) && mouseinbox(pos + [item_size[0]-item_framesize[1], 0], item_framesize[1] * '1 1')) + { + localcmd(strcat(item_command, "\n")); //console command to exec if someone clicks the close button. + this.item_remove(); + return TRUE; + } + draginfo[2] = this::whichgrabhandle(pos); + if (draginfo[2]) + ui.mgrabs = this; + if (draginfo[2] & (1|16)) + draginfo[0] = ui.mousepos[0] - pos_x; + if (draginfo[2] & 2) + draginfo[1] = (ui.mousepos[1] - pos_y - item_size[1]) + item_position[1]; + if (draginfo[2] & 4) + draginfo[0] = ui.mousepos[0] - pos_x - item_size[0] + item_position[0]; + if (draginfo[2] & (8|16)) + draginfo[1] = ui.mousepos[1] - pos_y; + if (draginfo[2]) + return TRUE; + } + else if (scan == K_MOUSE1 && draginfo[2]) + { + if (!draginfo[2] && (item_flags & IF_RESIZABLE)) + { + //if they dropped the window at the top of the screen, make sure its fullscreened to match how it was drawn... + queryscreensize(); + if (ui.mousepos[1] < 8) + item_size = ui.screensize; + if (this.item_resized) + item_resized(); + } + ui.mgrabs = __NULL__; + draginfo[2] = 0; + return TRUE; + } + + if (!super::item_keypress(pos, scan, char, down)) + { + down &= 1; + if (scan == K_ESCAPE && down) + { + if (this.item_flags & IF_NOKILL) + return FALSE; + else + { + localcmd(strcat(this.item_command, "\n")); //console command to exec if someone clicks the close button. + this.item_remove(); + return TRUE; + } + } + else if (scan == K_UPARROW && down) + this.item_focuschange(menu_nextitem(this, item_kactivechild?item_kactivechild:item_mactivechild), IF_KFOCUSED); + else if (scan == K_DOWNARROW && down) + this.item_focuschange(menu_previousitem(this, item_kactivechild?item_kactivechild:item_mactivechild), IF_KFOCUSED); + else if (scan == K_MOUSE2 && down && !(this.item_flags & IF_NOKILL)) + { //unhandled right click closes menus, if we're allowed + localcmd(strcat(item_command, "\n")); //console command to exec if someone clicks the close button. + this.item_remove(); + return TRUE; + } + else if (scan >= K_MOUSE1 && scan <= K_MWHEELDOWN) + return TRUE; //don't let the parent take unhandled mouse events. + else + return FALSE; + } + return TRUE; +}; +void(vector pos) mitem_menu::item_draw = +{ + float inset; + vector efsize = item_size; + if (draginfo[2]) + { + if (draginfo[2] & 1) + { //resize left + local float nmax = item_position[0] + item_size[0]; + local float nmin = item_position[0] + item_size[0]; + nmin = ui.mousepos[0] - (pos_x - item_position[0]) - draginfo[0]; + if (nmax-nmin < 128) + nmin = nmax - 128; + pos_x -= item_position[0]; + item_position[0] = nmin; + pos_x += item_position[0]; + item_size[0] = nmax - nmin; + if (item_resized) + item_resized(); + } + if (draginfo[2] & 2) + { //resize bottom + item_size[1] = ui.mousepos[1] - (pos_y - item_position[1]) - draginfo[1]; + if (item_size[1] < 16) + item_size[1] = 16; + if (item_resized) + item_resized(); + } + if (draginfo[2] & 4) + { //resize right + item_size[0] = ui.mousepos[0] - (pos_x - item_position[0]) - draginfo[0]; + if (item_size[0] < 128) + item_size[0] = 128; + if (item_resized) + item_resized(); + } + efsize = item_size; + if (draginfo[2]&16) + { + queryscreensize(); + if (ui.mousepos[1] < 8 && (item_flags & IF_RESIZABLE)) + { + //make it look as though its fullscreen, without destroying its actual size. + //yes, we will fake-resize everything inside. + item_position = '0 0'; + + item_size = ui.screensize; + if (this.item_resized) + this.item_resized(); + item_size = efsize; + efsize = ui.screensize; + } + else if (item_size == ui.screensize && (item_flags & IF_RESIZABLE)) + { + item_size = item_size*0.5; + draginfo[0] = draginfo[0]*0.5; + if (this.item_resized) + this.item_resized(); + efsize = item_size; + } + else + { + item_position = ui.mousepos - (pos - item_position) - draginfo; + item_position[2] = 0; + if (item_flags & IF_RESIZABLE) + if (item_resized) + item_resized(); + } + } + } + + //bound it to the screen... just in case the screen got resized or something + if (item_position[0] > ui.screensize[0] - item_size[0]*0.25) + item_position[0] = ui.screensize[0] - item_size[0]*0.25; + if (item_position[1] > ui.screensize[1] - item_framesize[1]) + item_position[1] = ui.screensize[1] - item_framesize[1]; + if (item_position[0] < item_size[0]*-0.75) + item_position[0] = item_size[0]*-0.75; + if (item_position[1] < 0) + item_position[1] = 0; + + local float drag = 0; + if (draginfo[2]) + drag = draginfo[2]; + else if (item_flags & IF_MFOCUSED) + drag = this::whichgrabhandle(pos); + if (drag) + this::drawgrabhandle(pos, drag); + + ui.drawfill(pos, efsize, item_rgb, item_alpha, 0); + local vector col = '1 1 1'; + if (item_flags & IF_KFOCUSED) + col_x = 0; + if ((item_flags & IF_MFOCUSED) && mouseinbox(pos, [efsize_x-item_framesize[1], item_framesize[1]])) + col_z = 0; + inset = (item_framesize[1]-item_scale)*0.5; + ui.drawstring(pos + '1 1'*inset, item_text, item_scale*'1 1', col, 1, 0); + if (!(item_flags & IF_NOKILL)) + { + if (mouseinbox(pos + [efsize_x-item_framesize[1], 0], item_framesize[1]*'1 1')) + col = '1 1 0'; + else + col = '1 1 1'; + ui.drawstring(pos + [efsize_x+inset-item_framesize[1], inset], "X", item_scale*'1 1', col, 1, 0); + } + super::item_draw(pos); +}; +mitem_menu(mitem_frame desktop, string mname, vector sz) menu_spawn = +{ + mitem_menu n = spawn(mitem_menu, item_text:mname, item_framesize:'4 16 4', item_size:sz); + +// n.item_flags |= IF_RESIZABLE; + + if (desktop) + desktop.addr(n, RS_X_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN, (desktop.item_size - n.item_size) * 0.5, n.item_size); + return n; +}; diff --git a/quakec/menusys/menusys/mitem_slider.qc b/quakec/menusys/menusys/mitem_slider.qc new file mode 100644 index 000000000..ca9c68e79 --- /dev/null +++ b/quakec/menusys/menusys/mitem_slider.qc @@ -0,0 +1,137 @@ +/*************************************************************************** +slider item, directly attached to a cvar. +interactable - executes a given console command. +*/ +class mitem_hslider : mitem +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; + + vector item_slidercontrols; //min, max, step +}; +void(vector pos) mitem_hslider::item_draw = +{ + local float curval; + + super::item_draw(pos); + pos_x += item_size_x / 2; + + if (ui.mgrabs == this) + { + //if we're sliding it, update the value + curval = (ui.mousepos[0] - pos_x-8) / (10*8); + curval = bound(0, curval, 1); + curval = curval * (item_slidercontrols_y - item_slidercontrols_x) + item_slidercontrols_x; + set(item_command, ftos(curval)); + } + curval = stof(get(item_command)); + + if (dp_workarounds) + { //no ^U markup support. chr2str avoids warnings about non-utf8 strings which at least allows bi-compat to work. + string s = strcat(chr2str(0xe080, 0xe081, 0xe081, 0xe081, 0xe081, 0xe081), chr2str(0xe081, 0xe081, 0xe081, 0xe081, 0xe081, 0xe082)); + //slider background uses the fallback quake chars + ui.drawstring(pos, sprintf("%s (%g)", s, curval), '1 1 0' * self.item_scale, self.item_rgb, self.item_alpha, 0); + //now draw an indicater char in the right place. + //the inner side of the boundary is 4 pixels wide so we can overlap the ends by that many pixels. + curval = (curval - self.item_slidercontrols_x) / (self.item_slidercontrols_y - self.item_slidercontrols_x); //fractionize it + curval = bound(0, curval, 1); + ui.drawstring(pos + [4 + curval*10*8, 0], chr2str(0xe083), '1 1 0' * self.item_scale, self.item_rgb, self.item_alpha, 0); + } + else + { + //slider background uses the fallback quake chars + ui.drawstring(pos, sprintf("^{e080}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e081}^{e082} (%g)", curval), '1 1 0' * self.item_scale, self.item_rgb, self.item_alpha, 0); + //now draw an indicater char in the right place. + //the inner side of the boundary is 4 pixels wide so we can overlap the ends by that many pixels. + curval = (curval - self.item_slidercontrols_x) / (self.item_slidercontrols_y - self.item_slidercontrols_x); //fractionize it + curval = bound(0, curval, 1); + ui.drawstring(pos + [4 + curval*10*8, 0], "^{e083}", '1 1 0' * self.item_scale, self.item_rgb, self.item_alpha, 0); + } +}; +float(vector pos, float scan, float char, float down) mitem_hslider::item_keypress = +{ + if (down&2) + { + //we have grabs, and mouse was released? + if (scan == K_MOUSE1 && !(down&1)) + { //we're done here. + ui.mgrabs = __NULL__; + return TRUE; + } + return FALSE; //not handled, don't inhibit + } + if (down) + { + local float curval = stof(get(item_command)); + if (scan == K_MWHEELUP || scan == K_MWHEELDOWN) + { + if (ui.mousepos[0] > pos_x + item_size[0]/2) + scan = ((scan == K_MWHEELDOWN)?K_LEFTARROW:K_RIGHTARROW); + } + if (scan == K_MOUSE1 && down) + { + pos_x += item_size_x / 2; + if (ui.mousepos[0] < pos_x) + return TRUE;//goto keyenter; + curval = (ui.mousepos[0] - pos_x-8) / (10*8); + if (curval < 0 || curval > 1) + return FALSE; + curval = curval * (item_slidercontrols_y - item_slidercontrols_x) + item_slidercontrols_x; + set(item_command, ftos(curval)); + ui.mgrabs = this; + } + else if (scan == K_DEL && down && cvar_type(item_command)) + set(item_command, cvar_defstring(item_command)); + else if ((scan == K_LEFTARROW || scan == K_MWHEELUP) && down) + { + if (item_slidercontrols_x > item_slidercontrols_y) + set(item_command, ftos(min(curval - item_slidercontrols_z, item_slidercontrols_x))); + else + set(item_command, ftos(max(curval - item_slidercontrols_z, item_slidercontrols_x))); + } + else if ((scan == K_RIGHTARROW || scan == K_MWHEELDOWN) && down) + { + if (item_slidercontrols_x > item_slidercontrols_y) + set(item_command, ftos(max(curval + item_slidercontrols_z, item_slidercontrols_y))); + else + set(item_command, ftos(min(curval + item_slidercontrols_z, item_slidercontrols_y))); + } + else if ((scan == K_ENTER || scan == K_SPACE) && down) + { +//keyenter: + if (item_slidercontrols_x > item_slidercontrols_y) + { + if (curval-0.001 <= item_slidercontrols_y) + set(item_command, ftos(item_slidercontrols_x)); + else + set(item_command, ftos(max(curval + item_slidercontrols_z, item_slidercontrols_y))); + } + else + { + if (curval+0.001 >= item_slidercontrols_y) + set(item_command, ftos(item_slidercontrols_x)); + else + set(item_command, ftos(min(curval + item_slidercontrols_z, item_slidercontrols_y))); + } + } + else + return FALSE; + return TRUE; + } + else if (scan == K_MOUSE1 && ui.mgrabs == this) + ui.mgrabs = __NULL__; + return FALSE; +}; +mitem_hslider(string text, string command, vector controls, vector sz) menuitemslider_spawn = +{ + mitem_hslider n = spawn(mitem_hslider); + n.item_scale = sz_y; + n.item_text = text; + n.item_size = sz; + + n.item_slidercontrols = controls; + + n.item_command = command; + n.item_flags |= IF_SELECTABLE; + return n; +}; diff --git a/quakec/menusys/menusys/mitem_spinnymodel.qc b/quakec/menusys/menusys/mitem_spinnymodel.qc new file mode 100644 index 000000000..bcab5bae2 --- /dev/null +++ b/quakec/menusys/menusys/mitem_spinnymodel.qc @@ -0,0 +1,139 @@ +/* +renderscene stuff is always available in csqc. +If the engine supports DP_QC_RENDER_SCENE then its meant to be available in menuqc too. +In practise, DP advertises this to menuqc even though it has never officially supported it. +At the time of writing, FTE's dev builds can do it (and only advertise the extension in builds that try to support it). + +Note: the basemenu mod does not make use of this in menuqc if only because its vaugely trying to support both engines, and its easier to just use an ifdef. + +The mitem_spinnymodel item just shows a rotating model centered on the z axis. Simple as that. +For the sake of fun gimmicks, you can specify frame information with firstframe and framecount. +Additionally, you can cause it to switch to a different animation when it faces towards the camera with shootframe and shootframes. +This style of animation shouldn't really be considered very complex, but might help give a small demo of the basic concept of animation. +*/ +/* +Note - DP Bugs: +1: viewport positions are interpreted in physical pixels in DP. + there is no reliable way to know how many physical pixels there actually are. + custom viewports are thus near unusable. +2: failure to revert the viewport to default afterwards fucks over any 2d stuff drawn afterwards. + including the console. +3: bloom on worldless models draws the world. +4: lighting is applied on models even if there's no world. + enabling rtlights really fucks everything up. +5: gamma/contrast/brightness are applied to the 3d view. even if there's no world. + this results in horrible squares if these settings are used. +6: DP_QC_RENDER_SCENE is advertised in menuqc, but renderscene is not supported there at all. +7: avoid the use of cltime. DP doesn't support it. + +probably others. it really wouldn't surprise me. +*/ + +//helper function to work around a DP bug. +static vector(vector v) vtodpp = +{ + //so fucking disgustingly ugly. + if (dp_workarounds) + { + v_x *= cvar("vid_width") / cvar("vid_conwidth"); + v_y *= cvar("vid_height") / cvar("vid_conheight"); + } + return v; +}; +//make sure the fields are all defined if we're in menuqc or something +.vector origin; +.vector angles; +.vector mins; +.vector maxs; +.string model; +.float frame, frame2, lerpfrac, renderflags; +float frametime; +class mitem_spinnymodel : mitem +{ + float zbias; + float firstframe; + float framecount; + float shootframe; + float shootframes; + + //might as well use the class as the entity that is drawn. + //it might end up clobbering any fields used for multiple things... + virtual void(vector pos) item_draw = + { + vector orgbias; + orgbias = '0 0 0'; + if (dp_workarounds) + orgbias = (vector)getviewprop(VF_ORIGIN); //DP still lights the entity even if the world isn't drawn. this results in inconsistant/buggy light levels. there's nothing we can do about that other than stopping it from being completely black. + origin = orgbias; + origin_z += zbias; + +// angles_y = cltime*90;//frametime*90; + angles_y += frametime*90; + + clearscene(); //wipe the scene, and apply the default rendering values. + setviewprop(VF_MIN, vtodpp(pos)); //min pos + setviewprop(VF_SIZE, vtodpp(item_size)); //size=maxpos-minpos + if (dp_workarounds) + setviewprop(VF_FOV, [25, atan(item_size_y/(item_size_x/tan(25/360*6.28))) * 360/6.28]); //set an explicit fov. this thing had better be square. DP doesn't support VF_AFOV + else + setviewprop(VF_AFOV, 25); //set the aproximate fov (ie: engine takes care of aspect ratio). +#ifdef CSQC + setproperty(VF_DRAWENGINESBAR, FALSE); + setproperty(VF_DRAWWORLD, FALSE); + setproperty(VF_DRAWCROSSHAIR, FALSE); +#endif + setviewprop(VF_ORIGIN, orgbias + '-128 0 0'); //look towards it. + setviewprop(VF_ANGLES, '0 0 0'); + + //animate it a bit + lerpfrac -= frametime*10; + while(lerpfrac < 0) + { + lerpfrac += 1; + frame2 = frame; + frame += 1; + if (angles_y >= 170 && shootframes) + { + if (frame == shootframe+shootframes) + { //reached the last frame. clear the shooting 'flag' and go back to idle + frame = firstframe; + angles_y -= 360; + } + else if (frame < shootframe || frame >= shootframe+shootframes) + frame = shootframe; //we were still idle, apparently. + } + else + { + if (frame < firstframe || frame >= firstframe+framecount) + frame = firstframe; + } + } + + addentity(this); + renderscene(); + if (dp_workarounds) + { //dp fucks up 2d stuff if we don't explicitly restore the 3d view to fullscreen + setviewprop(VF_MIN, vtodpp('0 0 0')); + setviewprop(VF_SIZE, vtodpp(ui.screensize)); + } + }; + + virtual void(vector pos) dp_draw = + { + }; + + void() mitem_spinnymodel = + { +#ifdef MENU + if (!checkextension("DP_QC_RENDER_SCENE") || dp_workarounds) + { + item_draw = dp_draw; + return; + } +#endif + precache_model(item_text); + setmodel(this, item_text); //use the size information from the engine, woo for unreliability. + zbias += (mins_z - maxs_z)/2 - mins_z; //center the model on its z axis, so the whole thing is visible. + frame = firstframe; + }; +}; diff --git a/quakec/menusys/menusys/mitem_tabs.qc b/quakec/menusys/menusys/mitem_tabs.qc new file mode 100644 index 000000000..84a917390 --- /dev/null +++ b/quakec/menusys/menusys/mitem_tabs.qc @@ -0,0 +1,168 @@ +/*************************************************************************** +tabs/tab widgits. +the 'tabs' widgit is simply a tab-selection control. horizontal multiple choice. it draws only its currently active child. +the 'tab' widgit is merely a container of other widgits, no different from a standard frame object, just has a name and a specific size. +*/ +class mitem_tabs : mitem_frame /*frame... but not really*/ +{ + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress; +// virtual void() item_resize; + + void() mitem_tabs = + { + item_framesize = '2 16 2'; + item_flags |= IF_SELECTABLE|IF_RESIZABLE; + }; +}; + +class mitem_tab : mitem_frame +{ + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (scan == K_UPARROW && down) + this.item_focuschange(menu_nextitem(this, item_kactivechild?item_kactivechild:item_mactivechild), IF_KFOCUSED); + else if (scan == K_DOWNARROW && down) + this.item_focuschange(menu_previousitem(this, item_kactivechild?item_kactivechild:item_mactivechild), IF_KFOCUSED); + else if (super::item_keypress(pos, scan, char, down)) + return TRUE; + else + return FALSE; + return TRUE; + }; + + void() mitem_tab = + { + item_framesize = '0 0 0'; + item_flags |= IF_SELECTABLE|IF_RESIZABLE; + }; +}; + +void(vector pos) mitem_tabs::item_draw = +{ + local mitem ch; + local vector tpos = pos; + local float w; + local vector col; + + //to highlight the active tab, we draw the top line 1 pixel higher, and no bottom line + for (ch = item_children; ch; ch = ch.item_next) + { + w = stringwidth(ch.item_text, TRUE, '8 8') + 8; + + ui.drawfill(tpos + '0 1', [1, 15], TD_LFT, item_alpha, 0); + ui.drawfill(tpos + [w-1, 1], [1, 14], TD_RGT, item_alpha, 0); + if (ch == item_kactivechild) + { + //top line + ui.drawfill(tpos, [w, 1], TD_TOP, item_alpha, 0); + } + else + { + //top line + ui.drawfill(tpos + '0 1', [w, 1], TD_TOP, item_alpha, 0); + //bottom + ui.drawfill(tpos + '0 15', [w, 1], TD_TOP, item_alpha, 0); + } + + col = item_rgb; + if (!(ch.item_flags & IF_SELECTABLE)) + col *= 0.2; + else + { + if (!item_kactivechild) + item_focuschange(ch, IF_KFOCUSED); + if (mouseinbox(tpos, [w, 16])) + col_z = 0; + if (ch.item_flags & IF_KFOCUSED) + col_x = 0; + } + + ui.drawstring(tpos + '4 4', ch.item_text, '8 8', col, item_alpha, 0); + tpos_x += w; + } + ui.drawfill(tpos + '0 15', [pos_x + item_size_x - tpos_x, 1], TD_TOP, item_alpha, 0); //top + ui.drawfill(pos + '0 16', [1, item_size_y - 16], TD_LFT, item_alpha, 0); //left + ui.drawfill(pos + [item_size_x-1, 16], [1, item_size_y - 17], TD_RGT, item_alpha, 0); //right + ui.drawfill(pos + [1, item_size_y-1], [item_size_x-1, 1], TD_BOT, item_alpha, 0); //bottom + + if (item_mactivechild != item_kactivechild) + item_focuschange(item_kactivechild, IF_MFOCUSED); //give the tab full focus. + ch = item_kactivechild; + if (ch) + ch.item_draw(pos + ch.item_position + [item_framesize[0], item_framesize[1]]); +}; +float(vector pos, float scan, float char, float down) mitem_tabs::item_keypress = +{ + local mitem ch; + local vector tpos = pos; + local vector sz; + local float result; + + if (down && (scan == K_MOUSE1 || scan == K_MOUSE2 || scan == K_MOUSE3)) + { + sz_y = 16; + //to highlight the active tab, we draw the top line 1 pixel higher, and no bottom line + for (ch = this.item_children; ch; ch = ch.item_next) + { + sz_x = stringwidth(ch.item_text, TRUE, '8 8') + 8; + if (mouseinbox(tpos, sz)) + { + item_focuschange(ch, IF_KFOCUSED); //give the tab full focus. + return TRUE; + } + + tpos_x += sz_x; + } + } + ch = item_kactivechild; + if (ch) + { + result = ch.item_keypress(pos + [item_framesize[0], item_framesize[1]] + ch.item_position, scan, char, down); + if (!result && down) + { + if (scan == K_TAB || scan == K_RIGHTARROW) + { + ch = ch.item_next; + if (!ch) + ch = item_children; + item_focuschange(ch, IF_KFOCUSED); + result = TRUE; + } +// else if (scan == K_LEFTARROW) +// { +// ch = ch.item_next; +// if (!ch) +// ch = item_children; +// item_focuschange((ch.item_next?ch.item_next:this.item_children), IF_KFOCUSED); +// result = TRUE; +// } + } + } + else + result = FALSE; + return result; +}; +/*void() mitem_tabs::item_resize = +{ + local mitem ch; + for (ch = this.item_children; ch; ch = ch.item_next) + { + ch.item_size = this.item_size; + if (ch.item_resized) + ch.item_resized(); + } +};*/ + +mitem_tabs(vector sz) menuitemtabs_spawn = +{ + return spawn(mitem_tabs, item_size:sz); +}; +mitem_tab(mitem_tabs tabs, string itname) menuitemtab_spawn = +{ + //a tab itself is little different from a frame, just has no implicit focus, and has a title + mitem_tab n = spawn(mitem_tab, item_text:itname, frame_hasscroll:TRUE); + + tabs.addr(n, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0'); + return n; +}; diff --git a/quakec/menusys/menusys/mitems.qc b/quakec/menusys/menusys/mitems.qc new file mode 100644 index 000000000..bb5299778 --- /dev/null +++ b/quakec/menusys/menusys/mitems.qc @@ -0,0 +1,412 @@ +/*all these .funcs etc need self assigned properly first, as is customary with qc*/ + +#ifdef MENU +#define cltime time +#endif + +//#include "mitems.qh" + +var float dp_workarounds; + +#define IF_SELECTABLE (1<<0) //can accept KFOCUSED/MFOCUSED and key events etc. cannot be selected otherwise. +#define IF_INTERACT (1<<1) //generic interaction flag for use by the widgets themselves. +#define IF_RESIZABLE (1<<2) //may be resized, either by parent (it takes the full space) or by user. +#define IF_MFOCUSED (1<<3) //mouse is currently sitting over it +#define IF_KFOCUSED (1<<4) //has keyboard focus +#define IF_NOKILL (1<<5) //kill button is disabled. move to frame/menu? +#define IF_DRAGGABLE (1<<6) //can be dragged. this is stupid. +#define IF_DROPPABLE (1<<7) //dragged items can be dropped on this item. +#define IF_CLIENTMOVED (1<<8) //recalc required client dimensions (and toggle scrollbars if needed). move to frame? +#define IF_CENTERALIGN (1<<9) // +#define IF_RIGHTALIGN (1<<10) // +#define IF_NOCURSOR (1<<11) //when mgrabbed, the cursor will not be shown +#define IF_ISFRAME (1<<12) //is derived from mitem_frame (helps debugging recurion). +#define IF_FOCUSFOLLOWSMOUSE (1<<13) //on frames, child keyboard focus (mostly) follows the mouse cursor. not like windows, but handy on things with lots of buttons. annoying on the desktop. move to frame? +#define IF_INVISIBLE (1<<14) + +#define RS_X_MIN_PARENT_MIN 0x0000 +#define RS_X_MIN_PARENT_MID 0x0001 +#define RS_X_MIN_PARENT_MAX 0x0002 +#define RS_X_FRACTION 0x0004 + +#define RS_X_MAX_OWN_MIN 0x0000 +#define RS_X_MAX_PARENT_MIN 0x0010 +#define RS_X_MAX_PARENT_MID 0x0020 +#define RS_X_MAX_PARENT_MAX 0x0040 + +#define RS_Y_MIN_PARENT_MIN 0x0000 +#define RS_Y_MIN_PARENT_MID 0x0100 +#define RS_Y_MIN_PARENT_MAX 0x0200 +#define RS_Y_FRACTION 0x0400 + +#define RS_Y_MAX_OWN_MIN 0x0000 +#define RS_Y_MAX_PARENT_MIN 0x1000 +#define RS_Y_MAX_PARENT_MID 0x2000 +#define RS_Y_MAX_PARENT_MAX 0x4000 + +//the 3d effect needs some sort of fake lighting values. +//these are for bumps. invert for inset things. +#define TD_TOP '0.8 0.8 0.8' +#define TD_LFT '0.6 0.6 0.6' +#define TD_RGT '0.2 0.2 0.2' +#define TD_BOT '0.0 0.0 0.0' + +#define MENUBACK_RGB '0.5 0.5 0.6' +#define MENUBACK_ALPHA 0.8 + +//#ifdef TARGET_FTE +//#pragma TARGET FTE +//#endif + +//#define HEIRACHYDEBUG //enable this and press f1 to print out the current menuitem tree + +class mitem_frame; +.vector mins; //gravity mins +.vector maxs; //gravity mins +class mitem +{ + void() mitem; + virtual void(vector pos) item_draw; + virtual float(vector pos, float scan, float char, float down) item_keypress = {return FALSE;}; + virtual void(mitem newfocus, float changedflag) item_focuschange; //move into frame? + virtual string(string key) get; + virtual void(string key, string newval) set; + virtual void() item_remove; + virtual void() item_resized; + float item_flags; //contains IF_ flags + vector item_position; //relative to the parent's client area. which is admittedly not well defined... + vector item_size; //interaction bounding box. + float item_scale; //text etc scale + vector item_rgb; //colours! + float item_alpha; //transparency value + string item_text; //used as a unique identifier + string item_command; //something to do when clicked. move out? + mitem_frame item_parent; //the item that contains us. make mitem_frame? + mitem item_next; //the next child within the parent + + +#ifdef MENU +// vector mins; //gravity mins +// vector maxs; //gravity mins +#endif + float resizeflags; + + + static void() totop; +}; + +//this struct is used to access the various drawing functions. this allows the functions to be replaced for worldspace stuff +typedef struct uiinfo_s +{ + void(float min_x, float min_y, float max_x, float max_y) setcliparea; + float(vector min, string imagename, vector size, vector col, float alph, float drawflag) drawpic; + float(vector min, vector size, vector col, float alph, float drawflag) drawfill; + float(vector min, float charcode, vector scale, vector col, float alph, float drawflag) drawcharacter; + float(vector min, string text, vector scale, vector col, float alph, float drawflag) drawstring; + + mitem kgrabs; //kfocused or mfocused or both or none to say what sort of grabs is in effect. + mitem mgrabs; //says who has stolen all input events. + + float mousedown; //which mouse buttons are currently held. + vector oldmousepos; //old mouse position, to track whether its moved. + vector mousepos; //current mouse position. + vector screensize; //total screen size + vector drawrectmin; //minimum scissor region, to clamp children to within the confines of its parent. + vector drawrectmax; //maximum scissor region + +#ifndef MENU + //menuqc has no concept of the world and cannot display or query 3d positions nor projections. Any related UI elements are thus not available to menuqc. + //these globals are not part of the ui struct either, because they're illegal in world UIs. + float havemouseworld; //whether the mouseworld stuff is valid - ie: that the cursor is in a 3d view + vector mouseworldnear; //position of the mouse cursor upon the near clip plane in world space + vector mouseworldfar; //position of the mouse cursor upon the far(ish) clip plane in world space +#endif +} uiinfo_t; +var uiinfo_t ui = +{ + drawsetcliparea, + drawpic, + drawfill, + drawcharacter, + drawstring +}; + + + +void() queryscreensize = +{ +#ifdef MENU + //there is no proper way to do this. + //fte thus has special checks for these cvars, and they should not be autocvars if you want them to work properly. + ui.screensize[0] = cvar("vid_conwidth"); + ui.screensize[1] = cvar("vid_conheight"); + ui.screensize[2] = 0; +#endif +#ifdef CSQC + //make sure the screensize is set. + normalize('0 0 1'); //when getproperty fails to return a meaningful value, make sure that we don't read some random stale value. + ui.screensize = (vector)getproperty(VF_SCREENVSIZE); + if (ui.screensize[2]) //lingering return value from normalize. + { + ui.screensize[2] = 0; + ui.screensize[0] = cvar("vid_conwidth"); + ui.screensize[1] = cvar("vid_conheight"); + } +#endif +}; + +//helper function +float(vector minp, vector sz) mouseinbox = +{ + if (ui.mousepos[0] < minp_x) + return FALSE; + if (ui.mousepos[1] < minp_y) + return FALSE; + if (ui.mousepos[0] >= minp_x + sz_x) + return FALSE; + if (ui.mousepos[1] >= minp_y + sz_y) + return FALSE; + + return TRUE; +}; + + +void(mitem ch, vector parentsize) mitem_parentresized = +{ + float f = ch.resizeflags; + + if (f & RS_X_FRACTION) + ch.item_position[0] = parentsize[0] * ch.mins[0]; + else if (f & RS_X_MIN_PARENT_MAX) + ch.item_position[0] = parentsize[0] + ch.mins[0]; + else if (f & RS_X_MIN_PARENT_MID) + ch.item_position[0] = parentsize[0]/2 + ch.mins[0]; + else //if (f & RS_X_MIN_PARENT_MIN) + ch.item_position[0] = ch.mins[0]; + + if (f & RS_X_FRACTION) + ch.item_position[0] = parentsize[0] * ch.maxs[0]; + else if (f & RS_X_MAX_PARENT_MIN) + ch.item_size[0] = ch.maxs[0] - ch.item_position[0]; + else if (f & RS_X_MAX_PARENT_MID) + ch.item_size[0] = ch.maxs[0]+parentsize[0]/2 - ch.item_position[0]; + else if (f & RS_X_MAX_PARENT_MAX) + ch.item_size[0] = ch.maxs[0]+parentsize[0] - ch.item_position[0]; + else //if (f & RS_X_MAX_OWN_MIN) + ch.item_size[0] = ch.maxs[0]; + + if (f & RS_Y_FRACTION) + ch.item_position[1] = parentsize[1] * ch.mins[1]; + else if (f & RS_Y_MIN_PARENT_MAX) + ch.item_position[1] = parentsize[1] + ch.mins[1]; + else if (f & RS_Y_MIN_PARENT_MID) + ch.item_position[1] = parentsize[1]/2 + ch.mins[1]; + else //if (f & RS_Y_MIN_PARENT_MIN) + ch.item_position[1] = ch.mins[1]; + + if (f & RS_Y_FRACTION) + ch.item_position[1] = parentsize[1] * ch.maxs[1]; + else if (f & RS_Y_MAX_PARENT_MIN) + ch.item_size[1] = ch.maxs[1] - ch.item_position[1]; + else if (f & RS_Y_MAX_PARENT_MID) + ch.item_size[1] = ch.maxs[1]+parentsize[1]/2 - ch.item_position[1]; + else if (f & RS_Y_MAX_PARENT_MAX) + ch.item_size[1] = ch.maxs[1]+parentsize[1] - ch.item_position[1]; + else //if (f & RS_Y_MAX_OWN_MIN) + ch.item_size[1] = ch.maxs[1]; +}; + +#include "mitem_frame.qc" +void() mitem::item_resized = +{ + if (this.item_parent) + this.item_parent.item_flags |= IF_CLIENTMOVED; +}; + + +#ifdef HEIRACHYDEBUG +void mitem_printnode(float depth, mitem root, string prefix) +{ + mitem ch; + string col = ""; + if (root.item_flags & IF_KFOCUSED && root.item_flags & IF_MFOCUSED) + col = "^2"; + else if (root.item_flags & IF_KFOCUSED) + col = "^5"; + else if (root.item_flags & IF_MFOCUSED) + col = "^3"; + + print(sprintf("%s%s%i:%s%s\n", col, strpad(depth, ""), root, prefix, root.item_text)); + + //can only recurse into items which are actually frames. + if (root.item_flags & IF_ISFRAME) + { + mitem_frame fr = (mitem_frame)root; + depth+=1; + for(ch = fr.item_children; ch; ch = ch.item_next) + { + if (ch.item_parent != root) + print("corrupt parent\n"); + if (ch.item_next == ch) + { + print("infinite loop\n"); + break; + } + string pre = ""; + if (fr.item_mactivechild == ch) + pre = strcat(pre, "m"); + if (fr.item_kactivechild == ch) + pre = strcat(pre, "k"); + if (pre) + pre = strcat(pre, " "); + mitem_printnode(depth, ch, pre); + } + } +}; +void mitem_printtree(mitem_frame root, string from, float line) +{ + print(sprintf("%s:%g\n", from, line)); + mitem_printnode(0, root, ""); +} +#endif + +string(string key) mitem::get = +{ + //walk through to the parent for menu parents to track all this. + if (this.item_parent) + return this.item_parent.get(key); + else //no parent, just assume its a cvar. + return cvar_string(key); +}; +void(string key, string newval) mitem::set = +{ + //walk through to the parent for menu parents to track all this. + if (this.item_parent) + this.item_parent.set(key, newval); + else //no parent, just assume its a cvar. + cvar_set(key, newval); +}; + + +/*************************************************************************** +basic 'null' item, for completeness, every other item logically inherits from this. +drawing this just shows its text right-aligned at w/2. most things will want to override this. +*/ +void(mitem newfocus, float flag) mitem::item_focuschange = +{ +}; + +//z order is determined by the list order. the ones at the end (oldest) are on top. +void() mitem::totop = +{ + mitem_frame p = item_parent; + mitem prev; + //unlink it + if (p.item_children == this) + prev = p.item_children = item_next; + else + { + for (prev = p.item_children; prev; prev = prev.item_next) + { + if (prev.item_next == this) + { + prev.item_next = item_next; + break; + } + } + } + + item_next = __NULL__; + if (prev) + { + //add it on the end + while(prev.item_next) + prev = prev.item_next; + prev.item_next = this; + } + else + p.item_children = this; +}; + +vector(mitem it) menuitem_textcolour = +{ + local vector col; + col = it.item_rgb; + if (!(it.item_flags & IF_SELECTABLE) && it.item_keypress && it.item_command != "") + col *= 0.2; + else + { + if (it.item_flags & IF_MFOCUSED) + col_z = 0; + if (it.item_flags & IF_KFOCUSED) + col_x = 0; + } + return col; +}; +void(vector pos) mitem::item_draw = +{ + vector col; + pos_x += this.item_size[0] / 2 - stringwidth(this.item_text, TRUE, '1 1 0'*this.item_scale) - 8; + col = menuitem_textcolour(this); + ui.drawstring(pos, this.item_text, '1 1 0' * this.item_scale, col, this.item_alpha, 0); +}; +void() mitem::item_remove = +{ + local mitem ch; + //any children got removed by the frame code. + + //make sure the item is removed from its parent. + local mitem_frame p = this.item_parent; + if (p) + { + if (p.item_exclusive == this) + p.item_exclusive = __NULL__; + + if (p.item_children == this) + p.item_children = this.item_next; + else + { + for(ch = p.item_children; ch; ch = ch.item_next) + { + if (ch.item_next == this) + { + ch.item_next = this.item_next; + break; + } + } + } + } + + //pick a different item on the parent. + if (p.item_mactivechild == this) + p.item_focuschange(__NULL__, IF_MFOCUSED); + if (p.item_kactivechild == this) + { + for (ch = p.item_children; ch; ch = ch.item_next) + { + if (ch.item_flags & IF_SELECTABLE) + break; + } + p.item_focuschange(ch, IF_KFOCUSED); + } + if (ui.kgrabs == this) + ui.kgrabs = __NULL__; + if (ui.mgrabs == this) + ui.mgrabs = __NULL__; + remove((entity)this); + + //force mousefocus to update + ui.oldmousepos = '-1 -1'; +}; +void() mitem::mitem = +{ + if (!this.item_scale) + this.item_scale = 1; + if (!this.item_rgb) + this.item_rgb = '1 1 1'; + if (!this.item_alpha) + this.item_alpha = 1; + + //force mousefocus to update + ui.oldmousepos = '-1 -1'; +}; diff --git a/quakec/menusys/menusys/mitems_common.qc b/quakec/menusys/menusys/mitems_common.qc new file mode 100644 index 000000000..fa0c8663f --- /dev/null +++ b/quakec/menusys/menusys/mitems_common.qc @@ -0,0 +1,184 @@ +/*************************************************************************** +simple block-fill item +non-interactable. +*/ +class mitem_fill : mitem +{ + virtual void(vector pos) item_draw = + { + ui.drawfill(pos, this.item_size, this.item_rgb, this.item_alpha, 0); + }; +}; +#define menuitemfill_spawn(sz,rgb,alph) spawn(mitem_fill, item_size:sz, item_rgb:rgb, item_alpha:alph) + +/*************************************************************************** +basic picture item. +non-interactable. + +item_text: the normal image to use +item_text_mactive: the image to use when the mouse is over it. +item_size: if not set, will be set to the size of the image named by item_text. if only y is set, x will be sized to match aspect with y. +*/ +class mitem_pic : mitem +{ + string item_text_mactive; + virtual void(vector pos) item_draw = + { + if (item_text_mactive != "" && (item_flags & IF_MFOCUSED)) + ui.drawpic(pos, item_text_mactive, item_size, item_rgb, item_alpha, 0); + else + ui.drawpic(pos, item_text, item_size, item_rgb, item_alpha, 0); + }; + void() mitem_pic = + { + if (dp_workarounds) + { + if (substring(item_text, -4, 4) == ".lmp") + item_text = substring(item_text, 0, -5); + if (substring(item_text_mactive , -4, 5) == ".lmp") + item_text_mactive = substring(item_text_mactive, 0, -4); + } + + item_text = strzone(item_text); + precache_pic(item_text); + if (item_text_mactive) + { + item_text_mactive = strzone(item_text_mactive); + precache_pic(item_text_mactive); + } + + if (!item_size[0]) + { + float y = item_size[1]; + item_size = drawgetimagesize(item_text); + if (y) //rescale x to ma + { + if (!item_size[1]) //bad image? don't glitch out too much. + item_size = item_size[0] * '1 1 0'; + else + item_size = [item_size[0] * (y / item_size[1]), y, 0]; + } + } + }; + virtual void() item_remove = + { + strunzone(item_text); + if (item_text_mactive) + strunzone(item_text_mactive); + super::item_remove(); + }; +}; +#define menuitempic_spawn(img,sz) spawn(mitem_pic, item_text:img, item_size:sz) + +/*************************************************************************** +basic text item. +interactable - executes a given console command. +*/ +class mitem_text : mitem +{ + virtual void(vector pos) item_draw = + { + float w; + if (item_flags & IF_CENTERALIGN) + { + w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); + ui.drawstring(pos + [(item_size_x-w)/2, 0], item_text, '1 1 0' * item_scale, menuitem_textcolour(this), item_alpha, 0); + } + else if (item_flags & IF_RIGHTALIGN) + { + w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); + ui.drawstring(pos + [(item_size_x-w), 0], item_text, '1 1 0' * item_scale, menuitem_textcolour(this), item_alpha, 0); + } + else + ui.drawstring(pos, item_text, '1 1 0' * item_scale, menuitem_textcolour(this), item_alpha, 0); + }; + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (this.item_command) + { + if (!down) + return FALSE; + if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) + { + item_parent.item_execcommand(this, this.item_command); +// localcmd(strcat(this.item_command, "\n")); + return TRUE; + } + } + return FALSE; + }; + + //zone+unzone input strings as needed + virtual void() item_remove = + { + strunzone(item_text); + if (item_command) + strunzone(item_command); + super::item_remove(); + }; + void() mitem_text = + { + item_text = strzone(item_text); + if (item_command != "") + { + item_command = strzone(item_command); + item_flags |= IF_SELECTABLE; + } + }; +}; +mitem(string text, string command, float height) menuitemtext_spawn = +{ + return spawn(mitem_text, item_scale:height, item_text:text, item_command:command, item_size:[stringwidth(text, TRUE, '1 1 0'*height), height, 0]); +}; + + +/*************************************************************************** +basic text item. +identical to text, but includes a 3dish border. +*/ +class mitem_button : mitem +{ + virtual void(vector pos) item_draw = + { + ui.drawfill(pos, [this.item_size[0], 1], TD_TOP, this.item_alpha, 0); + ui.drawfill(pos, [1, this.item_size[1] - 1], TD_LFT, this.item_alpha, 0); + ui.drawfill(pos + [this.item_size[0]-1, 1], [1, this.item_size[1] - 1], TD_RGT, this.item_alpha, 0); + ui.drawfill(pos + [0, this.item_size[1]-1], [this.item_size[0], 1], TD_BOT, this.item_alpha, 0); + + pos_x += (this.item_size[0] - stringwidth(this.item_text, TRUE, '1 1 0'*this.item_scale)) * 0.5; + pos_y += (this.item_size[1] - this.item_scale)*0.5; + ui.drawstring(pos, this.item_text, '1 1 0' * this.item_scale, menuitem_textcolour(this), this.item_alpha, 0); + }; + virtual float(vector pos, float scan, float char, float down) item_keypress = + { + if (!down) + return FALSE; + if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) + item_parent.item_execcommand(this, item_command); + else + return FALSE; + return TRUE; + }; + virtual void() item_remove = + { + strunzone(item_text); + if (item_command) + strunzone(item_command); + super::item_remove(); + }; +}; +mitem_button(string text, string command, vector sz) menuitembutton_spawn = +{ + mitem_button n = spawn(mitem_button); + n.item_scale = sz_y - 4; + n.item_text = strzone(text); + n.item_size = sz; + + if (command != "") + { + n.item_command = strzone(command); + n.item_flags |= IF_SELECTABLE; + } + return n; +}; + diff --git a/quakec/menusys/menusys/readme.txt b/quakec/menusys/menusys/readme.txt new file mode 100644 index 000000000..2d8a3f3a8 --- /dev/null +++ b/quakec/menusys/menusys/readme.txt @@ -0,0 +1,220 @@ +integrating into an existing mod: +the csqc/menu needs to instanciate a mitem_desktop (or derived class). items_keypress+items_draw need to be called in the relevent places with the relevent arguments, then you can start creating children and throwing them at the screen. +the desktop is the root object, and tracks whether the mouse/keys should be grabbed. + +general overview: +The menu system is build from a heirachy/tree of mitem nodes. +To draw the menu, the tree is walked from the root (the 'desktop' object) through its children (pictures, text, or whatever). +Each mitem inherits its various properties from a parent object using classes. For instance, the desktop inherits from mitem_frame(read: a container object), which in turn inherits from mitem(the root type). +When a container is moved or resized, all of its children (and their children) will automatically be resized accordingly. Once the container is destroyed, the children will also be automatically destroyed. +Drawing items is fairly simple. The root node is told to draw itself. Once it has drawn its background, it walks through its children asking them to draw themselves (telling the child exactly where it is on the scree). Other containers(like windows) do the same. In this way the entire tree is drawn. +Input happens in a somewhat similar manner. The parent decides which child the mouse is over, and sends mouse or keyboard to the relevent focused child. As a special exception, if an element has set ui.*grabs to refer to itself, that object gets to snoop on input first before even the desktop gets a chance. +To make a quake-style menu, you should create an mitem_exmenu, and then call the desktop's .add method to insert it. Then you need to spawn things like cvar sliders/combos/etc and use one of the menu's add methods to place it on the menu/window. Check the example code. + +Items that don't specify a method will inherit that method from its parent. +If you want only a minor change, for instance drawing a background under the item, you can make an item_draw method which draws the background then calls super::item_draw() with the same arguments in order to provide the parent's normal behaviour. Doing this with a menu's get+set methods allows you to use slider/etc mitems with things other than direct cvars (if you want an 'apply changes' button or so). + + +globals: +autocvar_menu_font: defaults to 'cour', which will only work in windows, fallback font otherwise. The menu code will register+use a font with this name suitable for 8 vpixels high, and 16vpixels. +dp_workarounds: needed to work around DP bugs. Set to 1 if you detect that the code is running inside DP. +ui: a struct containing the current ui state. Can be temporarily overwritten for alternative menu systems (like ones drawn on walls or whatever). + probably would have been nicer if it was a pointer, but I didn't want to depend upon those. + Within the ui struct are a few functions which typically just map to the equivelent builtins. + Placing a copy of these in the ui struct allows them to be overridden in order to project a menu/ui upon a wall efficiently without extra code. + these are the functions available: setcliparea, drawpic, drawfill, drawcharacter, drawstring +ui.mousedown: the mouse buttons that are currently pressed. +ui.oldmousepos: the mouse position that was set in the previous frame. this can be used to detect whether the mouse has moved. +ui.mousepos: the current virtual screen position of the mouse cursor. +ui.screensize: the virtual resolution of the screen - the dimensions the UI needs to fill. +ui.drawrectmin: the current viewport scissor min position (so over-sized items can draw outside of their containing frames without issue). +ui.drawrectmax: the current viewport scissor max position (so over-sized items can draw outside of their containing frames without issue). +ui.havemouseworld: whether the mouseworld[near/far] globals are valid - ie: that the cursor is in a 3d view. only exists in csqc. +ui.mouseworldnear: position of the mouse cursor upon the near clip plane in world space. +ui.mouseworldfar: position of the mouse cursor upon the far(ish) clip plane in world space. You can trace a line between these two vectors to detect which entities the mouse should interact with. +ui.kgrabs: set this global in order to redirect all keyboard buttons to this object. +ui.mgrabs: set this global in order to redirect all mouse buttons to this object. If the object that has focus also has item_flags&IF_NOCURSOR, the mouse cursor will be hidden. + +helper functions: +mouseinbox: returns whether ui.mousepos is within the box specified by the min+size arguments. + +mitems.qc:class mitem + root menuitem object that all else inherits from + + EVENTS: + void item_draw(pos): override this to replace how the item displays. the default is quite lame. pos is the virtual screen position. + float item_keypress(vector pos,float scan,float char, float down): the object got a keyboard or mouse click. pos is the virtual screen position. scan is the K_FOO scancode constant. char is the unicode value of the codepoint. either may be 0. some systems might always use 0 for one of scan+char. down says that the key was just pressed, false means it just got released. + void item_focuschange(mitem newfocus, float changedflag): the object's focus changed. newfocus is the item that gained focus, changedflag is either IF_MFOCUSED, IF_KFOCUSED, or both. check this against newfocus to see if this object gained focus. + string(string key) get: helper function. calls the parent's get method. + void(string key, string newval) set: helper function. calls the parent's set method. + void() item_remove: the item got removed. this callback can be used to free memory. be sure to notify super. + void() item_resized: the item has been resized or moved. provided to resize+move helper objects to match a parent, or recalculate cached extents or whatever. + + FIELDS: + item_flags: various flags + IF_SELECTABLE: item can be selected, either with mouse or keyboard. + IF_INTERACT: flag reserved for child classes to use. + IF_RESIZABLE: item can be resized. menus will show resize corners. + IF_MFOCUSED: item has mouse focus (parents will retain the flag) + IF_KFOCUSED: item has keyboard focus (parents will retain the flag) + IF_NOKILL: item has been marked as resisting closure. This applies to menus and will disable the implicit 'x' button. + IF_NOCURSOR: if the item grabs the mouse, the cursor will be hidden, allowing mlook to still work when some widget is grabbing everything (eg: an exmenu using right-click to look). + item_position: the (virtual) position of the object relative to its parent. + item_size: the current (virtual) size of the object. + item_scale: typically serves to rescale text. not used by the menu framework itself. + item_rgb: the colour field for the item. typically defaults to '1 1 1' if not otherwise set. + item_alpha: the alpha value for the item. typically defaults to 1. + item_text: typically used as the caption. also used as a searchable item name. + item_command: the cvar or console command associated with the object. potentially other uses. yay for repurposing fields. + item_parent: the frame that contains this object. + item_next: the next sibling within the parent. + mins: the resize gravity bias for the 'min' pos. + maxs: the resize gravity bias for the 'max' pos. + resizeflags: controls the meaning of the mins+maxs positions. + RS_[X/Y]_[MIN/MAX]_PARENT_MIN: the min/max position is relative to the top/left of the parent. + RS_[X/Y]_[MIN/MAX]_PARENT_MID: the min/max position is relative to the center of the parent. + RS_[X/Y]_[MIN/MAX]_PARENT_MAX: the min/max position is relative to the bottom/right of the parent. + RS_[X/Y]_FRACTION: the min+max positions are 0-1 values scaled within the min/max position of the parent. + RS_[X/Y]_MAX_OWN_MIN: the max position is set relative to the objects own minimum size, giving an absolute object size. + + static functions: + totop(): moves the object to the top of the parent's z-order. drawn last, this object will now appear over everything else. does not affect focus. + + non-member functions (FIXME): + mitem_printtree: debug function to print out a list of the items children+siblings. + queryscreensize: updates the screensize global. + mitem_parentresized: updates an item's position and size according to its resizeflags. + menuitem_textcolour: helper function. determines the text colour to use for cvar widgets based upon selectable and mouse/keyboard focus. + +mitem_frame.qc:class mitem_frame : mitem + generic borderless container object for other items. + + FIELDS: + item_framesize_x: border width. children will not overlap this. + item_framesize_y: border at top. children will not overlap this. + item_framesize_z: border at bottom. children will not overlap this. + frame_hasscroll: if true, enables a vertical slider so the frame is still usable if the screen is too small. + item_children: the first child object of the frame + item_mactivechild: the current child that has the mouse over it. + item_kactivechild: the current child that has keyboard focus. + + static functions: + findchild(string title): scans through a frame's children looking for an item with a matching item_text. + add(mitem newitem, float resizeflags, vector originmin, vector originmax): adds an item to a frame, with specified gravity+position etc settings. see mitem::resizeflags for details. + adda(mitem newitem, vector pos): adds an item to a frame with preset item_size and position relative to the top-left of the parent frame. + addr(mitem newitem, float resizeflags, vector originmin, vector originmax): adds an item to a frame, with specified gravity+position etc settings, with reversed z order. this item will be drawn over the top of the previous objects, and is the more natural ordering. + addc(mitem newitem, float ypos): adds an item to a frame with a preset item_size at a specific y position. multiple objects with the same mins_y+maxs_y will automatically be spread across horizontally with a gap between each. + +mitem_bind.qc:class mitem_bind : mitem + a widget to change a key binding. up to two keys will be listed. additional keys will currently remain hidden. + item_text: the description of the key binding (ie: "Attack") + item_command: the console command to bind the key to (ie: "+attack") + +mitem_checkbox.qc:class mitem_check : mitem + a true/false cvar checkbox. + item_text: the description of the setting + item_command: the name of the cvar to toggle + + optional factory: mitem_check(string text, string command, vector sz) menuitemcheck_spawn; + +mitem_colours.qc:class mitem_colours : mitem + A simple colour picker. supports only hue. + item_text: the description of the setting + item_command: the name of the cvar to change (sets to 0xRRGGBB notation). + + factory: mitem_colours(string text, string command, vector sz) menuitemcolour_spawn; + +mitem_combo.qc:class mitem_combo : mitem + multiple choice widget. + item_text: the description of the setting + mstrlist: a list of the valid settings, in "\"value\" \"description\" \"value\" \"description\"" notation. + FIXME: must spawn through: mitem_combo(string text, string command, vector sz, string valuelist) menuitemcombo_spawn; + +mitem_combo.qc:class mitem_combo_popup : mitem + internal friend class of the combo. + this holds the list of options when clicked. + you should not use this class directly. + +mitem_desktop.qc:class mitem_desktop : mitem_frame + the root item that should be used as the parent of all other elements in order to be visible. + forces itself to fullscreen, handles grabs, etc. + in csqc, will display the default game view (including split screen views). + + csqc + float(mitem_desktop desktop, float evtype, float scanx, float chary, float devid) items_keypress + menuqc: + float(mitem_desktop desktop, float scan, float char, float down) items_keypress + both: + void(mitem_desktop desktop) items_draw + + +mitem_edittext.qc:class mitem_edit : mitem + text entry widget (typically) attached to a cvar. + item_text: the short description of the setting. + item_command: the name of the cvar to receive a new value/be displayed + +mitem_exmenu.qc:class mitem_exmenu : mitem_frame + 'exclusive' menu object. + this container has no borders. + does not provide a background. use a mitem_pic or mitem_fill for that. + not much more than a frame, but can take unhandled escape/right click to close the menu. + typically used fullscreen. + item_text: a searchable identifier for the object + item_command: the console command to execute when the item is removed. used to restore the parent menu. + +mitem_frame.qc:class mitem_vslider : mitem + vertical up/down slider not attached to a cvar. + used to scroll frames up+down for when you have too many items in there for the current video mode. + fixme: document + + factory macro: menuitemframe_spawn(sz) + typically created via inheritance. + +mitem_menu.qc:class mitem_menu : mitem_frame + movable resizable window + + item_flags: + IF_RESIZABLE: enables resize handles on left+bottom+right+lower corners. + IF_NOKILL: disables the 'x' button on the top-right. + non-member construtor: + menu_spawn(mitem_frame desktop, string mname, vector sz): centers and automatically adds the object. required for the default border size. do not call any of the add* methods yourself if you use this. + +mitem_slider.qc:class mitem_hslider : mitem + horizontal slider attached to a cvar. + item_text: the short description of the setting. + item_command: the name of the cvar to change. + item_slidercontrols_x: the minimum value. + item_slidercontrols_y: the maximum value. + item_slidercontrols_z: how much to change the cvar by each time the user uses the left/right arrows to change its value. + +mitem_tabs.qc:class mitem_tabs : mitem_frame + tabstrip object that appears presenting the various tab options. + acts as a container only for mitem_tab objects which are the real containers. + +mitem_tabs.qc:class mitem_tab : mitem_frame + child of a tabstrip that contains the various elements sited upon a single tab. + item_text: the caption displayed for the tab. + +mitems_common.qc:class mitem_fill : mitem + fills the screen region with a block of colour. + item_rgb: colour to fill it with. + item_alpha: alpha blended by this much. 1 for full block colour. + +mitems_common.qc:class mitem_pic : mitem + simple image display. + item_text: the default image to display. + item_text_mactive: if specified, the image to display when the object has mouse focus. + item_command: console command to execute when clicked. + +mitems_common.qc:class mitem_text : mitem + simple plain text + item_text: the text to display. + item_command: the console command to execute when clicked. + item_scale: the height of the text, in virtual pixels, so ideally 8 or more. + +mitems_common.qc:class mitem_button : mitem + centered text with a noticable border that just seems to say 'click me'... + item_text: the text to display. + item_command: the console command to execute when clicked. + item_scale: the height of the text, in virtual pixels, so ideally 8 or more. + diff --git a/specs/csqc_for_idiots.txt b/specs/csqc_for_idiots.txt index a4b49f7d4..3c6bd9480 100644 --- a/specs/csqc_for_idiots.txt +++ b/specs/csqc_for_idiots.txt @@ -411,15 +411,15 @@ float(entity to, float sendflags) MySendEntity = WriteByte(MSG_ENTITY, sendflags); //tell the csqc what is actually present. if (sendflags & 1) { - WriteCoord(self.origin_x); - WriteCoord(self.origin_y); - WriteCoord(self.origin_z); + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); } if (sendflags & 2) - WriteByte(self.frame); + WriteByte(MSG_ENTITY, self.frame); if (sendflags & 128) - WriteByte(self.modelindex); //sending a modelindex is smaller than sending an entire string. + WriteByte(MSG_ENTITY, self.modelindex); //sending a modelindex is smaller than sending an entire string. }; In your spawn function: diff --git a/specs/splitscreen.txt b/specs/splitscreen.txt new file mode 100644 index 000000000..48e722ccd --- /dev/null +++ b/specs/splitscreen.txt @@ -0,0 +1,69 @@ +FTE's splitscreen is supported for Quake, Hexen2, or Quake2. It is not supported in Quake3. + + +Usage: +Set cl_splitscreen to the number of additional clients you wish to use. This can be changed mid-game, thereby adding or removing clients as needed. +0: disabled +1: 2-way +2: 3-way +4: 4-way +other values: not supported. +For convenience, if cl_splitscreen is enabled then servers will enable coop instead of running singleplayer. +Also note the input support section. + +To enable splitscreen on a server, you need to set allow_splitscreen 1 on that public server. Note that this is not normally needed unless you're using a separate/dedicated server. + +Additionally, protocol extensions must be enabled. cl_nopext 0 will reenable them (this is the default setting, so you won't need to worry too much). + + +Mod support: +Not all mods will work perfectly with it, an example being (especially q2) gamecode that handles the 'say' command which is not splitscreen-aware will end up sending multiple prints for each other player. This is not too much of an issue in private games, but can be annoying on public servers. +Other mods that specially craft sound events or other such things for clients may similarly have issues. + +The QC features viewmodelforclient and exteriormodelforclient are not supported in conjunction with splitscreen. These will be be checked against only the first player, but will apply to all players. + +Mods that use CSQC will typically only see the first player unless the csqc code explicitly supports splitscreen. + + + +UserInfo and Names: +You can change the second / 3rd / 4th player's name or other settings with: +p2 setinfo name Foo + + + +Input support: +There are multiple ways to deal with input, and multiple gotchas. +The engine generates a device-id per input device of each type. This device id is used to decide which player (aka seat) that input device controls. +These device ids typically depend upon the order that they are enumerated in, and can thus be renumbered between restarts or reboots, depending upon the operating system and device types. + +in_xinput cvar: +on windows, the xinput api provides support for up to 4 game controllers (so long as they're just clones of microsoft's console controllers...). Windows is responsible for deciding which controller is assigned to each seat, so don't blame me if they get reassigned randomly. +These controllers may optionally have headsets attached to them. If they do, each headset will automatically receive its own player-specific audio stream. +If this cvar is changed, you will need to use in_restart in order to apply that change. + +in_rawinput cvar: +Rawinput provides a way to determine which mouse generated which input events. It also disables mouse acceleration, which is generally a good thing. +If this cvar is changed, you will need to use in_restart in order to apply that change. + +in_rawinput_keyboard: +Rawinput also supports keyboards, along with actual device ids, thereby allowing each player to have their own keyboard, if your computer is set up weirdly like that. +If this cvar is changed, you will need to use in_restart in order to apply that change. + +in_dinput cvar: +DirectInput supports reporting device ids, however microsoft broke this with Vista where mice are concerned. Use rawinput instead. FTE only supports directinput for mice anyway. + +in_deviceids command: +in_deviceids with no arguments can be used to list the device types, mappings, and names. +You can reassign device ids with eg: in_deviceids mouse 1 "whateverwaslistedhere" +Note that device ids themselves are 0-based, so the previous line will remap the named mouse to player 1. + +in_forceseat cvar: +if set, the device id mechanism is ignored, and all input explicitly controls the specified seat. This is primarily only useful to developers that want to test multiplayer mods. However, it can also be set from the binds menu (where it configures which player's keys are being explicitly controlled). + +p2 / +p2 / p3 / etc commands: +When playing with only a single keyboard, individual keys can be bound to eg 'p2 impulse 4' or '+p2 attack'. By doing so, those keys will select player 2's nailgun, or move player 2 forward despite other keys controlling player 1 by default. +This can be configured via the binds menu (when splitscreen is enabled), but be sure to turn 'force client' back off. + +csqc: +csqc receives all input events before any in_forceseat modifiers or binds etc. This includes which device generated the event. This allows csqc mods to provide their own interpretation of any key events etc that it wants. \ No newline at end of file