diff --git a/Quake/Makefile b/Quake/Makefile old mode 100644 new mode 100755 index fe1ae833..65d27df5 --- a/Quake/Makefile +++ b/Quake/Makefile @@ -10,6 +10,9 @@ DO_USERDIRS=0 ### Enable/Disable SDL2 USE_SDL2=0 +### Enable the use of zlib, for compressed pk3s. +USE_ZLIB=1 + ### Enable/Disable codecs for streaming music support USE_CODEC_WAVE=1 USE_CODEC_FLAC=0 @@ -176,7 +179,12 @@ ifeq ($(USE_CODEC_UMX),1) CFLAGS+= -DUSE_CODEC_UMX endif -COMMON_LIBS:= -lm -lGL +COMMON_LIBS:= -ldl -lm -lGL + +ifeq ($(USE_ZLIB),1) +CFLAGS+= -DUSE_ZLIB +COMMON_LIBS+= -lz +endif LIBS := $(COMMON_LIBS) $(NET_LIBS) $(CODECLIBS) @@ -210,7 +218,7 @@ MUSIC_OBJS:= bgmusic.o \ snd_mikmod.o \ snd_xmp.o \ snd_umx.o -COMOBJ_SND := snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) +COMOBJ_SND := snd_voip.o snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) SYSOBJ_SND := snd_sdl.o SYSOBJ_CDA := cd_sdl.o SYSOBJ_INPUT := in_sdl.o @@ -227,6 +235,7 @@ GLOBJS = \ gl_fog.o \ gl_rmisc.o \ r_part.o \ + r_part_fte.o \ r_world.o \ gl_screen.o \ gl_sky.o \ @@ -266,6 +275,7 @@ OBJS := strlcat.o \ wad.o \ cmd.o \ common.o \ + fs_zip.o \ crc.o \ cvar.o \ cfgfile.o \ @@ -273,6 +283,7 @@ OBJS := strlcat.o \ host_cmd.o \ mathlib.o \ pr_cmds.o \ + pr_ext.o \ pr_edict.o \ pr_exec.o \ sv_main.o \ diff --git a/Quake/Makefile.darwin b/Quake/Makefile.darwin old mode 100644 new mode 100755 index aaad1967..a9e60119 --- a/Quake/Makefile.darwin +++ b/Quake/Makefile.darwin @@ -40,6 +40,7 @@ HOST_OS := $(shell uname|sed -e s/_.*//|tr '[:upper:]' '[:lower:]') MACH_TYPE= $(shell sh detect.sh arch) DEBUG ?= 0 +USE_ZLIB?= 1 # --------------------------- # build variables @@ -200,6 +201,11 @@ CFLAGS+= $(CODEC_INC) COMMON_LIBS:= -Wl,-framework,IOKit -Wl,-framework,OpenGL +ifeq ($(USE_ZLIB),1) +CFLAGS+= -DUSE_ZLIB +COMMON_LIBS+= `$(CC) -print-file-name=libz.a` +endif + LIBS := $(COMMON_LIBS) $(NET_LIBS) $(CODEC_LINK) $(CODECLIBS) # --------------------------- @@ -236,7 +242,7 @@ MUSIC_OBJS:= bgmusic.o \ snd_mikmod.o \ snd_xmp.o \ snd_umx.o -COMOBJ_SND := snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) +COMOBJ_SND := snd_voip.o snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) SYSOBJ_SND := snd_sdl.o SYSOBJ_CDA := cd_sdl.o SYSOBJ_INPUT := in_sdl.o @@ -253,6 +259,7 @@ GLOBJS = \ gl_fog.o \ gl_rmisc.o \ r_part.o \ + r_part_fte.o \ r_world.o \ gl_screen.o \ gl_sky.o \ @@ -292,6 +299,7 @@ OBJS := strlcat.o \ wad.o \ cmd.o \ common.o \ + fs_zip.o \ crc.o \ cvar.o \ cfgfile.o \ @@ -299,6 +307,7 @@ OBJS := strlcat.o \ host_cmd.o \ mathlib.o \ pr_cmds.o \ + pr_ext.o \ pr_edict.o \ pr_exec.o \ sv_main.o \ diff --git a/Quake/Makefile.w32 b/Quake/Makefile.w32 old mode 100644 new mode 100755 index 71cea828..9f1ae0f8 --- a/Quake/Makefile.w32 +++ b/Quake/Makefile.w32 @@ -33,7 +33,8 @@ check_gcc = $(shell if echo | $(CC) $(1) -Werror -S -o /dev/null -xc - > /dev/nu # --------------------------- DEBUG ?= 0 -WINSOCK2?= 0 +WINSOCK2?= 1 +USE_ZLIB?= 1 # --------------------------- # build variables @@ -48,7 +49,7 @@ STRIP = strip #CPUFLAGS= -mtune=i686 #CPUFLAGS= -march=pentium4 CPUFLAGS= -LDFLAGS = -m32 -mwindows +LDFLAGS = -m32 -mwindows -Wl,--large-address-aware DFLAGS ?= CFLAGS ?= -m32 -Wall -Wno-trigraphs CFLAGS += $(CPUFLAGS) @@ -166,6 +167,11 @@ CFLAGS+= $(CODEC_INC) COMMON_LIBS:= -lm -lopengl32 -lwinmm +ifeq ($(USE_ZLIB),1) +CFLAGS+= -DUSE_ZLIB +COMMON_LIBS+= `$(CC) -print-file-name=libz.a` +endif + LIBS := $(COMMON_LIBS) $(NET_LIBS) $(CODEC_LINK) $(CODECLIBS) # --------------------------- @@ -200,7 +206,7 @@ MUSIC_OBJS:= bgmusic.o \ snd_mikmod.o \ snd_xmp.o \ snd_umx.o -COMOBJ_SND := snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) +COMOBJ_SND := snd_voip.o snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) SYSOBJ_SND := snd_sdl.o SYSOBJ_CDA := cd_sdl.o SYSOBJ_INPUT := in_sdl.o @@ -217,6 +223,7 @@ GLOBJS = \ gl_fog.o \ gl_rmisc.o \ r_part.o \ + r_part_fte.o \ r_world.o \ gl_screen.o \ gl_sky.o \ @@ -256,6 +263,7 @@ OBJS := strlcat.o \ wad.o \ cmd.o \ common.o \ + fs_zip.o \ crc.o \ cvar.o \ cfgfile.o \ @@ -263,6 +271,7 @@ OBJS := strlcat.o \ host_cmd.o \ mathlib.o \ pr_cmds.o \ + pr_ext.o \ pr_edict.o \ pr_exec.o \ sv_main.o \ diff --git a/Quake/Makefile.w64 b/Quake/Makefile.w64 old mode 100644 new mode 100755 index ab640fdc..a3e99dcc --- a/Quake/Makefile.w64 +++ b/Quake/Makefile.w64 @@ -34,6 +34,7 @@ check_gcc = $(shell if echo | $(CC) $(1) -Werror -S -o /dev/null -xc - > /dev/nu DEBUG ?= 0 WINSOCK2?= 1 +USE_ZLIB?= 1 # --------------------------- # build variables @@ -101,11 +102,11 @@ $(error Invalid MP3LIB setting) endif endif ifeq ($(MP3LIB),mad) -mp3_obj=snd_mp3 +mp3_obj=snd_mp3.o lib_mp3dec=-lmad endif ifeq ($(MP3LIB),mpg123) -mp3_obj=snd_mpg123 +mp3_obj=snd_mpg123.o lib_mp3dec=-lmpg123 endif ifeq ($(VORBISLIB),vorbis) @@ -164,6 +165,11 @@ CFLAGS+= $(CODEC_INC) COMMON_LIBS:= -lm -lopengl32 -lwinmm +ifeq ($(USE_ZLIB),1) +CFLAGS+= -DUSE_ZLIB +COMMON_LIBS+= `$(CC) -print-file-name=libz.a` +endif + LIBS := $(COMMON_LIBS) $(NET_LIBS) $(CODEC_LINK) $(CODECLIBS) # --------------------------- @@ -193,12 +199,11 @@ MUSIC_OBJS:= bgmusic.o \ snd_wave.o \ snd_vorbis.o \ snd_opus.o \ - $(mp3_obj).o \ - snd_mp3tag.o \ + $(mp3_obj) \ snd_mikmod.o \ snd_xmp.o \ snd_umx.o -COMOBJ_SND := snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) +COMOBJ_SND := snd_voip.o snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) SYSOBJ_SND := snd_sdl.o SYSOBJ_CDA := cd_sdl.o SYSOBJ_INPUT := in_sdl.o @@ -215,6 +220,7 @@ GLOBJS = \ gl_fog.o \ gl_rmisc.o \ r_part.o \ + r_part_fte.o \ r_world.o \ gl_screen.o \ gl_sky.o \ @@ -254,6 +260,7 @@ OBJS := strlcat.o \ wad.o \ cmd.o \ common.o \ + fs_zip.o \ crc.o \ cvar.o \ cfgfile.o \ @@ -261,6 +268,7 @@ OBJS := strlcat.o \ host_cmd.o \ mathlib.o \ pr_cmds.o \ + pr_ext.o \ pr_edict.o \ pr_exec.o \ sv_main.o \ diff --git a/Quake/OWMakefile.win32 b/Quake/OWMakefile.win32 old mode 100644 new mode 100755 index 6bd01ad7..ec95772e --- a/Quake/OWMakefile.win32 +++ b/Quake/OWMakefile.win32 @@ -21,6 +21,8 @@ MP3LIB=mad VORBISLIB=vorbis WINSOCK2= 0 +#FIXME: set this to 1 +USE_ZLIB=0 # --------------------------- # build variables @@ -106,6 +108,10 @@ CFLAGS+= -DUSE_CODEC_UMX CFLAGS+= $(CODEC_INC) COMMON_LIBS= opengl32.lib winmm.lib +!ifeq USE_ZLIB 1 +CFLAGS+= -DUSE_ZLIB +COMMON_LIBS+= -lz +!endif LIBS = $(CODECLIBS) $(SDL_LIBS) $(COMMON_LIBS) $(NET_LIBS) @@ -145,7 +151,7 @@ MUSIC_OBJS= bgmusic.obj & snd_mikmod.obj & snd_xmp.obj & snd_umx.obj -COMOBJ_SND = snd_dma.obj snd_mix.obj snd_mem.obj $(MUSIC_OBJS) +COMOBJ_SND = snd_voip.obj snd_dma.obj snd_mix.obj snd_mem.obj $(MUSIC_OBJS) SYSOBJ_SND = snd_sdl.obj SYSOBJ_CDA = cd_sdl.obj SYSOBJ_INPUT = in_sdl.obj @@ -167,6 +173,7 @@ GLOBJS = & gl_fog.obj & gl_rmisc.obj & r_part.obj & + r_part_fte.obj & r_world.obj & gl_screen.obj & gl_sky.obj & @@ -206,6 +213,7 @@ OBJS = strlcat.obj & wad.obj & cmd.obj & common.obj & + fs_zip.obj & crc.obj & cvar.obj & cfgfile.obj & @@ -213,6 +221,7 @@ OBJS = strlcat.obj & host_cmd.obj & mathlib.obj & pr_cmds.obj & + pr_ext.obj & pr_edict.obj & pr_exec.obj & sv_main.obj & diff --git a/Quake/bspfile.h b/Quake/bspfile.h index 6da832f0..7a88ac92 100644 --- a/Quake/bspfile.h +++ b/Quake/bspfile.h @@ -98,7 +98,23 @@ typedef struct int headnode[MAX_MAP_HULLS]; int visleafs; // not including the solid leaf 0 int firstface, numfaces; -} dmodel_t; +} mmodel_t; +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[4]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodelq1_t; +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[8]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodelh2_t; typedef struct { diff --git a/Quake/chase.c b/Quake/chase.c index 6ddd25b5..bbac559c 100644 --- a/Quake/chase.c +++ b/Quake/chase.c @@ -111,7 +111,7 @@ void Chase_UpdateForDrawing (void) // calculate camera angles to look at the same spot VectorSubtract (crosshair, r_refdef.vieworg, temp); - VectorAngles (temp, r_refdef.viewangles); + VectorAngles (temp, NULL, r_refdef.viewangles); if (r_refdef.viewangles[PITCH] == 90 || r_refdef.viewangles[PITCH] == -90) r_refdef.viewangles[YAW] = cl.viewangles[YAW]; } diff --git a/Quake/cl_demo.c b/Quake/cl_demo.c index d1ad2bf5..7866052f 100644 --- a/Quake/cl_demo.c +++ b/Quake/cl_demo.c @@ -36,10 +36,6 @@ read from the demo file. ============================================================================== */ -// from ProQuake: space to fill out the demo header for record at any time -static byte demo_head[3][MAX_MSGLEN]; -static int demo_head_size[2]; - /* ============== CL_StopPlayback @@ -166,14 +162,6 @@ int CL_GetMessage (void) if (cls.demorecording) CL_WriteDemoMessage (); - if (cls.signon < 2) - { - // record messages before full connection, so that a - // demo record can happen after connection is done - memcpy(demo_head[cls.signon], net_message.data, net_message.cursize); - demo_head_size[cls.signon] = net_message.cursize; - } - return r; } @@ -206,11 +194,235 @@ void CL_Stop_f (void) cls.demofile = NULL; cls.demorecording = false; Con_Printf ("Completed demo\n"); + + Cvar_SetROM(cl_recordingdemo.name, ""); // ericw -- update demo tab-completion list DemoList_Rebuild (); } +static void CL_Record_Serverdata(void) +{ + size_t i; + MSG_WriteByte(&net_message, svc_serverinfo); + if (cl.protocol_pext2) + { + MSG_WriteLong (&net_message, PROTOCOL_FTE_PEXT2); + MSG_WriteLong (&net_message, cl.protocol_pext2); + } + MSG_WriteLong (&net_message, cl.protocol); + if (cl.protocol == PROTOCOL_RMQ) + MSG_WriteLong (&net_message, cl.protocolflags); + if (cl.protocol_pext2 & PEXT2_PREDINFO) + MSG_WriteString(&net_message, COM_SkipPath(com_gamedir)); + MSG_WriteByte (&net_message, cl.maxclients); + MSG_WriteByte (&net_message, cl.gametype); + MSG_WriteString (&net_message, cl.levelname); + for (i=1; cl.model_precache[i]; i++) + MSG_WriteString (&net_message, cl.model_precache[i]->name); + MSG_WriteByte (&net_message, 0); + for (i=1; cl.sound_precache[i]; i++) //FIXME: might not send any if nosound is set + MSG_WriteString (&net_message, cl.sound_precache[i]->name); + MSG_WriteByte (&net_message, 0); + //FIXME: cd track (current rather than initial?) + //FIXME: initial view entity (for clients that don't want to mess up scoreboards) + MSG_WriteByte (&net_message, svc_signonnum); + MSG_WriteByte (&net_message, 1); + CL_WriteDemoMessage(); + SZ_Clear (&net_message); +} + +//spins out a baseline(idx>=0) or static entity(idx<0) into net_message +void CL_Record_Prespawn(void) +{ + int idx, i; + + //baselines + for (idx = 0; idx < cl.num_entities; idx++) + { + entity_state_t *state = &cl.entities[idx].baseline; + if (!memcmp(state, &nullentitystate, sizeof(entity_state_t))) + continue; //no need + MSG_WriteStaticOrBaseLine(&net_message, idx, state, cl.protocol_pext2, cl.protocol, cl.protocolflags); + + if (net_message.cursize > 4096) + { //periodically flush so that large maps don't need larger than vanilla limits + CL_WriteDemoMessage(); + SZ_Clear (&net_message); + } + } + + //static ents + for (idx = 1; idx < cl.num_statics; idx++) + { + MSG_WriteStaticOrBaseLine(&net_message, -1, &cl.static_entities[idx]->baseline, cl.protocol_pext2, cl.protocol, cl.protocolflags); + + if (net_message.cursize > 4096) + { //periodically flush so that large maps don't need larger than vanilla limits + CL_WriteDemoMessage(); + SZ_Clear (&net_message); + } + } + + //static sounds + for (i = NUM_AMBIENTS; i < total_channels; i++) + { + channel_t *ss = &snd_channels[i]; + sfxcache_t *sc; + + if (!ss->sfx) + continue; + if (ss->entnum || ss->entchannel) + continue; //can't have been a static sound + sc = S_LoadSound(ss->sfx); + if (!sc || sc->loopstart == -1) + continue; //can't have been a (valid) static sound + + for (idx = 1; idx < MAX_SOUNDS && cl.sound_precache[idx]; idx++) + if (cl.sound_precache[idx] == ss->sfx) + break; + if (idx == MAX_SOUNDS) + continue; //can't figure out which sound it was + + MSG_WriteByte(&net_message, (idx > 255)?svc_spawnstaticsound2:svc_spawnstaticsound); + MSG_WriteCoord(&net_message, ss->origin[0], cl.protocolflags); + MSG_WriteCoord(&net_message, ss->origin[1], cl.protocolflags); + MSG_WriteCoord(&net_message, ss->origin[2], cl.protocolflags); + if (idx > 255) + MSG_WriteShort(&net_message, idx); + else + MSG_WriteByte(&net_message, idx); + MSG_WriteByte(&net_message, ss->master_vol); + MSG_WriteByte(&net_message, ss->dist_mult*1000*64); + + if (net_message.cursize > 4096) + { //periodically flush so that large maps don't need larger than vanilla limits + CL_WriteDemoMessage(); + SZ_Clear (&net_message); + } + } + +#ifdef PSET_SCRIPT + //particleindexes + for (idx = 0; idx < MAX_PARTICLETYPES; idx++) + { + if (!cl.particle_precache[idx].name) + continue; + MSG_WriteByte(&net_message, svcdp_precache); + MSG_WriteShort(&net_message, 0x4000 | idx); + MSG_WriteString(&net_message, cl.particle_precache[idx].name); + + if (net_message.cursize > 4096) + { //periodically flush so that large maps don't need larger than vanilla limits + CL_WriteDemoMessage(); + SZ_Clear (&net_message); + } + } +#endif + + MSG_WriteByte (&net_message, svc_signonnum); + MSG_WriteByte (&net_message, 2); + CL_WriteDemoMessage(); + SZ_Clear (&net_message); +} + +void CL_Record_Spawn(void) +{ + const char *cmd; + int i; + + // player names, colors, and frag counts + for (i = 0; i < cl.maxclients; i++) + { + MSG_WriteByte (&net_message, svc_updatename); + MSG_WriteByte (&net_message, i); + MSG_WriteString (&net_message, cl.scores[i].name); + MSG_WriteByte (&net_message, svc_updatefrags); + MSG_WriteByte (&net_message, i); + MSG_WriteShort (&net_message, cl.scores[i].frags); + MSG_WriteByte (&net_message, svc_updatecolors); + MSG_WriteByte (&net_message, i); + MSG_WriteByte (&net_message, cl.scores[i].colors); + } + + // send all current light styles + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + if (*cl_lightstyle[i].map) + { + MSG_WriteByte (&net_message, svc_lightstyle); + MSG_WriteByte (&net_message, i); + MSG_WriteString (&net_message, cl_lightstyle[i].map); + } + + if (net_message.cursize > 4096) + { //periodically flush so that large maps don't need larger than vanilla limits + CL_WriteDemoMessage(); + SZ_Clear (&net_message); + } + } + + // what about the current CD track... future consideration. + + //if this mod is using dynamic fog, make sure we start with the right values. + cmd = Fog_GetFogCommand(); + if (cmd) + { + MSG_WriteByte (&net_message, svc_stufftext); + MSG_WriteString (&net_message, cmd); + } + + //stats + for (i = 0; i < MAX_CL_STATS; i++) + { + if (!cl.stats[i] && !cl.statsf[i]) + continue; + + if (net_message.cursize > 4096) + { //periodically flush so that large maps don't need larger than vanilla limits + CL_WriteDemoMessage(); + SZ_Clear (&net_message); + } + + if ((double)cl.stats[i] != cl.statsf[i] && (unsigned int)cl.stats[i] <= 0x00ffffff) + { //if the float representation seems to have more precision then use that, unless its getting huge in which case we're probably getting fpu truncation, so go back to more compatible ints + MSG_WriteByte (&net_message, svcfte_updatestatfloat); + MSG_WriteByte (&net_message, i); + MSG_WriteFloat (&net_message, cl.statsf[i]); + } + else if (cl.stats[i] >= 0 && cl.stats[i] <= 255 && (cl.protocol_pext2 & PEXT2_PREDINFO)) + { + MSG_WriteByte (&net_message, svcdp_updatestatbyte); + MSG_WriteByte (&net_message, i); + MSG_WriteByte (&net_message, cl.stats[i]); + } + else + { + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, i); + MSG_WriteLong (&net_message, cl.stats[i]); + } + } + + // view entity + MSG_WriteByte (&net_message, svc_setview); + MSG_WriteShort (&net_message, cl.viewentity); + + // signon + MSG_WriteByte (&net_message, svc_signonnum); + MSG_WriteByte (&net_message, 3); + + CL_WriteDemoMessage(); + SZ_Clear (&net_message); + + //ask the server to reset entity deltas. yes this means playback will wait a couple of frames before it actually starts playing but oh well. + if (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + { + cl.ackframes_count = 0; + cl.ackframes[cl.ackframes_count++] = -1; + } +} + /* ==================== CL_Record_f @@ -260,6 +472,23 @@ void CL_Record_f (void) Con_Printf("Can't record - try again when connected\n"); return; } + switch(cl.protocol) + { + case PROTOCOL_NETQUAKE: + case PROTOCOL_FITZQUAKE: + case PROTOCOL_RMQ: + case PROTOCOL_VERSION_BJP3: + break; + //case PROTOCOL_VERSION_NEHD: + //case PROTOCOL_VERSION_DP5: + //case PROTOCOL_VERSION_DP6: + case PROTOCOL_VERSION_DP7: + //case PROTOCOL_VERSION_BJP1: + //case PROTOCOL_VERSION_BJP2: + default: + Con_Printf("Can not record - protocol not supported for recording mid-map\nClient demo recording must be started before connecting\n"); + return; + } } // write the forced cd track number, or -1 @@ -286,11 +515,14 @@ void CL_Record_f (void) // open the demo file COM_AddExtension (name, ".dem", sizeof(name)); + Cvar_SetROM(cl_recordingdemo.name, name); + Con_Printf ("recording to %s.\n", name); cls.demofile = fopen (name, "wb"); if (!cls.demofile) { Con_Printf ("ERROR: couldn't create %s\n", name); + Cvar_SetROM(cl_recordingdemo.name, ""); return; } @@ -304,66 +536,14 @@ void CL_Record_f (void) { byte *data = net_message.data; int cursize = net_message.cursize; - int i; + byte weirdaltbufferthatprobablyisntneeded[NET_MAXMESSAGE]; - for (i = 0; i < 2; i++) - { - net_message.data = demo_head[i]; - net_message.cursize = demo_head_size[i]; - CL_WriteDemoMessage(); - } - - net_message.data = demo_head[2]; + net_message.data = weirdaltbufferthatprobablyisntneeded; SZ_Clear (&net_message); - // current names, colors, and frag counts - for (i = 0; i < cl.maxclients; i++) - { - MSG_WriteByte (&net_message, svc_updatename); - MSG_WriteByte (&net_message, i); - MSG_WriteString (&net_message, cl.scores[i].name); - MSG_WriteByte (&net_message, svc_updatefrags); - MSG_WriteByte (&net_message, i); - MSG_WriteShort (&net_message, cl.scores[i].frags); - MSG_WriteByte (&net_message, svc_updatecolors); - MSG_WriteByte (&net_message, i); - MSG_WriteByte (&net_message, cl.scores[i].colors); - } - - // send all current light styles - for (i = 0; i < MAX_LIGHTSTYLES; i++) - { - MSG_WriteByte (&net_message, svc_lightstyle); - MSG_WriteByte (&net_message, i); - MSG_WriteString (&net_message, cl_lightstyle[i].map); - } - - // what about the CD track or SVC fog... future consideration. - MSG_WriteByte (&net_message, svc_updatestat); - MSG_WriteByte (&net_message, STAT_TOTALSECRETS); - MSG_WriteLong (&net_message, cl.stats[STAT_TOTALSECRETS]); - - MSG_WriteByte (&net_message, svc_updatestat); - MSG_WriteByte (&net_message, STAT_TOTALMONSTERS); - MSG_WriteLong (&net_message, cl.stats[STAT_TOTALMONSTERS]); - - MSG_WriteByte (&net_message, svc_updatestat); - MSG_WriteByte (&net_message, STAT_SECRETS); - MSG_WriteLong (&net_message, cl.stats[STAT_SECRETS]); - - MSG_WriteByte (&net_message, svc_updatestat); - MSG_WriteByte (&net_message, STAT_MONSTERS); - MSG_WriteLong (&net_message, cl.stats[STAT_MONSTERS]); - - // view entity - MSG_WriteByte (&net_message, svc_setview); - MSG_WriteShort (&net_message, cl.viewentity); - - // signon - MSG_WriteByte (&net_message, svc_signonnum); - MSG_WriteByte (&net_message, 3); - - CL_WriteDemoMessage(); + CL_Record_Serverdata(); + CL_Record_Prespawn(); + CL_Record_Spawn(); // restore net_message net_message.data = data; diff --git a/Quake/cl_input.c b/Quake/cl_input.c index 6e5fcdb5..35ba5d20 100644 --- a/Quake/cl_input.c +++ b/Quake/cl_input.c @@ -54,7 +54,7 @@ state bit 2 is edge triggered on the down to up transition kbutton_t in_mlook, in_klook; kbutton_t in_left, in_right, in_forward, in_back; kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; -kbutton_t in_strafe, in_speed, in_use, in_jump, in_attack; +kbutton_t in_strafe, in_speed, in_jump, in_attack, in_button3, in_button4, in_button5, in_button6, in_button7, in_button8; kbutton_t in_up, in_down; int in_impulse; @@ -156,11 +156,24 @@ void IN_StrafeUp(void) {KeyUp(&in_strafe);} void IN_AttackDown(void) {KeyDown(&in_attack);} void IN_AttackUp(void) {KeyUp(&in_attack);} -void IN_UseDown (void) {KeyDown(&in_use);} -void IN_UseUp (void) {KeyUp(&in_use);} +void IN_UseDown (void) {KeyDown(&in_button3);} +void IN_UseUp (void) {KeyUp(&in_button3);} void IN_JumpDown (void) {KeyDown(&in_jump);} void IN_JumpUp (void) {KeyUp(&in_jump);} +void IN_Button3Down(void) {KeyDown(&in_button3);} +void IN_Button3Up(void) {KeyUp(&in_button3);} +void IN_Button4Down(void) {KeyDown(&in_button4);} +void IN_Button4Up(void) {KeyUp(&in_button4);} +void IN_Button5Down(void) {KeyDown(&in_button5);} +void IN_Button5Up(void) {KeyUp(&in_button5);} +void IN_Button6Down(void) {KeyDown(&in_button6);} +void IN_Button6Up(void) {KeyUp(&in_button6);} +void IN_Button7Down(void) {KeyDown(&in_button7);} +void IN_Button7Up(void) {KeyUp(&in_button7);} +void IN_Button8Down(void) {KeyDown(&in_button8);} +void IN_Button8Up(void) {KeyUp(&in_button8);} + void IN_Impulse (void) {in_impulse=Q_atoi(Cmd_Argv(1));} /* @@ -339,65 +352,134 @@ CL_SendMove */ void CL_SendMove (const usercmd_t *cmd) { - int i; - int bits; - sizebuf_t buf; - byte data[128]; + unsigned int i; + int bits; + sizebuf_t buf; + byte data[1024]; - buf.maxsize = 128; + buf.maxsize = sizeof(data); buf.cursize = 0; buf.data = data; - cl.cmd = *cmd; + for (i = 0; i < cl.ackframes_count; i++) + { + MSG_WriteByte(&buf, clcdp_ackframe); + MSG_WriteLong(&buf, cl.ackframes[i]); + } + cl.ackframes_count = 0; -// -// send the movement message -// - MSG_WriteByte (&buf, clc_move); + if (cmd) + { + int dump = buf.cursize; + cl.cmd = *cmd; - MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times + // + // send the movement message + // + MSG_WriteByte (&buf, clc_move); - for (i=0 ; i<3 ; i++) - //johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE - if (cl.protocol == PROTOCOL_NETQUAKE) - MSG_WriteAngle (&buf, cl.viewangles[i], cl.protocolflags); + if (cl.protocol == PROTOCOL_VERSION_DP7) + { + if (1) + MSG_WriteLong(&buf, 0); + else + MSG_WriteLong(&buf, cl.movemessages); + } + else if (cl.protocol_pext2 & PEXT2_PREDINFO) + MSG_WriteShort(&buf, cl.movemessages&0xffff); //server will ack this once it has been applied to the player's entity state + MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times + + for (i=0 ; i<3 ; i++) + //johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE + //spike -- nq+bjp3 use 8bit angles. all other supported protocols use 16bit ones. + //spike -- proquake servers bump client->server angles up to at least 16bit. this is safe because it only happens when both client+server advertise it, and because it never actually gets recorded into demos anyway. + //spike -- predinfo also always means 16bit angles, even if for some reason the server doesn't advertise proquake (like dp). + if ((cl.protocol == PROTOCOL_NETQUAKE || cl.protocol == PROTOCOL_VERSION_BJP3) && !NET_QSocketGetProQuakeAngleHack(cls.netcon) && !(cl.protocol_pext2 & PEXT2_PREDINFO)) + MSG_WriteAngle (&buf, cl.viewangles[i], cl.protocolflags); + else + MSG_WriteAngle16 (&buf, cl.viewangles[i], cl.protocolflags); + //johnfitz + + MSG_WriteShort (&buf, cmd->forwardmove); + MSG_WriteShort (&buf, cmd->sidemove); + MSG_WriteShort (&buf, cmd->upmove); + + // + // send button bits + // + bits = 0; + + if ( in_attack.state & 3 ) + bits |= 1; + in_attack.state &= ~2; + + if (in_jump.state & 3) + bits |= 2; + in_jump.state &= ~2; + + if (in_button3.state & 3) + bits |= 4; + in_button3.state &= ~2; + + if (in_button4.state & 3) + bits |= 8; + in_button4.state &= ~2; + + if (in_button5.state & 3) + bits |= 16; + in_button5.state &= ~2; + + if (in_button6.state & 3) + bits |= 32; + in_button6.state &= ~2; + + if (in_button7.state & 3) + bits |= 64; + in_button7.state &= ~2; + + if (in_button8.state & 3) + bits |= 128; + in_button8.state &= ~2; + + if (cl.protocol == PROTOCOL_VERSION_DP7) + { + MSG_WriteLong (&buf, bits); + MSG_WriteByte (&buf, in_impulse); + MSG_WriteShort(&buf, 32767);//cursor x + MSG_WriteShort(&buf, 32767);//cursor y + MSG_WriteFloat(&buf, r_refdef.vieworg[0]); //start (view pos) + MSG_WriteFloat(&buf, r_refdef.vieworg[1]); + MSG_WriteFloat(&buf, r_refdef.vieworg[2]); + MSG_WriteFloat(&buf, r_refdef.vieworg[0]); //impact + MSG_WriteFloat(&buf, r_refdef.vieworg[1]); + MSG_WriteFloat(&buf, r_refdef.vieworg[2]); + MSG_WriteShort(&buf, 0); //entity + } else - MSG_WriteAngle16 (&buf, cl.viewangles[i], cl.protocolflags); - //johnfitz + { + MSG_WriteByte (&buf, bits); + MSG_WriteByte (&buf, in_impulse); + } + in_impulse = 0; - MSG_WriteShort (&buf, cmd->forwardmove); - MSG_WriteShort (&buf, cmd->sidemove); - MSG_WriteShort (&buf, cmd->upmove); + // + // allways dump the first two message, because it may contain leftover inputs + // from the last level + // + if (++cl.movemessages <= 2) + buf.cursize = dump; + else + S_Voip_Transmit(clcfte_voicechat, &buf);/*Spike: Add voice data*/ + } + else + S_Voip_Transmit(clcfte_voicechat, NULL);/*Spike: Add voice data (with cl_voip_test anyway)*/ -// -// send button bits -// - bits = 0; + //fixme: nops if we're still connecting, or something. - if ( in_attack.state & 3 ) - bits |= 1; - in_attack.state &= ~2; - - if (in_jump.state & 3) - bits |= 2; - in_jump.state &= ~2; - - MSG_WriteByte (&buf, bits); - - MSG_WriteByte (&buf, in_impulse); - in_impulse = 0; - -// -// deliver the message -// - if (cls.demoplayback) - return; - -// -// allways dump the first two message, because it may contain leftover inputs -// from the last level -// - if (++cl.movemessages <= 2) + // + // deliver the message + // + if (cls.demoplayback || !buf.cursize) return; if (NET_SendUnreliableMessage (cls.netcon, &buf) == -1) @@ -442,6 +524,18 @@ void CL_InitInput (void) Cmd_AddCommand ("-attack", IN_AttackUp); Cmd_AddCommand ("+use", IN_UseDown); Cmd_AddCommand ("-use", IN_UseUp); + Cmd_AddCommand ("+button3", IN_Button3Down); + Cmd_AddCommand ("-button3", IN_Button3Up); + Cmd_AddCommand ("+button4", IN_Button4Down); + Cmd_AddCommand ("-button4", IN_Button4Up); + Cmd_AddCommand ("+button5", IN_Button5Down); + Cmd_AddCommand ("-button5", IN_Button5Up); + Cmd_AddCommand ("+button6", IN_Button6Down); + Cmd_AddCommand ("-button6", IN_Button6Up); + Cmd_AddCommand ("+button7", IN_Button7Down); + Cmd_AddCommand ("-button7", IN_Button7Up); + Cmd_AddCommand ("+button8", IN_Button8Down); + Cmd_AddCommand ("-button8", IN_Button8Up); Cmd_AddCommand ("+jump", IN_JumpDown); Cmd_AddCommand ("-jump", IN_JumpUp); Cmd_AddCommand ("impulse", IN_Impulse); diff --git a/Quake/cl_main.c b/Quake/cl_main.c index 5d870bd9..c9c1c823 100644 --- a/Quake/cl_main.c +++ b/Quake/cl_main.c @@ -24,6 +24,12 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" #include "bgmusic.h" +#include "arch_def.h" +#ifdef PLATFORM_UNIX +//for unlink +#include +#endif + // we need to declare some mouse variables here, because the menu system // references them even when on a unix system. @@ -48,21 +54,39 @@ cvar_t m_side = {"m_side","0.8", CVAR_ARCHIVE}; cvar_t cl_maxpitch = {"cl_maxpitch", "90", CVAR_ARCHIVE}; //johnfitz -- variable pitch clamping cvar_t cl_minpitch = {"cl_minpitch", "-90", CVAR_ARCHIVE}; //johnfitz -- variable pitch clamping +cvar_t cl_recordingdemo = {"cl_recordingdemo", "", CVAR_ROM}; //the name of the currently-recording demo. + client_static_t cls; client_state_t cl; // FIXME: put these on hunk? -entity_t cl_static_entities[MAX_STATIC_ENTITIES]; lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES]; dlight_t cl_dlights[MAX_DLIGHTS]; -entity_t *cl_entities; //johnfitz -- was a static array, now on hunk -int cl_max_edicts; //johnfitz -- only changes when new map loads - int cl_numvisedicts; -entity_t *cl_visedicts[MAX_VISEDICTS]; +int cl_maxvisedicts; +entity_t **cl_visedicts; extern cvar_t r_lerpmodels, r_lerpmove; //johnfitz +void CL_ClearTrailStates(void) +{ + int i; + for (i = 0; i < cl.num_statics; i++) + { + PScript_DelinkTrailstate(&(cl.static_entities[i]->trailstate)); + PScript_DelinkTrailstate(&(cl.static_entities[i]->emitstate)); + } + for (i = 0; i < cl.max_edicts; i++) + { + PScript_DelinkTrailstate(&(cl.entities[i].trailstate)); + PScript_DelinkTrailstate(&(cl.entities[i].emitstate)); + } + for (i = 0; i < MAX_BEAMS; i++) + { + PScript_DelinkTrailstate(&(cl_beams[i].trailstate)); + } +} + /* ===================== CL_ClearState @@ -74,6 +98,8 @@ void CL_ClearState (void) if (!sv.active) Host_ClearMemory (); + CL_ClearTrailStates(); + // wipe the entire cl structure memset (&cl, 0, sizeof(cl)); @@ -86,9 +112,14 @@ void CL_ClearState (void) memset (cl_beams, 0, sizeof(cl_beams)); //johnfitz -- cl_entities is now dynamically allocated - cl_max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); - cl_entities = (entity_t *) Hunk_AllocName (cl_max_edicts*sizeof(entity_t), "cl_entities"); + cl.max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); + cl.entities = (entity_t *) Hunk_AllocName (cl.max_edicts*sizeof(entity_t), "cl_entities"); //johnfitz + + cl.viewent.netstate = nullentitystate; +#ifdef PSET_SCRIPT + PScript_Shutdown(); +#endif } /* @@ -132,7 +163,12 @@ void CL_Disconnect (void) cls.demoplayback = cls.timedemo = false; cls.demopaused = false; cls.signon = 0; + if (cls.download.file) + fclose(cls.download.file); + memset(&cls.download, 0, sizeof(cls.download)); cl.intermission = 0; + cl.worldmodel = NULL; + cl.sendprespawn = false; } void CL_Disconnect_f (void) @@ -152,11 +188,20 @@ Host should be either "local" or a net address to be passed on */ void CL_EstablishConnection (const char *host) { + static char lasthost[NET_NAMELEN]; if (cls.state == ca_dedicated) return; if (cls.demoplayback) return; + if (!host) + { + host = lasthost; + if (!*host) + return; + } + else + q_strlcpy(lasthost, host, sizeof(lasthost)); CL_Disconnect (); @@ -188,12 +233,12 @@ void CL_SignonReply (void) { case 1: MSG_WriteByte (&cls.message, clc_stringcmd); - MSG_WriteString (&cls.message, "prespawn"); + MSG_WriteString (&cls.message, va("name \"%s\"\n", cl_name.string)); + + cl.sendprespawn = true; break; case 2: - MSG_WriteByte (&cls.message, clc_stringcmd); - MSG_WriteString (&cls.message, va("name \"%s\"\n", cl_name.string)); MSG_WriteByte (&cls.message, clc_stringcmd); MSG_WriteString (&cls.message, va("color %i %i\n", ((int)cl_color.value)>>4, ((int)cl_color.value)&15)); @@ -261,7 +306,7 @@ void CL_PrintEntities_f (void) if (cls.state != ca_connected) return; - for (i=0,ent=cl_entities ; imodel) @@ -398,6 +443,120 @@ float CL_LerpPoint (void) return frac; } +static qboolean CL_LerpEntity(entity_t *ent, vec3_t org, vec3_t ang, float frac) +{ + float f, d; + int j; + vec3_t delta; + qboolean teleported = false; + //figure out the pos+angles of the parent + if (ent->forcelink) + { // the entity was not updated in the last message + // so move to the final spot + VectorCopy (ent->msg_origins[0], org); + VectorCopy (ent->msg_angles[0], ang); + } + else + { // if the delta is large, assume a teleport and don't lerp + f = frac; + for (j=0 ; j<3 ; j++) + { + delta[j] = ent->msg_origins[0][j] - ent->msg_origins[1][j]; + if (delta[j] > 100 || delta[j] < -100) + { + f = 1; // assume a teleportation, not a motion + teleported = true; //johnfitz -- don't lerp teleports + } + } + + //johnfitz -- don't cl_lerp entities that will be r_lerped + if (r_lerpmove.value && (ent->lerpflags & LERP_MOVESTEP)) + f = 1; + //johnfitz + + // interpolate the origin and angles + for (j=0 ; j<3 ; j++) + { + org[j] = ent->msg_origins[1][j] + f*delta[j]; + + d = ent->msg_angles[0][j] - ent->msg_angles[1][j]; + if (d > 180) + d -= 360; + else if (d < -180) + d += 360; + ang[j] = ent->msg_angles[1][j] + f*d; + } + } + return teleported; +} + +static qboolean CL_AttachEntity(entity_t *ent, float frac) +{ + entity_t *parent; + vec3_t porg, pang; + vec3_t paxis[3]; + vec3_t tmp, fwd, up; + unsigned int tagent = ent->netstate.tagentity; + int runaway = 0; + + while(1) + { + if (!tagent) + return true; //nothing to do. + if (runaway++==10 || tagent >= (unsigned int)cl.num_entities) + return false; //parent isn't valid + parent = &cl.entities[tagent]; + if (!parent->model) + return false; + if (0)//tagent < ent-cl_entities) + { + tagent = parent->netstate.tagentity; + VectorCopy(parent->origin, porg); + VectorCopy(parent->angles, pang); + } + else + { + tagent = parent->netstate.tagentity; + CL_LerpEntity(parent, porg, pang, frac); + } + + //FIXME: this code needs to know the exact lerp info of the underlaying model. + //however for some idiotic reason, someone decided to figure out what should be displayed somewhere far removed from the code that deals with timing + //so we have absolutely no way to get a reliable origin + //in the meantime, r_lerpmove 0; r_lerpmodels 0 + //you might be able to work around it by setting the attached entity to movetype_step to match the attachee, and to avoid EF_MUZZLEFLASH. + //personally I'm just going to call it a quakespasm bug that I cba to fix. + + //FIXME: update porg+pang according to the tag index (we don't support md3s/iqms, so we don't need to do anything here yet) + + if (parent->model && parent->model->type == mod_alias) + pang[0] *= -1; + AngleVectors(pang, paxis[0], paxis[1], paxis[2]); + + if (ent->model && ent->model->type == mod_alias) + ent->angles[0] *= -1; + AngleVectors(ent->angles, fwd, tmp, up); + + //transform the origin + VectorMA(parent->origin, ent->origin[0], paxis[0], tmp); + VectorMA(tmp, -ent->origin[1], paxis[1], tmp); + VectorMA(tmp, ent->origin[2], paxis[2], ent->origin); + + //transform the forward vector + VectorMA(vec3_origin, fwd[0], paxis[0], tmp); + VectorMA(tmp, -fwd[1], paxis[1], tmp); + VectorMA(tmp, fwd[2], paxis[2], fwd); + //transform the up vector + VectorMA(vec3_origin, up[0], paxis[0], tmp); + VectorMA(tmp, -up[1], paxis[1], tmp); + VectorMA(tmp, up[2], paxis[2], up); + //regenerate the new angles. + VectorAngles(fwd, up, ent->angles); + if (ent->model && ent->model->type == mod_alias) + ent->angles[0] *= -1; + } +} + /* =============== CL_RelinkEntities @@ -407,15 +566,27 @@ void CL_RelinkEntities (void) { entity_t *ent; int i, j; - float frac, f, d; - vec3_t delta; + float frac, d; float bobjrotate; vec3_t oldorg; dlight_t *dl; + float frametime; + int modelflags; // determine partial update time frac = CL_LerpPoint (); + frametime = cl.time - cl.oldtime; + if (frametime < 0) + frametime = 0; + if (frametime > 0.1) + frametime = 0.1; + + if (cl_numvisedicts + 64 > cl_maxvisedicts) + { + cl_maxvisedicts = cl_maxvisedicts+64; + cl_visedicts = realloc(cl_visedicts, sizeof(*cl_visedicts)*cl_maxvisedicts); + } cl_numvisedicts = 0; // @@ -442,10 +613,10 @@ void CL_RelinkEntities (void) bobjrotate = anglemod(100*cl.time); // start on the entity after the world - for (i=1,ent=cl_entities+1 ; imodel) - { // empty slot + { // empty slot, ish. // ericw -- efrags are only used for static entities in GLQuake // ent can't be static, so this is a no-op. @@ -464,46 +635,22 @@ void CL_RelinkEntities (void) VectorCopy (ent->origin, oldorg); - if (ent->forcelink) - { // the entity was not updated in the last message - // so move to the final spot - VectorCopy (ent->msg_origins[0], ent->origin); - VectorCopy (ent->msg_angles[0], ent->angles); + if (CL_LerpEntity(ent, ent->origin, ent->angles, frac)) + ent->lerpflags |= LERP_RESETMOVE; + + if (ent->netstate.tagentity) + if (!CL_AttachEntity(ent, frac)) + { + //can't draw it if we don't know where its parent is. + continue; } - else - { // if the delta is large, assume a teleport and don't lerp - f = frac; - for (j=0 ; j<3 ; j++) - { - delta[j] = ent->msg_origins[0][j] - ent->msg_origins[1][j]; - if (delta[j] > 100 || delta[j] < -100) - { - f = 1; // assume a teleportation, not a motion - ent->lerpflags |= LERP_RESETMOVE; //johnfitz -- don't lerp teleports - } - } - //johnfitz -- don't cl_lerp entities that will be r_lerped - if (r_lerpmove.value && (ent->lerpflags & LERP_MOVESTEP)) - f = 1; - //johnfitz - - // interpolate the origin and angles - for (j=0 ; j<3 ; j++) - { - ent->origin[j] = ent->msg_origins[1][j] + f*delta[j]; - - d = ent->msg_angles[0][j] - ent->msg_angles[1][j]; - if (d > 180) - d -= 360; - else if (d < -180) - d += 360; - ent->angles[j] = ent->msg_angles[1][j] + f*d; - } - } + modelflags = (ent->effects>>24)&0xff; + if (!(ent->effects & EF_NOMODELFLAGS)) + modelflags |= ent->model->flags; // rotate binary objects locally - if (ent->model->flags & EF_ROTATE) + if (modelflags & EF_ROTATE) ent->angles[1] = bobjrotate; if (ent->effects & EF_BRIGHTFIELD) @@ -526,10 +673,10 @@ void CL_RelinkEntities (void) //johnfitz -- assume muzzle flash accompanied by muzzle flare, which looks bad when lerped if (r_lerpmodels.value != 2) { - if (ent == &cl_entities[cl.viewentity]) - cl.viewent.lerpflags |= LERP_RESETANIM|LERP_RESETANIM2; //no lerping for two frames - else - ent->lerpflags |= LERP_RESETANIM|LERP_RESETANIM2; //no lerping for two frames + if (ent == &cl.entities[cl.viewentity]) + cl.viewent.lerpflags |= LERP_RESETANIM|LERP_RESETANIM2; //no lerping for two frames + else + ent->lerpflags |= LERP_RESETANIM|LERP_RESETANIM2; //no lerping for two frames } //johnfitz } @@ -549,33 +696,95 @@ void CL_RelinkEntities (void) dl->die = cl.time + 0.001; } - if (ent->model->flags & EF_GIB) - R_RocketTrail (oldorg, ent->origin, 2); - else if (ent->model->flags & EF_ZOMGIB) - R_RocketTrail (oldorg, ent->origin, 4); - else if (ent->model->flags & EF_TRACER) - R_RocketTrail (oldorg, ent->origin, 3); - else if (ent->model->flags & EF_TRACER2) - R_RocketTrail (oldorg, ent->origin, 5); - else if (ent->model->flags & EF_ROCKET) +#ifdef PSET_SCRIPT + if (cl.paused) + ; + else if (ent->netstate.traileffectnum > 0 && ent->netstate.traileffectnum < MAX_PARTICLETYPES) { - R_RocketTrail (oldorg, ent->origin, 0); + vec3_t axis[3]; + AngleVectors(ent->angles, axis[0], axis[1], axis[2]); + PScript_ParticleTrail(oldorg, ent->origin, cl.particle_precache[ent->netstate.traileffectnum].index, i, axis, &ent->trailstate); + } + else if (ent->model->traileffect >= 0) + { + vec3_t axis[3]; + AngleVectors(ent->angles, axis[0], axis[1], axis[2]); + PScript_ParticleTrail(oldorg, ent->origin, ent->model->traileffect, i, axis, &ent->trailstate); + } + else +#endif + if (modelflags & EF_GIB) + { + if (PScript_EntParticleTrail(oldorg, ent, "TR_BLOOD")) + R_RocketTrail (oldorg, ent->origin, 2); + } + else if (modelflags & EF_ZOMGIB) + { + if (PScript_EntParticleTrail(oldorg, ent, "TR_SLIGHTBLOOD")) + R_RocketTrail (oldorg, ent->origin, 4); + } + else if (modelflags & EF_TRACER) + { + if (PScript_EntParticleTrail(oldorg, ent, "TR_WIZSPIKE")) + R_RocketTrail (oldorg, ent->origin, 3); + } + else if (modelflags & EF_TRACER2) + { + if (PScript_EntParticleTrail(oldorg, ent, "TR_KNIGHTSPIKE")) + R_RocketTrail (oldorg, ent->origin, 5); + } + else if (modelflags & EF_ROCKET) + { + if (PScript_EntParticleTrail(oldorg, ent, "TR_ROCKET")) + R_RocketTrail (oldorg, ent->origin, 0); dl = CL_AllocDlight (i); VectorCopy (ent->origin, dl->origin); dl->radius = 200; dl->die = cl.time + 0.01; } - else if (ent->model->flags & EF_GRENADE) - R_RocketTrail (oldorg, ent->origin, 1); - else if (ent->model->flags & EF_TRACER3) - R_RocketTrail (oldorg, ent->origin, 6); + else if (modelflags & EF_GRENADE) + { + if (PScript_EntParticleTrail(oldorg, ent, "TR_GRENADE")) + R_RocketTrail (oldorg, ent->origin, 1); + } + else if (modelflags & EF_TRACER3) + { + if (PScript_EntParticleTrail(oldorg, ent, "TR_VORESPIKE")) + R_RocketTrail (oldorg, ent->origin, 6); + } ent->forcelink = false; +#ifdef PSET_SCRIPT + if (ent->netstate.emiteffectnum > 0) + { + vec3_t axis[3]; + AngleVectors(ent->angles, axis[0], axis[1], axis[2]); + if (ent->model->type == mod_alias) + axis[0][2] *= -1; //stupid vanilla bug + PScript_RunParticleEffectState(ent->origin, axis[0], frametime, cl.particle_precache[ent->netstate.emiteffectnum].index, &ent->emitstate); + } + else if (ent->model->emiteffect >= 0) + { + vec3_t axis[3]; + AngleVectors(ent->angles, axis[0], axis[1], axis[2]); + if (ent->model->flags & MOD_EMITFORWARDS) + { + if (ent->model->type == mod_alias) + axis[0][2] *= -1; //stupid vanilla bug + } + else + VectorScale(axis[2], -1, axis[0]); + PScript_RunParticleEffectState(ent->origin, axis[0], frametime, ent->model->emiteffect, &ent->emitstate); + if (ent->model->flags & MOD_EMITREPLACE) + continue; + } +#endif + if (i == cl.viewentity && !chase_active.value) continue; - if (cl_numvisedicts < MAX_VISEDICTS) + if (cl_numvisedicts < cl_maxvisedicts) { cl_visedicts[cl_numvisedicts] = ent; cl_numvisedicts++; @@ -583,6 +792,248 @@ void CL_RelinkEntities (void) } } +#ifdef PSET_SCRIPT +int CL_GenerateRandomParticlePrecache(const char *pname) +{ //for dpp7 compat + size_t i; + pname = va("%s", pname); + for (i = 1; i < MAX_PARTICLETYPES; i++) + { + if (!cl.particle_precache[i].name) + { + cl.particle_precache[i].name = strcpy(Hunk_Alloc(strlen(pname)+1), pname); + cl.particle_precache[i].index = PScript_FindParticleType(cl.particle_precache[i].name); + return i; + } + if (!strcmp(cl.particle_precache[i].name, pname)) + return i; + } + return 0; +} +#endif + +//sent by the server to let us know that dp downloads can be used +void CL_ServerExtension_Download_f(void) +{ + if (Cmd_Argc() == 2) + cl.protocol_dpdownload = atoi(Cmd_Argv(1)); +} + +//sent by the server to let us know when its finished sending the entire file +void CL_Download_Finished_f(void) +{ + if (cls.download.file) + { + char finalpath[MAX_OSPATH]; + unsigned int size = strtoul(Cmd_Argv(1), NULL, 0); + unsigned int hash = strtoul(Cmd_Argv(2), NULL, 0); + //const char *fname = Cmd_Argv(3); + qboolean hashokay = false; + if (size == cls.download.size) + { + byte *tmp = malloc(size); + if (tmp) + { + fseek(cls.download.file, 0, SEEK_SET); + fread(tmp, 1, size, cls.download.file); + hashokay = (hash == CRC_Block(tmp, size)); + free(tmp); + + if (!hashokay) Con_Warning("Download hash failure\n"); + } + else Con_Warning("Download size too large\n"); + } + else Con_Warning("Download size mismatch\n"); + + fclose(cls.download.file); + cls.download.file = NULL; + if (hashokay) + { + q_snprintf (finalpath, sizeof(finalpath), "%s/%s", com_gamedir, cls.download.current); + rename(cls.download.temp, finalpath); + Con_SafePrintf("Downloaded %s: %u bytes\n", cls.download.current, cls.download.size); + } + else + { + Con_Warning("Download of %s failed\n", cls.download.current); + unlink(cls.download.temp); //kill the temp + } + } + + cls.download.active = false; +} +//sent by the server (or issued by the user) to stop the current download for any reason. +void CL_StopDownload_f(void) +{ + if (cls.download.file) + { + fclose(cls.download.file); + cls.download.file = NULL; + unlink(cls.download.temp); + +// Con_SafePrintf("Download cancelled\n", cl.download_current, cl.download_size); + } + cls.download.active = false; +} +//sent by the server to let us know that its going to start spamming us now. +void CL_Download_Begin_f(void) +{ + if (!cls.download.active) + return; + + if (cls.download.file) + CL_StopDownload_f(); + + //cl_downloadbegin size "name" + cls.download.size = strtoul(Cmd_Argv(1), NULL, 0); + + COM_CreatePath(cls.download.temp); + cls.download.file = fopen(cls.download.temp, "wb+"); //+ so we can read the data back to validate it + + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, "sv_startdownload\n"); +} + +void CL_Download_Data(void) +{ + byte *data; + unsigned int start, size; + start = MSG_ReadLong(); + size = (unsigned short)MSG_ReadShort(); + data = MSG_ReadData(size); + if (msg_badread) + return; + if (!cls.download.file) + return; //demo started mid-record? something weird anyway + + fseek(cls.download.file, start, SEEK_SET); + fwrite(data, 1, size, cls.download.file); + + Con_SafePrintf("Downloading %s: %g%%\r", cls.download.current, 100*(start+size) / (double)cls.download.size); + + //should maybe use unreliables, but whatever, shouldn't matter too much, it'll still complete + MSG_WriteByte(&cls.message, clcdp_ackdownloaddata); + MSG_WriteLong(&cls.message, start); + MSG_WriteShort(&cls.message, size); +} + +//returns true if we should block waiting for a download, false if there's no point. +qboolean CL_CheckDownload(const char *filename) +{ + if (sv.active) + return false; //no point downloading if we're the server... + if (*filename == '*') + return false; //don't download these... + if (cls.download.active) + return true; //block while we're already downloading something + if (!cl.protocol_dpdownload) + return false; //can't download anyway + if (*cls.download.current && !strcmp(cls.download.current, filename)) + return false; //if the previous download failed, don't endlessly retry. + if (COM_FileExists(filename, NULL)) + return false; //no need to download anything. + if (!COM_DownloadNameOkay(filename)) + return false; //diediedie + + cls.download.active = true; + q_strlcpy(cls.download.current, filename, sizeof(cls.download.current)); + q_snprintf (cls.download.temp, sizeof(cls.download.temp), "%s/%s.tmp", com_gamedir, filename); + Con_Printf("Downloading %s...\r", filename); + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, va("download \"%s\"\n", filename)); + return true; +} + +//download+load models and sounds as needed, once complete let the server know we're ready for the next stage. +//returning false will trigger nops. +qboolean CL_CheckDownloads(void) +{ + int i; + if (cl.model_download == 0 && cl.model_count && cl.model_name[1]) + { //haxors, download the lit first, but only if we don't already have the bsp + //this ensures that we don't keep requesting the lit for maps that just don't have one (although may be problematic if the first server we find deleted them all, but oh well) + char litname[MAX_QPATH]; + char *ext; + q_strlcpy(litname, cl.model_name[1], sizeof(litname)); + ext = (char*)COM_FileGetExtension(litname); + if (!q_strcasecmp(ext, "bsp")) + { + if (!COM_FileExists(litname, NULL)) + { + strcpy(ext, "lit"); + if (CL_CheckDownload(litname)) + return false; + } + } + cl.model_download++; + } + for (; cl.model_download < cl.model_count; ) + { + if (*cl.model_name[cl.model_download]) + { + if (CL_CheckDownload(cl.model_name[cl.model_download])) + return false; + cl.model_precache[cl.model_download] = Mod_ForName (cl.model_name[cl.model_download], false); + if (cl.model_precache[cl.model_download] == NULL) + { + Host_Error ("Model %s not found", cl.model_name[cl.model_download]); + } + } + cl.model_download++; + } + + for (; cl.sound_download < cl.sound_count; ) + { + if (*cl.sound_name[cl.sound_download]) + { + if (CL_CheckDownload(va("sound/%s", cl.sound_name[cl.sound_download]))) + return false; + cl.sound_precache[cl.sound_download] = S_PrecacheSound (cl.sound_name[cl.sound_download]); + } + cl.sound_download++; + } + + if (!cl.worldmodel && cl.model_count >= 2) + { + // local state + cl.entities[0].model = cl.worldmodel = cl.model_precache[1]; + if (cl.worldmodel->type != mod_brush) + { + if (cl.worldmodel->type == mod_ext_invalid) + Host_Error ("Worldmodel %s was not loaded", cl.model_name[1]); + else + Host_Error ("Worldmodel %s is not a brushmodel", cl.model_name[1]); + } + + //fixme: deal with skybox somehow + + R_NewMap (); + +#ifdef PSET_SCRIPT + //the protocol changing depending upon files found on the client's computer is of course a really shit way to design things + //especially when users have a nasty habit of changing config files. + if (cl.protocol == PROTOCOL_VERSION_DP7) + { + PScript_FindParticleType("effectinfo."); //make sure this is implicitly loaded. + COM_Effectinfo_Enumerate(CL_GenerateRandomParticlePrecache); + cl.protocol_particles = true; + } + else if (cl.protocol_pext2) + cl.protocol_particles = true; //doesn't have a pext flag of its own, but at least we know what it is. +#endif + } + + //make sure ents have the correct models, now that they're actually loaded. + for (i = 0; i < cl.num_statics; i++) + { + if (cl.static_entities[i]->model) + continue; + cl.static_entities[i]->model = cl.model_precache[cl.static_entities[i]->netstate.modelindex]; + R_AddEfrags (cl.static_entities[i]); + } + return true; +} + /* =============== @@ -627,7 +1078,7 @@ int CL_ReadFromServer (void) //visedicts if (cl_numvisedicts > 256 && dev_peakstats.visedicts <= 256) - Con_DWarning ("%i visedicts exceeds standard limit of 256 (max = %d).\n", cl_numvisedicts, MAX_VISEDICTS); + Con_DWarning ("%i visedicts exceeds standard limit of 256.\n", cl_numvisedicts); dev_stats.visedicts = cl_numvisedicts; dev_peakstats.visedicts = q_max(cl_numvisedicts, dev_peakstats.visedicts); @@ -686,6 +1137,8 @@ void CL_SendCmd (void) // send the unreliable message CL_SendMove (&cmd); } + else + CL_SendMove (NULL); if (cls.demoplayback) { @@ -755,15 +1208,30 @@ void CL_Viewpos_f (void) #else //player position Con_Printf ("Viewpos: (%i %i %i) %i %i %i\n", - (int)cl_entities[cl.viewentity].origin[0], - (int)cl_entities[cl.viewentity].origin[1], - (int)cl_entities[cl.viewentity].origin[2], + (int)cl.entities[cl.viewentity].origin[0], + (int)cl.entities[cl.viewentity].origin[1], + (int)cl.entities[cl.viewentity].origin[2], (int)cl.viewangles[PITCH], (int)cl.viewangles[YAW], (int)cl.viewangles[ROLL]); #endif } +static void CL_ServerExtension_FullServerinfo_f(void) +{ +// const char *newserverinfo = Cmd_Argv(1); +} +static void CL_ServerExtension_ServerinfoUpdate_f(void) +{ +// const char *newserverkey = Cmd_Argv(1); +// const char *newservervalue = Cmd_Argv(2); +} + +static void CL_ServerExtension_Ignore_f(void) +{ + Con_DPrintf2("Ignoring stufftext: %s\n", Cmd_Argv(0)); +} + /* ================= CL_Init @@ -803,6 +1271,7 @@ void CL_Init (void) Cvar_RegisterVariable (&cl_maxpitch); //johnfitz -- variable pitch clamping Cvar_RegisterVariable (&cl_minpitch); //johnfitz -- variable pitch clamping + Cvar_RegisterVariable (&cl_recordingdemo); //spike -- for mod hacks. combine with cvar_string or something Cmd_AddCommand ("entities", CL_PrintEntities_f); Cmd_AddCommand ("disconnect", CL_Disconnect_f); @@ -813,5 +1282,27 @@ void CL_Init (void) Cmd_AddCommand ("tracepos", CL_Tracepos_f); //johnfitz Cmd_AddCommand ("viewpos", CL_Viewpos_f); //johnfitz + + //spike -- add stubs to mute various invalid stuffcmds + Cmd_AddCommand_ServerCommand ("fullserverinfo", CL_ServerExtension_FullServerinfo_f); //spike + Cmd_AddCommand_ServerCommand ("svi", CL_ServerExtension_ServerinfoUpdate_f); //spike + Cmd_AddCommand_ServerCommand ("paknames", CL_ServerExtension_Ignore_f); //package names in use by the server (including gamedir+extension) + Cmd_AddCommand_ServerCommand ("paks", CL_ServerExtension_Ignore_f); //provides hashes to go with the paknames list + //Cmd_AddCommand_ServerCommand ("vwep", CL_ServerExtension_Ignore_f); //invalid for nq, provides an alternative list of model precaches for vweps. + //Cmd_AddCommand_ServerCommand ("at", CL_ServerExtension_Ignore_f); //invalid for nq, autotrack info for mvds + Cmd_AddCommand_ServerCommand ("wps", CL_ServerExtension_Ignore_f); //ktx/cspree weapon stats + Cmd_AddCommand_ServerCommand ("it", CL_ServerExtension_Ignore_f); //cspree item timers + Cmd_AddCommand_ServerCommand ("tinfo", CL_ServerExtension_Ignore_f); //ktx team info + Cmd_AddCommand_ServerCommand ("exectrigger", CL_ServerExtension_Ignore_f); //spike + Cmd_AddCommand_ServerCommand ("csqc_progname", CL_ServerExtension_Ignore_f); //spike + Cmd_AddCommand_ServerCommand ("csqc_progsize", CL_ServerExtension_Ignore_f); //spike + Cmd_AddCommand_ServerCommand ("csqc_progcrc", CL_ServerExtension_Ignore_f); //spike + Cmd_AddCommand_ServerCommand ("cl_fullpitch", CL_ServerExtension_Ignore_f); //spike + Cmd_AddCommand_ServerCommand ("pq_fullpitch", CL_ServerExtension_Ignore_f); //spike + + Cmd_AddCommand_ServerCommand ("cl_serverextension_download", CL_ServerExtension_Download_f); //spike + Cmd_AddCommand_ServerCommand ("cl_downloadbegin", CL_Download_Begin_f); //spike + Cmd_AddCommand_ServerCommand ("cl_downloadfinished", CL_Download_Finished_f); //spike + Cmd_AddCommand ("stopdownload", CL_StopDownload_f); //spike } diff --git a/Quake/cl_parse.c b/Quake/cl_parse.c index 75bacdaf..46131654 100644 --- a/Quake/cl_parse.c +++ b/Quake/cl_parse.c @@ -3,6 +3,7 @@ Copyright (C) 1996-2001 Id Software, Inc. Copyright (C) 2002-2009 John Fitzgibbons and others Copyright (C) 2007-2008 Kristian Duske Copyright (C) 2010-2014 QuakeSpasm developers +Copyright (C) 2016 Spike This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -25,7 +26,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" #include "bgmusic.h" -const char *svc_strings[] = +const char *svc_strings[128] = { "svc_bad", "svc_nop", @@ -54,7 +55,7 @@ const char *svc_strings[] = "svc_damage", // [byte] impact [byte] blood [vec3] from "svc_spawnstatic", - "OBSOLETE svc_spawnbinary", + /*"OBSOLETE svc_spawnbinary"*/"21 svc_spawnstatic_fte", "svc_spawnbaseline", "svc_temp_entity", // @@ -70,23 +71,102 @@ const char *svc_strings[] = "svc_sellscreen", "svc_cutscene", //johnfitz -- new server messages - "", // 35 - "", // 36 - "svc_skybox", // 37 // [string] skyname - "", // 38 - "", // 39 - "svc_bf", // 40 // no data - "svc_fog", // 41 // [byte] density [byte] red [byte] green [byte] blue [float] time - "svc_spawnbaseline2", //42 // support for large modelindex, large framenum, alpha, using flags - "svc_spawnstatic2", // 43 // support for large modelindex, large framenum, alpha, using flags - "svc_spawnstaticsound2", // 44 // [coord3] [short] samp [byte] vol [byte] aten - "", // 44 - "", // 45 - "", // 46 - "", // 47 - "", // 48 - "", // 49 + "35", // 35 + "36", // 36 + "svc_skybox_fitz", // 37 // [string] skyname + "38", // 38 + "39", // 39 + "svc_bf_fitz", // 40 // no data + "svc_fog_fitz", // 41 // [byte] density [byte] red [byte] green [byte] blue [float] time + "svc_spawnbaseline2_fitz", //42 // support for large modelindex, large framenum, alpha, using flags + "svc_spawnstatic2_fitz", // 43 // support for large modelindex, large framenum, alpha, using flags + "svc_spawnstaticsound2_fitz", // 44 // [coord3] [short] samp [byte] vol [byte] aten + "45", // 45 + "46", // 46 + "47", // 47 + "48", // 48 + "49", // 49 //johnfitz + +//spike -- particle stuff, and padded to 128 to avoid possible crashes. + "50 svc_downloaddata_dp", // 50 + "51 svc_updatestatbyte", // 51 + "52 svc_effect_dp", // 52 + "53 svc_effect2_dp", // 53 + "54 svc_precache", // 54 //[short] type+idx [string] name + "55 svc_baseline2_dp", // 55 + "56 svc_spawnstatic2_dp", // 56 + "57 svc_entities_dp", // 57 + "58 svc_csqcentities", // 58 + "59 svc_spawnstaticsound2_dp", // 59 + "60 svc_trailparticles", // 60 + "61 svc_pointparticles", // 61 + "62 svc_pointparticles1", // 62 + "63 svc_particle2_fte", // 63 + "64 svc_particle3_fte", // 64 + "65 svc_particle4_fte", // 65 + "66 svc_spawnbaseline_fte", // 66 + "67 svc_customtempent_fte", // 67 + "68 svc_selectsplitscreen_fte", // 68 + "69 svc_showpic_fte", // 69 + "70 svc_hidepic_fte", // 70 + "71 svc_movepic_fte", // 71 + "72 svc_updatepic_fte", // 72 + "73", // 73 + "74", // 74 + "75", // 75 + "76 svc_csqcentities_fte", // 76 + "77", // 77 + "78 svc_updatestatstring_fte", // 78 + "79 svc_updatestatfloat_fte", // 79 + "80", // 80 + "81", // 81 + "82", // 82 + "83 svc_cgamepacket_fte", // 83 + "84 svc_voicechat_fte", // 84 + "85 svc_setangledelta_fte", // 85 + "86 svc_updateentities_fte", // 86 + "87 svc_brushedit_fte", // 87 + "88 svc_updateseats_fte", // 88 + "89", // 89 + "90", // 90 + "91", // 91 + "92", // 92 + "93", // 93 + "94", // 94 + "95", // 95 + "96", // 96 + "97", // 97 + "98", // 98 + "99", // 99 + "100", // 100 + "101", // 101 + "102", // 102 + "103", // 103 + "104", // 104 + "105", // 105 + "106", // 106 + "107", // 107 + "108", // 108 + "109", // 109 + "110", // 110 + "111", // 111 + "112", // 112 + "113", // 113 + "114", // 114 + "115", // 115 + "116", // 116 + "117", // 117 + "118", // 118 + "119", // 119 + "120", // 120 + "121", // 121 + "122", // 122 + "123", // 123 + "124", // 124 + "125", // 125 + "126", // 126 + "127", // 127 }; qboolean warn_about_nehahra_protocol; //johnfitz @@ -111,17 +191,808 @@ entity_t *CL_EntityNum (int num) if (num >= cl.num_entities) { - if (num >= cl_max_edicts) //johnfitz -- no more MAX_EDICTS + if (num >= cl.max_edicts) //johnfitz -- no more MAX_EDICTS Host_Error ("CL_EntityNum: %i is an invalid number",num); while (cl.num_entities<=num) { - cl_entities[cl.num_entities].colormap = vid.colormap; - cl_entities[cl.num_entities].lerpflags |= LERP_RESETMOVE|LERP_RESETANIM; //johnfitz + cl.entities[cl.num_entities].baseline = nullentitystate; + cl.entities[cl.num_entities].colormap = vid.colormap; + cl.entities[cl.num_entities].lerpflags |= LERP_RESETMOVE|LERP_RESETANIM; //johnfitz cl.num_entities++; } } - return &cl_entities[num]; + return &cl.entities[num]; +} + + + + + +static int MSG_ReadEntity(void) +{ + int e = (unsigned short)MSG_ReadShort(); + if (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + { + if (e & 0x8000) + { + e = (e & 0x7fff) << 8; + e |= MSG_ReadByte(); + } + } + return e; +} + +static unsigned int CLFTE_ReadDelta(unsigned int entnum, entity_state_t *news, const entity_state_t *olds, const entity_state_t *baseline) +{ + unsigned int predbits = 0; + unsigned int bits; + + bits = MSG_ReadByte(); + if (bits & UF_EXTEND1) + bits |= MSG_ReadByte()<<8; + if (bits & UF_EXTEND2) + bits |= MSG_ReadByte()<<16; + if (bits & UF_EXTEND3) + bits |= MSG_ReadByte()<<24; + + if (cl_shownet.value >= 3) + Con_SafePrintf("%3i: Update %4i 0x%x\n", msg_readcount, entnum, bits); + + if (bits & UF_RESET) + { +// Con_Printf("%3i: Reset %i @ %i\n", msg_readcount, entnum, cls.netchan.incoming_sequence); + *news = *baseline; + } + else if (!olds) + { + /*reset got lost, probably the data will be filled in later - FIXME: we should probably ignore this entity*/ + Con_DPrintf("New entity %i without reset\n", entnum); + *news = nullentitystate; + } + else + *news = *olds; + + if (bits & UF_FRAME) + { + if (bits & UF_16BIT) + news->frame = MSG_ReadShort(); + else + news->frame = MSG_ReadByte(); + } + + if (bits & UF_ORIGINXY) + { + news->origin[0] = MSG_ReadCoord(cl.protocolflags); + news->origin[1] = MSG_ReadCoord(cl.protocolflags); + } + if (bits & UF_ORIGINZ) + news->origin[2] = MSG_ReadCoord(cl.protocolflags); + + if ((bits & UF_PREDINFO) && !(cl.protocol_pext2 & PEXT2_PREDINFO)) + { + //predicted stuff gets more precise angles + if (bits & UF_ANGLESXZ) + { + news->angles[0] = MSG_ReadAngle16(cl.protocolflags); + news->angles[2] = MSG_ReadAngle16(cl.protocolflags); + } + if (bits & UF_ANGLESY) + news->angles[1] = MSG_ReadAngle16(cl.protocolflags); + } + else + { + if (bits & UF_ANGLESXZ) + { + news->angles[0] = MSG_ReadAngle(cl.protocolflags); + news->angles[2] = MSG_ReadAngle(cl.protocolflags); + } + if (bits & UF_ANGLESY) + news->angles[1] = MSG_ReadAngle(cl.protocolflags); + } + + if ((bits & (UF_EFFECTS | UF_EFFECTS2)) == (UF_EFFECTS | UF_EFFECTS2)) + news->effects = MSG_ReadLong(); + else if (bits & UF_EFFECTS2) + news->effects = (unsigned short)MSG_ReadShort(); + else if (bits & UF_EFFECTS) + news->effects = MSG_ReadByte(); + +// news->movement[0] = 0; +// news->movement[1] = 0; +// news->movement[2] = 0; + news->velocity[0] = 0; + news->velocity[1] = 0; + news->velocity[2] = 0; + if (bits & UF_PREDINFO) + { + predbits = MSG_ReadByte(); + + if (predbits & UFP_FORWARD) + /*news->movement[0] =*/ MSG_ReadShort(); + //else + // news->movement[0] = 0; + if (predbits & UFP_SIDE) + /*news->movement[1] =*/ MSG_ReadShort(); + //else + // news->movement[1] = 0; + if (predbits & UFP_UP) + /*news->movement[2] =*/ MSG_ReadShort(); + //else + // news->movement[2] = 0; + if (predbits & UFP_MOVETYPE) + news->pmovetype = MSG_ReadByte(); + if (predbits & UFP_VELOCITYXY) + { + news->velocity[0] = MSG_ReadShort(); + news->velocity[1] = MSG_ReadShort(); + } + else + { + news->velocity[0] = 0; + news->velocity[1] = 0; + } + if (predbits & UFP_VELOCITYZ) + news->velocity[2] = MSG_ReadShort(); + else + news->velocity[2] = 0; + if (predbits & UFP_MSEC) //the msec value is how old the update is (qw clients normally predict without the server running an update every frame) + /*news->msec =*/ MSG_ReadByte(); + //else + // news->msec = 0; + + if (cl.protocol_pext2 & PEXT2_PREDINFO) + { + if (predbits & UFP_VIEWANGLE) + { + if (bits & UF_ANGLESXZ) + { + /*news->vangle[0] =*/ MSG_ReadShort(); + /*news->vangle[2] =*/ MSG_ReadShort(); + } + if (bits & UF_ANGLESY) + /*news->vangle[1] =*/ MSG_ReadShort(); + } + } + else + { + if (predbits & UFP_WEAPONFRAME_OLD) + { + int wframe; + wframe = MSG_ReadByte(); + if (wframe & 0x80) + wframe = (wframe & 127) | (MSG_ReadByte()<<7); + } + } + } + else + { + //news->msec = 0; + } + + if (!(predbits & UFP_VIEWANGLE) || !(cl.protocol_pext2 & PEXT2_PREDINFO)) + {/* + if (bits & UF_ANGLESXZ) + news->vangle[0] = ANGLE2SHORT(news->angles[0] * ((bits & UF_PREDINFO)?-3:-1)); + if (bits & UF_ANGLESY) + news->vangle[1] = ANGLE2SHORT(news->angles[1]); + if (bits & UF_ANGLESXZ) + news->vangle[2] = ANGLE2SHORT(news->angles[2]); + */ + } + + if (bits & UF_MODEL) + { + if (bits & UF_16BIT) + news->modelindex = MSG_ReadShort(); + else + news->modelindex = MSG_ReadByte(); + } + if (bits & UF_SKIN) + { + if (bits & UF_16BIT) + news->skin = MSG_ReadShort(); + else + news->skin = MSG_ReadByte(); + } + if (bits & UF_COLORMAP) + news->colormap = MSG_ReadByte(); + + if (bits & UF_SOLID) + { //knowing the size of an entity is important for prediction + //without prediction, its a bit pointless. + if (cl.protocol_pext2 & PEXT2_NEWSIZEENCODING) + { + byte enc = MSG_ReadByte(); + if (enc == 0) + /*solidsize = ES_SOLID_NOT*/; + else if (enc == 1) + /*solidsize = ES_SOLID_BSP*/; + else if (enc == 2) + /*solidsize = ES_SOLID_HULL1*/; + else if (enc == 3) + /*solidsize = ES_SOLID_HULL2*/; + else if (enc == 16) + /*solidsize = */MSG_ReadShort();//MSG_ReadSize16(&net_message); + else if (enc == 32) + /*solidsize = */MSG_ReadLong(); + else + Sys_Error("Solid+Size encoding not known"); + } + else + /*solidsize =*/ MSG_ReadShort();//MSG_ReadSize16(&net_message); +// news->solidsize = solidsize; + } + + if (bits & UF_FLAGS) + news->eflags = MSG_ReadByte(); + + if (bits & UF_ALPHA) + news->alpha = (MSG_ReadByte()+1)&0xff; + if (bits & UF_SCALE) + news->scale = MSG_ReadByte(); + if (bits & UF_BONEDATA) + { + unsigned char fl = MSG_ReadByte(); + if (fl & 0x80) + { + //this is NOT finalized + int i; + int bonecount = MSG_ReadByte(); + //short *bonedata = AllocateBoneSpace(newp, bonecount, &news->boneoffset); + for (i = 0; i < bonecount*7; i++) + /*bonedata[i] =*/ MSG_ReadShort(); + //news->bonecount = bonecount; + } + //else + //news->bonecount = 0; //oo, it went away. + if (fl & 0x40) + { + /*news->basebone =*/ MSG_ReadByte(); + /*news->baseframe =*/ MSG_ReadShort(); + } + /*else + { + news->basebone = 0; + news->baseframe = 0; + }*/ + + //fixme: basebone, baseframe, etc. + if (fl & 0x3f) + Host_EndGame("unsupported entity delta info\n"); + } +// else if (news->bonecount) +// { //still has bone data from the previous frame. +// short *bonedata = AllocateBoneSpace(newp, news->bonecount, &news->boneoffset); +// memcpy(bonedata, oldp->bonedata+olds->boneoffset, sizeof(short)*7*news->bonecount); +// } + + if (bits & UF_DRAWFLAGS) + { + int drawflags = MSG_ReadByte(); + if ((drawflags & /*MLS_MASK*/7) == /*MLS_ABSLIGHT*/7) + /*news->abslight =*/ MSG_ReadByte(); + //else + // news->abslight = 0; + //news->drawflags = drawflags; + } + if (bits & UF_TAGINFO) + { + news->tagentity = MSG_ReadEntity(); + news->tagindex = MSG_ReadByte(); + } + if (bits & UF_LIGHT) + { + /*news->light[0] =*/ MSG_ReadShort(); + /*news->light[1] =*/ MSG_ReadShort(); + /*news->light[2] =*/ MSG_ReadShort(); + /*news->light[3] =*/ MSG_ReadShort(); + /*news->lightstyle =*/ MSG_ReadByte(); + /*news->lightpflags =*/ MSG_ReadByte(); + } + if (bits & UF_TRAILEFFECT) + { + unsigned short v = MSG_ReadShort(); + news->emiteffectnum = 0; + news->traileffectnum = v & 0x3fff; + if (v & 0x8000) + news->emiteffectnum = MSG_ReadShort() & 0x3fff; + if (news->traileffectnum >= MAX_PARTICLETYPES) + news->traileffectnum = 0; + if (news->emiteffectnum >= MAX_PARTICLETYPES) + news->emiteffectnum = 0; + } + + if (bits & UF_COLORMOD) + { + news->colormod[0] = MSG_ReadByte(); + news->colormod[1] = MSG_ReadByte(); + news->colormod[2] = MSG_ReadByte(); + } + if (bits & UF_GLOW) + { + /*news->glowsize =*/ MSG_ReadByte(); + /*news->glowcolour =*/ MSG_ReadByte(); + /*news->glowmod[0] =*/ MSG_ReadByte(); + /*news->glowmod[1] =*/ MSG_ReadByte(); + /*news->glowmod[2] =*/ MSG_ReadByte(); + } + if (bits & UF_FATNESS) + /*news->fatness =*/ MSG_ReadByte(); + if (bits & UF_MODELINDEX2) + { + if (bits & UF_16BIT) + /*news->modelindex2 =*/ MSG_ReadShort(); + else + /*news->modelindex2 =*/ MSG_ReadByte(); + } + if (bits & UF_GRAVITYDIR) + { + /*news->gravitydir[0] =*/ MSG_ReadByte(); + /*news->gravitydir[1] =*/ MSG_ReadByte(); + } + if (bits & UF_UNUSED2) + { + Host_EndGame("UF_UNUSED2 bit\n"); + } + if (bits & UF_UNUSED1) + { + Host_EndGame("UF_UNUSED1 bit\n"); + } + return bits; +} +static void CLFTE_ParseBaseline(entity_state_t *es) +{ + CLFTE_ReadDelta(0, es, &nullentitystate, &nullentitystate); +} + +//called with both fte+dp deltas +static void CL_EntitiesDeltaed(void) +{ + int i, newnum; + qmodel_t *model; + qboolean forcelink; + entity_t *ent; + int skin; + + for (newnum = 1; newnum < cl.num_entities; newnum++) + { + ent = CL_EntityNum(newnum); + if (!ent->update_type) + continue; //not interested in this one + + if (ent->msgtime != cl.mtime[1]) + forcelink = true; // no previous frame to lerp from + else + forcelink = false; + + //johnfitz -- lerping + if (ent->msgtime + 0.2 < cl.mtime[0]) //more than 0.2 seconds since the last message (most entities think every 0.1 sec) + ent->lerpflags |= LERP_RESETANIM; //if we missed a think, we'd be lerping from the wrong frame + + ent->msgtime = cl.mtime[0]; + + ent->frame = ent->netstate.frame; + + i = ent->netstate.colormap; + if (!i) + ent->colormap = vid.colormap; + else + { + if (i > cl.maxclients) + Sys_Error ("i >= cl.maxclients"); + ent->colormap = cl.scores[i-1].translations; + } + skin = ent->netstate.skin; + if (skin != ent->skinnum) + { + ent->skinnum = skin; + if (newnum > 0 && newnum <= cl.maxclients) + R_TranslateNewPlayerSkin (newnum - 1); //johnfitz -- was R_TranslatePlayerSkin + } + ent->effects = ent->netstate.effects; + + // shift the known values for interpolation + VectorCopy (ent->msg_origins[0], ent->msg_origins[1]); + VectorCopy (ent->msg_angles[0], ent->msg_angles[1]); + + ent->msg_origins[0][0] = ent->netstate.origin[0]; + ent->msg_angles[0][0] = ent->netstate.angles[0]; + ent->msg_origins[0][1] = ent->netstate.origin[1]; + ent->msg_angles[0][1] = ent->netstate.angles[1]; + ent->msg_origins[0][2] = ent->netstate.origin[2]; + ent->msg_angles[0][2] = ent->netstate.angles[2]; + + //johnfitz -- lerping for movetype_step entities + if (ent->netstate.eflags & EFLAGS_STEP) + { + ent->lerpflags |= LERP_MOVESTEP; + ent->forcelink = true; + } + else + ent->lerpflags &= ~LERP_MOVESTEP; + + ent->alpha = ent->netstate.alpha; + if (ent->alpha == 255) + ent->alpha = 0; //allows it to use r_wateralpha etc if its a water brush. +/* if (bits & U_LERPFINISH) + { + ent->lerpfinish = ent->msgtime + ((float)(MSG_ReadByte()) / 255); + ent->lerpflags |= LERP_FINISH; + } + else*/ + ent->lerpflags &= ~LERP_FINISH; + + model = cl.model_precache[ent->netstate.modelindex]; + if (model != ent->model) + { + ent->model = model; + // automatic animation (torches, etc) can be either all together + // or randomized + if (model) + { + if (model->synctype == ST_RAND) + ent->syncbase = (float)(rand()&0x7fff) / 0x7fff; + else + ent->syncbase = 0.0; + } + else + forcelink = true; // hack to make null model players work + if (newnum > 0 && newnum <= cl.maxclients) + R_TranslateNewPlayerSkin (newnum - 1); //johnfitz -- was R_TranslatePlayerSkin + + ent->lerpflags |= LERP_RESETANIM; //johnfitz -- don't lerp animation across model changes + } + + if ( forcelink ) + { // didn't have an update last message + VectorCopy (ent->msg_origins[0], ent->msg_origins[1]); + VectorCopy (ent->msg_origins[0], ent->origin); + VectorCopy (ent->msg_angles[0], ent->msg_angles[1]); + VectorCopy (ent->msg_angles[0], ent->angles); + ent->forcelink = true; + } + } +} + +static void CLFTE_ParseEntitiesUpdate(void) +{ + int newnum; + qboolean removeflag; + entity_t *ent; + float newtime; + + //so the server can know when we got it, and guess which frames we didn't get + if (cl.ackframes_count < sizeof(cl.ackframes)/sizeof(cl.ackframes[0])) + cl.ackframes[cl.ackframes_count++] = NET_QSocketGetSequenceIn(cls.netcon); + + if (cl.protocol_pext2 & PEXT2_PREDINFO) + MSG_ReadShort(); //an ack from our input sequences. strictly ascending-or-equal + + newtime = MSG_ReadFloat (); + if (newtime != cl.mtime[0]) + { //don't mess up lerps if the server is splitting entities into multiple packets. + cl.mtime[1] = cl.mtime[0]; + cl.mtime[0] = newtime; + } + + for (;;) + { + newnum = (unsigned short)(short)MSG_ReadShort(); + removeflag = !!(newnum & 0x8000); + if (newnum & 0x4000) + newnum = (newnum & 0x3fff) | (MSG_ReadByte()<<14); + else + newnum &= ~0x8000; + + if ((!newnum && !removeflag) || msg_badread) + break; + + ent = CL_EntityNum(newnum); + + if (removeflag) + { + if (cl_shownet.value >= 3) + Con_SafePrintf("%3i: Remove %i\n", msg_readcount, newnum); + + if (!newnum) + { + /*removal of world - means forget all entities, aka a full reset*/ + if (cl_shownet.value >= 3) + Con_SafePrintf("%3i: Reset all\n", msg_readcount); + for (newnum = 1; newnum < cl.num_entities; newnum++) + { + CL_EntityNum(newnum)->netstate.pmovetype = 0; + CL_EntityNum(newnum)->model = NULL; + } + cl.requestresend = false; //we got it. + continue; + } + ent->update_type = false; //no longer valid + ent->model = NULL; + continue; + } + else + { + CLFTE_ReadDelta(newnum, &ent->netstate, ent->update_type?&ent->netstate:NULL, &ent->baseline); + ent->update_type = true; + } + } + + CL_EntitiesDeltaed(); + + if (cl.protocol_pext2 & PEXT2_PREDINFO) + { //stats should normally be sent before the entity data. + VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); + ent = CL_EntityNum(cl.viewentity); + cl.mvelocity[0][0] = ent->netstate.velocity[0]*(1/8.0); + cl.mvelocity[0][1] = ent->netstate.velocity[1]*(1/8.0); + cl.mvelocity[0][2] = ent->netstate.velocity[2]*(1/8.0); + cl.onground = (ent->netstate.eflags & EFLAGS_ONGROUND)?true:false; + + + cl.punchangle[0] = cl.statsf[STAT_PUNCHANGLE_X]; + cl.punchangle[1] = cl.statsf[STAT_PUNCHANGLE_Y]; + cl.punchangle[2] = cl.statsf[STAT_PUNCHANGLE_Z]; + if (v_punchangles[0][0] != cl.punchangle[0] || v_punchangles[0][1] != cl.punchangle[1] || v_punchangles[0][2] != cl.punchangle[2]) + { + VectorCopy (v_punchangles[0], v_punchangles[1]); + VectorCopy (cl.punchangle, v_punchangles[0]); + } + } + + if (!cl.requestresend) + { + if (cls.signon == SIGNONS - 1) + { // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } + } +} + + +//darkplaces protocols 5 to 7 use these +#define E5_FULLUPDATE (1<<0) +#define E5_ORIGIN (1<<1) +#define E5_ANGLES (1<<2) +#define E5_MODEL (1<<3) + +#define E5_FRAME (1<<4) +#define E5_SKIN (1<<5) +#define E5_EFFECTS (1<<6) +#define E5_EXTEND1 (1<<7) + +#define E5_FLAGS (1<<8) +#define E5_ALPHA (1<<9) +#define E5_SCALE (1<<10) +#define E5_ORIGIN32 (1<<11) + +#define E5_ANGLES16 (1<<12) +#define E5_MODEL16 (1<<13) +#define E5_COLORMAP (1<<14) +#define E5_EXTEND2 (1<<15) + +#define E5_ATTACHMENT (1<<16) +#define E5_LIGHT (1<<17) +#define E5_GLOW (1<<18) +#define E5_EFFECTS16 (1<<19) + +#define E5_EFFECTS32 (1<<20) +#define E5_FRAME16 (1<<21) +#define E5_COLORMOD (1<<22) +#define E5_EXTEND3 (1<<23) + +#define E5_GLOWMOD (1<<24) +#define E5_COMPLEXANIMATION (1<<25) +#define E5_TRAILEFFECTNUM (1<<26) +#define E5_UNUSED27 (1<<27) +#define E5_UNUSED28 (1<<28) +#define E5_UNUSED29 (1<<29) +#define E5_UNUSED30 (1<<30) +#define E5_EXTEND4 (1<<31) + +#define E5_ALLUNUSED (E5_UNUSED27|E5_UNUSED28|E5_UNUSED29|E5_UNUSED30|E5_EXTEND4) +#define E5_ALLUNSUPPORTED (E5_LIGHT|E5_GLOW|E5_GLOWMOD|E5_COMPLEXANIMATION) + +static void CLDP_ReadDelta(unsigned int entnum, entity_state_t *s, const entity_state_t *olds, const entity_state_t *baseline) +{ + unsigned int bits = MSG_ReadByte(); + if (bits & E5_EXTEND1) + { + bits |= MSG_ReadByte() << 8; + if (bits & E5_EXTEND2) + { + bits |= MSG_ReadByte() << 16; + if (bits & E5_EXTEND3) + bits |= MSG_ReadByte() << 24; + } + } + if (bits & (E5_ALLUNSUPPORTED|E5_ALLUNUSED)) + { + if (bits & E5_ALLUNUSED) + Host_Error ("E5 update contains unknown bits %x", bits & E5_ALLUNUSED); + else + Con_DPrintf ("E5 update contains unsupported bits %x", bits & E5_ALLUNSUPPORTED); + } + + if (bits & E5_FULLUPDATE) + { +// Con_Printf("%3i: Reset %i @ %i\n", msg_readcount, entnum, cls.netchan.incoming_sequence); + *s = *baseline; + } + else if (!olds) + { + /*reset got lost, probably the data will be filled in later - FIXME: we should probably ignore this entity*/ + Con_DPrintf("New entity %i without reset\n", entnum); + *s = nullentitystate; + } + else + *s = *olds; + + if (bits & E5_FLAGS) + { + int i = MSG_ReadByte(); + s->eflags = i; + } + if (bits & E5_ORIGIN) + { + if (bits & E5_ORIGIN32) + { + s->origin[0] = MSG_ReadFloat(); + s->origin[1] = MSG_ReadFloat(); + s->origin[2] = MSG_ReadFloat(); + } + else + { + s->origin[0] = MSG_ReadShort()*(1/8.0f); + s->origin[1] = MSG_ReadShort()*(1/8.0f); + s->origin[2] = MSG_ReadShort()*(1/8.0f); + } + } + if (bits & E5_ANGLES) + { + if (bits & E5_ANGLES16) + { + s->angles[0] = MSG_ReadAngle(PRFL_SHORTANGLE); + s->angles[1] = MSG_ReadAngle(PRFL_SHORTANGLE); + s->angles[2] = MSG_ReadAngle(PRFL_SHORTANGLE); + } + else + { + s->angles[0] = MSG_ReadChar() * (360.0/256); + s->angles[1] = MSG_ReadChar() * (360.0/256); + s->angles[2] = MSG_ReadChar() * (360.0/256); + } + } + if (bits & E5_MODEL) + { + if (bits & E5_MODEL16) + s->modelindex = (unsigned short) MSG_ReadShort(); + else + s->modelindex = MSG_ReadByte(); + } + if (bits & E5_FRAME) + { + if (bits & E5_FRAME16) + s->frame = (unsigned short) MSG_ReadShort(); + else + s->frame = MSG_ReadByte(); + } + if (bits & E5_SKIN) + s->skin = MSG_ReadByte(); + if (bits & E5_EFFECTS) + { + if (bits & E5_EFFECTS32) + s->effects = (unsigned int) MSG_ReadLong(); + else if (bits & E5_EFFECTS16) + s->effects = (unsigned short) MSG_ReadShort(); + else + s->effects = MSG_ReadByte(); + } + if (bits & E5_ALPHA) + s->alpha = MSG_ReadByte()+1; + if (bits & E5_SCALE) + s->scale = MSG_ReadByte(); + if (bits & E5_COLORMAP) + s->colormap = MSG_ReadByte(); + if (bits & E5_ATTACHMENT) + { + s->tagentity = MSG_ReadEntity(); + s->tagindex = MSG_ReadByte(); + } + if (bits & E5_LIGHT) + { + /*s->light[0] =*/ MSG_ReadShort(); + /*s->light[1] =*/ MSG_ReadShort(); + /*s->light[2] =*/ MSG_ReadShort(); + /*s->light[3] =*/ MSG_ReadShort(); + /*s->lightstyle =*/ MSG_ReadByte(); + /*s->lightpflags =*/ MSG_ReadByte(); + } + if (bits & E5_GLOW) + { + /*s->glowsize =*/ MSG_ReadByte(); + /*s->glowcolour =*/ MSG_ReadByte(); + } + if (bits & E5_COLORMOD) + { + s->colormod[0] = MSG_ReadByte(); + s->colormod[1] = MSG_ReadByte(); + s->colormod[2] = MSG_ReadByte(); + } + if (bits & E5_GLOWMOD) + { + /*s->glowmod[0] =*/ MSG_ReadByte(); + /*s->glowmod[1] =*/ MSG_ReadByte(); + /*s->glowmod[2] =*/ MSG_ReadByte(); + } + if (bits & E5_COMPLEXANIMATION) + { + int type = MSG_ReadByte(); + int i, numbones; + if (type == 4) + { + /*modelindex = */MSG_ReadShort(); + numbones = MSG_ReadByte(); + for (i = 0; i < numbones*7; i++) + /*bonedata[i] =*/ MSG_ReadShort(); + } + else if (type < 4) + { //n-way blends + type++; + for (i = 0; i < type; i++) + /*frame = */MSG_ReadShort(); + for (i = 0; i < type; i++) + /*age = */MSG_ReadShort(); + for (i = 0; i < type; i++) + /*frac = */(type==1)?255:MSG_ReadByte(); + } + else + Host_Error("E5_COMPLEXANIMATION: Parse error - unknown type %i\n", type); + } + if (bits & E5_TRAILEFFECTNUM) + s->traileffectnum = MSG_ReadShort(); +} + +//dpp5-7 compat +static void CLDP_ParseEntitiesUpdate(void) +{ + entity_t *ent; + unsigned short id; + int ack; + ack = MSG_ReadLong(); //delta sequence number (must be acked) + if (cl.ackframes_count < sizeof(cl.ackframes)/sizeof(cl.ackframes[0])) + cl.ackframes[cl.ackframes_count++] = ack; + MSG_ReadLong(); //input sequence ack + + for(;;) + { + id = MSG_ReadShort(); + if (msg_badread) + break; + if (id & 0x8000) + { + id &= ~0x8000; + if (!id) + break; //no more + ent = CL_EntityNum(id); + ent->update_type = false; + ent->model = NULL; + } + else + { + ent = CL_EntityNum(id); + CLDP_ReadDelta(id, &ent->netstate, ent->update_type?&ent->netstate:NULL, &ent->baseline); + ent->update_type = true; + } + } + + if (cls.signon == SIGNONS - 1) + { // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } } @@ -130,7 +1001,7 @@ entity_t *CL_EntityNum (int num) CL_ParseStartSoundPacket ================== */ -void CL_ParseStartSoundPacket(void) +static void CL_ParseStartSoundPacket(void) { vec3_t pos; int channel, ent; @@ -142,6 +1013,13 @@ void CL_ParseStartSoundPacket(void) field_mask = MSG_ReadByte(); + if (cl.protocol == PROTOCOL_VERSION_BJP3) + field_mask |= SND_LARGESOUND; + + //spike -- extra channel flags + if (field_mask & SND_FTE_MOREFLAGS) + field_mask |= MSG_ReadByte()<<8; + if (field_mask & SND_VOLUME) volume = MSG_ReadByte (); else @@ -152,6 +1030,32 @@ void CL_ParseStartSoundPacket(void) else attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + //fte's sound extensions + if (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + { + //spike -- our mixer can't deal with these, so just parse and ignore + if (field_mask & SND_FTE_PITCHADJ) + MSG_ReadByte(); //percentage + if (field_mask & SND_FTE_TIMEOFS) + MSG_ReadShort(); //in ms + if (field_mask & SND_FTE_VELOCITY) + { + MSG_ReadShort(); //1/8th + MSG_ReadShort(); //1/8th + MSG_ReadShort(); //1/8th + } + } + else if (field_mask & (SND_FTE_MOREFLAGS|SND_FTE_PITCHADJ|SND_FTE_TIMEOFS)) + Con_Warning("Unknown meaning for sound flags\n"); + //dp's sound extension + if (cl.protocol == PROTOCOL_VERSION_DP7 || (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + { + if (field_mask & SND_DP_PITCH) + MSG_ReadShort(); + } + else if (field_mask & SND_DP_PITCH) + Con_Warning("Unknown meaning for sound flags\n"); + //johnfitz -- PROTOCOL_FITZQUAKE if (field_mask & SND_LARGEENTITY) { @@ -176,7 +1080,7 @@ void CL_ParseStartSoundPacket(void) Host_Error ("CL_ParseStartSoundPacket: %i > MAX_SOUNDS", sound_num); //johnfitz - if (ent > cl_max_edicts) //johnfitz -- no more MAX_EDICTS + if (ent > cl.max_edicts) //johnfitz -- no more MAX_EDICTS Host_Error ("CL_ParseStartSoundPacket: ent = %i", ent); for (i = 0; i < 3; i++) @@ -185,6 +1089,7 @@ void CL_ParseStartSoundPacket(void) S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0, attenuation); } +#if 0 /* ================== CL_KeepaliveMessage @@ -194,7 +1099,7 @@ so the server doesn't disconnect. ================== */ static byte net_olddata[NET_MAXMESSAGE]; -void CL_KeepaliveMessage (void) +static void CL_KeepaliveMessage (void) { float time; static float lastmsg; @@ -247,19 +1152,20 @@ void CL_KeepaliveMessage (void) NET_SendMessage (cls.netcon, &cls.message); SZ_Clear (&cls.message); } +#endif /* ================== CL_ParseServerInfo ================== */ -void CL_ParseServerInfo (void) +static void CL_ParseServerInfo (void) { const char *str; int i; - int nummodels, numsounds; - char model_precache[MAX_MODELS][MAX_QPATH]; - char sound_precache[MAX_SOUNDS][MAX_QPATH]; + qboolean gamedirswitchwarning = false; + char gamedir[1024]; + char protname[64]; Con_DPrintf ("Serverinfo packet received.\n"); @@ -271,12 +1177,26 @@ void CL_ParseServerInfo (void) // // wipe the client_state_t struct // + i = cl.protocol_dpdownload; //for some absurd reason, this is sent just before the serverinfo, which just confuses everything. CL_ClearState (); + cl.protocol_dpdownload = i; // parse protocol version number - i = MSG_ReadLong (); + for(;;) + { + i = MSG_ReadLong (); + if (i == PROTOCOL_FTE_PEXT2) + { + cl.protocol_pext2 = MSG_ReadLong(); + if (cl.protocol_pext2 & ~PEXT2_SUPPORTED_CLIENT) + Host_Error ("Server returned FTE protocol extensions that are not supported (%#x)", cl.protocol_pext2 & ~PEXT2_SUPPORTED_CLIENT); + continue; + } + break; + } + //johnfitz -- support multiple protocols - if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) { + if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ && i != PROTOCOL_VERSION_BJP3 && i != PROTOCOL_VERSION_DP7) { Con_Printf ("\n"); //because there's no newline after serverinfo print Host_Error ("Server returned version %i, not %i or %i or %i", i, PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ); } @@ -295,8 +1215,20 @@ void CL_ParseServerInfo (void) Con_Warning("PROTOCOL_RMQ protocolflags %i contains unsupported flags\n", cl.protocolflags); } } + else if (cl.protocol == PROTOCOL_VERSION_DP7) + cl.protocolflags = PRFL_SHORTANGLE|PRFL_FLOATCOORD; else cl.protocolflags = 0; - + + *gamedir = 0; + if (cl.protocol_pext2 & PEXT2_PREDINFO) + { + q_strlcpy(gamedir, MSG_ReadString(), sizeof(gamedir)); + if (!COM_GameDirMatches(gamedir)) + { + gamedirswitchwarning = true; + } + } + // parse maxclients cl.maxclients = MSG_ReadByte (); if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) @@ -317,7 +1249,48 @@ void CL_ParseServerInfo (void) Con_Printf ("%c%s\n", 2, str); //johnfitz -- tell user which protocol this is - Con_Printf ("Using protocol %i\n", i); + if (developer.value) + { + //spike: be a little more verbose about it + switch(cl.protocol) + { + case PROTOCOL_VERSION_DP7: + q_snprintf(protname, sizeof(protname), "%i(dpp7)", cl.protocol); + break; + case PROTOCOL_VERSION_BJP3: + q_snprintf(protname, sizeof(protname), "%i(bjp3)", cl.protocol); + break; + case PROTOCOL_RMQ: + q_snprintf(protname, sizeof(protname), "%i(rmq)", cl.protocol); + break; + case PROTOCOL_FITZQUAKE: + q_snprintf(protname, sizeof(protname), "%i(fitz)", cl.protocol); + break; + case PROTOCOL_NETQUAKE: + if (NET_QSocketGetProQuakeAngleHack(cls.netcon)) + q_snprintf(protname, sizeof(protname), "%i(proquake)", cl.protocol); + else + q_snprintf(protname, sizeof(protname), "%i(vanilla)", cl.protocol); + break; + default: + q_snprintf(protname, sizeof(protname), "%i", cl.protocol); + break; + } + if (cl.protocol_pext2) + { + if (cl.protocol == PROTOCOL_NETQUAKE) + *protname = 0; + else + q_strlcat(protname, "+", sizeof(protname)); + q_strlcat(protname, va("fte2(%#x)", cl.protocol_pext2), sizeof(protname)); + } + } + else if (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + q_snprintf(protname, sizeof(protname), "fte%i", cl.protocol); + else + q_snprintf(protname, sizeof(protname), "%i", cl.protocol); + Con_Printf ("Using protocol %s", protname); + Con_Printf ("\n"); // first we go through and touch all of the precache data that still // happens to be in the cache, so precaching something else doesn't @@ -325,73 +1298,49 @@ void CL_ParseServerInfo (void) // precache models memset (cl.model_precache, 0, sizeof(cl.model_precache)); - for (nummodels = 1 ; ; nummodels++) + for (cl.model_count = 1 ; ; cl.model_count++) { str = MSG_ReadString (); if (!str[0]) break; - if (nummodels==MAX_MODELS) + if (cl.model_count==MAX_MODELS) { Host_Error ("Server sent too many model precaches"); } - q_strlcpy (model_precache[nummodels], str, MAX_QPATH); + q_strlcpy (cl.model_name[cl.model_count], str, MAX_QPATH); Mod_TouchModel (str); } //johnfitz -- check for excessive models - if (nummodels >= 256) - Con_DWarning ("%i models exceeds standard limit of 256 (max = %d).\n", nummodels, MAX_MODELS); + if (cl.model_count >= 256) + Con_DWarning ("%i models exceeds standard limit of 256 (max = %d).\n", cl.model_count, MAX_MODELS); //johnfitz // precache sounds memset (cl.sound_precache, 0, sizeof(cl.sound_precache)); - for (numsounds = 1 ; ; numsounds++) + for (cl.sound_count = 1 ; ; cl.sound_count++) { str = MSG_ReadString (); if (!str[0]) break; - if (numsounds==MAX_SOUNDS) + if (cl.sound_count==MAX_SOUNDS) { Host_Error ("Server sent too many sound precaches"); } - q_strlcpy (sound_precache[numsounds], str, MAX_QPATH); + q_strlcpy (cl.sound_name[cl.sound_count], str, MAX_QPATH); S_TouchSound (str); } //johnfitz -- check for excessive sounds - if (numsounds >= 256) - Con_DWarning ("%i sounds exceeds standard limit of 256 (max = %d).\n", numsounds, MAX_SOUNDS); - //johnfitz + if (cl.sound_count >= 256) + Con_DWarning ("%i sounds exceeds standard limit of 256 (max = %d).\n", cl.sound_count, MAX_SOUNDS); // // now we try to load everything else until a cache allocation fails // // copy the naked name of the map file to the cl structure -- O.S - COM_StripExtension (COM_SkipPath(model_precache[1]), cl.mapname, sizeof(cl.mapname)); - - for (i = 1; i < nummodels; i++) - { - cl.model_precache[i] = Mod_ForName (model_precache[i], false); - if (cl.model_precache[i] == NULL) - { - Host_Error ("Model %s not found", model_precache[i]); - } - CL_KeepaliveMessage (); - } - - S_BeginPrecaching (); - for (i = 1; i < numsounds; i++) - { - cl.sound_precache[i] = S_PrecacheSound (sound_precache[i]); - CL_KeepaliveMessage (); - } - S_EndPrecaching (); - -// local state - cl_entities[0].model = cl.worldmodel = cl.model_precache[1]; - - R_NewMap (); + COM_StripExtension (COM_SkipPath(cl.model_name[1]), cl.mapname, sizeof(cl.mapname)); //johnfitz -- clear out string; we don't consider identical //messages to be duplicates if the map has changed in between @@ -408,6 +1357,22 @@ void CL_ParseServerInfo (void) memset(&dev_stats, 0, sizeof(dev_stats)); memset(&dev_peakstats, 0, sizeof(dev_peakstats)); memset(&dev_overflows, 0, sizeof(dev_overflows)); + + cl.requestresend = true; + cl.ackframes_count = 0; + if (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + cl.ackframes[cl.ackframes_count++] = -1; + + //this is here, to try to make sure its a little more obvious that its there. + if (gamedirswitchwarning) + { + Con_Warning("Server is using a different gamedir.\n"); + Con_Warning("Current: %s\n", COM_GetGameNames(false)); + Con_Warning("Server: %s\n", gamedir); + Con_Warning("You will probably want to switch gamedir to match the server.\n"); + } + + S_Voip_MapChange(); } /* @@ -419,7 +1384,7 @@ If an entities model or origin changes from frame to frame, it must be relinked. Other attributes can change without relinking. ================== */ -void CL_ParseUpdate (int bits) +static void CL_ParseUpdate (int bits) { int i; qmodel_t *model; @@ -469,10 +1434,14 @@ void CL_ParseUpdate (int bits) //johnfitz ent->msgtime = cl.mtime[0]; + ent->netstate = ent->baseline; if (bits & U_MODEL) { - modnum = MSG_ReadByte (); + if (cl.protocol == PROTOCOL_VERSION_BJP3) + modnum = MSG_ReadShort (); + else + modnum = MSG_ReadByte (); if (modnum >= MAX_MODELS) Host_Error ("CL_ParseModel: bad modnum"); } @@ -560,7 +1529,7 @@ void CL_ParseUpdate (int bits) else ent->alpha = ent->baseline.alpha; if (bits & U_SCALE) - MSG_ReadByte(); // PROTOCOL_RMQ: currently ignored + ent->netstate.scale = MSG_ReadByte(); // PROTOCOL_RMQ if (bits & U_FRAME2) ent->frame = (ent->frame & 0x00FF) | (MSG_ReadByte() << 8); if (bits & U_MODEL2) @@ -573,14 +1542,14 @@ void CL_ParseUpdate (int bits) else ent->lerpflags &= ~LERP_FINISH; } - else if (cl.protocol == PROTOCOL_NETQUAKE) + else if (cl.protocol == PROTOCOL_NETQUAKE || cl.protocol == PROTOCOL_VERSION_BJP3) { - //HACK: if this bit is set, assume this is PROTOCOL_NEHAHRA + //HACK: if this bit is set, assume this is PROTOCOL_NEHAHRA instead of PROTOCOL_NETQUAKE if (bits & U_TRANS) { float a, b; - if (warn_about_nehahra_protocol) + if (cl.protocol == PROTOCOL_NETQUAKE && warn_about_nehahra_protocol) { Con_Warning ("nonstandard update bit, assuming Nehahra protocol\n"); warn_about_nehahra_protocol = false; @@ -589,12 +1558,17 @@ void CL_ParseUpdate (int bits) a = MSG_ReadFloat(); b = MSG_ReadFloat(); //alpha if (a == 2) - MSG_ReadFloat(); //fullbright (not using this yet) + { + if (MSG_ReadFloat() >= 0.5) //parse fullbright, even if we don't use it yet. + ent->effects |= EF_FULLBRIGHT; + } ent->alpha = ENTALPHA_ENCODE(b); } else ent->alpha = ent->baseline.alpha; } + else + ent->alpha = ent->baseline.alpha; //johnfitz //johnfitz -- moved here from above @@ -635,13 +1609,26 @@ void CL_ParseUpdate (int bits) CL_ParseBaseline ================== */ -void CL_ParseBaseline (entity_t *ent, int version) //johnfitz -- added argument +static void CL_ParseBaseline (entity_t *ent, int version) //johnfitz -- added argument { int i; int bits; //johnfitz + if (version == 6) + { + CLFTE_ParseBaseline(&ent->baseline); + return; + } + + ent->baseline = nullentitystate; + //johnfitz -- PROTOCOL_FITZQUAKE - bits = (version == 2) ? MSG_ReadByte() : 0; + if (cl.protocol == PROTOCOL_VERSION_BJP3 && version == 1) + bits = B_LARGEMODEL; + else if (version == 7) + bits = B_LARGEMODEL|B_LARGEFRAME; //dpp7's spawnstatic2 + else + bits = (version == 2) ? MSG_ReadByte() : 0; ent->baseline.modelindex = (bits & B_LARGEMODEL) ? MSG_ReadShort() : MSG_ReadByte(); ent->baseline.frame = (bits & B_LARGEFRAME) ? MSG_ReadShort() : MSG_ReadByte(); //johnfitz @@ -665,7 +1652,7 @@ CL_ParseClientdata Server information pertaining to this client only ================== */ -void CL_ParseClientdata (void) +static void CL_ParseClientdata (void) { int i, j; int bits; //johnfitz @@ -679,26 +1666,33 @@ void CL_ParseClientdata (void) bits |= (MSG_ReadByte() << 24); //johnfitz + if (cl.protocol != PROTOCOL_VERSION_DP7) + bits |= SU_ITEMS; + if (bits & SU_VIEWHEIGHT) - cl.viewheight = MSG_ReadChar (); - else - cl.viewheight = DEFAULT_VIEWHEIGHT; + cl.stats[STAT_VIEWHEIGHT] = MSG_ReadChar (); + else if (cl.protocol != PROTOCOL_VERSION_DP7) + cl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT; if (bits & SU_IDEALPITCH) - cl.idealpitch = MSG_ReadChar (); + cl.statsf[STAT_IDEALPITCH] = MSG_ReadChar (); else - cl.idealpitch = 0; + cl.statsf[STAT_IDEALPITCH] = 0; VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); for (i = 0; i < 3; i++) { if (bits & (SU_PUNCH1<= MAX_STATIC_ENTITIES) - Host_Error ("Too many static entities"); + if (i >= cl.max_static_entities) + { + int ec = 64; + entity_t **newstatics = realloc(cl.static_entities, sizeof(*newstatics) * (cl.max_static_entities+ec)); + entity_t *newents = Hunk_Alloc(sizeof(*newents) * ec); + if (!newstatics || !newents) + Host_Error ("Too many static entities"); + cl.static_entities = newstatics; + while (ec--) + cl.static_entities[cl.max_static_entities++] = newents++; + } - ent = &cl_static_entities[i]; + ent = cl.static_entities[i]; cl.num_statics++; CL_ParseBaseline (ent, version); //johnfitz -- added second parameter // copy it to the current state + ent->netstate = ent->baseline; + + ent->trailstate = NULL; + ent->emitstate = NULL; ent->model = cl.model_precache[ent->baseline.modelindex]; ent->lerpflags |= LERP_RESETANIM; //johnfitz -- lerping ent->frame = ent->baseline.frame; @@ -897,7 +1907,8 @@ void CL_ParseStatic (int version) //johnfitz -- added a parameter VectorCopy (ent->baseline.origin, ent->origin); VectorCopy (ent->baseline.angles, ent->angles); - R_AddEfrags (ent); + if (ent->model) + R_AddEfrags (ent); } /* @@ -905,7 +1916,7 @@ void CL_ParseStatic (int version) //johnfitz -- added a parameter CL_ParseStaticSound =================== */ -void CL_ParseStaticSound (int version) //johnfitz -- added argument +static void CL_ParseStaticSound (int version) //johnfitz -- added argument { vec3_t org; int sound_num, vol, atten; @@ -927,9 +1938,278 @@ void CL_ParseStaticSound (int version) //johnfitz -- added argument S_StaticSound (cl.sound_precache[sound_num], org, vol, atten); } +/* +CL_ParsePrecache + +spike -- added this mostly for particle effects, but its also used for models+sounds (if needed) +*/ +static void CL_ParsePrecache(void) +{ + unsigned short code = MSG_ReadShort(); + unsigned int index = code&0x3fff; + const char *name = MSG_ReadString(); + switch((code>>14) & 0x3) + { + case 0: //models + if (index < MAX_MODELS) + { + cl.model_precache[index] = Mod_ForName (name, false); + //FIXME: if its a bsp model, generate lightmaps. + //FIXME: update static entities with that modelindex + } + break; +#ifdef PSET_SCRIPT + case 1: //particles + if (index < MAX_PARTICLETYPES) + { + if (*name) + { + cl.particle_precache[index].name = strcpy(Hunk_Alloc(strlen(name)+1), name); + cl.particle_precache[index].index = PScript_FindParticleType(cl.particle_precache[index].name); + } + else + { + cl.particle_precache[index].name = NULL; + cl.particle_precache[index].index = -1; + } + } + break; +#endif + case 2: //sounds + if (index < MAX_SOUNDS) + cl.sound_precache[index] = S_PrecacheSound (name); + break; +// case 3: //unused + default: + Con_Warning("CL_ParsePrecache: unsupported precache type\n"); + break; + } +} +#ifdef PSET_SCRIPT +int CL_GenerateRandomParticlePrecache(const char *pname); +//small function for simpler reuse +static void CL_ForceProtocolParticles(void) +{ + cl.protocol_particles = true; + PScript_FindParticleType("effectinfo."); //make sure this is implicitly loaded. + COM_Effectinfo_Enumerate(CL_GenerateRandomParticlePrecache); + Con_Warning("Received svcdp_pointparticles1 but extension not active"); +} + +/* +CL_RegisterParticles +called when the particle system has changed, and any cached indexes are now probably stale. +*/ +void CL_RegisterParticles(void) +{ + extern qmodel_t mod_known[]; + extern int mod_numknown; + int i; + + if (cl.protocol == PROTOCOL_VERSION_DP7) //dpp7 sucks. + PScript_FindParticleType("effectinfo."); //make sure this is implicitly loaded. + + //make sure the precaches know the right effects + for (i = 0; i < MAX_PARTICLETYPES; i++) + { + if (cl.particle_precache[i].name) + cl.particle_precache[i].index = PScript_FindParticleType(cl.particle_precache[i].name); + else + cl.particle_precache[i].index = -1; + } + + //and make sure models get the right effects+trails etc too + for (i = 0; i < mod_numknown; i++) + PScript_UpdateModelEffects(&mod_known[i]); +} + +/* +CL_ParseParticles + +spike -- this handles the various ssqc builtins (the ones that were based on csqc) +*/ +static void CL_ParseParticles(int type) +{ + vec3_t org, vel; + if (type < 0) + { //trail + entity_t *ent; + int entity = MSG_ReadShort(); + int efnum = MSG_ReadShort(); + org[0] = MSG_ReadCoord(cl.protocolflags); + org[1] = MSG_ReadCoord(cl.protocolflags); + org[2] = MSG_ReadCoord(cl.protocolflags); + vel[0] = MSG_ReadCoord(cl.protocolflags); + vel[1] = MSG_ReadCoord(cl.protocolflags); + vel[2] = MSG_ReadCoord(cl.protocolflags); + + ent = CL_EntityNum(entity); + + if (efnum < MAX_PARTICLETYPES && cl.particle_precache[efnum].name) + PScript_ParticleTrail(org, vel, cl.particle_precache[efnum].index, 0, NULL, &ent->trailstate); + } + else + { //point + int efnum = MSG_ReadShort(); + int count; + org[0] = MSG_ReadCoord(cl.protocolflags); + org[1] = MSG_ReadCoord(cl.protocolflags); + org[2] = MSG_ReadCoord(cl.protocolflags); + if (type) + { + vel[0] = vel[1] = vel[2] = 0; + count = 1; + } + else + { + vel[0] = MSG_ReadCoord(cl.protocolflags); + vel[1] = MSG_ReadCoord(cl.protocolflags); + vel[2] = MSG_ReadCoord(cl.protocolflags); + count = MSG_ReadShort(); + } + if (efnum < MAX_PARTICLETYPES && cl.particle_precache[efnum].name) + { + PScript_RunParticleEffectState (org, vel, count, cl.particle_precache[efnum].index, NULL); + } + } +} +#endif + #define SHOWNET(x) if(cl_shownet.value==2)Con_Printf ("%3i:%s\n", msg_readcount-1, x); +static void CL_ParseStatNumeric(int stat, int ival, float fval) +{ + if (stat < 0 || stat >= MAX_CL_STATS) + { + Con_DWarning ("svc_updatestat: %i is invalid\n", stat); + return; + } + cl.stats[stat] = ival; + cl.statsf[stat] = fval; + if (stat == STAT_VIEWZOOM) + vid.recalc_refdef = true; + //just assume that they all affect the hud + Sbar_Changed (); +} +static void CL_ParseStatFloat(int stat, float fval) +{ + CL_ParseStatNumeric(stat,fval,fval); +} +static void CL_ParseStatInt(int stat, int ival) +{ + CL_ParseStatNumeric(stat,ival,ival); +} + +//mods and servers might not send the \n instantly. +//some mods bug out and omit the \n entirely, this function helps prevent the damage from spreading too much. +//some servers or mods use //prefixed commands as extensions to avoid spam about unrecognised commands. +//proquake has its own extension coding thing. +static void CL_ParseStuffText(const char *msg) +{ + const char *str; + q_strlcat(cl.stuffcmdbuf, msg, sizeof(cl.stuffcmdbuf)); + while ((str = strchr(cl.stuffcmdbuf, '\n'))) + { + qboolean handled; + if (*cl.stuffcmdbuf == 0x01 && cl.protocol == PROTOCOL_NETQUAKE) //proquake message, just strip this and try again (doesn't necessarily have a trailing \n straight away) + { + for (str = cl.stuffcmdbuf+1; *str >= 0x01 && *str <= 0x1f; str++) + ;//FIXME: parse properly + memmove(cl.stuffcmdbuf, str, Q_strlen(str)+1); + continue; + } + str++;//skip past the \n + + //handle //prefixed commands + if (cl.stuffcmdbuf[0] == '/' && cl.stuffcmdbuf[1] == '/') + { + handled = Cmd_ExecuteString(cl.stuffcmdbuf+2, src_server); + if (!handled) + Con_DPrintf("Server sent unknown command %s\n", Cmd_Argv(1)); + } + else + handled = Cmd_ExecuteString(cl.stuffcmdbuf, src_server); + if (!handled) + Cbuf_AddTextLen(cl.stuffcmdbuf, str-cl.stuffcmdbuf); + memmove(cl.stuffcmdbuf, str, Q_strlen(str)+1); + } +} + +//warning: this text might not even be a complete line. +//we're screwed if someone names themselves something that triggers this or some such +//however, that's what has become standard for nq clients +static qboolean CL_ParseSpecialPrints(const char *printtext) +{ + const char *e = printtext+strlen(printtext); + if (cl.printtype == PRINT_PINGS) + { + //players are expected to be listed in slot order. + //names might need to be a little fuzzy due to some mods toying with svc_updatename. + //'unconnected' players might cause issues as they might be listed without being in . + const char *t = printtext; + char *n; + int ping; + while(*t == ' ') + t++; + ping = strtol(t, &n, 10); + if (t != n && *n == ' ' && e[-1] == '\n') + { + int i; + n++; + e--; + //warning: some servers might have set svc_playernames with extra text on the end that isn't known to the server itself, and thus won't appear here + //that text should at least be aligned such that the name part is padded to 15 chars + + if (!strcmp(n, "unconnected\n")) + return true; //just ignore unconnecteds, too many dupes etc + + for (i = cl.printplayer; i < MAX_SCOREBOARD; i++) + { + if (!*cl.scores[i].name) + continue; //player slot is empty + if (strncmp(cl.scores[i].name, n, e-n)) + continue; //reported name is screwy + + cl.scores[i++].ping = ping; + cl.printplayer = i; + return true; + } + } + cl.printtype = PRINT_NONE; + } + + if (!strcmp(printtext, "Client ping times:\n") && cl.expectingpingtimes > realtime) + { + cl.printtype = PRINT_PINGS; + cl.printplayer = 0; + return true; + } + + /*if (!strncmp(printtext, "host: ", 9) && cl.expectingstatus > Sys_DoubleTime()) + { + //host: *\n + // *:*\n + //players: \n\n + //#%i name frags time + // ipaddress + return true; + }*/ + + //check for chat messages of the form 'name: q_version' + if (!cls.demoplayback && *printtext == 1 && e-printtext > 13 && (!strcmp(e-12, ": f_version\n") || !strcmp(e-12, ": q_version\n"))) + { + if (realtime > cl.printversionresponse) + { + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString(&cls.message,va("say "ENGINE_NAME_AND_VER)); + cl.printversionresponse = realtime+20; + } + } + + return false; +} + /* ===================== CL_ParseServerMessage @@ -950,7 +2230,8 @@ void CL_ParseServerMessage (void) else if (cl_shownet.value == 2) Con_Printf ("------------------\n"); - cl.onground = false; // unless the server says otherwise + if (!(cl.protocol_pext2 & PEXT2_PREDINFO)) + cl.onground = false; // unless the server says otherwise // // parse the message // @@ -967,6 +2248,17 @@ void CL_ParseServerMessage (void) if (cmd == -1) { SHOWNET("END OF MESSAGE"); + + if (cl.items != cl.stats[STAT_ITEMS]) + { + for (i = 0; i < 32; i++) + if ( (cl.stats[STAT_ITEMS] & (1<= MAX_LIGHTSTYLES) - Sys_Error ("svc_lightstyle > MAX_LIGHTSTYLES"); - q_strlcpy (cl_lightstyle[i].map, MSG_ReadString(), MAX_STYLESTRING); - cl_lightstyle[i].length = Q_strlen(cl_lightstyle[i].map); - //johnfitz -- save extra info - if (cl_lightstyle[i].length) + str = MSG_ReadString(); + if ((unsigned)i < MAX_LIGHTSTYLES) { - total = 0; - cl_lightstyle[i].peak = 'a'; - for (j=0; j 128) - Con_DWarning ("%i static entities exceeds standard limit of 128 (max = %d).\n", cl.num_statics, MAX_STATIC_ENTITIES); + Con_DWarning ("%i static entities exceeds standard limit of 128.\n", cl.num_statics); R_CheckEfrags (); } //johnfitz @@ -1161,9 +2462,7 @@ void CL_ParseServerMessage (void) case svc_updatestat: i = MSG_ReadByte (); - if (i < 0 || i >= MAX_CL_STATS) - Sys_Error ("svc_updatestat: %i is invalid", i); - cl.stats[i] = MSG_ReadLong ();; + CL_ParseStatInt(i, MSG_ReadLong()); break; case svc_spawnstaticsound: @@ -1192,7 +2491,6 @@ void CL_ParseServerMessage (void) //johnfitz -- log centerprints to console str = MSG_ReadString (); SCR_CenterPrint (str); - Con_LogCenterPrint (str); //johnfitz break; @@ -1203,7 +2501,6 @@ void CL_ParseServerMessage (void) //johnfitz -- log centerprints to console str = MSG_ReadString (); SCR_CenterPrint (str); - Con_LogCenterPrint (str); //johnfitz break; @@ -1238,6 +2535,122 @@ void CL_ParseServerMessage (void) CL_ParseStaticSound (2); break; //johnfitz + + //spike -- for particles more than anything else + case svcdp_precache: + if (cl.protocol != PROTOCOL_VERSION_DP7 && !cl.protocol_pext2) + Host_Error ("Received svcdp_precache but extension not active"); + CL_ParsePrecache(); + break; +#ifdef PSET_SCRIPT + case svcdp_trailparticles: + if (!cl.protocol_particles) + CL_ForceProtocolParticles(); + CL_ParseParticles(-1); + break; + case svcdp_pointparticles: + if (!cl.protocol_particles) + CL_ForceProtocolParticles(); + CL_ParseParticles(0); + break; + case svcdp_pointparticles1: + if (!cl.protocol_particles) + CL_ForceProtocolParticles(); + CL_ParseParticles(1); + break; +#endif + + + case svcdp_effect: + case svcdp_effect2: + if (cl.protocol != PROTOCOL_VERSION_DP7) + Host_Error ("Received svcdp_effect[1|2] but extension not active"); + CL_ParseEffect(cmd==svcdp_effect2); + break; + case svcdp_csqcentities: + if (cl.protocol != PROTOCOL_VERSION_DP7) + Host_Error ("Received svcdp_csqcentities but extension not active"); + Host_Error ("csqc is not supported"); + break; + case svcdp_spawnbaseline2: + if (cl.protocol != PROTOCOL_VERSION_DP7) + Host_Error ("Received svcdp_spawnbaseline2 but extension not active"); + i = MSG_ReadShort (); + CL_ParseBaseline (CL_EntityNum(i), 7); + break; + case svcdp_spawnstaticsound2: + if (cl.protocol != PROTOCOL_VERSION_DP7) + Host_Error ("Received svcdp_spawnstaticsound2 but extension not active"); + CL_ParseStaticSound (2); + break; + case svcdp_spawnstatic2: + if (cl.protocol != PROTOCOL_VERSION_DP7) + Host_Error ("Received svcdp_spawnstatic2 but extension not active"); + CL_ParseStatic (7); + break; + case svcdp_entities: + if (cl.protocol != PROTOCOL_VERSION_DP7) + Host_Error ("Received svcdp_entities but extension not active"); + CLDP_ParseEntitiesUpdate(); + break; + + case svcdp_downloaddata: + if (cl.protocol != PROTOCOL_VERSION_DP7 && !cl.protocol_dpdownload) + Host_Error ("Received svcdp_entities but extension not active"); + CL_Download_Data(); + break; + + //spike -- new deltas (including new fields etc) + //stats also changed, and are sent unreliably using the same ack mechanism (which means they're not blocked until the reliables are acked, preventing the need to spam them in every packet). + case svcdp_updatestatbyte: + if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) && cl.protocol != PROTOCOL_VERSION_DP7) + Host_Error ("Received svcdp_updatestatbyte but extension not active"); + i = MSG_ReadByte (); + CL_ParseStatInt(i, MSG_ReadByte()); + break; + case svcfte_updatestatstring: + if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + Host_Error ("Received svcfte_updatestatstring but extension not active"); + i = MSG_ReadByte (); + if (i >= 0 && i < MAX_CL_STATS) + /*cl.statss[i] =*/ MSG_ReadString (); + else + Con_Warning ("svcfte_updatestatstring: %i is invalid\n", i); + break; + case svcfte_updatestatfloat: + if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + Host_Error ("Received svcfte_updatestatfloat but extension not active"); + i = MSG_ReadByte (); + CL_ParseStatFloat(i, MSG_ReadFloat()); + break; + //static ents get all the new fields too, even if the client will probably ignore most of them, the option is at least there to fix it without updating protocols separately. + case svcfte_spawnstatic2: + if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + Host_Error ("Received svcfte_spawnstatic2 but extension not active"); + CL_ParseStatic (6); + break; + //baselines have all fields. hurrah for the same delta mechanism + case svcfte_spawnbaseline2: + if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + Host_Error ("Received svcfte_spawnbaseline2 but extension not active"); + i = MSG_ReadEntity (); + // must use CL_EntityNum() to force cl.num_entities up + CL_ParseBaseline (CL_EntityNum(i), 6); + break; + //ent updates replace svc_time too + case svcfte_updateentities: + if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + Host_Error ("Received svcfte_updateentities but extension not active"); + CLFTE_ParseEntitiesUpdate(); + break; + + //voicechat, because we can. why reduce packet sizes if you're not going to use that extra space?!? + case svcfte_voicechat: + if (!(cl.protocol_pext2 & PEXT2_VOICECHAT)) + Host_Error ("Received svcfte_voicechat but extension not active"); + S_Voip_Parse(); + break; + //spike } lastcmd = cmd; //johnfitz diff --git a/Quake/cl_tent.c b/Quake/cl_tent.c index b1c9b8cf..ee2b3e87 100644 --- a/Quake/cl_tent.c +++ b/Quake/cl_tent.c @@ -56,7 +56,7 @@ void CL_InitTEnts (void) CL_ParseBeam ================= */ -void CL_ParseBeam (qmodel_t *m) +void CL_ParseBeam (qmodel_t *m, const char *trailname, const char *impactname) { int ent; vec3_t start, end; @@ -73,12 +73,24 @@ void CL_ParseBeam (qmodel_t *m) end[1] = MSG_ReadCoord (cl.protocolflags); end[2] = MSG_ReadCoord (cl.protocolflags); +#ifdef PSET_SCRIPT + { + vec3_t normal, extra, impact; + VectorSubtract(end, start, normal); + VectorNormalize(normal); + VectorMA(end, 4, normal, extra); //extend the end-point by four + if (CL_TraceLine(start, extra, impact, normal, NULL)<1) + PScript_RunParticleEffectTypeString(impact, normal, 1, impactname); + } +#endif + // override any beam with the same entity for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) if (b->entity == ent) { b->entity = ent; b->model = m; + b->trailname = trailname; b->endtime = cl.time + 0.2; VectorCopy (start, b->start); VectorCopy (end, b->end); @@ -92,6 +104,7 @@ void CL_ParseBeam (qmodel_t *m) { b->entity = ent; b->model = m; + b->trailname = trailname; b->endtime = cl.time + 0.2; VectorCopy (start, b->start); VectorCopy (end, b->end); @@ -108,6 +121,15 @@ void CL_ParseBeam (qmodel_t *m) //johnfitz } +void CL_SpawnSpriteEffect(vec3_t org/*, vec3_t dir, vec3_t orientationup*/, qmodel_t *model, int startframe, int framecount, float framerate/*, float alpha, float scale, float randspin, float gravity, int traileffect, unsigned int renderflags, int skinnum*/) +{ + if (startframe < 0) + startframe = framecount = 0; + if (!framecount) + framecount = model->numframes; + Con_DPrintf("CL_SpawnSpriteEffect: not implemented\n"); +} + /* ================= CL_ParseTEnt @@ -128,7 +150,8 @@ void CL_ParseTEnt (void) pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_RunParticleEffect (pos, vec3_origin, 20, 30); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_WIZSPIKE")) + R_RunParticleEffect (pos, vec3_origin, 20, 30); S_StartSound (-1, 0, cl_sfx_wizhit, pos, 1, 1); break; @@ -136,15 +159,18 @@ void CL_ParseTEnt (void) pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_RunParticleEffect (pos, vec3_origin, 226, 20); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_KNIGHTSPIKE")) + R_RunParticleEffect (pos, vec3_origin, 226, 20); S_StartSound (-1, 0, cl_sfx_knighthit, pos, 1, 1); break; + case TEDP_SPIKEQUAD: case TE_SPIKE: // spike hitting wall pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_RunParticleEffect (pos, vec3_origin, 0, 10); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, (type==TEDP_SPIKEQUAD)?"TE_SPIKEQUAD":"TE_SPIKE")) + R_RunParticleEffect (pos, vec3_origin, 0, 10); if ( rand() % 5 ) S_StartSound (-1, 0, cl_sfx_tink1, pos, 1, 1); else @@ -158,11 +184,13 @@ void CL_ParseTEnt (void) S_StartSound (-1, 0, cl_sfx_ric3, pos, 1, 1); } break; + case TEDP_SUPERSPIKEQUAD: case TE_SUPERSPIKE: // super spike hitting wall pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_RunParticleEffect (pos, vec3_origin, 0, 20); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, (type==TEDP_SUPERSPIKEQUAD)?"TE_SUPERSPIKEQUAD":"TE_SUPERSPIKE")) + R_RunParticleEffect (pos, vec3_origin, 0, 20); if ( rand() % 5 ) S_StartSound (-1, 0, cl_sfx_tink1, pos, 1, 1); @@ -178,50 +206,73 @@ void CL_ParseTEnt (void) } break; + case TEDP_GUNSHOTQUAD: + case TEFTE_GUNSHOT_COUNT: //for compat with qw mods case TE_GUNSHOT: // bullet hitting wall + rnd = 20; + if (type == TEFTE_GUNSHOT_COUNT) + rnd *= MSG_ReadByte(); pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_RunParticleEffect (pos, vec3_origin, 0, 20); + if (PScript_RunParticleEffectTypeString(pos, NULL, rnd, (type==TEDP_GUNSHOTQUAD)?"TE_GUNSHOTQUAD":"TE_GUNSHOT")) + R_RunParticleEffect (pos, vec3_origin, 0, rnd); break; + case TEFTE_EXPLOSION_SPRITE://for compat with qw mods + case TEDP_EXPLOSIONQUAD: + case TENEH_EXPLOSION3: case TE_EXPLOSION: // rocket explosion pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_ParticleExplosion (pos); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, (type==TEDP_EXPLOSIONQUAD)?"TE_EXPLOSIONQUAD":"TE_EXPLOSION")) + R_ParticleExplosion (pos); dl = CL_AllocDlight (0); VectorCopy (pos, dl->origin); dl->radius = 350; dl->die = cl.time + 0.5; dl->decay = 300; + if (type == TENEH_EXPLOSION3) + { //the *2 is to match dp's expectations, for some reason. + dl->color[0] = MSG_ReadCoord(cl.protocolflags)*2.0; + dl->color[1] = MSG_ReadCoord(cl.protocolflags)*2.0; + dl->color[2] = MSG_ReadCoord(cl.protocolflags)*2.0; + } S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); + + if (type==TEFTE_EXPLOSION_SPRITE) + { + qmodel_t *mod = Mod_ForName ("progs/s_explod.spr", false); + CL_SpawnSpriteEffect(pos/*, NULL, NULL*/, mod, 0, 0, 10/*, mod->type==mod_sprite?-1:1, 1, 0, 0, P_INVALID, 0, 0*/); + } break; case TE_TAREXPLOSION: // tarbaby explosion pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_BlobExplosion (pos); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_TAREXPLOSION")) + R_BlobExplosion (pos); S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); break; case TE_LIGHTNING1: // lightning bolts - CL_ParseBeam (Mod_ForName("progs/bolt.mdl", true)); + CL_ParseBeam (Mod_ForName("progs/bolt.mdl", true), "TE_LIGHTNING1", "TE_LIGHTNING1_END"); break; case TE_LIGHTNING2: // lightning bolts - CL_ParseBeam (Mod_ForName("progs/bolt2.mdl", true)); + CL_ParseBeam (Mod_ForName("progs/bolt2.mdl", true), "TE_LIGHTNING2", "TE_LIGHTNING2_END"); break; case TE_LIGHTNING3: // lightning bolts - CL_ParseBeam (Mod_ForName("progs/bolt3.mdl", true)); + CL_ParseBeam (Mod_ForName("progs/bolt3.mdl", true), "TE_LIGHTNING3", "TE_LIGHTNING3_END"); break; // PGM 01/21/97 case TE_BEAM: // grappling hook beam - CL_ParseBeam (Mod_ForName("progs/beam.mdl", true)); + CL_ParseBeam (Mod_ForName("progs/beam.mdl", true), "TE_BEAM", "TE_BEAM_END"); break; // PGM 01/21/97 @@ -229,14 +280,16 @@ void CL_ParseTEnt (void) pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_LavaSplash (pos); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_LAVASPLASH")) + R_LavaSplash (pos); break; case TE_TELEPORT: pos[0] = MSG_ReadCoord (cl.protocolflags); pos[1] = MSG_ReadCoord (cl.protocolflags); pos[2] = MSG_ReadCoord (cl.protocolflags); - R_TeleportSplash (pos); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_TELEPORT")) + R_TeleportSplash (pos); break; case TE_EXPLOSION2: // color mapped explosion @@ -245,7 +298,8 @@ void CL_ParseTEnt (void) pos[2] = MSG_ReadCoord (cl.protocolflags); colorStart = MSG_ReadByte (); colorLength = MSG_ReadByte (); - R_ParticleExplosion2 (pos, colorStart, colorLength); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, va("TE_EXPLOSION2_%i_%i", colorStart, colorLength))) + R_ParticleExplosion2 (pos, colorStart, colorLength); dl = CL_AllocDlight (0); VectorCopy (pos, dl->origin); dl->radius = 350; @@ -254,11 +308,196 @@ void CL_ParseTEnt (void) S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); break; + case TENEH_LIGHTNING4: + { + const char *beamname = MSG_ReadString(); + CL_ParseBeam (Mod_ForName(beamname, true), "TE_LIGHTNING4", "TE_LIGHTNING4_END"); + } + break; + + case TEDP_CUSTOMFLASH: + dl = CL_AllocDlight (0); + dl->origin[0] = MSG_ReadCoord(cl.protocolflags); + dl->origin[1] = MSG_ReadCoord(cl.protocolflags); + dl->origin[2] = MSG_ReadCoord(cl.protocolflags); + dl->radius = 8*MSG_ReadByte(); + dl->die = (MSG_ReadByte()+1)*(1/256.0); + dl->decay = dl->radius / dl->die; + dl->die += cl.time; + dl->color[0] = MSG_ReadByte()*(1/127.0); + dl->color[1] = MSG_ReadByte()*(1/127.0); + dl->color[2] = MSG_ReadByte()*(1/127.0); + break; + + case TEDP_PARTICLERAIN: + case TEDP_PARTICLESNOW: + { + vec3_t dir, pos2; + int cnt, colour; + + //min + pos[0] = MSG_ReadCoord(cl.protocolflags); + pos[1] = MSG_ReadCoord(cl.protocolflags); + pos[2] = MSG_ReadCoord(cl.protocolflags); + + //max + pos2[0] = MSG_ReadCoord(cl.protocolflags); + pos2[1] = MSG_ReadCoord(cl.protocolflags); + pos2[2] = MSG_ReadCoord(cl.protocolflags); + + //dir + dir[0] = MSG_ReadCoord(cl.protocolflags); + dir[1] = MSG_ReadCoord(cl.protocolflags); + dir[2] = MSG_ReadCoord(cl.protocolflags); + + cnt = (unsigned short)MSG_ReadShort(); //count + colour = MSG_ReadByte (); //colour + + PScript_RunParticleWeather(pos, pos2, dir, cnt, colour, ((type==TEDP_PARTICLESNOW)?"snow":"rain")); + } + break; + + case TEDP_BLOOD: + { + vec3_t dir; + int cnt; + pos[0] = MSG_ReadCoord(cl.protocolflags); + pos[1] = MSG_ReadCoord(cl.protocolflags); + pos[2] = MSG_ReadCoord(cl.protocolflags); + dir[0] = MSG_ReadChar(); + dir[1] = MSG_ReadChar(); + dir[2] = MSG_ReadChar(); + cnt = MSG_ReadByte(); + if (PScript_RunParticleEffectTypeString(pos, dir, cnt, "TE_BLOOD")) + Con_Printf ("CL_ParseTEnt: TEDP_BLOOD unavailable\n"); + } + break; + + case TEDP_SPARK: + { + vec3_t dir; + int cnt; + pos[0] = MSG_ReadCoord(cl.protocolflags); + pos[1] = MSG_ReadCoord(cl.protocolflags); + pos[2] = MSG_ReadCoord(cl.protocolflags); + dir[0] = MSG_ReadChar(); + dir[1] = MSG_ReadChar(); + dir[2] = MSG_ReadChar(); + cnt = MSG_ReadByte(); + if (PScript_RunParticleEffectTypeString(pos, dir, cnt, "TE_SPARK")) + Con_Printf ("CL_ParseTEnt: TEDP_SPARK unavailable\n"); + } + break; + case TEDP_SMALLFLASH: // [vector] origin + pos[0] = MSG_ReadCoord(cl.protocolflags); + pos[1] = MSG_ReadCoord(cl.protocolflags); + pos[2] = MSG_ReadCoord(cl.protocolflags); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_SMALLFLASH")) + Con_Printf ("CL_ParseTEnt: TEDP_SMALLFLASH unavailable\n"); + break; + + //spike: these are all kinda useless once the ssqc has access to pointparticles... + //I'm too lazy to bother implementing them properly. + case TEDP_BLOODSHOWER: + /*mins[0] =*/ MSG_ReadCoord(cl.protocolflags); + /*mins[1] =*/ MSG_ReadCoord(cl.protocolflags); + /*mins[2] =*/ MSG_ReadCoord(cl.protocolflags); + /*maxs[0] =*/ MSG_ReadCoord(cl.protocolflags); + /*maxs[1] =*/ MSG_ReadCoord(cl.protocolflags); + /*maxs[2] =*/ MSG_ReadCoord(cl.protocolflags); + /*velspeed =*/ MSG_ReadCoord(cl.protocolflags); + /*count =*/ MSG_ReadShort(); + Con_Printf ("CL_ParseTEnt: TEDP_BLOODSHOWER unsupported\n"); + break; + case TEDP_EXPLOSIONRGB: + /*pos[0] =*/ MSG_ReadCoord(cl.protocolflags); + /*pos[1] =*/ MSG_ReadCoord(cl.protocolflags); + /*pos[2] =*/ MSG_ReadCoord(cl.protocolflags); + /*col[0] =*/ MSG_ReadByte(); + /*col[1] =*/ MSG_ReadByte(); + /*col[2] =*/ MSG_ReadByte(); + Con_Printf ("CL_ParseTEnt: TEDP_EXPLOSIONRGB unsupported\n"); + break; + case TEDP_PARTICLECUBE: + /*mins[0] =*/ MSG_ReadCoord(cl.protocolflags); + /*mins[1] =*/ MSG_ReadCoord(cl.protocolflags); + /*mins[2] =*/ MSG_ReadCoord(cl.protocolflags); + /*maxs[0] =*/ MSG_ReadCoord(cl.protocolflags); + /*maxs[1] =*/ MSG_ReadCoord(cl.protocolflags); + /*maxs[2] =*/ MSG_ReadCoord(cl.protocolflags); + /*dir[0] =*/ MSG_ReadCoord(cl.protocolflags); + /*dir[1] =*/ MSG_ReadCoord(cl.protocolflags); + /*dir[2] =*/ MSG_ReadCoord(cl.protocolflags); + /*count =*/ MSG_ReadShort(); + /*pal_start =*/ MSG_ReadByte(); + /*pal_rand =*/ MSG_ReadByte(); + /*velspeed =*/ MSG_ReadCoord(cl.protocolflags); + Con_Printf ("CL_ParseTEnt: TEDP_PARTICLECUBE unsupported\n"); + break; + case TEDP_FLAMEJET: + { + vec3_t dir; + int cnt; + pos[0] = MSG_ReadCoord(cl.protocolflags); + pos[1] = MSG_ReadCoord(cl.protocolflags); + pos[2] = MSG_ReadCoord(cl.protocolflags); + dir[0] = MSG_ReadCoord(cl.protocolflags); + dir[1] = MSG_ReadCoord(cl.protocolflags); + dir[2] = MSG_ReadCoord(cl.protocolflags); + cnt = MSG_ReadByte(); + if (PScript_RunParticleEffectTypeString(pos, dir, cnt, "TE_FLAMEJET")) + Con_Printf ("CL_ParseTEnt: TEDP_FLAMEJET unavailable\n"); + } + break; + case TEDP_PLASMABURN: + pos[0] = MSG_ReadCoord(cl.protocolflags); + pos[1] = MSG_ReadCoord(cl.protocolflags); + pos[2] = MSG_ReadCoord(cl.protocolflags); + if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_PLASMABURN")) + Con_Printf ("CL_ParseTEnt: TEDP_PLASMABURN unavailable\n"); + break; + case TEDP_TEI_G3: + Host_Error ("CL_ParseTEnt: TEDP_TEI_G3 unsupported"); + case TEDP_SMOKE: + Host_Error ("CL_ParseTEnt: TEDP_SMOKE unsupported"); + case TEDP_TEI_BIGEXPLOSION: + Host_Error ("CL_ParseTEnt: TEDP_TEI_BIGEXPLOSION unsupported"); + case TEDP_TEI_PLASMAHIT: + Host_Error ("CL_ParseTEnt: TEDP_TEI_PLASMAHIT unsupported"); default: - Sys_Error ("CL_ParseTEnt: bad type"); + Host_Error ("CL_ParseTEnt: unsupported tempentity type %i", type); } } +void CL_ParseEffect (qboolean big) +{ + vec3_t org; + int modelindex; + int startframe; + int framecount; + int framerate; + qmodel_t *mod; + + org[0] = MSG_ReadCoord(cl.protocolflags); + org[1] = MSG_ReadCoord(cl.protocolflags); + org[2] = MSG_ReadCoord(cl.protocolflags); + + if (big) + modelindex = MSG_ReadShort(); + else + modelindex = MSG_ReadByte(); + + if (big) + startframe = MSG_ReadShort(); + else + startframe = MSG_ReadByte(); + + framecount = MSG_ReadByte(); + framerate = MSG_ReadByte(); + + mod = cl.model_precache[modelindex]; + CL_SpawnSpriteEffect(org/*, NULL, NULL*/, mod, startframe, framecount, framerate/*, mod->type==mod_sprite?-1:1, 1, 0, 0, P_INVALID, 0, 0*/); +} /* ================= @@ -269,7 +508,7 @@ entity_t *CL_NewTempEntity (void) { entity_t *ent; - if (cl_numvisedicts == MAX_VISEDICTS) + if (cl_numvisedicts == cl_maxvisedicts) return NULL; if (num_temp_entities == MAX_TEMP_ENTITIES) return NULL; @@ -279,6 +518,8 @@ entity_t *CL_NewTempEntity (void) cl_visedicts[cl_numvisedicts] = ent; cl_numvisedicts++; + ent->netstate.scale = 16; + ent->netstate.colormod[0] = ent->netstate.colormod[1] = ent->netstate.colormod[2] = 32; ent->colormap = vid.colormap; return ent; } @@ -301,7 +542,8 @@ void CL_UpdateTEnts (void) num_temp_entities = 0; - srand ((int) (cl.time * 1000)); //johnfitz -- freeze beams when paused + if (cl.paused) + srand ((int) (cl.time * 1000)); //johnfitz -- freeze beams when paused // update lightning for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) @@ -310,11 +552,14 @@ void CL_UpdateTEnts (void) continue; // if coming from the player, update the start position - if (b->entity == cl.viewentity) + if (b->entity == cl.viewentity && cl.entities) { - VectorCopy (cl_entities[cl.viewentity].origin, b->start); + VectorCopy (cl.entities[cl.viewentity].origin, b->start); } + if (!PScript_ParticleTrail(b->start, b->end, PScript_FindParticleType(b->trailname), b->entity, NULL, &b->trailstate)) + continue; + // calculate pitch and yaw VectorSubtract (b->end, b->start, dist); diff --git a/Quake/client.h b/Quake/client.h index 1c7db181..25c49721 100644 --- a/Quake/client.h +++ b/Quake/client.h @@ -39,6 +39,7 @@ typedef struct float entertime; int frags; int colors; // two 4 bit fields + int ping; byte translations[VID_GRADES*256]; } scoreboard_t; @@ -83,6 +84,8 @@ typedef struct struct qmodel_s *model; float endtime; vec3_t start, end; + const char *trailname; + struct trailstate_s *trailstate; } beam_t; #define MAX_MAPSTRING 2048 @@ -131,6 +134,16 @@ typedef struct struct qsocket_s *netcon; sizebuf_t message; // writing buffer to send to server +//downloads don't restart/fail when the server sends random serverinfo packets + struct + { + qboolean active; + unsigned int size; + FILE *file; + char current[MAX_QPATH]; //also prevents us from repeatedly trying to download the same file + char temp[MAX_OSPATH]; //the temp filename for the download, will be renamed to current + float starttime; + } download; } client_static_t; extern client_static_t cls; @@ -149,6 +162,7 @@ typedef struct // information for local display int stats[MAX_CL_STATS]; // health, etc + float statsf[MAX_CL_STATS]; int items; // inventory bit flags float item_gettime[32]; // cl.time of aquiring item, for blinking float faceanimtime; // use anim frame if cl.time < this @@ -171,13 +185,11 @@ typedef struct vec3_t punchangle; // temporary offset // pitch drifting vars - float idealpitch; float pitchvel; qboolean nodrift; float driftmove; double laststop; - float viewheight; float crouch; // local amount for smoothing stepups qboolean paused; // send over by server @@ -213,10 +225,18 @@ typedef struct struct qmodel_s *worldmodel; // cl_entitites[0].model struct efrag_s *free_efrags; int num_efrags; - int num_entities; // held in cl_entities array - int num_statics; // held in cl_staticentities array +// int num_entities; // held in cl_entities array +// int num_statics; // held in cl_staticentities array entity_t viewent; // the gun model + entity_t *entities; //spike -- moved into here + int max_edicts; + int num_entities; + + entity_t **static_entities; //spike -- was static + int max_static_entities; + int num_statics; + int cdtrack, looptrack; // cd audio // frag scoreboard @@ -224,6 +244,43 @@ typedef struct unsigned protocol; //johnfitz unsigned protocolflags; + unsigned protocol_pext2; //spike -- flag of fte protocol extensions + qboolean protocol_dpdownload; + +#ifdef PSET_SCRIPT + qboolean protocol_particles; + struct + { + const char *name; + int index; + } particle_precache[MAX_PARTICLETYPES]; +#endif + int ackframes[8]; //big enough to cover burst + unsigned int ackframes_count; + qboolean requestresend; + + char stuffcmdbuf[1024]; //comment-extensions are a thing with certain servers, make sure we can handle them properly without further hacks/breakages. there's also some server->client only console commands that we might as well try to handle a bit better, like reconnect + enum + { + PRINT_NONE, + PRINT_PINGS, +// PRINT_STATUSINFO, +// PRINT_STATUSPLAYER, +// PRINT_STATUSIP, + } printtype; + int printplayer; + float expectingpingtimes; + float printversionresponse; + + //spike -- moved this stuff here to deal with downloading content named by the server + qboolean sendprespawn; //download+load content, send the prespawn command once done + int model_count; + int model_download; + char model_name[MAX_MODELS][MAX_QPATH]; + int sound_count; + int sound_download; + char sound_name[MAX_SOUNDS][MAX_QPATH]; + //spike -- end downloads } client_state_t; @@ -249,6 +306,7 @@ extern cvar_t cl_alwaysrun; // QuakeSpasm extern cvar_t cl_autofire; +extern cvar_t cl_recordingdemo; extern cvar_t cl_shownet; extern cvar_t cl_nolerp; @@ -265,23 +323,18 @@ extern cvar_t m_forward; extern cvar_t m_side; -#define MAX_TEMP_ENTITIES 256 //johnfitz -- was 64 -#define MAX_STATIC_ENTITIES 4096 //ericw -- was 512 //johnfitz -- was 128 -#define MAX_VISEDICTS 4096 // larger, now we support BSP2 +#define MAX_TEMP_ENTITIES 256 //johnfitz -- was 64 extern client_state_t cl; // FIXME, allocate dynamically -extern entity_t cl_static_entities[MAX_STATIC_ENTITIES]; extern lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES]; extern dlight_t cl_dlights[MAX_DLIGHTS]; extern entity_t cl_temp_entities[MAX_TEMP_ENTITIES]; extern beam_t cl_beams[MAX_BEAMS]; -extern entity_t *cl_visedicts[MAX_VISEDICTS]; +extern entity_t **cl_visedicts; extern int cl_numvisedicts; - -extern entity_t *cl_entities; //johnfitz -- was a static array, now on hunk -extern int cl_max_edicts; //johnfitz -- only changes when new map loads +extern int cl_maxvisedicts; //extended if we exceeded it the previous frame //============================================================================= @@ -322,10 +375,15 @@ void CL_SendMove (const usercmd_t *cmd); int CL_ReadFromServer (void); void CL_BaseMove (usercmd_t *cmd); +void CL_Download_Data(void); +qboolean CL_CheckDownloads(void); + +void CL_ParseEffect (qboolean big); void CL_ParseTEnt (void); void CL_UpdateTEnts (void); void CL_ClearState (void); +void CL_ClearTrailStates(void); // // cl_demo.c @@ -342,7 +400,8 @@ void CL_TimeDemo_f (void); // cl_parse.c // void CL_ParseServerMessage (void); -void CL_NewTranslation (int slot); +void CL_RegisterParticles(void); +//void CL_NewTranslation (int slot); // // view @@ -361,6 +420,7 @@ void V_SetContentsColor (int contents); // void CL_InitTEnts (void); void CL_SignonReply (void); +float CL_TraceLine (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal, int *ent); // // chase diff --git a/Quake/cmd.c b/Quake/cmd.c index 783475fd..b7dc0ef7 100644 --- a/Quake/cmd.c +++ b/Quake/cmd.c @@ -24,6 +24,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" +cvar_t cl_nopext = {"cl_nopext","0",CVAR_NONE}; //Spike -- prevent autodetection of protocol extensions, so that servers fall back to only their base protocol (without needing to reconfigure the server. Requires reconnect. +cvar_t cmd_warncmd = {"cl_warncmd","1",CVAR_NONE}; //Spike -- prevent autodetection of protocol extensions, so that servers fall back to only their base protocol (without needing to reconfigure the server. Requires reconnect. void Cmd_ForwardToServer (void); #define MAX_ALIAS_NAME 32 @@ -97,7 +99,17 @@ void Cbuf_AddText (const char *text) return; } - SZ_Write (&cmd_text, text, Q_strlen (text)); + SZ_Write (&cmd_text, text, l); +} +void Cbuf_AddTextLen (const char *text, int l) +{ + if (cmd_text.cursize + l >= cmd_text.maxsize) + { + Con_Printf ("Cbuf_AddText: overflow\n"); + return; + } + + SZ_Write (&cmd_text, text, l); } @@ -147,7 +159,7 @@ void Cbuf_Execute (void) int i; char *text; char line[1024]; - int quotes; + int quotes, comment; while (cmd_text.cursize) { @@ -155,11 +167,14 @@ void Cbuf_Execute (void) text = (char *)cmd_text.data; quotes = 0; + comment = 0; for (i=0 ; i< cmd_text.cursize ; i++) { if (text[i] == '"') quotes++; - if ( !(quotes&1) && text[i] == ';') + if (text[i] == '/' && text[i+1] == '/') + comment=true; + if ( !(quotes&1) && !comment && text[i] == ';') break; // don't break if inside a quoted string if (text[i] == '\n') break; @@ -270,10 +285,12 @@ void Cmd_Exec_f (void) f = (char *)COM_LoadHunkFile (Cmd_Argv(1), NULL); if (!f) { - Con_Printf ("couldn't exec %s\n",Cmd_Argv(1)); + if (cmd_warncmd.value) + Con_Printf ("couldn't exec %s\n",Cmd_Argv(1)); return; } - Con_Printf ("execing %s\n",Cmd_Argv(1)); + if (cmd_warncmd.value) + Con_Printf ("execing %s\n",Cmd_Argv(1)); Cbuf_InsertText (f); Hunk_FreeToLowMark (mark); @@ -410,6 +427,17 @@ void Cmd_Unalias_f (void) } } +qboolean Cmd_AliasExists (const char *aliasname) +{ + cmdalias_t *a; + for (a=cmd_alias ; a ; a=a->next) + { + if (!q_strcasecmp (aliasname, a->name)) + return true; + } + return false; +} + /* =============== Cmd_Unaliasall_f -- johnfitz @@ -436,14 +464,6 @@ void Cmd_Unaliasall_f (void) ============================================================================= */ -typedef struct cmd_function_s -{ - struct cmd_function_s *next; - const char *name; - xcommand_t function; -} cmd_function_t; - - #define MAX_ARGS 80 static int cmd_argc; @@ -536,13 +556,13 @@ void Cmd_Apropos_f(void) } for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { - if (q_strcasestr(cmd->name, substr)) + if (q_strcasestr(cmd->name, substr) && cmd->srctype != src_server) { hits++; Con_SafePrintf ("%s\n", Cmd_TintSubstring(cmd->name, substr, tmpbuf, sizeof(tmpbuf))); } } - + for (var=Cvar_FindVarAfter("", 0) ; var ; var=var->next) { if (q_strcasestr(var->name, substr)) @@ -575,6 +595,9 @@ void Cmd_Init (void) Cmd_AddCommand ("apropos", Cmd_Apropos_f); Cmd_AddCommand ("find", Cmd_Apropos_f); + + Cvar_RegisterVariable (&cl_nopext); + Cvar_RegisterVariable (&cmd_warncmd); } /* @@ -664,9 +687,11 @@ void Cmd_TokenizeString (const char *text) /* ============ Cmd_AddCommand + +spike -- added an extra arg for client (also renamed and made a macro) ============ */ -void Cmd_AddCommand (const char *cmd_name, xcommand_t function) +void Cmd_AddCommand2 (const char *cmd_name, xcommand_t function, cmd_source_t srctype) { cmd_function_t *cmd; cmd_function_t *cursor,*prev; //johnfitz -- sorted list insert @@ -684,7 +709,7 @@ void Cmd_AddCommand (const char *cmd_name, xcommand_t function) // fail if the command already exists for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { - if (!Q_strcmp (cmd_name, cmd->name)) + if (!Q_strcmp (cmd_name, cmd->name) && cmd->srctype == srctype) { Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); return; @@ -694,6 +719,7 @@ void Cmd_AddCommand (const char *cmd_name, xcommand_t function) cmd = (cmd_function_t *) Hunk_Alloc (sizeof(cmd_function_t)); cmd->name = cmd_name; cmd->function = function; + cmd->srctype = srctype; //johnfitz -- insert each entry in alphabetical order if (cmd_functions == NULL || strcmp(cmd->name, cmd_functions->name) < 0) //insert at front @@ -767,7 +793,7 @@ A complete command line has been parsed, so try to execute it FIXME: lookupnoadd the token to speed search? ============ */ -void Cmd_ExecuteString (const char *text, cmd_source_t src) +qboolean Cmd_ExecuteString (const char *text, cmd_source_t src) { cmd_function_t *cmd; cmdalias_t *a; @@ -777,32 +803,49 @@ void Cmd_ExecuteString (const char *text, cmd_source_t src) // execute the command line if (!Cmd_Argc()) - return; // no tokens + return true; // no tokens // check functions for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { if (!q_strcasecmp (cmd_argv[0],cmd->name)) { - cmd->function (); - return; + if (src == src_client && cmd->srctype != src_client) + Con_DPrintf("%s tried to %s\n", host_client->name, text); //src_client only allows client commands + else if (src == src_command && cmd->srctype == src_server) + continue; //src_command can execute anything but server commands (which it ignores, allowing for alternative behaviour) + else if (src == src_server && cmd->srctype != src_server) + continue; //src_server may only execute server commands (such commands must be safe to parse within the context of a network message, so no disconnect/connect/playdemo/etc) + else + cmd->function (); + return true; } } + if (src == src_client) + { //spike -- please don't execute similarly named aliases, nor custom cvars... + Con_DPrintf("%s tried to %s\n", host_client->name, text); + return false; + } + if (src != src_command) + return false; + // check alias for (a=cmd_alias ; a ; a=a->next) { if (!q_strcasecmp (cmd_argv[0], a->name)) { Cbuf_InsertText (a->value); - return; + return true; } } // check cvars if (!Cvar_Command ()) - Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0)); + if (cmd_warncmd.value || developer.value) + Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0)); + return true; } @@ -830,6 +873,22 @@ void Cmd_ForwardToServer (void) SZ_Print (&cls.message, Cmd_Argv(0)); SZ_Print (&cls.message, " "); } + else + { + //hack zone for compat. + //stuffcmd("cmd foo\n") is a good way to query the client to see if it knows foo because the server is guarenteed a response even if it doesn't understand it, saving a timeout + if (!strcmp(Cmd_Args(), "protocols")) + { //server asked us for a list of protocol numbers that we claim to support. this allows cool servers like fte to autodetect higher limits etc. + //servers may assume that the client's preferred protocol will be listed first. + SZ_Print (&cls.message, va("protocols %i %i %i %i %i", PROTOCOL_RMQ, PROTOCOL_FITZQUAKE, PROTOCOL_VERSION_BJP3, PROTOCOL_VERSION_DP7, PROTOCOL_NETQUAKE)); + return; + } + if (!strcmp(Cmd_Args(), "pext") && !cl_nopext.value) + { //server asked us for a key+value list of the extensions+attributes we support + SZ_Print (&cls.message, va("pext %#x %#x", PROTOCOL_FTE_PEXT2, PEXT2_SUPPORTED_CLIENT)); + return; + } + } if (Cmd_Argc() > 1) SZ_Print (&cls.message, Cmd_Args()); else diff --git a/Quake/cmd.h b/Quake/cmd.h index 9f688e21..71b8daf3 100644 --- a/Quake/cmd.h +++ b/Quake/cmd.h @@ -42,6 +42,7 @@ The game starts with a Cbuf_AddText ("exec quake.rc\n"); Cbuf_Execute (); void Cbuf_Init (void); // allocates an initial text buffer that will grow as needed +void Cbuf_AddTextLen (const char *text, int l); void Cbuf_AddText (const char *text); // as new commands are generated from the console or keybindings, // the text is added to the end of the command buffer. @@ -70,24 +71,36 @@ not apropriate. */ -typedef void (*xcommand_t) (void); - typedef enum { src_client, // came in over a net connection as a clc_stringcmd // host_client will be valid during this state. - src_command // from the command buffer + src_command, // from the command buffer + src_server // from a svc_stufftext } cmd_source_t; - extern cmd_source_t cmd_source; +typedef void (*xcommand_t) (void); +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + const char *name; + xcommand_t function; + cmd_source_t srctype; +} cmd_function_t; + void Cmd_Init (void); -void Cmd_AddCommand (const char *cmd_name, xcommand_t function); +void Cmd_AddCommand2 (const char *cmd_name, xcommand_t function, cmd_source_t srctype); // called by the init functions of other parts of the program to // register commands and functions to call for them. // The cmd_name is referenced later, so it should not be in temp memory +#define Cmd_AddCommand(cmdname,func) Cmd_AddCommand2(cmdname,func,src_command) //regular console commands +#define Cmd_AddCommand_ClientCommand(cmdname,func) Cmd_AddCommand2(cmdname,func,src_client) //command is meant to be safe for anyone to execute. +#define Cmd_AddCommand_ServerCommand(cmdname,func) Cmd_AddCommand2(cmdname,func,src_server) //command came from a server +#define Cmd_AddCommand_Console Cmd_AddCommand //to make the disabiguation more obvious +qboolean Cmd_AliasExists (const char *aliasname); qboolean Cmd_Exists (const char *cmd_name); // used by the cvar code to check for cvar / command name overlap @@ -110,7 +123,7 @@ void Cmd_TokenizeString (const char *text); // Takes a null terminated string. Does not need to be /n terminated. // breaks the string up into arg tokens. -void Cmd_ExecuteString (const char *text, cmd_source_t src); +qboolean Cmd_ExecuteString (const char *text, cmd_source_t src); // Parses a single line of text into arguments and tries to execute it. // The text can come from the command buffer, a remote client, or stdin. diff --git a/Quake/common.c b/Quake/common.c index a4490016..b31566a5 100644 --- a/Quake/common.c +++ b/Quake/common.c @@ -26,6 +26,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "q_ctype.h" #include +#ifndef _WIN32 +#include +#endif + static char *largv[MAX_NUM_ARGVS + 1]; static char argvdummy[] = " "; @@ -33,6 +37,7 @@ int safemode; cvar_t registered = {"registered","1",CVAR_ROM}; /* set to correct value in COM_CheckRegistered() */ cvar_t cmdline = {"cmdline","",CVAR_ROM/*|CVAR_SERVERINFO*/}; /* sending cmdline upon CCREQ_RULE_INFO is evil */ +cvar_t allow_download = {"allow_download", "1"}; /*set to 0 to block file downloads, both client+server*/ static qboolean com_modified; // set true if using non-id files @@ -923,6 +928,23 @@ float MSG_ReadAngle16 (unsigned int flags) } //johnfitz +//spike -- for downloads +byte *MSG_ReadData (unsigned int length) +{ + byte *data; + + if (msg_readcount+length > (unsigned int)net_message.cursize) + { + msg_badread = true; + return NULL; + } + + data = net_message.data+msg_readcount; + msg_readcount += length; + return data; +} + + //=========================================================================== void SZ_Alloc (sizebuf_t *buf, int startsize) @@ -946,6 +968,7 @@ void SZ_Free (sizebuf_t *buf) void SZ_Clear (sizebuf_t *buf) { buf->cursize = 0; + buf->overflowed = false; } void *SZ_GetSpace (sizebuf_t *buf, int length) @@ -960,9 +983,9 @@ void *SZ_GetSpace (sizebuf_t *buf, int length) if (length > buf->maxsize) Sys_Error ("SZ_GetSpace: %i is > full buffer size", length); - buf->overflowed = true; - Con_Printf ("SZ_GetSpace: overflow"); + Con_Printf ("SZ_GetSpace: overflow\n"); SZ_Clear (buf); + buf->overflowed = true; } data = buf->data + buf->cursize; @@ -1152,6 +1175,56 @@ void COM_AddExtension (char *path, const char *extension, size_t len) q_strlcat(path, extension, len); } +/* +spike -- this function simply says whether a filename is acceptable for downloading (used by both client+server) +*/ +qboolean COM_DownloadNameOkay(const char *filename) +{ + if (!allow_download.value) + return false; + + //quickly test the prefix to ensure that its in one of the allowed subdirs + if (strncmp(filename, "sound/", 6) && + strncmp(filename, "progs/", 6) && + strncmp(filename, "maps/", 5) && + strncmp(filename, "models/", 7)) + return false; + //windows paths are NOT permitted, nor are alternative data streams, nor wildcards, and double quotes are always bad(which allows for spaces) + if (strchr(filename, '\\') || strchr(filename, ':') || strchr(filename, '*') || strchr(filename, '?') || strchr(filename, '\"')) + return false; + //some operating systems interpret this as 'parent directory' + if (strstr(filename, "//")) + return false; + //block unix hidden files, also blocks relative paths. + if (*filename == '.' || strstr(filename, "/.")) + return false; + //test the extension to ensure that its in one of the allowed file types + //(no .dll, .so, .com, .exe, .bat, .vbs, .xls, .doc, etc please) + //also don't allow config files. + filename = COM_FileGetExtension(filename); + if ( + //model formats + q_strcasecmp(filename, "bsp") && + q_strcasecmp(filename, "mdl") && + q_strcasecmp(filename, "iqm") && //in case we ever support these later + q_strcasecmp(filename, "md3") && //in case we ever support these later + q_strcasecmp(filename, "spr") && + q_strcasecmp(filename, "spr32") && + //audio formats + q_strcasecmp(filename, "wav") && + q_strcasecmp(filename, "ogg") && + //image formats (if we ever need that) + q_strcasecmp(filename, "tga") && + q_strcasecmp(filename, "png") && + //misc stuff + q_strcasecmp(filename, "lux") && + q_strcasecmp(filename, "lit2") && + q_strcasecmp(filename, "lit")) + return false; + //okay, well, we didn't throw a hissy fit, so whatever dude, go ahead and download + return true; +} + /* ============== @@ -1251,11 +1324,11 @@ Returns the position (1 to argc-1) in the program's argument list where the given parameter apears, or 0 if not present ================ */ -int COM_CheckParm (const char *parm) +int COM_CheckParmNext (int last, const char *parm) { int i; - for (i = 1; i < com_argc; i++) + for (i = last+1; i < com_argc; i++) { if (!com_argv[i]) continue; // NEXTSTEP sometimes clears appkit vars. @@ -1265,6 +1338,10 @@ int COM_CheckParm (const char *parm) return 0; } +int COM_CheckParm (const char *parm) +{ + return COM_CheckParmNext(0, parm); +} /* ================ @@ -1384,6 +1461,23 @@ static void FitzTest_f (void) } #endif + +entity_state_t nullentitystate; +static void COM_SetupNullState(void) +{ + //the null state has some specific default values +// nullentitystate.drawflags = /*SCALE_ORIGIN_ORIGIN*/96; + nullentitystate.colormod[0] = 32; + nullentitystate.colormod[1] = 32; + nullentitystate.colormod[2] = 32; +// nullentitystate.glowmod[0] = 32; +// nullentitystate.glowmod[1] = 32; +// nullentitystate.glowmod[2] = 32; + nullentitystate.alpha = 0; //fte has 255 by default, with 0 for invisible. fitz uses 1 for invisible, 0 default, and 255=full alpha + nullentitystate.scale = 16; +// nullentitystate.solidsize = 0;//ES_SOLID_BSP; +} + /* ================ COM_Init @@ -1435,6 +1529,8 @@ void COM_Init (void) #ifdef _DEBUG Cmd_AddCommand ("fitztest", FitzTest_f); //johnfitz #endif + + COM_SetupNullState(); } @@ -1501,6 +1597,7 @@ typedef struct #define MAX_FILES_IN_PACK 2048 +char com_gamenames[1024]; //eg: "hipnotic;quoth;warp", no id1, no private stuff char com_gamedir[MAX_OSPATH]; char com_basedir[MAX_OSPATH]; int file_from_pak; // ZOID: global indicating that file came from a pak @@ -1636,15 +1733,38 @@ static int COM_FindFile (const char *filename, int *handle, FILE **file, *path_id = search->path_id; if (handle) { - *handle = pak->handle; - Sys_FileSeek (pak->handle, pak->files[i].filepos); + if (pak->files[i].deflatedsize) + { + FILE *f; + f = fopen (pak->filename, "rb"); + if (f) + { + fseek (f, pak->files[i].filepos, SEEK_SET); + f = FSZIP_Deflate(f, pak->files[i].deflatedsize, pak->files[i].filelen); + *handle = Sys_FileOpenStdio(f); + } + else + { //error! + com_filesize = -1; + *handle = -1; + } + } + else + { + *handle = pak->handle; + Sys_FileSeek (pak->handle, pak->files[i].filepos); + } return com_filesize; } else if (file) { /* open a new file on the pakfile */ *file = fopen (pak->filename, "rb"); if (*file) + { fseek (*file, pak->files[i].filepos, SEEK_SET); + if (pak->files[i].deflatedsize) + *file = FSZIP_Deflate(*file, pak->files[i].deflatedsize, pak->files[i].filelen); + } return com_filesize; } else /* for COM_FileExists() */ @@ -1659,6 +1779,8 @@ static int COM_FindFile (const char *filename, int *handle, FILE **file, { /* if not a registered version, don't ever go beyond base */ if ( strchr (filename, '/') || strchr (filename,'\\')) continue; + if (q_strcasecmp(COM_FileGetExtension(filename), "dat")) //don't load custom progs.dats either + continue; } q_snprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename); @@ -1981,20 +2103,22 @@ static pack_t *COM_LoadPackFile (const char *packfile) if (numpackfiles > MAX_FILES_IN_PACK) Sys_Error ("%s has %i files", packfile, numpackfiles); - if (numpackfiles != PAK0_COUNT) - com_modified = true; // not the original file - newfiles = (packfile_t *) Z_Malloc(numpackfiles * sizeof(packfile_t)); Sys_FileSeek (packhandle, header.dirofs); Sys_FileRead (packhandle, (void *)info, header.dirlen); - // crc the directory to check for modifications - CRC_Init (&crc); - for (i = 0; i < header.dirlen; i++) - CRC_ProcessByte (&crc, ((byte *)info)[i]); - if (crc != PAK0_CRC_V106 && crc != PAK0_CRC_V101 && crc != PAK0_CRC_V100) - com_modified = true; + if (numpackfiles != PAK0_COUNT) + com_modified = true; // not the original file + else + { + // crc the directory to check for modifications + CRC_Init (&crc); + for (i = 0; i < header.dirlen; i++) + CRC_ProcessByte (&crc, ((byte *)info)[i]); + if (crc != PAK0_CRC_V106 && crc != PAK0_CRC_V101 && crc != PAK0_CRC_V100) + com_modified = true; + } // parse the directory for (i = 0; i < numpackfiles; i++) @@ -2014,19 +2138,172 @@ static pack_t *COM_LoadPackFile (const char *packfile) return pack; } + +static void COM_ListSystemFiles(void *ctx, const char *gamedir, const char *ext, qboolean (*cb)(void *ctx, const char *fname)) +{ +#ifdef _WIN32 + WIN32_FIND_DATA fdat; + HANDLE fhnd; + char filestring[MAX_OSPATH]; + q_snprintf (filestring, sizeof(filestring), "%s/*.%s", gamedir, ext); + fhnd = FindFirstFile(filestring, &fdat); + if (fhnd == INVALID_HANDLE_VALUE) + return; + do + { + cb (ctx, fdat.cFileName); + } while (FindNextFile(fhnd, &fdat)); + FindClose(fhnd); +#else + DIR *dir_p; + struct dirent *dir_t; + dir_p = opendir(gamedir); + if (dir_p == NULL) + return; + while ((dir_t = readdir(dir_p)) != NULL) + { + if (q_strcasecmp(COM_FileGetExtension(dir_t->d_name), ext) != 0) + continue; + cb (ctx, dir_t->d_name); + } + closedir(dir_p); +#endif +} + + +static qboolean COM_AddPackage(searchpath_t *basepath, const char *pakfile) +{ + searchpath_t *search; + pack_t *pak; + const char *ext = COM_FileGetExtension(pakfile); + + //don't add the same pak twice. + for (search = com_searchpaths; search; search = search->next) + { + if (search->pack) + if (!q_strcasecmp(pakfile, search->pack->filename)) + return true; + } + + if (!q_strcasecmp(ext, "pak")) + pak = COM_LoadPackFile (pakfile); + else if (!q_strcasecmp(ext, "pk3") || !q_strcasecmp(ext, "pk4") || !q_strcasecmp(ext, "zip") || !q_strcasecmp(ext, "apk")) + { + pak = FSZIP_LoadArchive(pakfile); + if (pak) + com_modified = true; //would always be true, so we don't bother with crcs. + } + else + pak = NULL; + + if (!pak) + return false; + + search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); + search->path_id = basepath->path_id; + search->pack = pak; + search->next = com_searchpaths; + com_searchpaths = search; + + return true; +} + +static qboolean COM_AddEnumeratedPackage(void *ctx, const char *pakfile) +{ + searchpath_t *basepath = ctx; + char fullpakfile[MAX_OSPATH]; + q_snprintf (fullpakfile, sizeof(fullpakfile), "%s/%s", basepath->filename, pakfile); + return COM_AddPackage(basepath, fullpakfile); +} + +const char *COM_GetGameNames(qboolean full) +{ + if (full) + { + if (*com_gamenames) + return va("%s;%s", GAMENAME, com_gamenames); + else + return GAMENAME; + } + return com_gamenames; +// return COM_SkipPath(com_gamedir); +} +//if either contain id1 then that gets ignored +qboolean COM_GameDirMatches(const char *tdirs) +{ + int gnl = strlen(GAMENAME); + const char *odirs = COM_GetGameNames(false); + + //ignore any core paths. + if (!strncmp(tdirs, GAMENAME, gnl) && (tdirs[gnl] == ';' || !tdirs[gnl])) + { + tdirs+=gnl; + if (*tdirs == ';') + tdirs++; + } + if (!strncmp(odirs, GAMENAME, gnl) && (odirs[gnl] == ';' || !odirs[gnl])) + { + odirs+=gnl; + if (*odirs == ';') + odirs++; + } + //skip any qw in there from quakeworld (remote servers should really be skipping this, unless its maybe the only one in the path). + if (!strncmp(tdirs, "qw;", 3) || !strcmp(tdirs, "qw")) + { + tdirs+=2; + if (*tdirs==';') + tdirs++; + } + if (!strncmp(odirs, "qw;", 3) || !strcmp(odirs, "qw")) //need to cope with ourselves setting it that way too, just in case. + { + odirs+=2; + if (*odirs==';') + odirs++; + } + + //okay, now check it properly + if (!strcmp(odirs, tdirs)) + return true; + return false; +} + /* ================= COM_AddGameDirectory -- johnfitz -- modified based on topaz's tutorial ================= */ -static void COM_AddGameDirectory (const char *base, const char *dir) +static void COM_AddGameDirectory (const char *dir) { + const char *base = com_basedir; int i; unsigned int path_id; - searchpath_t *search; - pack_t *pak, *qspak; + searchpath_t *searchdir; char pakfile[MAX_OSPATH]; qboolean been_here = false; + FILE *listing; + qboolean found; + const char *enginepackname = "quakespasm"; + + if (*dir == '*') + dir++; + else if (!strchr(dir, '/') && !strchr(dir, '\\')) + { + //fixme: block dupes + if (*com_gamenames) + q_strlcat(com_gamenames, ";", sizeof(com_gamenames)); + q_strlcat(com_gamenames, dir, sizeof(com_gamenames)); + } + + //quakespasm enables mission pack flags automatically, so -game rogue works without breaking the hud + //we might as well do that here to simplify the code. + if (!q_strcasecmp(dir,"rogue")) { + rogue = true; + standard_quake = false; + } + if (!q_strcasecmp(dir,"hipnotic") || !q_strcasecmp(dir,"quoth")) { + hipnotic = true; + standard_quake = false; + } q_strlcpy (com_gamedir, va("%s/%s", base, dir), sizeof(com_gamedir)); @@ -2036,44 +2313,85 @@ static void COM_AddGameDirectory (const char *base, const char *dir) else path_id = 1U; _add_path: - // add the directory to the search path - search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); - search->path_id = path_id; - q_strlcpy (search->filename, com_gamedir, sizeof(search->filename)); - search->next = com_searchpaths; - com_searchpaths = search; + searchdir = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); + searchdir->path_id = path_id; + q_strlcpy (searchdir->filename, com_gamedir, sizeof(searchdir->filename)); + + q_snprintf (pakfile, sizeof(pakfile), "%s/pak.lst", com_gamedir); + listing = fopen(pakfile, "rb"); + if (listing) + { + int len; + char *buffer; + const char *name; + fseek(listing, 0, SEEK_END); + len = ftell(listing); + fseek(listing, 0, SEEK_SET); + buffer = Z_Malloc(len+1); + fread(buffer, 1, len, listing); + buffer[len] = 0; + fclose(listing); + + name = buffer; + com_modified = true; //any reordering of paks should be frowned upon + while ((name = COM_Parse(name))) + { + if (!*com_token) + continue; + if (strchr(com_token, '/') || strchr(com_token, '\\') || strchr(com_token, ':')) + continue; + q_snprintf (pakfile, sizeof(pakfile), "%s/%s", com_gamedir, com_token); + COM_AddPackage(searchdir, pakfile); + + if (path_id == 1 && !fitzmode && !q_strncasecmp(com_token, "pak0.", 5)) + { //add this now, to try to retain correct ordering. + qboolean old = com_modified; + if (been_here) base = host_parms->userdir; + q_snprintf (pakfile, sizeof(pakfile), "%s/%s.%s", base, enginepackname, COM_FileGetExtension(com_token)); + COM_AddPackage(searchdir, pakfile); + com_modified = old; + } + } + } // add any pak files in the format pak0.pak pak1.pak, ... for (i = 0; ; i++) { + found = false; + q_snprintf (pakfile, sizeof(pakfile), "%s/pak%i.pak", com_gamedir, i); - pak = COM_LoadPackFile (pakfile); - if (i != 0 || path_id != 1 || fitzmode) - qspak = NULL; - else { + found |= COM_AddPackage(searchdir, pakfile); + q_snprintf (pakfile, sizeof(pakfile), "%s/pak%i.pk3", com_gamedir, i); + found |= COM_AddPackage(searchdir, pakfile); + + if (i == 0 && path_id == 1 && !fitzmode) + { qboolean old = com_modified; if (been_here) base = host_parms->userdir; - q_snprintf (pakfile, sizeof(pakfile), "%s/quakespasm.pak", base); - qspak = COM_LoadPackFile (pakfile); + + q_snprintf (pakfile, sizeof(pakfile), "%s/%s.pak", base, enginepackname); + COM_AddPackage(searchdir, pakfile); + q_snprintf (pakfile, sizeof(pakfile), "%s/%s.pk3", base, enginepackname); + COM_AddPackage(searchdir, pakfile); + com_modified = old; } - if (pak) { - search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); - search->path_id = path_id; - search->pack = pak; - search->next = com_searchpaths; - com_searchpaths = search; - } - if (qspak) { - search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); - search->path_id = path_id; - search->pack = qspak; - search->next = com_searchpaths; - com_searchpaths = search; - } - if (!pak) break; + if (!found) + break; } + i = COM_CheckParm ("-nowildpaks"); + if (!i) + { + COM_ListSystemFiles(searchdir, com_gamedir, "pak", COM_AddEnumeratedPackage); + COM_ListSystemFiles(searchdir, com_gamedir, "pk3", COM_AddEnumeratedPackage); + } + + // then finally link the directory to the search path + //spike -- moved this last (also explicitly blocked loading progs.dat from system paths when running the demo) + searchdir->next = com_searchpaths; + com_searchpaths = searchdir; + if (!been_here && host_parms->userdir != host_parms->basedir) { been_here = true; @@ -2083,6 +2401,51 @@ _add_path: } } +void COM_ResetGameDirectories(char *newgamedirs) +{ + char *newpath, *path; + searchpath_t *search; + //Kill the extra game if it is loaded + while (com_searchpaths != com_base_searchpaths) + { + if (com_searchpaths->pack) + { + Sys_FileClose (com_searchpaths->pack->handle); + Z_Free (com_searchpaths->pack->files); + Z_Free (com_searchpaths->pack); + } + search = com_searchpaths->next; + Z_Free (com_searchpaths); + com_searchpaths = search; + } + hipnotic = false; + rogue = false; + standard_quake = true; + //wipe the list of mod gamedirs + *com_gamenames = 0; + //reset this too + q_strlcpy (com_gamedir, va("%s/%s", (host_parms->userdir != host_parms->basedir)?host_parms->userdir:com_basedir, GAMENAME), sizeof(com_gamedir)); + + for(newpath = newgamedirs; newpath && *newpath; ) + { + char *e = strchr(newpath, ';'); + if (e) + *e++ = 0; + + if (!q_strcasecmp(GAMENAME, newpath)) + path = NULL; + else for (path = newgamedirs; path < newpath; path += strlen(path)+1) + { + if (!q_strcasecmp(path, newpath)) + break; + } + + if (path == newpath) //not already loaded + COM_AddGameDirectory(newpath); + newpath = e; + } +} + //============================================================================== //johnfitz -- dynamic gamedir stuff -- modified by QuakeSpasm team. //============================================================================== @@ -2091,9 +2454,8 @@ static void COM_Game_f (void) { if (Cmd_Argc() > 1) { - const char *p = Cmd_Argv(1); - const char *p2 = Cmd_Argv(2); - searchpath_t *search; + int i, pri; + char paths[1024]; if (!registered.value) //disable shareware quake { @@ -2101,44 +2463,45 @@ static void COM_Game_f (void) return; } - if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":")) + *paths = 0; + q_strlcat(paths, GAMENAME, sizeof(paths)); + for (pri = 0; pri <= 1; pri++) { - Con_Printf ("gamedir should be a single directory name, not a path\n"); - return; - } - - if (*p2) - { - if (strcmp(p2,"-hipnotic") && strcmp(p2,"-rogue") && strcmp(p2,"-quoth")) { - Con_Printf ("invalid mission pack argument to \"game\"\n"); - return; - } - if (!q_strcasecmp(p, GAMENAME)) { - Con_Printf ("no mission pack arguments to %s game\n", GAMENAME); - return; - } - } - - if (!q_strcasecmp(p, COM_SkipPath(com_gamedir))) //no change - { - if (com_searchpaths->path_id > 1) { //current game not id1 - if (*p2 && com_searchpaths->path_id == 2) { - // rely on QuakeSpasm extension treating '-game missionpack' - // as '-missionpack', otherwise would be a mess - if (!q_strcasecmp(p, &p2[1])) - goto _same; - Con_Printf("reloading game \"%s\" with \"%s\" support\n", p, &p2[1]); + for (i = 1; i < Cmd_Argc(); i++) + { + const char *p = Cmd_Argv(i); + if (!*p) + p = GAMENAME; + if (pri == 0) + { + if (*p != '-') + continue; + p++; } - else if (!*p2 && com_searchpaths->path_id > 2) - Con_Printf("reloading game \"%s\" without mission pack support\n", p); - else goto _same; - } - else { _same: - Con_Printf("\"game\" is already \"%s\"\n", COM_SkipPath(com_gamedir)); - return; + else if (*p == '-') + continue; + + if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":")) + { + Con_Printf ("gamedir should be a single directory name, not a path\n"); + return; + } + + if (!q_strcasecmp(p, GAMENAME)) + continue; //don't add id1, its not interesting enough. + + if (*paths) + q_strlcat(paths, ";", sizeof(paths)); + q_strlcat(paths, p, sizeof(paths)); } } + if (!q_strcasecmp(paths, COM_GetGameNames(true))) + { + Con_Printf("\"game\" is already \"%s\"\n", COM_GetGameNames(true)); + return; + } + com_modified = true; //Kill the server @@ -2146,57 +2509,10 @@ static void COM_Game_f (void) Host_ShutdownServer(true); //Write config file + //fixme -- writing configs without reloading when switching between many mods is SERIOUSLY dangerous. ignore if no 'exec default.cfg' commands were used? Host_WriteConfiguration (); - //Kill the extra game if it is loaded - while (com_searchpaths != com_base_searchpaths) - { - if (com_searchpaths->pack) - { - Sys_FileClose (com_searchpaths->pack->handle); - Z_Free (com_searchpaths->pack->files); - Z_Free (com_searchpaths->pack); - } - search = com_searchpaths->next; - Z_Free (com_searchpaths); - com_searchpaths = search; - } - hipnotic = false; - rogue = false; - standard_quake = true; - - if (q_strcasecmp(p, GAMENAME)) //game is not id1 - { - if (*p2) { - COM_AddGameDirectory (com_basedir, &p2[1]); - standard_quake = false; - if (!strcmp(p2,"-hipnotic") || !strcmp(p2,"-quoth")) - hipnotic = true; - else if (!strcmp(p2,"-rogue")) - rogue = true; - if (q_strcasecmp(p, &p2[1])) //don't load twice - COM_AddGameDirectory (com_basedir, p); - } - else { - COM_AddGameDirectory (com_basedir, p); - // QuakeSpasm extension: treat '-game missionpack' as '-missionpack' - if (!q_strcasecmp(p,"hipnotic") || !q_strcasecmp(p,"quoth")) { - hipnotic = true; - standard_quake = false; - } - else if (!q_strcasecmp(p,"rogue")) { - rogue = true; - standard_quake = false; - } - } - } - else // just update com_gamedir - { - q_snprintf (com_gamedir, sizeof(com_gamedir), "%s/%s", - (host_parms->userdir != host_parms->basedir)? - host_parms->userdir : com_basedir, - GAMENAME); - } + COM_ResetGameDirectories(paths); //clear out and reload appropriate data Cache_Flush (); @@ -2210,14 +2526,14 @@ static void COM_Game_f (void) ExtraMaps_NewGame (); DemoList_Rebuild (); - Con_Printf("\"game\" changed to \"%s\"\n", COM_SkipPath(com_gamedir)); + Con_Printf("\"game\" changed to \"%s\"\n", COM_GetGameNames(true)); VID_Lock (); Cbuf_AddText ("exec quake.rc\n"); Cbuf_AddText ("vid_unlock\n"); } else //Diplay the current gamedir - Con_Printf("\"game\" is \"%s\"\n", COM_SkipPath(com_gamedir)); + Con_Printf("\"game\" is \"%s\"\n", COM_GetGameNames(true)); } /* @@ -2228,11 +2544,14 @@ COM_InitFilesystem void COM_InitFilesystem (void) //johnfitz -- modified based on topaz's tutorial { int i, j; + const char *p; + Cvar_RegisterVariable (&allow_download); Cvar_RegisterVariable (®istered); Cvar_RegisterVariable (&cmdline); Cmd_AddCommand ("path", COM_Path_f); Cmd_AddCommand ("game", COM_Game_f); //johnfitz + Cmd_AddCommand ("gamedir", COM_Game_f); //Spike -- alternative name for it, consistent with quakeworld and a few other engines i = COM_CheckParm ("-basedir"); if (i && i < com_argc-1) @@ -2246,46 +2565,36 @@ void COM_InitFilesystem (void) //johnfitz -- modified based on topaz's tutorial com_basedir[j-1] = 0; // start up with GAMENAME by default (id1) - COM_AddGameDirectory (com_basedir, GAMENAME); + COM_AddGameDirectory (GAMENAME); /* this is the end of our base searchpath: * any set gamedirs, such as those from -game command line * arguments or by the 'game' console command will be freed * up to here upon a new game command. */ com_base_searchpaths = com_searchpaths; + COM_ResetGameDirectories(""); // add mission pack requests (only one should be specified) if (COM_CheckParm ("-rogue")) - COM_AddGameDirectory (com_basedir, "rogue"); + COM_AddGameDirectory ("rogue"); if (COM_CheckParm ("-hipnotic")) - COM_AddGameDirectory (com_basedir, "hipnotic"); + COM_AddGameDirectory ("hipnotic"); if (COM_CheckParm ("-quoth")) - COM_AddGameDirectory (com_basedir, "quoth"); + COM_AddGameDirectory ("quoth"); - i = COM_CheckParm ("-game"); - if (i && i < com_argc-1) + for(i = 0;;) { - const char *p = com_argv[i + 1]; - if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":")) + i = COM_CheckParmNext (i, "-game"); + if (!i || i >= com_argc-1) + break; + + p = com_argv[i + 1]; + if (!*p || !strcmp(p, ".") || strstr(p, "..") || *p=='/' || *p=='\\' || strstr(p, ":")) Sys_Error ("gamedir should be a single directory name, not a path\n"); com_modified = true; - // don't load mission packs twice - if (COM_CheckParm ("-rogue") && !q_strcasecmp(p, "rogue")) p = NULL; - if (COM_CheckParm ("-hipnotic") && !q_strcasecmp(p, "hipnotic")) p = NULL; - if (COM_CheckParm ("-quoth") && !q_strcasecmp(p, "quoth")) p = NULL; - if (p != NULL) { - COM_AddGameDirectory (com_basedir, p); - // QuakeSpasm extension: treat '-game missionpack' as '-missionpack' - if (!q_strcasecmp(p,"rogue")) { - rogue = true; - standard_quake = false; - } - if (!q_strcasecmp(p,"hipnotic") || !q_strcasecmp(p,"quoth")) { - hipnotic = true; - standard_quake = false; - } - } + if (p != NULL) + COM_AddGameDirectory (p); } COM_CheckRegistered (); @@ -2461,3 +2770,69 @@ long FS_filelength (fshandle_t *fh) return fh->length; } +//for compat with dpp7 protocols, and mods that cba to precache things. +void COM_Effectinfo_Enumerate(int (*cb)(const char *pname)) +{ + int i; + const char *f, *e; + char *buf; + static const char *dpnames[] = + { + "TE_GUNSHOT", + "TE_GUNSHOTQUAD", + "TE_SPIKE", + "TE_SPIKEQUAD", + "TE_SUPERSPIKE", + "TE_SUPERSPIKEQUAD", + "TE_WIZSPIKE", + "TE_KNIGHTSPIKE", + "TE_EXPLOSION", + "TE_EXPLOSIONQUAD", + "TE_TAREXPLOSION", + "TE_TELEPORT", + "TE_LAVASPLASH", + "TE_SMALLFLASH", + "TE_FLAMEJET", + "EF_FLAME", + "TE_BLOOD", + "TE_SPARK", + "TE_PLASMABURN", + "TE_TEI_G3", + "TE_TEI_SMOKE", + "TE_TEI_BIGEXPLOSION", + "TE_TEI_PLASMAHIT", + "EF_STARDUST", + "TR_ROCKET", + "TR_GRENADE", + "TR_BLOOD", + "TR_WIZSPIKE", + "TR_SLIGHTBLOOD", + "TR_KNIGHTSPIKE", + "TR_VORESPIKE", + "TR_NEHAHRASMOKE", + "TR_NEXUIZPLASMA", + "TR_GLOWTRAIL", + "SVC_PARTICLE", + NULL + }; + + buf = (char*)COM_LoadMallocFile("effectinfo.txt", NULL); + if (!buf) + return; + + for (i = 0; dpnames[i]; i++) + cb(dpnames[i]); + + for (f = buf; f; f = e) + { + e = COM_Parse (f); + if (!strcmp(com_token, "effect")) + { + e = COM_Parse (e); + cb(com_token); + } + while (e && *e && *e != '\n') + e++; + } + free(buf); +} diff --git a/Quake/common.h b/Quake/common.h index eff6b649..b6a653bc 100644 --- a/Quake/common.h +++ b/Quake/common.h @@ -103,6 +103,8 @@ void MSG_WriteString (sizebuf_t *sb, const char *s); void MSG_WriteCoord (sizebuf_t *sb, float f, unsigned int flags); void MSG_WriteAngle (sizebuf_t *sb, float f, unsigned int flags); void MSG_WriteAngle16 (sizebuf_t *sb, float f, unsigned int flags); //johnfitz +struct entity_state_s; +void MSG_WriteStaticOrBaseLine(sizebuf_t *buf, int idx, struct entity_state_s *state, unsigned int protocol_pext2, unsigned int protocol, unsigned int protocolflags); //spike extern int msg_readcount; extern qboolean msg_badread; // set if a read goes beyond end of message @@ -118,6 +120,9 @@ const char *MSG_ReadString (void); float MSG_ReadCoord (unsigned int flags); float MSG_ReadAngle (unsigned int flags); float MSG_ReadAngle16 (unsigned int flags); //johnfitz +byte *MSG_ReadData (unsigned int length); // spike + +void COM_Effectinfo_Enumerate(int (*cb)(const char *pname)); //spike -- for dp compat //============================================================================ @@ -171,6 +176,7 @@ extern int safemode; */ int COM_CheckParm (const char *parm); +int COM_CheckParmNext (int last, const char *parm); void COM_Init (void); void COM_InitArgv (int argc, char **argv); @@ -180,6 +186,7 @@ const char *COM_SkipPath (const char *pathname); void COM_StripExtension (const char *in, char *out, size_t outsize); void COM_FileBase (const char *in, char *out, size_t outsize); void COM_AddExtension (char *path, const char *extension, size_t len); +qboolean COM_DownloadNameOkay(const char *filename); #if 0 /* COM_DefaultExtension can be dangerous */ void COM_DefaultExtension (char *path, const char *extension, size_t len); #endif @@ -198,6 +205,7 @@ typedef struct { char name[MAX_QPATH]; int filepos, filelen; + int deflatedsize; } packfile_t; typedef struct pack_s @@ -228,6 +236,12 @@ extern char com_basedir[MAX_OSPATH]; extern char com_gamedir[MAX_OSPATH]; extern int file_from_pak; // global indicating that file came from a pak +const char *COM_GetGameNames(qboolean full); +qboolean COM_GameDirMatches(const char *tdirs); + +pack_t *FSZIP_LoadArchive (const char *packfile); +FILE *FSZIP_Deflate(FILE *src, int srcsize, int outsize); + void COM_WriteFile (const char *filename, const void *data, int len); int COM_OpenFile (const char *filename, int *handle, unsigned int *path_id); int COM_FOpenFile (const char *filename, FILE **file, unsigned int *path_id); diff --git a/Quake/console.c b/Quake/console.c index 58847046..0f223451 100644 --- a/Quake/console.c +++ b/Quake/console.c @@ -31,6 +31,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #endif #include "quakedef.h" +#include "q_ctype.h" int con_linewidth; @@ -54,6 +55,9 @@ cvar_t con_logcenterprint = {"con_logcenterprint", "1", CVAR_NONE}; //johnfitz char con_lastcenterstring[1024]; //johnfitz +void (*con_redirect_flush)(const char *buffer); //call this to flush the redirection buffer (for rcon) +char con_redirect_buffer[8192]; + #define NUM_CON_TIMES 4 float con_times[NUM_CON_TIMES]; // realtime time the line was generated // for transparent notify lines @@ -358,6 +362,17 @@ static void Con_Linefeed (void) Q_memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth); } +#define ishex(c) ((c>='0' && c<= '9') || (c>='a' && c<='f') || (c>='A' && c<='F')) +static int dehex(char c) +{ + if (c >= '0' && c <= '9') + return c-'0'; + if (c >= 'A' && c <= 'F') + return c-('A'-10); + if (c >= 'a' && c <= 'f') + return c-('a'-10); + return 0; +} /* ================ Con_Print @@ -395,6 +410,95 @@ static void Con_Print (const char *txt) while ( (c = *txt) ) { + if (c == '^' && pr_checkextension.value) + { //parse markup like FTE/DP might. + switch(txt[1]) + { + case '^': //doubled up char for escaping. + txt++; + break; + case '0': //black + case '1': //red + case '2': //green + case '3': //yellow + case '4': //blue + case '5': //cyan + case '6': //magenta + case '7': //white + case '8': //white+half-alpha + case '9': //grey + case 'h': //toggle half-alpha + case 'b': //blink + case 'd': //reset to defaults (fixme: should reset ^m without resetting \1) + case 's': //modstack push + case 'r': //modstack restore + txt+=2; + continue; + case 'x': //RGB 12-bit colour + if (ishex(txt[2]) && ishex(txt[3]) && ishex(txt[4])) + { + txt+=4; + continue; + } + break; //malformed + case '[': //start fte's ^[text\key\value\key\value^] links + case ']': //end link + break; //fixme... skip the keys, recolour properly, etc +// txt+=2; +// continue; + case '&': + if ((ishex(txt[2])||txt[2]=='-') && (ishex(txt[3])||txt[3]=='-')) + { //ignore fte's fore/back ansi colours + txt += 4; + continue; + } + break; //malformed + case 'm': //toggle masking. + txt+=2; + mask ^= 128; + continue; + case 'U': //ucs-2 unicode codepoint + if (ishex(txt[2]) && ishex(txt[3]) && ishex(txt[4]) && ishex(txt[5])) + { + c = (dehex(txt[2])<<12) | (dehex(txt[3])<<8) | (dehex(txt[4])<<4) | dehex(txt[5]); + txt += 6-1; + + if (c >= 0xe000 && c <= 0xe0ff) + c &= 0xff; //private-use 0xE0XX maps to quake's chars + else if (c >= 0x20 && c <= 0x7f) + c &= 0x7f; //ascii is okay too. + else + c = '?'; //otherwise its some unicode char that we don't know how to handle. + break; + } + break; //malformed + case '{': //full unicode codepoint, for chars up to 0x10ffff + txt += 2; + c = 0; //no idea + while(*txt) + { + if (*txt == '}') + { + txt++; + break; + } + if (!ishex(*txt)) + break; + c<<=4; + c |= dehex(*txt++); + } + txt--; // for the ++ below + + if (c >= 0xe000 && c <= 0xe0ff) + c &= 0xff; //private-use 0xE0XX maps to quake's chars + else if (c >= 0x20 && c <= 0x7f) + c &= 0x7f; //ascii is okay too. + else + c = '?'; //otherwise its some unicode char that we don't know how to handle. + break; + } + } + if (c <= ' ') { boundary = true; @@ -489,6 +593,9 @@ void Con_Printf (const char *fmt, ...) q_vsnprintf (msg, sizeof(msg), fmt, argptr); va_end (argptr); + if (con_redirect_flush) + q_strlcat(con_redirect_buffer, msg, sizeof(con_redirect_buffer)); + // also echo to debugging console Sys_Printf ("%s", msg); @@ -533,15 +640,17 @@ void Con_DWarning (const char *fmt, ...) va_list argptr; char msg[MAXPRINTMSG]; - if (!developer.value) - return; // don't confuse non-developers with techie stuff... + if (developer.value >= 2) + { // don't confuse non-developers with techie stuff... + // (this is limit exceeded warnings) - va_start (argptr, fmt); - q_vsnprintf (msg, sizeof(msg), fmt, argptr); - va_end (argptr); + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); - Con_SafePrintf ("\x02Warning: "); - Con_Printf ("%s", msg); + Con_SafePrintf ("\x02Warning: "); + Con_Printf ("%s", msg); + } } /* @@ -695,6 +804,18 @@ void Con_LogCenterPrint (const char *str) } } +qboolean Con_IsRedirected(void) +{ + return !!con_redirect_flush; +} +void Con_Redirect(void(*flush)(const char *)) +{ + if (con_redirect_flush) + con_redirect_flush(con_redirect_buffer); + *con_redirect_buffer = 0; + con_redirect_flush = flush; +} + /* ============================================================================== @@ -717,12 +838,6 @@ tab_t *tablist; //defs from elsewhere extern qboolean keydown[256]; -typedef struct cmd_function_s -{ - struct cmd_function_s *next; - const char *name; - xcommand_t function; -} cmd_function_t; extern cmd_function_t *cmd_functions; #define MAX_ALIAS_NAME 32 typedef struct cmdalias_s @@ -763,7 +878,7 @@ void AddToTabList (const char *name, const char *type) // find max common between bash_partial and name i_bash = bash_partial; i_name = name; - while (*i_bash && (*i_bash == *i_name)) + while (*i_bash && (q_tolower(*i_bash) == q_tolower(*i_name))) { i_bash++; i_name++; @@ -901,15 +1016,15 @@ void BuildTabList (const char *partial) cvar = Cvar_FindVarAfter ("", CVAR_NONE); for ( ; cvar ; cvar=cvar->next) - if (!Q_strncmp (partial, cvar->name, len)) + if (!q_strncasecmp (partial, cvar->name, len)) AddToTabList (cvar->name, "cvar"); for (cmd=cmd_functions ; cmd ; cmd=cmd->next) - if (!Q_strncmp (partial,cmd->name, len)) + if (!q_strncasecmp (partial,cmd->name, len) && cmd->srctype != src_server) AddToTabList (cmd->name, "command"); for (alias=cmd_alias ; alias ; alias=alias->next) - if (!Q_strncmp (partial, alias->name, len)) + if (!q_strncasecmp (partial, alias->name, len)) AddToTabList (alias->name, "alias"); } @@ -1184,7 +1299,7 @@ void Con_DrawConsole (int lines, qboolean drawinput) { int i, x, y, j, sb, rows; const char *text; - char ver[32]; + const char *ver = ENGINE_NAME_AND_VER; if (lines <= 0) return; @@ -1227,7 +1342,6 @@ void Con_DrawConsole (int lines, qboolean drawinput) //draw version number in bottom right y += 8; - q_snprintf (ver, sizeof(ver), "QuakeSpasm " QUAKESPASM_VER_STRING); for (x = 0; x < (int)strlen(ver); x++) Draw_Character ((con_linewidth - strlen(ver) + x + 2)<<3, y, ver[x] /*+ 128*/); } diff --git a/Quake/console.h b/Quake/console.h index 0fa4a8c1..df56a280 100644 --- a/Quake/console.h +++ b/Quake/console.h @@ -48,6 +48,8 @@ void Con_SafePrintf (const char *fmt, ...) FUNC_PRINTF(1,2); void Con_DrawNotify (void); void Con_ClearNotify (void); void Con_ToggleConsole_f (void); +qboolean Con_IsRedirected(void); //returns true if its redirected. this generally means that things are a little more verbose. +void Con_Redirect(void(*flush)(const char *text)); void Con_NotifyBox (const char *text); // during startup for sound / cd warnings diff --git a/Quake/cvar.c b/Quake/cvar.c index aaa8089a..1db9a830 100644 --- a/Quake/cvar.c +++ b/Quake/cvar.c @@ -101,6 +101,40 @@ void Cvar_Inc_f (void) } } +/* +============ +Cvar_Set_f -- spike + +both set+seta commands +============ +*/ +void Cvar_Set_f (void) +{ + //q2: set name value flags + //dp: set name value description + //fte: set name some freeform value with spaces or whatever //description + //to avoid politics, its easier to just stick with name+value only. + //that leaves someone else free to pick a standard for what to do with extra args. + const char *varname = Cmd_Argv(1); + const char *varvalue = Cmd_Argv(2); + cvar_t *var; + if (Cmd_Argc() < 3) + { + Con_Printf("%s \n", Cmd_Argv(0)); + return; + } + if (Cmd_Argc() > 3) + { + Con_Warning("%s \"%s\" command with extra args\n", Cmd_Argv(0), varname); + return; + } + var = Cvar_Create(varname, varvalue); + Cvar_SetQuick(var, varvalue); + + if (!strcmp(Cmd_Argv(0), "seta")) + var->flags |= CVAR_ARCHIVE|CVAR_SETA; +} + /* ============ Cvar_Toggle_f -- johnfitz @@ -232,6 +266,8 @@ void Cvar_Init (void) Cmd_AddCommand ("reset", Cvar_Reset_f); Cmd_AddCommand ("resetall", Cvar_ResetAll_f); Cmd_AddCommand ("resetcfg", Cvar_ResetCfg_f); + Cmd_AddCommand ("set", Cvar_Set_f); + Cmd_AddCommand ("seta", Cvar_Set_f); } //============================================================================== @@ -426,6 +462,8 @@ void Cvar_SetQuick (cvar_t *var, const char *value) if (var->callback) var->callback (var); + if (var->flags & CVAR_AUTOCVAR) + PR_AutoCvarChanged(var); } void Cvar_SetValueQuick (cvar_t *var, const float value) @@ -588,6 +626,34 @@ void Cvar_RegisterVariable (cvar_t *variable) variable->flags |= CVAR_ROM; } +/* +============ +Cvar_Create -- spike + +Creates a cvar if it does not already exist, otherwise does nothing. +Must not be used until after all other cvars are registered. +Cvar will be persistent. +============ +*/ +cvar_t *Cvar_Create (const char *name, const char *value) +{ + cvar_t *newvar; + newvar = Cvar_FindVar(name); + if (newvar) + return newvar; //already exists. + if (Cmd_Exists (name)) + return NULL; //error! panic! oh noes! + + newvar = Z_Malloc(sizeof(cvar_t) + strlen(name)+1); + newvar->name = (char*)(newvar+1); + strcpy((char*)(newvar+1), name); + newvar->flags = CVAR_USERDEFINED; + + newvar->string = value; + Cvar_RegisterVariable(newvar); + return newvar; +} + /* ============ Cvar_SetCallback @@ -626,6 +692,9 @@ qboolean Cvar_Command (void) return true; } + if (Con_IsRedirected()) + Con_Printf ("changing \"%s\" from \"%s\" to \"%s\"\n", v->name, v->string, Cmd_Argv(1)); + Cvar_Set (v->name, Cmd_Argv(1)); return true; } @@ -646,7 +715,11 @@ void Cvar_WriteVariables (FILE *f) for (var = cvar_vars ; var ; var = var->next) { if (var->flags & CVAR_ARCHIVE) + { + if (var->flags & (CVAR_USERDEFINED|CVAR_SETA)) + fprintf (f, "seta "); fprintf (f, "%s \"%s\"\n", var->name, var->string); + } } } diff --git a/Quake/cvar.h b/Quake/cvar.h index 495be2e8..5005ffe6 100644 --- a/Quake/cvar.h +++ b/Quake/cvar.h @@ -63,16 +63,19 @@ interface from being ambiguous. */ -#define CVAR_NONE 0 +#define CVAR_NONE 0 #define CVAR_ARCHIVE (1U << 0) // if set, causes it to be saved to config -#define CVAR_NOTIFY (1U << 1) // changes will be broadcasted to all players (q1) +#define CVAR_NOTIFY (1U << 1) // changes will be broadcasted to all players (q1) #define CVAR_SERVERINFO (1U << 2) // added to serverinfo will be sent to clients (q1/net_dgrm.c and qwsv) #define CVAR_USERINFO (1U << 3) // added to userinfo, will be sent to server (qwcl) #define CVAR_CHANGED (1U << 4) -#define CVAR_ROM (1U << 6) -#define CVAR_LOCKED (1U << 8) // locked temporarily +#define CVAR_ROM (1U << 6) +#define CVAR_LOCKED (1U << 8) // locked temporarily #define CVAR_REGISTERED (1U << 10) // the var is added to the list of variables #define CVAR_CALLBACK (1U << 16) // var has a callback +#define CVAR_USERDEFINED (1U << 17) // cvar was created by the user/mod, and needs to be saved a bit differently. +#define CVAR_AUTOCVAR (1U << 18) // cvar changes need to feed back to qc global changes. +#define CVAR_SETA (1U << 19) // cvar will be saved with seta. typedef void (*cvarcallback_t) (struct cvar_s *); @@ -92,6 +95,9 @@ void Cvar_RegisterVariable (cvar_t *variable); // registers a cvar that already has the name, string, and optionally // the archive elements set. +cvar_t *Cvar_Create (const char *name, const char *value); +//creates+registers a cvar, otherwise just returns it. + void Cvar_SetCallback (cvar_t *var, cvarcallback_t func); // set a callback function to the var diff --git a/Quake/fs_zip.c b/Quake/fs_zip.c new file mode 100755 index 00000000..c493a3a0 --- /dev/null +++ b/Quake/fs_zip.c @@ -0,0 +1,822 @@ +#include "quakedef.h" + +#ifdef USE_ZLIB +#include +#else +#pragma message("zips supported but with no zlib") +#endif + +//ZIP features: +//zip64 for huge zips, except that quakespasm's filesystem calls are all otherwise 31bit. +//utf-8 encoding support (non-utf-8 is always ibm437) +//compression mode: store +//compression mode: deflate (via zlib) +//bigendian cpus. everything misaligned. + +//NOT supported: +//symlink flag (files are ignored completely) +//compression mode: deflate64 +//other compression modes +//split archives +//if central dir is crypted/compressed, the archive will fail to open +//if a file is crypted/compressed, the file will (internally) be marked as corrupt +//crc verification +//infozip utf-8 name override. +//other 'extra' fields. + +#define qofs_t long long +#define qofs_Make(low,high) ((unsigned int)(low) | ((qofs_t)(high)<<32)) + +struct zipinfo +{ + unsigned int thisdisk; //this disk number + unsigned int centraldir_startdisk; //central directory starts on this disk + qofs_t centraldir_numfiles_disk; //number of files in the centraldir on this disk + qofs_t centraldir_numfiles_all; //number of files in the centraldir on all disks + qofs_t centraldir_size; //total size of the centraldir + qofs_t centraldir_offset; //start offset of the centraldir with respect to the first disk that contains it + unsigned short commentlength; //length of the comment at the end of the archive. + + unsigned int zip64_centraldirend_disk; //zip64 weirdness + qofs_t zip64_centraldirend_offset; + unsigned int zip64_diskcount; + qofs_t zip64_eocdsize; + unsigned short zip64_version_madeby; + unsigned short zip64_version_needed; + + unsigned short centraldir_compressionmethod; + unsigned short centraldir_algid; + + qofs_t centraldir_end; + qofs_t zipoffset; +}; +struct zipcentralentry +{ + unsigned char *fname; + qofs_t cesize; + unsigned int flags; + time_t mtime; + + //PK12 + unsigned short version_madeby; + unsigned short version_needed; + unsigned short gflags; + unsigned short cmethod; + unsigned short lastmodfiletime; + unsigned short lastmodfiledate; + unsigned int crc32; + qofs_t csize; + qofs_t usize; + unsigned short fnane_len; + unsigned short extra_len; + unsigned short comment_len; + unsigned int disknum; + unsigned short iattributes; + unsigned int eattributes; + qofs_t localheaderoffset; //from start of disk +}; + +struct ziplocalentry +{ + //PK34 + unsigned short version_needed; + unsigned short gpflags; + unsigned short cmethod; + unsigned short lastmodfiletime; + unsigned short lastmodfiledate; + unsigned int crc32; + qofs_t csize; + qofs_t usize; + unsigned short fname_len; + unsigned short extra_len; +}; + +#define LittleU2FromPtr(p) (unsigned int)((p)[0] | ((p)[1]<<8u)) +#define LittleU4FromPtr(p) (unsigned int)((p)[0] | ((p)[1]<<8u) | ((p)[2]<<16u) | ((p)[3]<<24u)) +#define LittleU8FromPtr(p) qofs_Make(LittleU4FromPtr(p), LittleU4FromPtr((p)+4)) + +#define SIZE_LOCALENTRY 30 +#define SIZE_ENDOFCENTRALDIRECTORY 22 +#define SIZE_ZIP64ENDOFCENTRALDIRECTORY_V1 56 +#define SIZE_ZIP64ENDOFCENTRALDIRECTORY_V2 84 +#define SIZE_ZIP64ENDOFCENTRALDIRECTORY SIZE_ZIP64ENDOFCENTRALDIRECTORY_V2 +#define SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR 20 + + +typedef struct +{ + char name[MAX_QPATH]; + qofs_t localpos; + qofs_t filelen; + unsigned int crc; + unsigned int flags; +// time_t time; +} zpackfile_t; + +typedef struct +{ + int raw; + qofs_t rawsize; + + size_t numfiles; + zpackfile_t *files; +} zipfile_t; +#define ZFL_DEFLATED 1 //need to use zlib +#define ZFL_STORED 2 //direct access is okay +#define ZFL_SYMLINK 4 //file is a symlink +#define ZFL_CORRUPT 8 //file is corrupt or otherwise unreadable. +#define ZFL_WEAKENCRYPT 16 //traditional zip encryption + + +//for zip->quake charsets +unsigned short ibmtounicode[256] = +{ + 0x0000,0x263A,0x263B,0x2665,0x2666,0x2663,0x2660,0x2022,0x25D8,0x25CB,0x25D9,0x2642,0x2640,0x266A,0x266B,0x263C, //0x00(non-ascii, display-only) + 0x25BA,0x25C4,0x2195,0x203C,0x00B6,0x00A7,0x25AC,0x21A8,0x2191,0x2193,0x2192,0x2190,0x221F,0x2194,0x25B2,0x25BC, //0x10(non-ascii, display-only) + 0x0020,0x0021,0x0022,0x0023,0x0024,0x0025,0x0026,0x0027,0x0028,0x0029,0x002A,0x002B,0x002C,0x002D,0x002E,0x002F, //0x20(ascii) + 0x0030,0x0031,0x0032,0x0033,0x0034,0x0035,0x0036,0x0037,0x0038,0x0039,0x003A,0x003B,0x003C,0x003D,0x003E,0x003F, //0x30(ascii) + 0x0040,0x0041,0x0042,0x0043,0x0044,0x0045,0x0046,0x0047,0x0048,0x0049,0x004A,0x004B,0x004C,0x004D,0x004E,0x004F, //0x40(ascii) + 0x0050,0x0051,0x0052,0x0053,0x0054,0x0055,0x0056,0x0057,0x0058,0x0059,0x005A,0x005B,0x005C,0x005D,0x005E,0x005F, //0x50(ascii) + 0x0060,0x0061,0x0062,0x0063,0x0064,0x0065,0x0066,0x0067,0x0068,0x0069,0x006A,0x006B,0x006C,0x006D,0x006E,0x006F, //0x60(ascii) + 0x0070,0x0071,0x0072,0x0073,0x0074,0x0075,0x0076,0x0077,0x0078,0x0079,0x007A,0x007B,0x007C,0x007D,0x007E,0x2302, //0x70(mostly ascii, one display-only) + 0x00C7,0x00FC,0x00E9,0x00E2,0x00E4,0x00E0,0x00E5,0x00E7,0x00EA,0x00EB,0x00E8,0x00EF,0x00EE,0x00EC,0x00C4,0x00C5, //0x80(non-ascii, printable) + 0x00C9,0x00E6,0x00C6,0x00F4,0x00F6,0x00F2,0x00FB,0x00F9,0x00FF,0x00D6,0x00DC,0x00A2,0x00A3,0x00A5,0x20A7,0x0192, //0x90(non-ascii, printable) + 0x00E1,0x00ED,0x00F3,0x00FA,0x00F1,0x00D1,0x00AA,0x00BA,0x00BF,0x2310,0x00AC,0x00BD,0x00BC,0x00A1,0x00AB,0x00BB, //0xa0(non-ascii, printable) + 0x2591,0x2592,0x2593,0x2502,0x2524,0x2561,0x2562,0x2556,0x2555,0x2563,0x2551,0x2557,0x255D,0x255C,0x255B,0x2510, //0xb0(box-drawing, printable) + 0x2514,0x2534,0x252C,0x251C,0x2500,0x253C,0x255E,0x255F,0x255A,0x2554,0x2569,0x2566,0x2560,0x2550,0x256C,0x2567, //0xc0(box-drawing, printable) + 0x2568,0x2564,0x2565,0x2559,0x2558,0x2552,0x2553,0x256B,0x256A,0x2518,0x250C,0x2588,0x2584,0x258C,0x2590,0x2580, //0xd0(box-drawing, printable) + 0x03B1,0x00DF,0x0393,0x03C0,0x03A3,0x03C3,0x00B5,0x03C4,0x03A6,0x0398,0x03A9,0x03B4,0x221E,0x03C6,0x03B5,0x2229, //0xe0(maths(greek), printable) + 0x2261,0x00B1,0x2265,0x2264,0x2320,0x2321,0x00F7,0x2248,0x00B0,0x2219,0x00B7,0x221A,0x207F,0x00B2,0x25A0,0x00A0, //0xf0(maths, printable) +}; + + +static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip, struct zipinfo *info) +{ + qboolean result = false; + //zip comment is capped to 65k or so, so we can use a single buffer for this + byte traildata[0x10000 + SIZE_ENDOFCENTRALDIRECTORY+SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR]; + byte *magic; + unsigned int trailsize = 0x10000 + SIZE_ENDOFCENTRALDIRECTORY+SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR; + if ((qofs_t)trailsize > zip->rawsize) + trailsize = zip->rawsize; + //FIXME: do in a loop to avoid a huge block of stack use + Sys_FileSeek(zip->raw, zip->rawsize - trailsize); + Sys_FileRead(zip->raw, traildata, trailsize); + + memset(info, 0, sizeof(*info)); + + for (magic = traildata+trailsize-SIZE_ENDOFCENTRALDIRECTORY; magic >= traildata; magic--) + { + if (magic[0] == 'P' && + magic[1] == 'K' && + magic[2] == 5 && + magic[3] == 6) + { + info->centraldir_end = (zip->rawsize-trailsize)+(magic-traildata); + + info->thisdisk = LittleU2FromPtr(magic+4); + info->centraldir_startdisk = LittleU2FromPtr(magic+6); + info->centraldir_numfiles_disk = LittleU2FromPtr(magic+8); + info->centraldir_numfiles_all = LittleU2FromPtr(magic+10); + info->centraldir_size = LittleU4FromPtr(magic+12); + info->centraldir_offset = LittleU4FromPtr(magic+16); + info->commentlength = LittleU2FromPtr(magic+20); + + result = true; + break; + } + } + + if (!result) + Con_Printf("zip: unable to find end-of-central-directory\n"); + else + + //now look for a zip64 header. + //this gives more larger archives, more files, larger files, more spanned disks. + //note that the central directory itself is the same, it just has a couple of extra attributes on files that need them. + for (magic -= SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR; magic >= traildata; magic--) + { + if (magic[0] == 'P' && + magic[1] == 'K' && + magic[2] == 6 && + magic[3] == 7) + { + byte z64eocd[SIZE_ZIP64ENDOFCENTRALDIRECTORY]; + + info->zip64_centraldirend_disk = LittleU4FromPtr(magic+4); + info->zip64_centraldirend_offset = LittleU8FromPtr(magic+8); + info->zip64_diskcount = LittleU4FromPtr(magic+16); + + if (info->zip64_diskcount != 1 || info->zip64_centraldirend_disk != 0) + { + Con_Printf("zip: archive is spanned\n"); + return false; + } + + Sys_FileSeek(zip->raw, info->zip64_centraldirend_offset); + Sys_FileRead(zip->raw, z64eocd, sizeof(z64eocd)); + + if (z64eocd[0] == 'P' && + z64eocd[1] == 'K' && + z64eocd[2] == 6 && + z64eocd[3] == 6) + { + info->zip64_eocdsize = LittleU8FromPtr(z64eocd+4) + 12; + info->zip64_version_madeby = LittleU2FromPtr(z64eocd+12); + info->zip64_version_needed = LittleU2FromPtr(z64eocd+14); + info->thisdisk = LittleU4FromPtr(z64eocd+16); + info->centraldir_startdisk = LittleU4FromPtr(z64eocd+20); + info->centraldir_numfiles_disk = LittleU8FromPtr(z64eocd+24); + info->centraldir_numfiles_all = LittleU8FromPtr(z64eocd+32); + info->centraldir_size = LittleU8FromPtr(z64eocd+40); + info->centraldir_offset = LittleU8FromPtr(z64eocd+48); + + if (info->zip64_eocdsize >= 84) + { + info->centraldir_compressionmethod = LittleU2FromPtr(z64eocd+56); +// info->zip64_2_centraldir_csize = LittleU8FromPtr(z64eocd+58); +// info->zip64_2_centraldir_usize = LittleU8FromPtr(z64eocd+66); + info->centraldir_algid = LittleU2FromPtr(z64eocd+74); +// info.zip64_2_bitlen = LittleU2FromPtr(z64eocd+76); +// info->zip64_2_flags = LittleU2FromPtr(z64eocd+78); +// info->zip64_2_hashid = LittleU2FromPtr(z64eocd+80); +// info->zip64_2_hashlength = LittleU2FromPtr(z64eocd+82); + //info->zip64_2_hashdata = LittleUXFromPtr(z64eocd+84, info->zip64_2_hashlength); + } + } + else + { + Con_Printf("zip: zip64 end-of-central directory at unknown offset.\n"); + result = false; + } + + break; + } + } + + if (info->thisdisk || info->centraldir_startdisk || info->centraldir_numfiles_disk != info->centraldir_numfiles_all) + { + Con_Printf("zip: archive is spanned\n"); + result = false; + } + if (info->centraldir_compressionmethod || info->centraldir_algid) + { + Con_Printf("zip: encrypted centraldir\n"); + result = false; + } + + return result; +} + +static qboolean FSZIP_ReadCentralEntry(zipfile_t *zip, byte *data, struct zipcentralentry *entry) +{ + entry->flags = 0; + entry->fname = (unsigned char*)""; + entry->fnane_len = 0; + + if (data[0] != 'P' || + data[1] != 'K' || + data[2] != 1 || + data[3] != 2) + return false; //verify the signature + + //if we read too much, we'll catch it after. + entry->version_madeby = LittleU2FromPtr(data+4); + entry->version_needed = LittleU2FromPtr(data+6); + entry->gflags = LittleU2FromPtr(data+8); + entry->cmethod = LittleU2FromPtr(data+10); + entry->lastmodfiletime = LittleU2FromPtr(data+12); + entry->lastmodfiledate = LittleU2FromPtr(data+12); + entry->crc32 = LittleU4FromPtr(data+16); + entry->csize = LittleU4FromPtr(data+20); + entry->usize = LittleU4FromPtr(data+24); + entry->fnane_len = LittleU2FromPtr(data+28); + entry->extra_len = LittleU2FromPtr(data+30); + entry->comment_len = LittleU2FromPtr(data+32); + entry->disknum = LittleU2FromPtr(data+34); + entry->iattributes = LittleU2FromPtr(data+36); + entry->eattributes = LittleU4FromPtr(data+38); + entry->localheaderoffset = LittleU4FromPtr(data+42); + entry->cesize = 46; + + //mark the filename position + entry->fname = data+entry->cesize; + entry->cesize += entry->fnane_len; + + entry->mtime = 0; + + //parse extra + if (entry->extra_len) + { + byte *extra = data + entry->cesize; + byte *extraend = extra + entry->extra_len; + unsigned short extrachunk_tag; + unsigned short extrachunk_len; + while(extra+4 < extraend) + { + extrachunk_tag = LittleU2FromPtr(extra+0); + extrachunk_len = LittleU2FromPtr(extra+2); + if (extra + extrachunk_len > extraend) + break; //error + extra += 4; + + switch(extrachunk_tag) + { + case 1: //zip64 extended information extra field. the attributes are only present if the reegular file info is nulled out with a -1 + if (entry->usize == 0xffffffffu) + { + entry->usize = LittleU8FromPtr(extra); + extra += 8; + } + if (entry->csize == 0xffffffffu) + { + entry->csize = LittleU8FromPtr(extra); + extra += 8; + } + if (entry->localheaderoffset == 0xffffffffu) + { + entry->localheaderoffset = LittleU8FromPtr(extra); + extra += 8; + } + if (entry->disknum == 0xffffu) + { + entry->disknum = LittleU4FromPtr(extra); + extra += 4; + } + break; +#if !defined(_MSC_VER) || _MSC_VER > 1200 + case 0x000a: //NTFS extra field + //0+4: reserved + //4+2: subtag(must be 1, for times) + //6+2: subtagsize(times: must be == 8*3+4) + //8+8: mtime + //16+8: atime + //24+8: ctime + if (extrachunk_len >= 32 && LittleU2FromPtr(extra+4) == 1 && LittleU2FromPtr(extra+6) == 8*3) + entry->mtime = LittleU8FromPtr(extra+8) / 10000000ULL - 11644473600ULL; + else + Con_Printf("zip: unsupported ntfs subchunk %x\n", extrachunk_tag); + extra += extrachunk_len; + break; +#endif + case 0x5455: + if (extra[0] & 1) + entry->mtime = LittleU4FromPtr(extra+1); + //access and creation do NOT exist in the central header. + extra += extrachunk_len; + break; + default: +/* Con_Printf("Unknown chunk %x\n", extrachunk_tag); + case 0x5455: //extended timestamp + case 0x7875: //unix uid/gid + case 0x9901: //aes crypto +*/ extra += extrachunk_len; + break; + } + } + entry->cesize += entry->extra_len; + } + + //parse comment + entry->cesize += entry->comment_len; + + //check symlink flags + { + byte madeby = entry->version_madeby>>8; //system + //vms, unix, or beos file attributes includes a symlink attribute. + //symlinks mean the file contents is just the name of another file. + if (madeby == 2 || madeby == 3 || madeby == 16) + { + unsigned short unixattr = entry->eattributes>>16; + if ((unixattr & 0xF000) == 0xA000)//fa&S_IFMT==S_IFLNK + entry->flags |= ZFL_SYMLINK; + else if ((unixattr & 0xA000) == 0xA000)//fa&S_IFMT==S_IFLNK + entry->flags |= ZFL_SYMLINK; + } + } + + if (entry->gflags & (1u<<0)) //encrypted + { +#ifdef ZIPCRYPT + entry->flags |= ZFL_WEAKENCRYPT; +#else + entry->flags |= ZFL_CORRUPT; +#endif + } + + + if (entry->gflags & (1u<<5)) //is patch data + entry->flags |= ZFL_CORRUPT; + else if (entry->gflags & (1u<<6)) //strong encryption + entry->flags |= ZFL_CORRUPT; + else if (entry->gflags & (1u<<13)) //strong encryption + entry->flags |= ZFL_CORRUPT; + else if (entry->cmethod == 0) + entry->flags |= ZFL_STORED; + //1: shrink + //2-5: reduce + //6: implode + //7: tokenize + else if (entry->cmethod == 8) + entry->flags |= ZFL_DEFLATED; + //8: deflate64 - patented. sometimes written by microsoft's crap, so this might be problematic. only minor improvements. + //10: implode + //12: bzip2 +// else if (entry->cmethod == 12) +// entry->flags |= ZFL_BZIP2; +// else if (entry->cmethod == 14) +// entry->flags |= ZFL_LZMA; + //19: lz77 + //97: wavpack + //98: ppmd + else + entry->flags |= ZFL_CORRUPT; //unsupported compression method. + + if ((entry->flags & ZFL_WEAKENCRYPT) && !(entry->flags & ZFL_DEFLATED)) + entry->flags |= ZFL_CORRUPT; //only support decryption with deflate. + return true; +} + +static qboolean FSZIP_EnumerateCentralDirectory(zipfile_t *zip, struct zipinfo *info, const char *prefix) +{ + qboolean success = false; + zpackfile_t *f; + struct zipcentralentry entry; + qofs_t ofs = 0; + unsigned int i; + //lazily read the entire thing. + byte *centraldir = malloc(info->centraldir_size); + if (centraldir) + { + Sys_FileSeek(zip->raw, info->centraldir_offset+info->zipoffset); + if ((qofs_t)Sys_FileRead(zip->raw, centraldir, info->centraldir_size) == info->centraldir_size) + { + zip->numfiles = info->centraldir_numfiles_disk; + zip->files = f = Z_Malloc (zip->numfiles * sizeof(*f)); + + for (i = 0; i < zip->numfiles; i++) + { + if (!FSZIP_ReadCentralEntry(zip, centraldir+ofs, &entry) || ofs + entry.cesize > info->centraldir_size) + break; + + f->crc = entry.crc32; + + //copy out the filename and lowercase it + if (entry.gflags & (1u<<11)) + { //already utf-8 encoding + if (entry.fnane_len > sizeof(f->name)-1) + entry.fnane_len = sizeof(f->name)-1; + memcpy(f->name, entry.fname, entry.fnane_len); + f->name[entry.fnane_len] = 0; + } + else + { //legacy charset + int i; + int nlen = 0; + int cl; + for (i = 0; i < entry.fnane_len; i++) + { +#if 1 + cl = ibmtounicode[entry.fname[i]]; + if (cl > 127) + f->name[nlen] = '?'; //we can't encode non-ascii chars + else + f->name[nlen] = cl; + cl = 1; +#else + cl = utf8_encode(f->name+nlen, ibmtounicode[entry.fname[i]], sizeof(f->name)-1 - nlen); +#endif + if (!cl) //overflowed, truncate cleanly. + break; + nlen += cl; + } + f->name[nlen] = 0; + + } + + if (prefix && *prefix) + { + if (!strcmp(prefix, "..")) + { + char *c; + for (c = f->name; *c; ) + { + if (*c++ == '/') + break; + } + memmove(f->name, c, strlen(c)+1); + } + else + { + size_t prelen = strlen(prefix); + size_t oldlen = strlen(f->name); + if (prelen+1+oldlen+1 > sizeof(f->name)) + *f->name = 0; + else + { + memmove(f->name+prelen+1, f->name, oldlen); + f->name[prelen] = '/'; + memmove(f->name, prefix, prelen); + } + } + } + q_strlwr(f->name); + + f->filelen = *f->name?entry.usize:0; + f->localpos = entry.localheaderoffset+info->zipoffset; + f->flags = entry.flags; +// f->mtime = entry.mtime; + + ofs += entry.cesize; + f++; + } + + success = i == zip->numfiles; + if (!success) + { + free(zip->files); + zip->files = NULL; + zip->numfiles = 0; + } + } + } + + free(centraldir); + return success; +} + +static qboolean FSZIP_ValidateLocalHeader(zipfile_t *zip, zpackfile_t *zfile, qofs_t *datastart, qofs_t *datasize) +{ + struct ziplocalentry local; + byte localdata[SIZE_LOCALENTRY]; + qofs_t localstart = zfile->localpos; + + Sys_FileSeek(zip->raw, localstart); + Sys_FileRead(zip->raw, localdata, sizeof(localdata)); + + //make sure we found the right sort of table. + if (localdata[0] != 'P' || + localdata[1] != 'K' || + localdata[2] != 3 || + localdata[3] != 4) + return false; + + local.version_needed = LittleU2FromPtr(localdata+4); + local.gpflags = LittleU2FromPtr(localdata+6); + local.cmethod = LittleU2FromPtr(localdata+8); + local.lastmodfiletime = LittleU2FromPtr(localdata+10); + local.lastmodfiledate = LittleU2FromPtr(localdata+12); + local.crc32 = LittleU4FromPtr(localdata+14); + local.csize = LittleU4FromPtr(localdata+18); + local.usize = LittleU4FromPtr(localdata+22); + local.fname_len = LittleU2FromPtr(localdata+26); + local.extra_len = LittleU2FromPtr(localdata+28); + + localstart += SIZE_LOCALENTRY; + localstart += local.fname_len; + + //parse extra + if (local.usize == 0xffffffffu || local.csize == 0xffffffffu) //don't bother otherwise. + if (local.extra_len) + { + byte extradata[65536]; + byte *extra = extradata; + byte *extraend = extradata + local.extra_len; + unsigned short extrachunk_tag; + unsigned short extrachunk_len; + + Sys_FileSeek(zip->raw, localstart); + Sys_FileRead(zip->raw, extradata, sizeof(extradata)); + + while(extra+4 < extraend) + { + extrachunk_tag = LittleU2FromPtr(extra+0); + extrachunk_len = LittleU2FromPtr(extra+2); + if (extra + extrachunk_len > extraend) + break; //error + extra += 4; + + switch(extrachunk_tag) + { + case 1: //zip64 extended information extra field. the attributes are only present if the reegular file info is nulled out with a -1 + if (local.usize == 0xffffffffu) + { + local.usize = LittleU8FromPtr(extra); + extra += 8; + } + if (local.csize == 0xffffffffu) + { + local.csize = LittleU8FromPtr(extra); + extra += 8; + } + break; + default: +/* Con_Printf("Unknown chunk %x\n", extrachunk_tag); + case 0x000a: //NTFS (timestamps) + case 0x5455: //extended timestamp + case 0x7875: //unix uid/gid +*/ extra += extrachunk_len; + break; + } + } + } + localstart += local.extra_len; + *datastart = localstart; //this is the end of the local block, and the start of the data block (well, actually, should be encryption, but we don't support that). + *datasize = local.csize; + + if (local.gpflags & (1u<<3)) + { + //crc, usize, and csize were not known at the time the file was compressed. + //there is a 'data descriptor' after the file data, but to parse that we would need to decompress the file. + //instead, just depend upon upon the central directory and don't bother checking. + } + else + { + if (local.crc32 != zfile->crc) + return false; + if (local.usize != zfile->filelen) + return false; + } + + //FIXME: with pure paths, we still don't bother checking the crc (again, would require decompressing the entire file in advance). + + if (local.cmethod == 0) + return (zfile->flags & (ZFL_STORED|ZFL_CORRUPT|ZFL_DEFLATED)) == ZFL_STORED; + if (local.cmethod == 8) + return (zfile->flags & (ZFL_STORED|ZFL_CORRUPT|ZFL_DEFLATED)) == ZFL_DEFLATED; + return false; //some other method that we don't know. +} + +pack_t *FSZIP_LoadArchive (const char *packfile) +{ + size_t i; + packfile_t *newfiles; + int numpackfiles; + pack_t *pack; + + zipfile_t zip; + struct zipinfo info; + +#ifndef USE_ZLIB + qboolean zlibneeded = false; +#endif + + zip.rawsize = Sys_FileOpenRead(packfile, &zip.raw); + if (zip.raw < 0) + return NULL; + + //try to find the header + if (!FSZIP_FindEndCentralDirectory(&zip, &info)) + { + Sys_FileClose(zip.raw); + return NULL; + } + + //now try to read it. + if (!FSZIP_EnumerateCentralDirectory(&zip, &info, "") && !info.zip64_diskcount) + { + //uh oh... the central directory wasn't where it was meant to be! + //assuming that the endofcentraldir is packed at the true end of the centraldir (and that we're not zip64 and thus don't have an extra block), then we can guess based upon the offset difference + info.zipoffset = info.centraldir_end - (info.centraldir_offset+info.centraldir_size); + if (!FSZIP_EnumerateCentralDirectory(&zip, &info, "")) + { + Sys_FileClose(zip.raw); + Con_Printf ("zipfile \"%s\" appears to be missing its central directory\n", packfile); + return NULL; + } + } + + //lame zone. + //copy the files into something compatible with quake's pak support. + //ignore compressed / corrupt / unusable files + pack = (pack_t *) Z_Malloc (sizeof (pack_t)); + q_strlcpy (pack->filename, packfile, sizeof(pack->filename)); + pack->handle = zip.raw; + + numpackfiles = 0; + newfiles = NULL; + + newfiles = Z_Malloc(sizeof(*newfiles) * zip.numfiles); + for (numpackfiles = 0, i = 0; i < zip.numfiles; i++) + { + qofs_t startpos, datasize; + zpackfile_t *zp = &zip.files[i]; + if (zp->flags & ZFL_CORRUPT) + continue; //we can't cope with this. + if (zp->flags & ZFL_SYMLINK) + continue; //file data is just a filename + + if (!FSZIP_ValidateLocalHeader(&zip, zp, &startpos, &datasize)) + continue; //local header is corrupt + + if (zp->flags & ZFL_DEFLATED) + { +#ifndef USE_ZLIB + zlibneeded = true; + continue; +#endif + } + else if (zp->flags & ZFL_STORED) + { + if (datasize != zp->filelen) + continue; + datasize = 0; + } + else + continue; + + //usable file. + memcpy(newfiles[numpackfiles].name, zp->name, MAX_QPATH-1); + newfiles[numpackfiles].name[MAX_QPATH-1] = 0; + newfiles[numpackfiles].filelen = zp->filelen; + newfiles[numpackfiles].filepos = startpos; + newfiles[numpackfiles].deflatedsize = datasize; + numpackfiles++; + } + pack->numfiles = numpackfiles; + pack->files = newfiles; + + //we don't need this stuff now. + Z_Free(zip.files); + +#ifndef USE_ZLIB + if (zlibneeded) + Con_Printf ("zipfile \"%s\" contains compressed files, but zlib was disabled at compile time.\n", packfile); +#endif + //Sys_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles); + return pack; +} + +FILE *FSZIP_Deflate(FILE *src, int srcsize, int outsize) +{ +#ifdef USE_ZLIB + byte inbuffer[65536]; + byte outbuffer[65536]; + z_stream strm; + int ret; + + FILE *of; +#ifdef _WIN32 + /*warning: annother app might manage to open the file before we can. if the file is not opened exclusively then we can end up with issues + on windows, fopen is typically exclusive anyway, but not on unix. but on unix, tmpfile is actually usable, so special-case the windows code and hope that its never an issue + tmpfile isn't usable in windows. it creates the file in the root dir and requires admin rights, which is stupid. + */ + char *fname = _tempnam(NULL, "ftemp"); + of = fopen(fname, "w+bD"); +#else + of = tmpfile(); +#endif + + if (!of) + { + fclose(src); + return NULL; + } + + memset(&strm, 0, sizeof(strm)); + strm.data_type = Z_UNKNOWN; + inflateInit2(&strm, -MAX_WBITS); + while ((ret=inflate(&strm, Z_SYNC_FLUSH)) != Z_STREAM_END) + { + if (strm.avail_in == 0 || strm.avail_out == 0) + { + if (strm.avail_in == 0) + { + strm.avail_in = fread(inbuffer, 1, sizeof(inbuffer), src); + strm.next_in = inbuffer; + if (!strm.avail_in) + break; + } + if (strm.avail_out == 0) + { + strm.next_out = outbuffer; + fwrite(outbuffer, 1, strm.total_out, of); + strm.total_out = 0; + strm.avail_out = sizeof(outbuffer); + } + continue; + } + + //doh, it terminated for no reason + if (ret != Z_STREAM_END) + { + inflateEnd(&strm); + fclose(src); + fclose(of); + Con_Printf("Couldn't decompress file\n"); + return NULL; + } + + } + fwrite(outbuffer, 1, strm.total_out, of); + inflateEnd(&strm); + + fclose(src); + + fseek(of, SEEK_SET, 0); + return of; +#else + fclose(src); + return NULL; +#endif +} diff --git a/Quake/gl_draw.c b/Quake/gl_draw.c index 0f679044..c05d4fb2 100644 --- a/Quake/gl_draw.c +++ b/Quake/gl_draw.c @@ -27,6 +27,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //extern unsigned char d_15to8table[65536]; //johnfitz -- never used +qboolean premul_hud = false;//true; cvar_t scr_conalpha = {"scr_conalpha", "0.5", CVAR_ARCHIVE}; //johnfitz qpic_t *draw_disc; @@ -202,7 +203,7 @@ void Scrap_Upload (void) { sprintf (name, "scrap%i", i); scrap_textures[i] = TexMgr_LoadImage (NULL, name, BLOCK_WIDTH, BLOCK_HEIGHT, SRC_INDEXED, scrap_texels[i], - "", (src_offset_t)scrap_texels[i], TEXPREF_ALPHA | TEXPREF_OVERWRITE | TEXPREF_NOPICMIP); + "", (src_offset_t)scrap_texels[i], (premul_hud?TEXPREF_PREMULTIPLY:0)|TEXPREF_ALPHA | TEXPREF_OVERWRITE | TEXPREF_NOPICMIP); } scrap_dirty = false; @@ -252,7 +253,7 @@ qpic_t *Draw_PicFromWad (const char *name) offset = (src_offset_t)p - (src_offset_t)wad_base + sizeof(int)*2; //johnfitz gl.gltexture = TexMgr_LoadImage (NULL, texturename, p->width, p->height, SRC_INDEXED, p->data, WADFILENAME, - offset, TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr + offset, (premul_hud?TEXPREF_PREMULTIPLY:0)|TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr gl.sl = 0; gl.sh = (float)p->width/(float)TexMgr_PadConditional(p->width); //johnfitz gl.tl = 0; @@ -304,7 +305,7 @@ qpic_t *Draw_CachePic (const char *path) pic->pic.height = dat->height; gl.gltexture = TexMgr_LoadImage (NULL, path, dat->width, dat->height, SRC_INDEXED, dat->data, path, - sizeof(int)*2, TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr + sizeof(int)*2, (premul_hud?TEXPREF_PREMULTIPLY:0)|TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr gl.sl = 0; gl.sh = (float)dat->width/(float)TexMgr_PadConditional(dat->width); //johnfitz gl.tl = 0; @@ -359,7 +360,7 @@ void Draw_LoadPics (void) if (!data) Sys_Error ("Draw_LoadPics: couldn't load conchars"); offset = (src_offset_t)data - (src_offset_t)wad_base; char_texture = TexMgr_LoadImage (NULL, WADFILENAME":conchars", 128, 128, SRC_INDEXED, data, - WADFILENAME, offset, TEXPREF_ALPHA | TEXPREF_NEAREST | TEXPREF_NOPICMIP | TEXPREF_CONCHARS); + WADFILENAME, offset, (premul_hud?TEXPREF_PREMULTIPLY:0)|TEXPREF_ALPHA | TEXPREF_NEAREST | TEXPREF_NOPICMIP | TEXPREF_CONCHARS); draw_disc = Draw_PicFromWad ("disc"); draw_backtile = Draw_PicFromWad ("backtile"); @@ -567,19 +568,28 @@ void Draw_ConsoleBackground (void) { if (alpha < 1.0) { - glEnable (GL_BLEND); - glColor4f (1,1,1,alpha); - glDisable (GL_ALPHA_TEST); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + if (premul_hud) + glColor4f (alpha,alpha,alpha,alpha); + else + { + glEnable (GL_BLEND); + glDisable (GL_ALPHA_TEST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glColor4f (1,1,1,alpha); + } } Draw_Pic (0, 0, pic); if (alpha < 1.0) { - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glEnable (GL_ALPHA_TEST); - glDisable (GL_BLEND); + if (!premul_hud) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glEnable (GL_ALPHA_TEST); + glDisable (GL_BLEND); + } glColor4f (1,1,1,1); } } @@ -766,7 +776,17 @@ void GL_Set2D (void) glDisable (GL_DEPTH_TEST); glDisable (GL_CULL_FACE); - glDisable (GL_BLEND); - glEnable (GL_ALPHA_TEST); + + if (premul_hud) + { + glEnable (GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + } + else + { + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); + } glColor4f (1,1,1,1); } diff --git a/Quake/gl_fog.c b/Quake/gl_fog.c index 8629a22f..ddde3604 100644 --- a/Quake/gl_fog.c +++ b/Quake/gl_fog.c @@ -375,6 +375,23 @@ void Fog_NewMap (void) Fog_MarkModels (); //for volumetric fog } +/* +============= +Fog_NewMap + +so fog is preserved when starting a demo recording +============= +*/ +const char *Fog_GetFogCommand(void) +{ + if (fade_done) + { //if this mod is using dynamic fog, make sure we start with the right values. + //don't bother with this if we got fog from a clientside worldspawn key. + return va("\nfog %g %g %g %g\n", fog_density, fog_red, fog_green, fog_blue); + } + return NULL; +} + /* ============= Fog_Init diff --git a/Quake/gl_mesh.c b/Quake/gl_mesh.c index c362fce3..b114ffb4 100644 --- a/Quake/gl_mesh.c +++ b/Quake/gl_mesh.c @@ -32,333 +32,11 @@ ALIAS MODEL DISPLAY LIST GENERATION ================================================================= */ -qmodel_t *aliasmodel; -aliashdr_t *paliashdr; - -int used[8192]; // qboolean - -// the command list holds counts and s/t values that are valid for -// every frame -int commands[8192]; -int numcommands; - -// all frames will have their vertexes rearranged and expanded -// so they are in the order expected by the command list -int vertexorder[8192]; -int numorder; - -int allverts, alltris; - -int stripverts[128]; -int striptris[128]; -int stripcount; - -/* -================ -StripLength -================ -*/ -int StripLength (int starttri, int startv) -{ - int m1, m2; - int j; - mtriangle_t *last, *check; - int k; - - used[starttri] = 2; - - last = &triangles[starttri]; - - stripverts[0] = last->vertindex[(startv)%3]; - stripverts[1] = last->vertindex[(startv+1)%3]; - stripverts[2] = last->vertindex[(startv+2)%3]; - - striptris[0] = starttri; - stripcount = 1; - - m1 = last->vertindex[(startv+2)%3]; - m2 = last->vertindex[(startv+1)%3]; - - // look for a matching triangle -nexttri: - for (j=starttri+1, check=&triangles[starttri+1] ; jnumtris ; j++, check++) - { - if (check->facesfront != last->facesfront) - continue; - for (k=0 ; k<3 ; k++) - { - if (check->vertindex[k] != m1) - continue; - if (check->vertindex[ (k+1)%3 ] != m2) - continue; - - // this is the next part of the fan - - // if we can't use this triangle, this tristrip is done - if (used[j]) - goto done; - - // the new edge - if (stripcount & 1) - m2 = check->vertindex[ (k+2)%3 ]; - else - m1 = check->vertindex[ (k+2)%3 ]; - - stripverts[stripcount+2] = check->vertindex[ (k+2)%3 ]; - striptris[stripcount] = j; - stripcount++; - - used[j] = 2; - goto nexttri; - } - } -done: - - // clear the temp used flags - for (j=starttri+1 ; jnumtris ; j++) - if (used[j] == 2) - used[j] = 0; - - return stripcount; -} - -/* -=========== -FanLength -=========== -*/ -int FanLength (int starttri, int startv) -{ - int m1, m2; - int j; - mtriangle_t *last, *check; - int k; - - used[starttri] = 2; - - last = &triangles[starttri]; - - stripverts[0] = last->vertindex[(startv)%3]; - stripverts[1] = last->vertindex[(startv+1)%3]; - stripverts[2] = last->vertindex[(startv+2)%3]; - - striptris[0] = starttri; - stripcount = 1; - - m1 = last->vertindex[(startv+0)%3]; - m2 = last->vertindex[(startv+2)%3]; - - - // look for a matching triangle -nexttri: - for (j=starttri+1, check=&triangles[starttri+1] ; jnumtris ; j++, check++) - { - if (check->facesfront != last->facesfront) - continue; - for (k=0 ; k<3 ; k++) - { - if (check->vertindex[k] != m1) - continue; - if (check->vertindex[ (k+1)%3 ] != m2) - continue; - - // this is the next part of the fan - - // if we can't use this triangle, this tristrip is done - if (used[j]) - goto done; - - // the new edge - m2 = check->vertindex[ (k+2)%3 ]; - - stripverts[stripcount+2] = m2; - striptris[stripcount] = j; - stripcount++; - - used[j] = 2; - goto nexttri; - } - } -done: - - // clear the temp used flags - for (j=starttri+1 ; jnumtris ; j++) - if (used[j] == 2) - used[j] = 0; - - return stripcount; -} - - -/* -================ -BuildTris - -Generate a list of trifans or strips -for the model, which holds for all frames -================ -*/ -void BuildTris (void) -{ - int i, j, k; - int startv; - float s, t; - int len, bestlen, besttype; - int bestverts[1024]; - int besttris[1024]; - int type; - - // - // build tristrips - // - numorder = 0; - numcommands = 0; - memset (used, 0, sizeof(used)); - for (i = 0; i < pheader->numtris; i++) - { - // pick an unused triangle and start the trifan - if (used[i]) - continue; - - bestlen = 0; - besttype = 0; - for (type = 0 ; type < 2 ; type++) -// type = 1; - { - for (startv = 0; startv < 3; startv++) - { - if (type == 1) - len = StripLength (i, startv); - else - len = FanLength (i, startv); - if (len > bestlen) - { - besttype = type; - bestlen = len; - for (j = 0; j < bestlen+2; j++) - bestverts[j] = stripverts[j]; - for (j = 0; j < bestlen; j++) - besttris[j] = striptris[j]; - } - } - } - - // mark the tris on the best strip as used - for (j = 0; j < bestlen; j++) - used[besttris[j]] = 1; - - if (besttype == 1) - commands[numcommands++] = (bestlen+2); - else - commands[numcommands++] = -(bestlen+2); - - for (j = 0; j < bestlen+2; j++) - { - int tmp; - - // emit a vertex into the reorder buffer - k = bestverts[j]; - vertexorder[numorder++] = k; - - // emit s/t coords into the commands stream - s = stverts[k].s; - t = stverts[k].t; - if (!triangles[besttris[0]].facesfront && stverts[k].onseam) - s += pheader->skinwidth / 2; // on back side - s = (s + 0.5) / pheader->skinwidth; - t = (t + 0.5) / pheader->skinheight; - - // *(float *)&commands[numcommands++] = s; - // *(float *)&commands[numcommands++] = t; - // NOTE: 4 == sizeof(int) - // == sizeof(float) - memcpy (&tmp, &s, 4); - commands[numcommands++] = tmp; - memcpy (&tmp, &t, 4); - commands[numcommands++] = tmp; - } - } - - commands[numcommands++] = 0; // end of list marker - - Con_DPrintf2 ("%3i tri %3i vert %3i cmd\n", pheader->numtris, numorder, numcommands); - - allverts += numorder; - alltris += pheader->numtris; -} - -static void GL_MakeAliasModelDisplayLists_VBO (void); -static void GLMesh_LoadVertexBuffer (qmodel_t *m, const aliashdr_t *hdr); +#define countof(x) (sizeof(x)/sizeof((x)[0])) /* ================ GL_MakeAliasModelDisplayLists -================ -*/ -void GL_MakeAliasModelDisplayLists (qmodel_t *m, aliashdr_t *hdr) -{ - int i, j; - int *cmds; - trivertx_t *verts; - float hscale, vscale; //johnfitz -- padded skins - int count; //johnfitz -- precompute texcoords for padded skins - int *loadcmds; //johnfitz - - //johnfitz -- padded skins - hscale = (float)hdr->skinwidth/(float)TexMgr_PadConditional(hdr->skinwidth); - vscale = (float)hdr->skinheight/(float)TexMgr_PadConditional(hdr->skinheight); - //johnfitz - - aliasmodel = m; - paliashdr = hdr; // (aliashdr_t *)Mod_Extradata (m); - -//johnfitz -- generate meshes - Con_DPrintf2 ("meshing %s...\n",m->name); - BuildTris (); - - // save the data out - - paliashdr->poseverts = numorder; - - cmds = (int *) Hunk_Alloc (numcommands * 4); - paliashdr->commands = (byte *)cmds - (byte *)paliashdr; - - //johnfitz -- precompute texcoords for padded skins - loadcmds = commands; - while(1) - { - *cmds++ = count = *loadcmds++; - - if (!count) - break; - - if (count < 0) - count = -count; - - do - { - *(float *)cmds++ = hscale * (*(float *)loadcmds++); - *(float *)cmds++ = vscale * (*(float *)loadcmds++); - } while (--count); - } - //johnfitz - - verts = (trivertx_t *) Hunk_Alloc (paliashdr->numposes * paliashdr->poseverts * sizeof(trivertx_t)); - paliashdr->posedata = (byte *)verts - (byte *)paliashdr; - for (i=0 ; inumposes ; i++) - for (j=0 ; jnumposes * paliashdr->numverts * sizeof(trivertx_t)); - paliashdr->vertexes = (byte *)verts - (byte *)paliashdr; - for (i=0 ; inumposes ; i++) - for (j=0 ; jnumverts ; j++) - verts[i*paliashdr->numverts + j] = poseverts[i][j]; - // there can never be more than this number of verts and we just put them all on the hunk - maxverts_vbo = pheader->numtris * 3; + // front/back logic says we can never have more than numverts*2 + maxverts_vbo = paliashdr->numverts * 2; desc = (aliasmesh_t *) Hunk_Alloc (sizeof (aliasmesh_t) * maxverts_vbo); // there will always be this number of indexes - indexes = (unsigned short *) Hunk_Alloc (sizeof (unsigned short) * maxverts_vbo); + indexes = (unsigned short *) Hunk_Alloc (sizeof (unsigned short) * paliashdr->numtris * 3); - pheader->indexes = (intptr_t) indexes - (intptr_t) pheader; - pheader->meshdesc = (intptr_t) desc - (intptr_t) pheader; - pheader->numindexes = 0; - pheader->numverts_vbo = 0; + paliashdr->indexes = (intptr_t) indexes - (intptr_t) paliashdr; + paliashdr->meshdesc = (intptr_t) desc - (intptr_t) paliashdr; + paliashdr->numindexes = 0; + paliashdr->numverts_vbo = 0; - for (i = 0; i < pheader->numtris; i++) + for (i = 0; i < paliashdr->numtris; i++) { for (j = 0; j < 3; j++) { @@ -410,36 +79,39 @@ void GL_MakeAliasModelDisplayLists_VBO (void) int t = stverts[vertindex].t; // check for back side and adjust texcoord s - if (!triangles[i].facesfront && stverts[vertindex].onseam) s += pheader->skinwidth / 2; + if (!triangles[i].facesfront && stverts[vertindex].onseam) s += paliashdr->skinwidth / 2; // see does this vert already exist - for (v = 0; v < pheader->numverts_vbo; v++) + for (v = 0; v < paliashdr->numverts_vbo; v++) { // it could use the same xyz but have different s and t if (desc[v].vertindex == vertindex && (int) desc[v].st[0] == s && (int) desc[v].st[1] == t) { // exists; emit an index for it - indexes[pheader->numindexes++] = v; + indexes[paliashdr->numindexes++] = v; // no need to check any more break; } } - if (v == pheader->numverts_vbo) + if (v == paliashdr->numverts_vbo) { // doesn't exist; emit a new vert and index - indexes[pheader->numindexes++] = pheader->numverts_vbo; + indexes[paliashdr->numindexes++] = paliashdr->numverts_vbo; - desc[pheader->numverts_vbo].vertindex = vertindex; - desc[pheader->numverts_vbo].st[0] = s; - desc[pheader->numverts_vbo++].st[1] = t; + desc[paliashdr->numverts_vbo].vertindex = vertindex; + desc[paliashdr->numverts_vbo].st[0] = s; + desc[paliashdr->numverts_vbo++].st[1] = t; } } } - - // upload immediately - GLMesh_LoadVertexBuffer (aliasmodel, pheader); + + verts = (trivertx_t *) Hunk_Alloc (paliashdr->numposes * paliashdr->numverts_vbo * sizeof(*verts)); + paliashdr->vertexes = (byte *)verts - (byte *)paliashdr; + for (i=0 ; inumposes ; i++) + for (j=0 ; jnumverts_vbo ; j++) + verts[i*paliashdr->numverts_vbo + j] = poseverts_mdl[i][desc[j].vertindex]; } #define NUMVERTEXNORMALS 162 @@ -452,105 +124,201 @@ GLMesh_LoadVertexBuffer Upload the given alias model's mesh to a VBO Original code by MH from RMQEngine + +may update the mesh vbo/ebo offsets. ================ */ -static void GLMesh_LoadVertexBuffer (qmodel_t *m, const aliashdr_t *hdr) +void GLMesh_LoadVertexBuffer (qmodel_t *m, aliashdr_t *mainhdr) { + //we always need vertex array data. + //if we don't support vbos(gles?) then we just use system memory. + //if we're not using glsl(gles1?), then we don't actually need all the data, but we do still need some so its easier to just alloc the lot. int totalvbosize = 0; const aliasmesh_t *desc; - const short *indexes; - const trivertx_t *trivertexes; + const void *trivertexes; + byte *ebodata; byte *vbodata; int f; + aliashdr_t *hdr; + unsigned int numindexes, numverts; + intptr_t stofs; + intptr_t vertofs; + + //count how much space we're going to need. + for(hdr = mainhdr, numverts = 0, numindexes = 0; ; ) + { + if (hdr->posevertssize == 1) + totalvbosize += (hdr->numposes * hdr->numverts_vbo * sizeof (meshxyz_mdl_t)); // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + else if (hdr->posevertssize == 2) + totalvbosize += (hdr->numposes * hdr->numverts_vbo * sizeof (meshxyz_md3_t)); // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + + numverts += hdr->numverts_vbo; + numindexes += hdr->numindexes; + + if (hdr->nextsurface) + hdr = (aliashdr_t*)((byte*)hdr + hdr->nextsurface); + else + break; + } + hdr = NULL; + + vertofs = 0; + totalvbosize = (totalvbosize+7)&~7; //align it. + stofs = totalvbosize; + totalvbosize += (numverts * sizeof (meshst_t)); - if (!gl_glsl_alias_able) - return; - -// count the sizes we need - - // ericw -- RMQEngine stored these vbo*ofs values in aliashdr_t, but we must not - // mutate Mod_Extradata since it might be reloaded from disk, so I moved them to qmodel_t - // (test case: roman1.bsp from arwop, 64mb heap) - m->vboindexofs = 0; - - m->vboxyzofs = 0; - totalvbosize += (hdr->numposes * hdr->numverts_vbo * sizeof (meshxyz_t)); // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm - - m->vbostofs = totalvbosize; - totalvbosize += (hdr->numverts_vbo * sizeof (meshst_t)); - - if (!hdr->numindexes) return; if (!totalvbosize) return; - -// grab the pointers to data in the extradata + if (!numindexes) return; - desc = (aliasmesh_t *) ((byte *) hdr + hdr->meshdesc); - indexes = (short *) ((byte *) hdr + hdr->indexes); - trivertexes = (trivertx_t *) ((byte *)hdr + hdr->vertexes); - -// upload indices buffer - - GL_DeleteBuffersFunc (1, &m->meshindexesvbo); - GL_GenBuffersFunc (1, &m->meshindexesvbo); - GL_BindBufferFunc (GL_ELEMENT_ARRAY_BUFFER, m->meshindexesvbo); - GL_BufferDataFunc (GL_ELEMENT_ARRAY_BUFFER, hdr->numindexes * sizeof (unsigned short), indexes, GL_STATIC_DRAW); - -// create the vertex buffer (empty) + //create an elements buffer + ebodata = (byte *) malloc(numindexes * sizeof(unsigned short)); + if (!ebodata) + return; //fatal + // create the vertex buffer (empty) vbodata = (byte *) malloc(totalvbosize); + if (!vbodata) + { //fatal + free(ebodata); + return; + } memset(vbodata, 0, totalvbosize); -// fill in the vertices at the start of the buffer - for (f = 0; f < hdr->numposes; f++) // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + numindexes = 0; + + for(hdr = mainhdr, numverts = 0, numindexes = 0; ; ) { - int v; - meshxyz_t *xyz = (meshxyz_t *) (vbodata + (f * hdr->numverts_vbo * sizeof (meshxyz_t))); - const trivertx_t *tv = trivertexes + (hdr->numverts * f); + // grab the pointers to data in the extradata + desc = (aliasmesh_t *) ((byte *) hdr + hdr->meshdesc); + trivertexes = (void *) ((byte *)hdr + hdr->vertexes); - for (v = 0; v < hdr->numverts_vbo; v++) + //submit the index data. + hdr->eboofs = numindexes * sizeof (unsigned short); + numindexes += hdr->numindexes; + memcpy(ebodata + hdr->eboofs, (short *) ((byte *) hdr + hdr->indexes), hdr->numindexes * sizeof (unsigned short)); + + hdr->vbovertofs = vertofs; + + // fill in the vertices at the start of the buffer + if (hdr->posevertssize == 1) { - trivertx_t trivert = tv[desc[v].vertindex]; + for (f = 0; f < hdr->numposes; f++) // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + { + int v; + meshxyz_mdl_t *xyz = (meshxyz_mdl_t *) (vbodata + vertofs); + const trivertx_t *tv = (trivertx_t*)trivertexes + (hdr->numverts_vbo * f); + vertofs += hdr->numverts_vbo * sizeof (*xyz); - xyz[v].xyz[0] = trivert.v[0]; - xyz[v].xyz[1] = trivert.v[1]; - xyz[v].xyz[2] = trivert.v[2]; - xyz[v].xyz[3] = 1; // need w 1 for 4 byte vertex compression + for (v = 0; v < hdr->numverts_vbo; v++, tv++) + { + xyz[v].xyz[0] = tv->v[0]; + xyz[v].xyz[1] = tv->v[1]; + xyz[v].xyz[2] = tv->v[2]; + xyz[v].xyz[3] = 1; // need w 1 for 4 byte vertex compression - // map the normal coordinates in [-1..1] to [-127..127] and store in an unsigned char. - // this introduces some error (less than 0.004), but the normals were very coarse - // to begin with - xyz[v].normal[0] = 127 * r_avertexnormals[trivert.lightnormalindex][0]; - xyz[v].normal[1] = 127 * r_avertexnormals[trivert.lightnormalindex][1]; - xyz[v].normal[2] = 127 * r_avertexnormals[trivert.lightnormalindex][2]; - xyz[v].normal[3] = 0; // unused; for 4-byte alignment + // map the normal coordinates in [-1..1] to [-127..127] and store in an unsigned char. + // this introduces some error (less than 0.004), but the normals were very coarse + // to begin with + xyz[v].normal[0] = 127 * r_avertexnormals[tv->lightnormalindex][0]; + xyz[v].normal[1] = 127 * r_avertexnormals[tv->lightnormalindex][1]; + xyz[v].normal[2] = 127 * r_avertexnormals[tv->lightnormalindex][2]; + xyz[v].normal[3] = 0; // unused; for 4-byte alignment + } + } } - } + else if (hdr->posevertssize == 2) + { + for (f = 0; f < hdr->numposes; f++) // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + { + int v; + meshxyz_md3_t *xyz = (meshxyz_md3_t *) (vbodata + vertofs); + const md3XyzNormal_t *tv = (md3XyzNormal_t*)trivertexes + (hdr->numverts_vbo * f); + float lat,lng; + vertofs += hdr->numverts_vbo * sizeof (*xyz); -// fill in the ST coords at the end of the buffer + for (v = 0; v < hdr->numverts_vbo; v++, tv++) + { + xyz[v].xyz[0] = tv->xyz[0]; + xyz[v].xyz[1] = tv->xyz[1]; + xyz[v].xyz[2] = tv->xyz[2]; + xyz[v].xyz[3] = 1; // need w 1 for 4 byte vertex compression + + // map the normal coordinates in [-1..1] to [-127..127] and store in an unsigned char. + // this introduces some error (less than 0.004), but the normals were very coarse + // to begin with + lat = (float)tv->latlong[0] * (2 * M_PI)*(1.0 / 255.0); + lng = (float)tv->latlong[1] * (2 * M_PI)*(1.0 / 255.0); + xyz[v].normal[0] = 127 * cos ( lng ) * sin ( lat ); + xyz[v].normal[1] = 127 * sin ( lng ) * sin ( lat ); + xyz[v].normal[2] = 127 * cos ( lat ); + xyz[v].normal[3] = 0; // unused; for 4-byte alignment + } + } + } + + // fill in the ST coords at the end of the buffer + { + meshst_t *st; + float hscale, vscale; + + //johnfitz -- padded skins + hscale = (float)hdr->skinwidth/(float)TexMgr_PadConditional(hdr->skinwidth); + vscale = (float)hdr->skinheight/(float)TexMgr_PadConditional(hdr->skinheight); + //johnfitz + + hdr->vbostofs = stofs; + st = (meshst_t *) (vbodata + stofs); + stofs += hdr->numverts_vbo*sizeof(*st); + if (hdr->posevertssize == 2) + { + for (f = 0; f < hdr->numverts_vbo; f++) + { //md3 has floating-point skin coords. use the values directly. + st[f].st[0] = hscale * desc[f].st[0]; + st[f].st[1] = vscale * desc[f].st[1]; + } + } + else + { + for (f = 0; f < hdr->numverts_vbo; f++) + { + st[f].st[0] = hscale * ((float) desc[f].st[0] + 0.5f) / (float) hdr->skinwidth; + st[f].st[1] = vscale * ((float) desc[f].st[1] + 0.5f) / (float) hdr->skinheight; + } + } + } + + if (hdr->nextsurface) + hdr = (aliashdr_t*)((byte*)hdr + hdr->nextsurface); + else + break; + } + hdr = NULL; + + if (gl_vbo_able) { - meshst_t *st; - float hscale, vscale; + // upload indexes buffer + GL_DeleteBuffersFunc (1, &m->meshindexesvbo); + GL_GenBuffersFunc (1, &m->meshindexesvbo); + GL_BindBufferFunc (GL_ELEMENT_ARRAY_BUFFER, m->meshindexesvbo); + GL_BufferDataFunc (GL_ELEMENT_ARRAY_BUFFER, numindexes * sizeof (unsigned short), ebodata, GL_STATIC_DRAW); - //johnfitz -- padded skins - hscale = (float)hdr->skinwidth/(float)TexMgr_PadConditional(hdr->skinwidth); - vscale = (float)hdr->skinheight/(float)TexMgr_PadConditional(hdr->skinheight); - //johnfitz + // upload vertexes buffer + GL_DeleteBuffersFunc (1, &m->meshvbo); + GL_GenBuffersFunc (1, &m->meshvbo); + GL_BindBufferFunc (GL_ARRAY_BUFFER, m->meshvbo); + GL_BufferDataFunc (GL_ARRAY_BUFFER, totalvbosize, vbodata, GL_STATIC_DRAW); - st = (meshst_t *) (vbodata + m->vbostofs); - for (f = 0; f < hdr->numverts_vbo; f++) - { - st[f].st[0] = hscale * ((float) desc[f].st[0] + 0.5f) / (float) hdr->skinwidth; - st[f].st[1] = vscale * ((float) desc[f].st[1] + 0.5f) / (float) hdr->skinheight; - } + free (vbodata); + free (ebodata); + + m->meshvboptr = NULL; + m->meshindexesvboptr = NULL; + } + else + { + m->meshvboptr = vbodata; + m->meshindexesvboptr = ebodata; } - -// upload vertexes buffer - GL_DeleteBuffersFunc (1, &m->meshvbo); - GL_GenBuffersFunc (1, &m->meshvbo); - GL_BindBufferFunc (GL_ARRAY_BUFFER, m->meshvbo); - GL_BufferDataFunc (GL_ARRAY_BUFFER, totalvbosize, vbodata, GL_STATIC_DRAW); - - free (vbodata); // invalidate the cached bindings GL_ClearBufferBindings (); @@ -567,17 +335,14 @@ void GLMesh_LoadVertexBuffers (void) { int j; qmodel_t *m; - const aliashdr_t *hdr; + aliashdr_t *hdr; - if (!gl_glsl_alias_able) - return; - for (j = 1; j < MAX_MODELS; j++) { if (!(m = cl.model_precache[j])) break; if (m->type != mod_alias) continue; - hdr = (const aliashdr_t *) Mod_Extradata (m); + hdr = (aliashdr_t *) Mod_Extradata (m); GLMesh_LoadVertexBuffer (m, hdr); } @@ -595,20 +360,290 @@ void GLMesh_DeleteVertexBuffers (void) int j; qmodel_t *m; - if (!gl_glsl_alias_able) + if (!gl_vbo_able) return; for (j = 1; j < MAX_MODELS; j++) { if (!(m = cl.model_precache[j])) break; if (m->type != mod_alias) continue; - - GL_DeleteBuffersFunc (1, &m->meshvbo); - m->meshvbo = 0; - GL_DeleteBuffersFunc (1, &m->meshindexesvbo); + if (m->meshvbo) + GL_DeleteBuffersFunc (1, &m->meshvbo); + m->meshvbo = 0; + free(m->meshvboptr); + m->meshvboptr = NULL; + + if (m->meshindexesvbo) + GL_DeleteBuffersFunc (1, &m->meshindexesvbo); m->meshindexesvbo = 0; + free(m->meshindexesvboptr); + m->meshindexesvboptr = NULL; } - + GL_ClearBufferBindings (); } + + + + + + +//from gl_model.c +extern char loadname[]; // for hunk tags +void Mod_CalcAliasBounds (aliashdr_t *a); + + +#define MD3_VERSION 15 +//structures from Tenebrae +typedef struct { + int ident; + int version; + + char name[64]; + + int flags; //assumed to match quake1 models, for lack of somewhere better. + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; + int ofsTags; + int ofsSurfaces; + int ofsEnd; +} md3Header_t; + +//then has header->numFrames of these at header->ofs_Frames +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +//there are header->numSurfaces of these at header->ofsSurfaces, following from ofsEnd +typedef struct { + int ident; // + + char name[64]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +//at surf+surf->ofsXyzNormals +/*typedef struct { + short xyz[3]; + byte latlong[2]; +} md3XyzNormal_t;*/ + +//surf->numTriangles at surf+surf->ofsTriangles +typedef struct { + int indexes[3]; +} md3Triangle_t; + +//surf->numVerts at surf+surf->ofsSt +typedef struct { + float s; + float t; +} md3St_t; + +typedef struct { + char name[64]; + int shaderIndex; +} md3Shader_t; + + + +void Mod_LoadMD3Model (qmodel_t *mod, void *buffer) +{ + md3Header_t *pinheader; + md3Surface_t *pinsurface; + md3Frame_t *pinframes; + md3Triangle_t *pintriangle; + unsigned short *poutindexes; + md3XyzNormal_t *pinvert; + md3XyzNormal_t *poutvert; + md3St_t *pinst; + aliasmesh_t *poutst; + md3Shader_t *pinshader; + int size; + int start, end, total; + int ival, j; + int numsurfs, surf; + int numframes; + aliashdr_t *outhdr; + + start = Hunk_LowMark (); + + pinheader = (md3Header_t *)buffer; + + ival = LittleLong (pinheader->version); + if (ival != MD3_VERSION) + Sys_Error ("%s has wrong version number (%i should be %i)", + mod->name, ival, MD3_VERSION); + + numsurfs = LittleLong (pinheader->numSurfaces); + numframes = LittleLong(pinheader->numFrames); + + if (numframes > MAXALIASFRAMES) + Sys_Error ("%s has too many frames (%i vs %i)", + mod->name, numframes, MAXALIASFRAMES); + if (!numsurfs) + Sys_Error ("%s has nosurfaces", mod->name); + + pinframes = (md3Frame_t*)((byte*)buffer + LittleLong(pinheader->ofsFrames)); +// +// allocate space for a working header, plus all the data except the frames, +// skin and group info +// + size = sizeof(aliashdr_t) + (numframes-1) * sizeof (outhdr->frames[0]); + outhdr = (aliashdr_t *) Hunk_AllocName (size * numsurfs, loadname); + + for (surf = 0, pinsurface = (md3Surface_t*)((byte*)buffer + LittleLong(pinheader->ofsSurfaces)); surf < numsurfs; surf++, pinsurface = (md3Surface_t*)((byte*)pinsurface + LittleLong(pinsurface->ofsEnd))) + { + aliashdr_t *osurf = (aliashdr_t*)((byte*)outhdr + size*surf); + if (LittleLong(pinsurface->ident) != (('I'<<0)|('D'<<8)|('P'<<16)|('3'<<24))) + Sys_Error ("%s corrupt surface ident", mod->name); + if (LittleLong(pinsurface->numFrames) != numframes) + Sys_Error ("%s mismatched framecounts", mod->name); + + if (surf+1 < numsurfs) + osurf->nextsurface = size; + else + osurf->nextsurface = 0; + + osurf->posevertssize = 2; + osurf->numverts_vbo = osurf->numverts = LittleLong(pinsurface->numVerts); + pinvert = (md3XyzNormal_t*)((byte*)pinsurface + LittleLong(pinsurface->ofsXyzNormals)); + poutvert = (md3XyzNormal_t *) Hunk_Alloc (numframes * osurf->numverts * sizeof(*poutvert)); + osurf->vertexes = (byte *)poutvert - (byte *)osurf; + for (ival = 0; ival < numframes; ival++) + { + osurf->frames[ival].firstpose = ival; + osurf->frames[ival].numposes = 1; + osurf->frames[ival].interval = 0.1; + osurf->frames[ival].frame = ival; + + q_strlcpy(osurf->frames[ival].name, pinframes->name, sizeof(osurf->frames[ival].name)); + for (j = 0; j < 3; j++) + { //fixme... + osurf->frames[ival].bboxmin.v[j] = 0; + osurf->frames[ival].bboxmax.v[j] = 255; + } + + for (j=0 ; jnumverts ; j++) + poutvert[j] = pinvert[j]; + poutvert += osurf->numverts; + pinvert += osurf->numverts; + } + osurf->numposes = osurf->numframes = numframes; + + osurf->numtris = LittleLong(pinsurface->numTriangles); + osurf->numindexes = osurf->numtris*3; + pintriangle = (md3Triangle_t*)((byte*)pinsurface + LittleLong(pinsurface->ofsTriangles)); + poutindexes = (unsigned short *) Hunk_Alloc (sizeof (*poutindexes) * osurf->numindexes); + osurf->indexes = (intptr_t) poutindexes - (intptr_t) osurf; + for (ival = 0; ival < osurf->numtris; ival++, pintriangle++, poutindexes+=3) + { + for (j = 0; j < 3; j++) + poutindexes[j] = LittleLong(pintriangle->indexes[j]); + } + + for (j = 0; j < 3; j++) + { + osurf->scale_origin[j] = 0; + osurf->scale[j] = 1/64.0; + } + + //guess at skin sizes + osurf->skinwidth = 320; + osurf->skinheight = 200; + + //load the textures + pinshader = (md3Shader_t*)((byte*)pinsurface + LittleLong(pinsurface->ofsShaders)); + osurf->numskins = LittleLong(pinsurface->numShaders); + for (j = 0; j < osurf->numskins; j++, pinshader++) + { + char texturename[MAX_QPATH]; + char fullbrightname[MAX_QPATH]; + char *ext; + //texture names in md3s are kinda fucked. they could be just names relative to the mdl, or full paths, or just simple shader names. + //our texture manager is too lame to scan all 1000 possibilities + if (strchr(pinshader->name, '/') || strchr(pinshader->name, '\\')) + { //so if there's a path then we want to use that. + q_strlcpy(texturename, pinshader->name, sizeof(texturename)); + } + else + { //and if there's no path then we want to prefix it with our own. + q_strlcpy(texturename, mod->name, sizeof(texturename)); + *(char*)COM_SkipPath(texturename) = 0; + //and concat the specified name + q_strlcat(texturename, pinshader->name, sizeof(texturename)); + } + //and make sure there's no extensions. these get ignored in q3, which is kinda annoying, but this is an md3 and standards are standards (and it makes luma easier). + ext = (char*)COM_FileGetExtension(texturename); + if (*ext) + *--ext = 0; + //luma has an extra postfix. + q_snprintf(fullbrightname, sizeof(fullbrightname), "%s_luma", texturename); + osurf->gltextures[j][0] = TexMgr_LoadImage(mod, texturename, osurf->skinwidth, osurf->skinheight, SRC_EXTERNAL, NULL, texturename, 0, TEXPREF_PAD|TEXPREF_ALPHA|TEXPREF_NOBRIGHT|TEXPREF_MIPMAP); + osurf->fbtextures[j][0] = TexMgr_LoadImage(mod, fullbrightname, osurf->skinwidth, osurf->skinheight, SRC_EXTERNAL, NULL, texturename, 0, TEXPREF_PAD|TEXPREF_ALPHA|TEXPREF_FULLBRIGHT|TEXPREF_MIPMAP); + osurf->gltextures[j][3] = osurf->gltextures[j][2] = osurf->gltextures[j][1] = osurf->gltextures[j][0]; + osurf->fbtextures[j][3] = osurf->fbtextures[j][2] = osurf->fbtextures[j][1] = osurf->fbtextures[j][0]; + } + if (osurf->numskins) + { + osurf->skinwidth = osurf->gltextures[0][0]->source_width; + osurf->skinheight = osurf->gltextures[0][0]->source_height; + } + + //and figure out the texture coords properly, now we know the actual sizes. + pinst = (md3St_t*)((byte*)pinsurface + LittleLong(pinsurface->ofsSt)); + poutst = (aliasmesh_t *) Hunk_Alloc (sizeof (*poutst) * osurf->numverts); + osurf->meshdesc = (intptr_t) poutst - (intptr_t) osurf; + for (j = 0; j < osurf->numverts; j++) + { + poutst[j].vertindex = j; //how is this useful? + poutst[j].st[0] = pinst->s; + poutst[j].st[1] = pinst->t; + } + } + GLMesh_LoadVertexBuffer (mod, outhdr); + + //small violation of the spec, but it seems like noone else uses it. + mod->flags = LittleLong (pinheader->flags); + + + mod->type = mod_alias; + + Mod_CalcAliasBounds (outhdr); //johnfitz + +// +// move the complete, relocatable alias model to the cache +// + end = Hunk_LowMark (); + total = end - start; + + Cache_Alloc (&mod->cache, total, loadname); + if (!mod->cache.data) + return; + memcpy (mod->cache.data, outhdr, total); + + Hunk_FreeToLowMark (start); +} diff --git a/Quake/gl_model.c b/Quake/gl_model.c index 2e3c31e3..cad5e728 100644 --- a/Quake/gl_model.c +++ b/Quake/gl_model.c @@ -32,9 +32,11 @@ char loadname[32]; // for hunk tags void Mod_LoadSpriteModel (qmodel_t *mod, void *buffer); void Mod_LoadBrushModel (qmodel_t *mod, void *buffer); void Mod_LoadAliasModel (qmodel_t *mod, void *buffer); +void Mod_LoadMD3Model (qmodel_t *mod, void *buffer); qmodel_t *Mod_LoadModel (qmodel_t *mod, qboolean crash); cvar_t external_ents = {"external_ents", "1", CVAR_ARCHIVE}; +cvar_t gl_load24bit = {"gl_load24bit", "1", CVAR_ARCHIVE}; static byte *mod_novis; static int mod_novis_capacity; @@ -42,7 +44,7 @@ static int mod_novis_capacity; static byte *mod_decompressed; static int mod_decompressed_capacity; -#define MAX_MOD_KNOWN 2048 /*johnfitz -- was 512 */ +#define MAX_MOD_KNOWN 8192 /*spike -- new value, was 2048 in qs, 512 in vanilla. Needs to be big for big maps with many many inline models. */ qmodel_t mod_known[MAX_MOD_KNOWN]; int mod_numknown; @@ -58,6 +60,7 @@ void Mod_Init (void) { Cvar_RegisterVariable (&gl_subdivide_size); Cvar_RegisterVariable (&external_ents); + Cvar_RegisterVariable (&gl_load24bit); //johnfitz -- create notexture miptex r_notexture_mip = (texture_t *) Hunk_AllocName (sizeof(texture_t), "r_notexture_mip"); @@ -166,6 +169,8 @@ byte *Mod_DecompressVis (byte *in, qmodel_t *model) c = in[1]; in += 2; + if (c > row - (out - mod_decompressed)) + c = row - (out - mod_decompressed); //now that we're dynamically allocating pvs buffers, we have to be more careful to avoid heap overflows with buggy maps. while (c) { if (out == outend) @@ -223,6 +228,7 @@ void Mod_ClearAll (void) { mod->needload = true; TexMgr_FreeTexturesForOwner (mod); //johnfitz + PScript_ClearSurfaceParticles(mod); } } @@ -237,7 +243,10 @@ void Mod_ResetAll (void) for (i=0 , mod=mod_known ; ineedload) //otherwise Mod_ClearAll() did it already + { TexMgr_FreeTexturesForOwner (mod); + PScript_ClearSurfaceParticles(mod); + } memset(mod, 0, sizeof(qmodel_t)); } mod_numknown = 0; @@ -319,23 +328,29 @@ qmodel_t *Mod_LoadModel (qmodel_t *mod, qboolean crash) return mod; // not cached at all } -// -// because the world is so huge, load it one piece at a time -// - if (!crash) - { - - } - // // load the file // - buf = COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf), & mod->path_id); + if (*mod->name == '*') + buf = NULL; + else + buf = COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf), & mod->path_id); if (!buf) { if (crash) Host_Error ("Mod_LoadModel: %s not found", mod->name); //johnfitz -- was "Mod_NumForName" - return NULL; + else if (mod->name[0] == '*' && (mod->name[1] < '0' || mod->name[1] > '9')) + ; //*foo doesn't warn, unless its *NUM. inline models. gah. + else + Con_Warning("Mod_LoadModel: %s not found\n", mod->name); + + //avoid crashes + mod->needload = false; + mod->type = mod_ext_invalid; + mod->flags = 0; + + Mod_SetExtraFlags (mod); //johnfitz. spike -- moved this to be generic, because most of the flags are anyway. + return mod; } // @@ -363,11 +378,52 @@ qmodel_t *Mod_LoadModel (qmodel_t *mod, qboolean crash) Mod_LoadSpriteModel (mod, buf); break; + //Spike -- md3 support + case (('I'<<0)+('D'<<8)+('P'<<16)+('3'<<24)): //md3 + Mod_LoadMD3Model(mod, buf); + break; + + //Spike -- added checks for a few other model types. + //this is useful because of the number of models with renamed extensions. + //that and its hard to test the extension stuff when this was crashing. + case (('R'<<0)+('A'<<8)+('P'<<16)+('O'<<24)): //h2mp + Con_Warning("%s is a hexen2-missionpack model (unsupported)\n", mod->name); + mod->type = mod_ext_invalid; + break; + case (('I'<<0)+('D'<<8)+('P'<<16)+('2'<<24)): //md2 + Con_Warning("%s is an md2 (unsupported)\n", mod->name); + mod->type = mod_ext_invalid; + break; + case (('I'<<0)+('N'<<8)+('T'<<16)+('E'<<24)): //iqm + Con_Warning("%s is an iqm (unsupported)\n", mod->name); + mod->type = mod_ext_invalid; + break; + case (('D'<<0)+('A'<<8)+('R'<<16)+('K'<<24)): //dpm + Con_Warning("%s is an dpm (unsupported)\n", mod->name); + mod->type = mod_ext_invalid; + break; + case (('A'<<0)+('C'<<8)+('T'<<16)+('R'<<24)): //psk + Con_Warning("%s is a psk (unsupported)\n", mod->name); + mod->type = mod_ext_invalid; + break; + case (('I'<<0)+('B'<<8)+('S'<<16)+('P'<<24)): //q2/q3bsp + Con_Warning("%s is a q2/q3bsp (unsupported)\n", mod->name); + mod->type = mod_ext_invalid; + break; + default: Mod_LoadBrushModel (mod, buf); break; } + if (crash && mod->type == mod_ext_invalid) + { //any of those formats for a world map will be screwed up. + Sys_Error ("Mod_LoadModel: couldn't load %s", mod->name); //johnfitz -- was "Mod_NumForName" + return NULL; + } + + Mod_SetExtraFlags (mod); //johnfitz. spike -- moved this to be generic, because most of the flags are anyway. + return mod; } @@ -396,7 +452,82 @@ qmodel_t *Mod_ForName (const char *name, qboolean crash) =============================================================================== */ -byte *mod_base; +static byte *mod_base; + + +typedef struct { + char lumpname[24]; // up to 23 chars, zero-padded + int fileofs; // from file start + int filelen; +} bspx_lump_t; +typedef struct { + char id[4]; // 'BSPX' + int numlumps; + bspx_lump_t lumps[1]; +} bspx_header_t; +static char *bspxbase; +static bspx_header_t *bspxheader; +//supported lumps: +//RGBLIGHTING (.lit) +//unsupported lumps ('documented' elsewhere): +//BRUSHLIST (because hulls suck) +//LIGHTINGDIR (.lux) +//LMSHIFT (.lit2) +//LMOFFSET (.lit2) +//LMSTYLE (.lit2) +static void *Q1BSPX_FindLump(char *lumpname, int *lumpsize) +{ + int i; + *lumpsize = 0; + if (!bspxheader) + return NULL; + + for (i = 0; i < bspxheader->numlumps; i++) + { + if (!strncmp(bspxheader->lumps[i].lumpname, lumpname, 24)) + { + *lumpsize = bspxheader->lumps[i].filelen; + return bspxbase + bspxheader->lumps[i].fileofs; + } + } + return NULL; +} +static void Q1BSPX_Setup(qmodel_t *mod, char *filebase, unsigned int filelen, lump_t *lumps, int numlumps) +{ + int i; + int offs = 0; + bspx_header_t *h; + + bspxbase = filebase; + bspxheader = NULL; + + for (i = 0; i < numlumps; i++, lumps++) + { + if (offs < lumps->fileofs + lumps->filelen) + offs = lumps->fileofs + lumps->filelen; + } + offs = (offs + 3) & ~3; + if (offs + sizeof(*bspxheader) > filelen) + return; /*no space for it*/ + h = (bspx_header_t*)(filebase + offs); + + i = LittleLong(h->numlumps); + /*verify the header*/ + if (!strncmp(h->id, "BSPX", 4) || + i < 0 || + offs + sizeof(*h) + sizeof(h->lumps[0])*(i-1) > filelen) + return; + h->numlumps = i; + while(i-->0) + { + h->lumps[i].fileofs = LittleLong(h->lumps[i].fileofs); + h->lumps[i].filelen = LittleLong(h->lumps[i].filelen); + if ((unsigned int)h->lumps[i].fileofs + (unsigned int)h->lumps[i].filelen > filelen) + return; + } + + bspxheader = h; +} /* ================= @@ -434,6 +565,7 @@ void Mod_LoadTextures (lump_t *l) byte *data; extern byte *hunk_base; //johnfitz + qboolean malloced; //spike //johnfitz -- don't return early if no textures; still need to create dummy texture if (!l->filelen) @@ -461,32 +593,50 @@ void Mod_LoadTextures (lump_t *l) mt = (miptex_t *)((byte *)m + m->dataofs[i]); mt->width = LittleLong (mt->width); mt->height = LittleLong (mt->height); - for (j=0 ; joffsets[j] = LittleLong (mt->offsets[j]); +// for (j=0 ; joffsets[j] = LittleLong (mt->offsets[j]); if ( (mt->width & 15) || (mt->height & 15) ) Sys_Error ("Texture %s is not 16 aligned", mt->name); - pixels = mt->width*mt->height/64*85; + // spike -- quakespasm doesn't use the submips anyway + pixels = mt->width*mt->height; + //pixels = mt->width*mt->height/64*85; + tx = (texture_t *) Hunk_AllocName (sizeof(texture_t) +pixels, loadname ); loadmodel->textures[i] = tx; memcpy (tx->name, mt->name, sizeof(tx->name)); tx->width = mt->width; tx->height = mt->height; - for (j=0 ; joffsets[j] = mt->offsets[j] + sizeof(texture_t) - sizeof(miptex_t); +// for (j=0 ; joffsets[j] = mt->offsets[j] + sizeof(texture_t) - sizeof(miptex_t); // the pixels immediately follow the structures - // ericw -- check for pixels extending past the end of the lump. - // appears in the wild; e.g. jam2_tronyn.bsp (func_mapjam2), - // kellbase1.bsp (quoth), and can lead to a segfault if we read past - // the end of the .bsp file buffer - if (((byte*)(mt+1) + pixels) > (mod_base + l->fileofs + l->filelen)) - { - Con_DPrintf("Texture %s extends past end of lump\n", mt->name); - pixels = q_max(0, (mod_base + l->fileofs + l->filelen) - (byte*)(mt+1)); + if (!LittleLong (mt->offsets[0])) + { // spike -- support tyrlight-ericw's -notex argument to avoid gpl violations from embedded textures + size_t x,y; + for(y=0;ywidth;y++) + for(x=0;xwidth;x++) + ((byte*)(tx+1))[y*tx->width+x] = (((x>>2)^(y>>2))&1)?6:2; + } + else + { + // ericw -- check for pixels extending past the end of the lump. + // appears in the wild; e.g. jam2_tronyn.bsp (func_mapjam2), + // kellbase1.bsp (quoth), and can lead to a segfault if we read past + // the end of the .bsp file buffer + if (((byte*)(mt+1) + pixels) > (mod_base + l->fileofs + l->filelen)) + { + Con_DPrintf("Texture %s extends past end of lump\n", mt->name); + pixels = q_max(0, (mod_base + l->fileofs + l->filelen) - (byte*)(mt+1)); + } + + //spike -- this is actually a pointless waste of memory. + //its not like this data will actually be used beyond this function in any gl renderer. + //this makes copying it pointless + //which in turn makes the pointer-to-array-of-pointers-to-texture a bit silly. + memcpy ( tx+1, mt+1, pixels); } - memcpy ( tx+1, mt+1, pixels); tx->update_warp = false; //johnfitz tx->warpimage = NULL; //johnfitz @@ -503,11 +653,11 @@ void Mod_LoadTextures (lump_t *l) mark = Hunk_LowMark(); COM_StripExtension (loadmodel->name + 5, mapname, sizeof(mapname)); q_snprintf (filename, sizeof(filename), "textures/%s/#%s", mapname, tx->name+1); //this also replaces the '*' with a '#' - data = Image_LoadImage (filename, &fwidth, &fheight); + data = !gl_load24bit.value?NULL:Image_LoadImage (filename, &fwidth, &fheight, &malloced); if (!data) { q_snprintf (filename, sizeof(filename), "textures/#%s", tx->name+1); - data = Image_LoadImage (filename, &fwidth, &fheight); + data = !gl_load24bit.value?NULL:Image_LoadImage (filename, &fwidth, &fheight, &malloced); } //now load whatever we found @@ -532,6 +682,8 @@ void Mod_LoadTextures (lump_t *l) tx->warpimage = TexMgr_LoadImage (loadmodel, texturename, gl_warpimagesize, gl_warpimagesize, SRC_RGBA, hunk_base, "", (src_offset_t)hunk_base, TEXPREF_NOPICMIP | TEXPREF_WARPIMAGE); tx->update_warp = true; + if (malloced) + free(data); } else //regular texture { @@ -547,11 +699,11 @@ void Mod_LoadTextures (lump_t *l) mark = Hunk_LowMark (); COM_StripExtension (loadmodel->name + 5, mapname, sizeof(mapname)); q_snprintf (filename, sizeof(filename), "textures/%s/%s", mapname, tx->name); - data = Image_LoadImage (filename, &fwidth, &fheight); + data = !gl_load24bit.value?NULL:Image_LoadImage (filename, &fwidth, &fheight, &malloced); if (!data) { q_snprintf (filename, sizeof(filename), "textures/%s", tx->name); - data = Image_LoadImage (filename, &fwidth, &fheight); + data = !gl_load24bit.value?NULL:Image_LoadImage (filename, &fwidth, &fheight, &malloced); } //now load whatever we found @@ -561,13 +713,15 @@ void Mod_LoadTextures (lump_t *l) SRC_RGBA, data, filename, 0, TEXPREF_MIPMAP | extraflags ); //now try to load glow/luma image from the same place + if (malloced) + free(data); Hunk_FreeToLowMark (mark); q_snprintf (filename2, sizeof(filename2), "%s_glow", filename); - data = Image_LoadImage (filename2, &fwidth, &fheight); + data = !gl_load24bit.value?NULL:Image_LoadImage (filename2, &fwidth, &fheight, &malloced); if (!data) { q_snprintf (filename2, sizeof(filename2), "%s_luma", filename); - data = Image_LoadImage (filename2, &fwidth, &fheight); + data = !gl_load24bit.value?NULL:Image_LoadImage (filename2, &fwidth, &fheight, &malloced); } if (data) @@ -592,6 +746,8 @@ void Mod_LoadTextures (lump_t *l) SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_MIPMAP | extraflags); } } + if (malloced) + free(data); Hunk_FreeToLowMark (mark); } } @@ -708,6 +864,7 @@ void Mod_LoadLighting (lump_t *l) byte d; char litfilename[MAX_OSPATH]; unsigned int path_id; + int bspxsize; loadmodel->lightdata = NULL; // LordHavoc: check for a .lit file @@ -755,16 +912,22 @@ void Mod_LoadLighting (lump_t *l) // LordHavoc: no .lit found, expand the white lighting data to color if (!l->filelen) return; - loadmodel->lightdata = (byte *) Hunk_AllocName ( l->filelen*3, litfilename); - in = loadmodel->lightdata + l->filelen*2; // place the file at the end, so it will not be overwritten until the very last write - out = loadmodel->lightdata; - memcpy (in, mod_base + l->fileofs, l->filelen); - for (i = 0;i < l->filelen;i++) + loadmodel->lightdata = Q1BSPX_FindLump("RGBLIGHTING", &bspxsize); + if (loadmodel->lightdata && bspxsize == l->filelen*3) + Con_DPrintf("bspx lighting loaded\n"); + else { - d = *in++; - *out++ = d; - *out++ = d; - *out++ = d; + loadmodel->lightdata = (byte *) Hunk_AllocName ( l->filelen*3, litfilename); + in = loadmodel->lightdata + l->filelen*2; // place the file at the end, so it will not be overwritten until the very last write + out = loadmodel->lightdata; + memcpy (in, mod_base + l->fileofs, l->filelen); + for (i = 0;i < l->filelen;i++) + { + d = *in++; + *out++ = d; + *out++ = d; + *out++ = d; + } } } @@ -1457,7 +1620,7 @@ void Mod_LoadNodes (lump_t *l, int bsp2) else Mod_LoadNodes_S(l); - Mod_SetParent (loadmodel->nodes, NULL); // sets nodes and leafs +// Mod_SetParent (loadmodel->nodes, NULL); // sets nodes and leafs } void Mod_ProcessLeafs_S (dsleaf_t *in, int filelen) @@ -1609,6 +1772,96 @@ void Mod_LoadLeafs (lump_t *l, int bsp2) Mod_ProcessLeafs_S ((dsleaf_t *) in, l->filelen); } +void Mod_CheckWaterVis(void) +{ + mleaf_t *leaf, *other; + int i, j, k; + int numclusters = loadmodel->submodels[0].visleafs; + int contentfound = 0; + int contenttransparent = 0; + int contenttype; + + if (r_novis.value) + { //all can be + loadmodel->contentstransparent = (SURF_DRAWWATER|SURF_DRAWTELE|SURF_DRAWSLIME|SURF_DRAWLAVA); + return; + } + + //pvs is 1-based. leaf 0 sees all (the solid leaf). + //leaf 0 has no pvs, and does not appear in other leafs either, so watch out for the biases. + for (i=0,leaf=loadmodel->leafs+1 ; icontents == CONTENTS_WATER) + { + if ((contenttransparent & (SURF_DRAWWATER|SURF_DRAWTELE))==(SURF_DRAWWATER|SURF_DRAWTELE)) + continue; + //this check is somewhat risky, but we should be able to get away with it. + for (contenttype = 0, i = 0; i < leaf->nummarksurfaces; i++) + if (leaf->firstmarksurface[i]->flags & (SURF_DRAWWATER|SURF_DRAWTELE)) + { + contenttype = leaf->firstmarksurface[i]->flags & (SURF_DRAWWATER|SURF_DRAWTELE); + break; + } + //its possible that this leaf has absolutely no surfaces in it, turb or otherwise. + if (contenttype == 0) + continue; + } + else if (leaf->contents == CONTENTS_SLIME) + contenttype = SURF_DRAWSLIME; + else if (leaf->contents == CONTENTS_LAVA) + contenttype = SURF_DRAWLAVA; + //fixme: tele + else + continue; + if (contenttransparent & contenttype) + { + nextleaf: + continue; //found one of this type already + } + contentfound |= contenttype; + vis = Mod_DecompressVis(leaf->compressed_vis, loadmodel); + for (j = 0; j < (numclusters+7)/8; j++) + { + if (vis[j]) + { + for (k = 0; k < 8; k++) + { + if (vis[j] & (1u<leafs[(j<<3)+k+1]; + if (leaf->contents != other->contents) + { +// Con_Printf("%p:%i sees %p:%i\n", leaf, leaf->contents, other, other->contents); + contenttransparent |= contenttype; + goto nextleaf; + } + } + } + } + } + } + + if (!contenttransparent) + Con_DPrintf("%s is not watervised\n", loadmodel->name); + else + { + Con_DPrintf("%s is vised for transparent", loadmodel->name); + if (contenttransparent & SURF_DRAWWATER) + Con_DPrintf(" water"); + if (contenttransparent & SURF_DRAWTELE) + Con_DPrintf(" tele"); + if (contenttransparent & SURF_DRAWLAVA) + Con_DPrintf(" lava"); + if (contenttransparent & SURF_DRAWSLIME) + Con_DPrintf(" slime"); + Con_DPrintf("\n"); + } + //any types that we didn't find are assumed to be transparent. + //this allows submodels to work okay (eg: ad uses func_illusionary teleporters for some reason). + loadmodel->contentstransparent = contenttransparent | (~contentfound & (SURF_DRAWWATER|SURF_DRAWTELE|SURF_DRAWSLIME|SURF_DRAWLAVA)); +} + /* ================= Mod_LoadClipnodes @@ -1641,7 +1894,10 @@ void Mod_LoadClipnodes (lump_t *l, qboolean bsp2) count = l->filelen / sizeof(*ins); } - out = (mclipnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + if (count) + out = (mclipnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + else + out = NULL; //will use rnodes. //johnfitz -- warn about exceeding old limits if (count > 32767 && !bsp2) @@ -1752,6 +2008,26 @@ void Mod_MakeHull0 (void) out->children[j] = child - loadmodel->nodes; } } + + //if qbsp was run with -noclip, make sure the extra hulls use the rnodes instead of the missing clipnodes + //this won't 'fix' it, but it will stop it from crashing if it was just quickly built for debugging or whatever. + if (!loadmodel->hulls[1].clipnodes) + { //hulls will be point-sized. + //bias that point so that its mid,mid,bottom instead of at the absmin or origin. this will retain view offsets. + loadmodel->hulls[1].clip_maxs[2] -= loadmodel->hulls[1].clip_mins[2]; + loadmodel->hulls[1].clip_mins[2] = 0; + loadmodel->hulls[1].clipnodes = hull->clipnodes; + loadmodel->hulls[1].firstclipnode = hull->firstclipnode; + loadmodel->hulls[1].lastclipnode = hull->lastclipnode; + } + if (!loadmodel->hulls[2].clipnodes) + { + loadmodel->hulls[2].clip_maxs[2] -= loadmodel->hulls[2].clip_mins[2]; + loadmodel->hulls[2].clip_mins[2] = 0; + loadmodel->hulls[2].clipnodes = loadmodel->hulls[1].clipnodes; + loadmodel->hulls[2].firstclipnode = loadmodel->hulls[1].firstclipnode; + loadmodel->hulls[2].lastclipnode = loadmodel->hulls[1].lastclipnode; + } } /* @@ -1899,32 +2175,73 @@ Mod_LoadSubmodels */ void Mod_LoadSubmodels (lump_t *l) { - dmodel_t *in; - dmodel_t *out; - int i, j, count; + mmodel_t *out; + size_t i, j, count; - in = (dmodel_t *)(mod_base + l->fileofs); - if (l->filelen % sizeof(*in)) - Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); - count = l->filelen / sizeof(*in); - out = (dmodel_t *) Hunk_AllocName ( count*sizeof(*out), loadname); - - loadmodel->submodels = out; - loadmodel->numsubmodels = count; - - for (i=0 ; ifileofs); + dmodelh2_t *inh2 = (dmodelh2_t *)(mod_base + l->fileofs); + //the numfaces is a bit of a hack. hexen2 only actually uses 6 of its 8 hulls and we depend upon this. + //this means that the 7th and 8th are null. q1.numfaces of the world equates to h2.hull[6], so should have a value for q1, and be 0 for hexen2. + //this should work even for maps that have enough submodels to realign the size. + //note that even if the map loads, you're on your own regarding the palette (hurrah for retexturing projects?). + //fixme: we don't fix up the clipnodes yet, the player is fine, shamblers/ogres/fiends/vores will have issues. + //unfortunately c doesn't do templates, which would make all this code a bit less copypastay + if ((size_t)l->filelen >= sizeof(*inh2) && !(l->filelen % sizeof(*inh2)) && !inq1->numfaces && inq1[1].firstface) { - for (j=0 ; j<3 ; j++) - { // spread the mins / maxs by a pixel - out->mins[j] = LittleFloat (in->mins[j]) - 1; - out->maxs[j] = LittleFloat (in->maxs[j]) + 1; - out->origin[j] = LittleFloat (in->origin[j]); + dmodelh2_t *in = inh2; + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mmodel_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->submodels = out; + loadmodel->numsubmodels = count; + + for (i=0 ; imins[j] = LittleFloat (in->mins[j]) - 1; + out->maxs[j] = LittleFloat (in->maxs[j]) + 1; + out->origin[j] = LittleFloat (in->origin[j]); + } + for (j=0 ; jheadnode)/sizeof(in->headnode[0]) ; j++) + out->headnode[j] = LittleLong (in->headnode[j]); + for (; jheadnode[j] = 0; + out->visleafs = LittleLong (in->visleafs); + out->firstface = LittleLong (in->firstface); + out->numfaces = LittleLong (in->numfaces); + } + } + else + { + dmodelq1_t *in = inq1; + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mmodel_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->submodels = out; + loadmodel->numsubmodels = count; + + for (i=0 ; imins[j] = LittleFloat (in->mins[j]) - 1; + out->maxs[j] = LittleFloat (in->maxs[j]) + 1; + out->origin[j] = LittleFloat (in->origin[j]); + } + for (j=0 ; jheadnode)/sizeof(in->headnode[0]) ; j++) + out->headnode[j] = LittleLong (in->headnode[j]); + for (; jheadnode[j] = 0; + out->visleafs = LittleLong (in->visleafs); + out->firstface = LittleLong (in->firstface); + out->numfaces = LittleLong (in->numfaces); } - for (j=0 ; jheadnode[j] = LittleLong (in->headnode[j]); - out->visleafs = LittleLong (in->visleafs); - out->firstface = LittleLong (in->firstface); - out->numfaces = LittleLong (in->numfaces); } // johnfitz -- check world visleafs -- adapted from bjp @@ -1997,7 +2314,7 @@ void Mod_LoadBrushModel (qmodel_t *mod, void *buffer) int i, j; int bsp2; dheader_t *header; - dmodel_t *bm; + mmodel_t *bm; float radius; //johnfitz loadmodel->type = mod_brush; @@ -2018,8 +2335,9 @@ void Mod_LoadBrushModel (qmodel_t *mod, void *buffer) bsp2 = 2; //sanitised revision break; default: - Sys_Error ("Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, mod->bspversion, BSPVERSION); - break; + loadmodel->type = mod_ext_invalid; + Con_Warning ("Mod_LoadBrushModel: %s has wrong version number (%i should be %i)\n", mod->name, mod->bspversion, BSPVERSION); + return; } // swap all the lumps @@ -2028,6 +2346,8 @@ void Mod_LoadBrushModel (qmodel_t *mod, void *buffer) for (i = 0; i < (int) sizeof(dheader_t) / 4; i++) ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + Q1BSPX_Setup(mod, buffer, com_filesize, header->lumps, HEADER_LUMPS); + // load into heap Mod_LoadVertexes (&header->lumps[LUMP_VERTEXES]); @@ -2050,6 +2370,8 @@ void Mod_LoadBrushModel (qmodel_t *mod, void *buffer) mod->numframes = 2; // regular and alternate animation + Mod_CheckWaterVis(); + // // set up the submodels (FIXME: this is confusing) // @@ -2066,7 +2388,10 @@ void Mod_LoadBrushModel (qmodel_t *mod, void *buffer) for (j=1 ; jhulls[j].firstclipnode = bm->headnode[j]; - mod->hulls[j].lastclipnode = mod->numclipnodes-1; + if (mod->hulls[j].clipnodes == mod->hulls[0].clipnodes) + mod->hulls[j].lastclipnode = mod->hulls[0].lastclipnode; + else + mod->hulls[j].lastclipnode = mod->numclipnodes-1; } mod->firstmodelsurface = bm->firstface; @@ -2105,6 +2430,8 @@ void Mod_LoadBrushModel (qmodel_t *mod, void *buffer) *loadmodel = *mod; strcpy (loadmodel->name, name); mod = loadmodel; + + Mod_SetExtraFlags(mod); } } } @@ -2120,11 +2447,12 @@ ALIAS MODELS aliashdr_t *pheader; stvert_t stverts[MAXALIASVERTS]; -mtriangle_t triangles[MAXALIASTRIS]; +mtriangle_t *triangles; +int max_triangles; // a pose is a single set of vertexes. a frame may be // an animating sequence of poses -trivertx_t *poseverts[MAXALIASFRAMES]; +trivertx_t *poseverts_mdl[MAXALIASFRAMES]; int posenum; byte **player_8bit_texels_tbl; @@ -2161,7 +2489,7 @@ void * Mod_LoadAliasFrame (void * pin, maliasframedesc_t *frame) pinframe = (trivertx_t *)(pdaliasframe + 1); - poseverts[posenum] = pinframe; + poseverts_mdl[posenum] = pinframe; posenum++; pinframe += pheader->numverts; @@ -2209,7 +2537,7 @@ void *Mod_LoadAliasGroup (void * pin, maliasframedesc_t *frame) { if (posenum >= MAXALIASFRAMES) Sys_Error ("posenum >= MAXALIASFRAMES"); - poseverts[posenum] = (trivertx_t *)((daliasframe_t *)ptemp + 1); + poseverts_mdl[posenum] = (trivertx_t *)((daliasframe_t *)ptemp + 1); posenum++; ptemp = (trivertx_t *)((daliasframe_t *)ptemp + 1) + pheader->numverts; @@ -2262,7 +2590,7 @@ void Mod_FloodFillSkin( byte *skin, int skinwidth, int skinheight ) filledcolor = 0; // attempt to find opaque black for (i = 0; i < 256; ++i) - if (d_8to24table[i] == (255 << 0)) // alpha 1.0 + if (d_8to24table[i] == (unsigned int)LittleLong(255 << 24)) // alpha 1.0 { filledcolor = i; break; @@ -2423,28 +2751,76 @@ void Mod_CalcAliasBounds (aliashdr_t *a) { loadmodel->mins[i] = loadmodel->ymins[i] = loadmodel->rmins[i] = FLT_MAX; loadmodel->maxs[i] = loadmodel->ymaxs[i] = loadmodel->rmaxs[i] = -FLT_MAX; - radius = yawradius = 0; + } + radius = yawradius = 0; + + for (;;) + { + if (a->numposes && a->numverts) + { + if (a->posevertssize == 1) + { + //process verts + for (i=0 ; inumposes; i++) + for (j=0; jnumverts; j++) + { + for (k=0; k<3;k++) + v[k] = poseverts_mdl[i][j].v[k] * pheader->scale[k] + pheader->scale_origin[k]; + + for (k=0; k<3;k++) + { + loadmodel->mins[k] = q_min(loadmodel->mins[k], v[k]); + loadmodel->maxs[k] = q_max(loadmodel->maxs[k], v[k]); + } + dist = v[0] * v[0] + v[1] * v[1]; + if (yawradius < dist) + yawradius = dist; + dist += v[2] * v[2]; + if (radius < dist) + radius = dist; + } + } + else if (a->posevertssize == 2) + { + //process verts + for (i=0 ; inumposes; i++) + { + md3XyzNormal_t *pv = (md3XyzNormal_t *)((byte*)a+a->vertexes) + i*a->numverts; + for (j=0; jnumverts; j++) + { + for (k=0; k<3;k++) + v[k] = pv[j].xyz[k] * 1/64.0; + + for (k=0; k<3;k++) + { + loadmodel->mins[k] = q_min(loadmodel->mins[k], v[k]); + loadmodel->maxs[k] = q_max(loadmodel->maxs[k], v[k]); + } + dist = v[0] * v[0] + v[1] * v[1]; + if (yawradius < dist) + yawradius = dist; + dist += v[2] * v[2]; + if (radius < dist) + radius = dist; + } + } + } + } + + if (!a->nextsurface) + break; + a = (aliashdr_t*)((byte*)a + a->nextsurface); } - //process verts - for (i=0 ; inumposes; i++) - for (j=0; jnumverts; j++) - { - for (k=0; k<3;k++) - v[k] = poseverts[i][j].v[k] * pheader->scale[k] + pheader->scale_origin[k]; - - for (k=0; k<3;k++) - { - loadmodel->mins[k] = q_min(loadmodel->mins[k], v[k]); - loadmodel->maxs[k] = q_max(loadmodel->maxs[k], v[k]); - } - dist = v[0] * v[0] + v[1] * v[1]; - if (yawradius < dist) - yawradius = dist; - dist += v[2] * v[2]; - if (radius < dist) - radius = dist; + //dodgy model that lacks any frames or verts + for (i=0; i<3;i++) + { + if (loadmodel->mins[i] > loadmodel->maxs[i]) + { //set sizes to 0 if its invalid. + loadmodel->mins[i] = 0; + loadmodel->maxs[i] = 0; } + } //rbounds will be used when entity has nonzero pitch or roll radius = sqrt(radius); @@ -2500,24 +2876,31 @@ void Mod_SetExtraFlags (qmodel_t *mod) { extern cvar_t r_nolerp_list, r_noshadow_list; - if (!mod || mod->type != mod_alias) + if (!mod) return; mod->flags &= (0xFF | MF_HOLEY); //only preserve first byte, plus MF_HOLEY - // nolerp flag - if (nameInList(r_nolerp_list.string, mod->name)) - mod->flags |= MOD_NOLERP; + if (mod->type == mod_alias) + { + // nolerp flag + if (nameInList(r_nolerp_list.string, mod->name)) + mod->flags |= MOD_NOLERP; - // noshadow flag - if (nameInList(r_noshadow_list.string, mod->name)) - mod->flags |= MOD_NOSHADOW; + // noshadow flag + if (nameInList(r_noshadow_list.string, mod->name)) + mod->flags |= MOD_NOSHADOW; - // fullbright hack (TODO: make this a cvar list) - if (!strcmp (mod->name, "progs/flame2.mdl") || - !strcmp (mod->name, "progs/flame.mdl") || - !strcmp (mod->name, "progs/boss.mdl")) - mod->flags |= MOD_FBRIGHTHACK; + // fullbright hack (TODO: make this a cvar list) + if (!strcmp (mod->name, "progs/flame2.mdl") || + !strcmp (mod->name, "progs/flame.mdl") || + !strcmp (mod->name, "progs/boss.mdl")) + mod->flags |= MOD_FBRIGHTHACK; + } + +#ifdef PSET_SCRIPT + PScript_UpdateModelEffects(mod); +#endif } /* @@ -2566,29 +2949,54 @@ void Mod_LoadAliasModel (qmodel_t *mod, void *buffer) pheader->skinheight = LittleLong (pinmodel->skinheight); if (pheader->skinheight > MAX_LBM_HEIGHT) - Sys_Error ("model %s has a skin taller than %d", mod->name, - MAX_LBM_HEIGHT); + Con_DWarning ("model %s has a skin taller than %d\n", mod->name, + MAX_LBM_HEIGHT); //Spike -- this was always a bogus error in gl renderers. its width*height that really matters. pheader->numverts = LittleLong (pinmodel->numverts); if (pheader->numverts <= 0) Sys_Error ("model %s has no vertices", mod->name); - if (pheader->numverts > MAXALIASVERTS) - Sys_Error ("model %s has too many vertices (%d; max = %d)", mod->name, pheader->numverts, MAXALIASVERTS); + { //Spike -- made this more tollerant. its still an error of course. + Con_Warning("model %s has too many vertices (%i > %i)\n", mod->name, pheader->numverts, MAXALIASVERTS); + mod->type = mod_ext_invalid; + return; + } + if (pheader->numverts > VANILLA_MAXALIASVERTS) + Con_DWarning("model %s exceeds standard vertex limit (%i > %i)\n", mod->name, pheader->numverts, VANILLA_MAXALIASVERTS); pheader->numtris = LittleLong (pinmodel->numtris); if (pheader->numtris <= 0) Sys_Error ("model %s has no triangles", mod->name); - - if (pheader->numtris > MAXALIASTRIS) - Sys_Error ("model %s has too many triangles (%d; max = %d)", mod->name, pheader->numtris, MAXALIASTRIS); + if (pheader->numtris > max_triangles) + { + mtriangle_t *n = malloc(sizeof(*triangles) * pheader->numtris); + if (n) + { + free(triangles); + triangles = n; + max_triangles = pheader->numtris; + } + else + { + max_triangles = 0; + //Spike -- added this check, because I'm segfaulting out. + Con_Warning("model %s has too many triangles (%i)\n", mod->name, pheader->numtris); + mod->type = mod_ext_invalid; + return; + } + } pheader->numframes = LittleLong (pinmodel->numframes); numframes = pheader->numframes; if (numframes < 1) Sys_Error ("Mod_LoadAliasModel: Invalid # of frames: %d\n", numframes); + if (numframes > MAXALIASFRAMES) + { + numframes = MAXALIASFRAMES; + Con_Warning("model %s has too many frames (%i > %i)\n", mod->name, numframes, MAXALIASFRAMES); + } pheader->size = LittleFloat (pinmodel->size) * ALIAS_BASE_SIZE_RATIO; mod->synctype = (synctype_t) LittleLong (pinmodel->synctype); @@ -2653,17 +3061,17 @@ void Mod_LoadAliasModel (qmodel_t *mod, void *buffer) } pheader->numposes = posenum; + pheader->posevertssize = 1; mod->type = mod_alias; - Mod_SetExtraFlags (mod); //johnfitz - Mod_CalcAliasBounds (pheader); //johnfitz // // build the draw lists // GL_MakeAliasModelDisplayLists (mod, pheader); + GLMesh_LoadVertexBuffer (mod, pheader); // // move the complete, relocatable alias model to the cache @@ -2686,7 +3094,7 @@ void Mod_LoadAliasModel (qmodel_t *mod, void *buffer) Mod_LoadSpriteFrame ================= */ -void * Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum) +void * Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum, enum srcformat fmt) { dspriteframe_t *pinframe; mspriteframe_t *pspriteframe; @@ -2699,6 +3107,8 @@ void * Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum) width = LittleLong (pinframe->width); height = LittleLong (pinframe->height); size = width * height; + if (fmt == SRC_RGBA) + size *= 4; pspriteframe = (mspriteframe_t *) Hunk_AllocName (sizeof (mspriteframe_t),loadname); *ppframe = pspriteframe; @@ -2721,7 +3131,7 @@ void * Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum) q_snprintf (name, sizeof(name), "%s:frame%i", loadmodel->name, framenum); offset = (src_offset_t)(pinframe+1) - (src_offset_t)mod_base; //johnfitz pspriteframe->gltexture = - TexMgr_LoadImage (loadmodel, name, width, height, SRC_INDEXED, + TexMgr_LoadImage (loadmodel, name, width, height, fmt, (byte *)(pinframe + 1), loadmodel->name, offset, TEXPREF_PAD | TEXPREF_ALPHA | TEXPREF_NOPICMIP); //johnfitz -- TexMgr @@ -2734,7 +3144,7 @@ void * Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum) Mod_LoadSpriteGroup ================= */ -void * Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum) +void * Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum, enum srcformat fmt) { dspritegroup_t *pingroup; mspritegroup_t *pspritegroup; @@ -2742,6 +3152,7 @@ void * Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum) dspriteinterval_t *pin_intervals; float *poutintervals; void *ptemp; + float prevtime; pingroup = (dspritegroup_t *)pin; @@ -2760,11 +3171,13 @@ void * Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum) pspritegroup->intervals = poutintervals; - for (i=0 ; iinterval); if (*poutintervals <= 0.0) Sys_Error ("Mod_LoadSpriteGroup: interval<=0"); + //Spike -- we need to accumulate the previous time too, so we get actual timestamps, otherwise spritegroups won't animate (vanilla bug). + prevtime = *poutintervals = prevtime+*poutintervals; poutintervals++; pin_intervals++; @@ -2774,7 +3187,7 @@ void * Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum) for (i=0 ; iframes[i], framenum * 100 + i); + ptemp = Mod_LoadSpriteFrame (ptemp, &pspritegroup->frames[i], framenum * 100 + i, fmt); } return ptemp; @@ -2795,14 +3208,22 @@ void Mod_LoadSpriteModel (qmodel_t *mod, void *buffer) int numframes; int size; dspriteframetype_t *pframetype; + enum srcformat fmt = SRC_INDEXED; pin = (dsprite_t *)buffer; mod_base = (byte *)buffer; //johnfitz version = LittleLong (pin->version); - if (version != SPRITE_VERSION) - Sys_Error ("%s has wrong version number " - "(%i should be %i)", mod->name, version, SPRITE_VERSION); + if (version == 32) + fmt = SRC_RGBA; //Spike -- spr32 is identical to regular sprites, but uses rgba instead of indexed values. should probably also blend these sprites instead of alphatest, but meh. + else if (version != SPRITE_VERSION) + { + //Spike -- made this more tolerant. its still an error, it just won't crash us out + Con_Printf( "%s has wrong version number " + "(%i should be %i)\n", mod->name, version, SPRITE_VERSION); + mod->type = mod_ext_invalid; + return; + } numframes = LittleLong (pin->numframes); @@ -2844,12 +3265,12 @@ void Mod_LoadSpriteModel (qmodel_t *mod, void *buffer) if (frametype == SPR_SINGLE) { pframetype = (dspriteframetype_t *) - Mod_LoadSpriteFrame (pframetype + 1, &psprite->frames[i].frameptr, i); + Mod_LoadSpriteFrame (pframetype + 1, &psprite->frames[i].frameptr, i, fmt); } else { pframetype = (dspriteframetype_t *) - Mod_LoadSpriteGroup (pframetype + 1, &psprite->frames[i].frameptr, i); + Mod_LoadSpriteGroup (pframetype + 1, &psprite->frames[i].frameptr, i, fmt); } } diff --git a/Quake/gl_model.h b/Quake/gl_model.h index 9eeaebdd..5a1db951 100644 --- a/Quake/gl_model.h +++ b/Quake/gl_model.h @@ -33,14 +33,6 @@ m*_t structures are in-memory */ -// entity effects - -#define EF_BRIGHTFIELD 1 -#define EF_MUZZLEFLASH 2 -#define EF_BRIGHTLIGHT 4 -#define EF_DIMLIGHT 8 - - /* ============================================================================== @@ -95,7 +87,7 @@ typedef struct texture_s int anim_min, anim_max; // time for this frame min <=time< max struct texture_s *anim_next; // in the animation sequence struct texture_s *alternate_anims; // bmodels in frmae 1 use these - unsigned offsets[MIPLEVELS]; // four mip maps stored +// unsigned offsets[MIPLEVELS]; // four mip maps stored } texture_t; @@ -294,11 +286,17 @@ typedef struct aliasmesh_s unsigned short vertindex; } aliasmesh_t; -typedef struct meshxyz_s +typedef struct meshxyz_mdl_s { byte xyz[4]; signed char normal[4]; -} meshxyz_t; +} meshxyz_mdl_t; + +typedef struct meshxyz_md3_s +{ + signed short xyz[4]; + signed char normal[4]; +} meshxyz_md3_t; typedef struct meshst_s { @@ -358,29 +356,37 @@ typedef struct { //ericw -- used to populate vbo int numverts_vbo; // number of verts with unique x,y,z,s,t - intptr_t meshdesc; // offset into extradata: numverts_vbo aliasmesh_t + intptr_t meshdesc; // offset into extradata: numverts_vbo aliasmesh_t int numindexes; - intptr_t indexes; // offset into extradata: numindexes unsigned shorts - intptr_t vertexes; // offset into extradata: numposes*vertsperframe trivertx_t + intptr_t indexes; // offset into extradata: numindexes unsigned shorts + intptr_t vertexes; // offset into extradata: numposes*vertsperframe trivertx_t + + intptr_t vbovertofs; + intptr_t vbostofs; + intptr_t eboofs; //ericw -- + intptr_t nextsurface; //spike int numposes; - int poseverts; - int posedata; // numposes*poseverts trivert_t - int commands; // gl command list with embedded s/t + int posevertssize; //spike 1=mdl, 2=md3 struct gltexture_s *gltextures[MAX_SKINS][4]; //johnfitz struct gltexture_s *fbtextures[MAX_SKINS][4]; //johnfitz - int texels[MAX_SKINS]; // only for player skins + intptr_t texels[MAX_SKINS]; // only for player skins maliasframedesc_t frames[1]; // variable sized } aliashdr_t; -#define MAXALIASVERTS 2000 //johnfitz -- was 1024 -#define MAXALIASFRAMES 256 -#define MAXALIASTRIS 4096 //ericw -- was 2048 -extern aliashdr_t *pheader; -extern stvert_t stverts[MAXALIASVERTS]; -extern mtriangle_t triangles[MAXALIASTRIS]; -extern trivertx_t *poseverts[MAXALIASFRAMES]; +typedef struct { + short xyz[3]; + byte latlong[2]; +} md3XyzNormal_t; + +#define VANILLA_MAXALIASVERTS 1024 +#define MAXALIASVERTS 65536 // spike -- was 2000 //johnfitz -- was 1024 +#define MAXALIASFRAMES 1024 //spike -- was 256 +extern stvert_t stverts[MAXALIASVERTS]; +extern mtriangle_t *triangles; +extern trivertx_t *poseverts_mdl[MAXALIASFRAMES]; +extern md3XyzNormal_t *poseverts_md3[MAXALIASFRAMES]; //=================================================================== @@ -388,8 +394,9 @@ extern trivertx_t *poseverts[MAXALIASFRAMES]; // Whole model // -typedef enum {mod_brush, mod_sprite, mod_alias} modtype_t; +typedef enum {mod_brush, mod_sprite, mod_alias, mod_ext_invalid} modtype_t; +//Spike -- these are misnamed/ambiguous. #define EF_ROCKET 1 // leave a trail #define EF_GRENADE 2 // leave a trail #define EF_GIB 4 // leave a trail @@ -405,6 +412,10 @@ typedef enum {mod_brush, mod_sprite, mod_alias} modtype_t; #define MOD_NOSHADOW 512 //don't cast a shadow #define MOD_FBRIGHTHACK 1024 //when fullbrights are disabled, use a hack to render this model brighter //johnfitz +//spike -- added this for particle stuff +#define MOD_EMITREPLACE 2048 //particle effect completely replaces the model (for flames or whatever). +#define MOD_EMITFORWARDS 4096 //particle effect is emitted forwards, rather than downwards. why down? good question. +//spike typedef struct qmodel_s { @@ -419,6 +430,13 @@ typedef struct qmodel_s int flags; +#ifdef PSET_SCRIPT + int emiteffect; //spike -- this effect is emitted per-frame by entities with this model + int traileffect; //spike -- this effect is used when entities move + struct skytris_s *skytris; //spike -- surface-based particle emission for this model + struct skytriblock_s *skytrimem; //spike -- surface-based particle emission for this model (for better cache performance+less allocs) + double skytime; //doesn't really cope with multiples. oh well... +#endif // // volume occupied by the model graphics // @@ -439,7 +457,7 @@ typedef struct qmodel_s int firstmodelsurface, nummodelsurfaces; int numsubmodels; - dmodel_t *submodels; + mmodel_t *submodels; int numplanes; mplane_t *planes; @@ -483,16 +501,16 @@ typedef struct qmodel_s qboolean viswarn; // for Mod_DecompressVis() int bspversion; + int contentstransparent; //spike -- added this so we can disable glitchy wateralpha where its not supported. // // alias model // - GLuint meshvbo; - GLuint meshindexesvbo; - int vboindexofs; // offset in vbo of the hdr->numindexes unsigned shorts - int vboxyzofs; // offset in vbo of hdr->numposes*hdr->numverts_vbo meshxyz_t - int vbostofs; // offset in vbo of hdr->numverts_vbo meshst_t + GLuint meshvbo; + byte *meshvboptr; //for non-vbo fallback. + GLuint meshindexesvbo; + byte *meshindexesvboptr; //for non-ebo fallback. // // additional model data diff --git a/Quake/gl_refrag.c b/Quake/gl_refrag.c index ee2350ce..094c4cd9 100644 --- a/Quake/gl_refrag.c +++ b/Quake/gl_refrag.c @@ -208,8 +208,37 @@ void R_StoreEfrags (efrag_t **ppefrag) { pent = pefrag->entity; - if ((pent->visframe != r_framecount) && (cl_numvisedicts < MAX_VISEDICTS)) + if ((pent->visframe != r_framecount) && (cl_numvisedicts < cl_maxvisedicts)) { +#ifdef PSET_SCRIPT + if (pent->netstate.emiteffectnum > 0) + { + float t = cl.time-cl.oldtime; + vec3_t axis[3]; + if (t < 0) t = 0; else if (t > 0.1) t= 0.1; + AngleVectors(pent->angles, axis[0], axis[1], axis[2]); + if (pent->model->type == mod_alias) + axis[0][2] *= -1; //stupid vanilla bug + PScript_RunParticleEffectState(pent->origin, axis[0], t, cl.particle_precache[pent->netstate.emiteffectnum].index, &pent->emitstate); + } + else if (pent->model->emiteffect >= 0) + { + float t = cl.time-cl.oldtime; + vec3_t axis[3]; + if (t < 0) t = 0; else if (t > 0.1) t= 0.1; + AngleVectors(pent->angles, axis[0], axis[1], axis[2]); + if (pent->model->flags & MOD_EMITFORWARDS) + { + if (pent->model->type == mod_alias) + axis[0][2] *= -1; //stupid vanilla bug + } + else + VectorScale(axis[2], -1, axis[0]); + PScript_RunParticleEffectState(pent->origin, axis[0], t, pent->model->emiteffect, &pent->emitstate); + if (pent->model->flags & MOD_EMITREPLACE) + continue; + } +#endif cl_visedicts[cl_numvisedicts++] = pent; pent->visframe = r_framecount; } diff --git a/Quake/gl_rlight.c b/Quake/gl_rlight.c index 86c73b87..eee4e9fb 100644 --- a/Quake/gl_rlight.c +++ b/Quake/gl_rlight.c @@ -172,7 +172,8 @@ void R_MarkLights (dlight_t *light, int num, mnode_t *node) msurface_t *surf; vec3_t impact; float dist, l, maxdist; - int i, j, s, t; + unsigned int i; + int j, s, t; start: @@ -311,7 +312,8 @@ loc0: return true; // hit something else { - int i, ds, dt; + unsigned int i; + int ds, dt; msurface_t *surf; // check for impact on this node VectorCopy (mid, lightspot); diff --git a/Quake/gl_rmain.c b/Quake/gl_rmain.c index ffad937e..99fb6b61 100644 --- a/Quake/gl_rmain.c +++ b/Quake/gl_rmain.c @@ -109,6 +109,7 @@ cvar_t r_telealpha = {"r_telealpha","0",CVAR_NONE}; cvar_t r_slimealpha = {"r_slimealpha","0",CVAR_NONE}; float map_wateralpha, map_lavaalpha, map_telealpha, map_slimealpha; +float map_fallbackalpha; qboolean r_drawflat_cheatsafe, r_fullbright_cheatsafe, r_lightmap_cheatsafe, r_drawworld_cheatsafe; //johnfitz @@ -347,12 +348,15 @@ qboolean R_CullModelForEntity (entity_t *e) R_RotateForEntity -- johnfitz -- modified to take origin and angles instead of pointer to entity =============== */ -void R_RotateForEntity (vec3_t origin, vec3_t angles) +void R_RotateForEntity (vec3_t origin, vec3_t angles, unsigned char scale) { glTranslatef (origin[0], origin[1], origin[2]); glRotatef (angles[1], 0, 0, 1); glRotatef (-angles[0], 0, 1, 0); glRotatef (angles[2], 1, 0, 0); + + if (scale != 16) + glScalef (scale/16.0, scale/16.0, scale/16.0); } /* @@ -635,7 +639,7 @@ void R_DrawEntitiesOnList (qboolean alphapass) //johnfitz -- added parameter continue; //johnfitz -- chasecam - if (currententity == &cl_entities[cl.viewentity]) + if (currententity == &cl.entities[cl.viewentity]) currententity->angles[0] *= 0.3; //johnfitz @@ -650,6 +654,9 @@ void R_DrawEntitiesOnList (qboolean alphapass) //johnfitz -- added parameter case mod_sprite: R_DrawSpriteModel (currententity); break; + case mod_ext_invalid: + //nothing. could draw a blob instead. + break; } } } @@ -812,7 +819,7 @@ void R_ShowTris (void) { currententity = cl_visedicts[i]; - if (currententity == &cl_entities[cl.viewentity]) // chasecam + if (currententity == &cl.entities[cl.viewentity]) // chasecam currententity->angles[0] *= 0.3; switch (currententity->model->type) @@ -931,6 +938,9 @@ void R_RenderScene (void) R_RenderDlights (); //triangle fan dlights -- johnfitz -- moved after water R_DrawParticles (); +#ifdef PSET_SCRIPT + PScript_DrawParticles(); +#endif Fog_DisableGFog (); //johnfitz @@ -1120,9 +1130,9 @@ void R_RenderView (void) time2 = Sys_DoubleTime (); if (r_pos.value) Con_Printf ("x %i y %i z %i (pitch %i yaw %i roll %i)\n", - (int)cl_entities[cl.viewentity].origin[0], - (int)cl_entities[cl.viewentity].origin[1], - (int)cl_entities[cl.viewentity].origin[2], + (int)cl.entities[cl.viewentity].origin[0], + (int)cl.entities[cl.viewentity].origin[1], + (int)cl.entities[cl.viewentity].origin[2], (int)cl.viewangles[PITCH], (int)cl.viewangles[YAW], (int)cl.viewangles[ROLL]); diff --git a/Quake/gl_rmisc.c b/Quake/gl_rmisc.c index 4aac8221..5724a85c 100644 --- a/Quake/gl_rmisc.c +++ b/Quake/gl_rmisc.c @@ -116,7 +116,10 @@ R_SetWateralpha_f -- ericw */ static void R_SetWateralpha_f (cvar_t *var) { + if (cls.signon == SIGNONS && cl.worldmodel && !(cl.worldmodel->contentstransparent&SURF_DRAWWATER) && var->value < 1) + Con_Warning("Map does not appear to be water-vised\n"); map_wateralpha = var->value; + map_fallbackalpha = var->value; } /* @@ -126,6 +129,8 @@ R_SetLavaalpha_f -- ericw */ static void R_SetLavaalpha_f (cvar_t *var) { + if (cls.signon == SIGNONS && cl.worldmodel && !(cl.worldmodel->contentstransparent&SURF_DRAWLAVA) && var->value && var->value < 1) + Con_Warning("Map does not appear to be lava-vised\n"); map_lavaalpha = var->value; } @@ -136,6 +141,8 @@ R_SetTelealpha_f -- ericw */ static void R_SetTelealpha_f (cvar_t *var) { + if (cls.signon == SIGNONS && cl.worldmodel && !(cl.worldmodel->contentstransparent&SURF_DRAWTELE) && var->value && var->value < 1) + Con_Warning("Map does not appear to be tele-vised\n"); map_telealpha = var->value; } @@ -146,6 +153,8 @@ R_SetSlimealpha_f -- ericw */ static void R_SetSlimealpha_f (cvar_t *var) { + if (cls.signon == SIGNONS && cl.worldmodel && !(cl.worldmodel->contentstransparent&SURF_DRAWSLIME) && var->value && var->value < 1) + Con_Warning("Map does not appear to be slime-vised\n"); map_slimealpha = var->value; } @@ -157,13 +166,13 @@ GL_WaterAlphaForSurfface -- ericw float GL_WaterAlphaForSurface (msurface_t *fa) { if (fa->flags & SURF_DRAWLAVA) - return map_lavaalpha > 0 ? map_lavaalpha : map_wateralpha; + return map_lavaalpha > 0 ? map_lavaalpha : map_fallbackalpha; else if (fa->flags & SURF_DRAWTELE) - return map_telealpha > 0 ? map_telealpha : map_wateralpha; + return map_telealpha > 0 ? map_telealpha : map_fallbackalpha; else if (fa->flags & SURF_DRAWSLIME) - return map_slimealpha > 0 ? map_slimealpha : map_wateralpha; + return map_slimealpha > 0 ? map_slimealpha : map_fallbackalpha; else - return map_wateralpha; + return map_wateralpha;// > 0 ? map_wateralpha : map_fallbackalpha; } @@ -242,6 +251,9 @@ void R_Init (void) Cvar_SetCallback (&r_slimealpha, R_SetSlimealpha_f); R_InitParticles (); +#ifdef PSET_SCRIPT + PScript_InitParticles(); +#endif R_SetClearColor_f (&r_clearcolor); //johnfitz Sky_Init (); //johnfitz @@ -281,7 +293,7 @@ void R_TranslateNewPlayerSkin (int playernum) int skinnum; //get correct texture pixels - currententity = &cl_entities[1+playernum]; + currententity = &cl.entities[1+playernum]; if (!currententity->model || currententity->model->type != mod_alias) return; @@ -290,19 +302,28 @@ void R_TranslateNewPlayerSkin (int playernum) skinnum = currententity->skinnum; - //TODO: move these tests to the place where skinnum gets received from the server - if (skinnum < 0 || skinnum >= paliashdr->numskins) + if (paliashdr->numskins) { - Con_DPrintf("(%d): Invalid player skin #%d\n", playernum, skinnum); - skinnum = 0; + //TODO: move these tests to the place where skinnum gets received from the server + if (skinnum < 0 || skinnum >= paliashdr->numskins) + { + Con_DPrintf("(%d): Invalid player skin #%d\n", playernum, skinnum); + skinnum = 0; + } + + pixels = (byte *)paliashdr + paliashdr->texels[skinnum]; // This is not a persistent place! + + //upload new image + q_snprintf(name, sizeof(name), "player_%i", playernum); + playertextures[playernum] = TexMgr_LoadImage (currententity->model, name, paliashdr->skinwidth, paliashdr->skinheight, + SRC_INDEXED, pixels, paliashdr->gltextures[skinnum][0]->source_file, paliashdr->gltextures[skinnum][0]->source_offset, TEXPREF_PAD | TEXPREF_OVERWRITE); + } + else + { + q_snprintf(name, sizeof(name), "player_%i", playernum); + playertextures[playernum] = TexMgr_LoadImage (currententity->model, name, paliashdr->skinwidth, paliashdr->skinheight, + SRC_EXTERNAL, NULL, "skins/base.pcx", 0, TEXPREF_PAD | TEXPREF_OVERWRITE); } - - pixels = (byte *)paliashdr + paliashdr->texels[skinnum]; // This is not a persistent place! - -//upload new image - q_snprintf(name, sizeof(name), "player_%i", playernum); - playertextures[playernum] = TexMgr_LoadImage (currententity->model, name, paliashdr->skinwidth, paliashdr->skinheight, - SRC_INDEXED, pixels, paliashdr->gltextures[skinnum][0]->source_file, paliashdr->gltextures[skinnum][0]->source_offset, TEXPREF_PAD | TEXPREF_OVERWRITE); //now recolor it R_TranslatePlayerSkin (playernum); @@ -334,10 +355,11 @@ static void R_ParseWorldspawn (void) char key[128], value[4096]; const char *data; - map_wateralpha = r_wateralpha.value; - map_lavaalpha = r_lavaalpha.value; - map_telealpha = r_telealpha.value; - map_slimealpha = r_slimealpha.value; + map_fallbackalpha = r_wateralpha.value; + map_wateralpha = (cl.worldmodel->contentstransparent&SURF_DRAWWATER)?r_wateralpha.value:1; + map_lavaalpha = (cl.worldmodel->contentstransparent&SURF_DRAWLAVA)?r_lavaalpha.value:1; + map_telealpha = (cl.worldmodel->contentstransparent&SURF_DRAWTELE)?r_telealpha.value:1; + map_slimealpha = (cl.worldmodel->contentstransparent&SURF_DRAWSLIME)?r_slimealpha.value:1; data = COM_Parse(cl.worldmodel->entities); if (!data) @@ -363,7 +385,7 @@ static void R_ParseWorldspawn (void) q_strlcpy(value, com_token, sizeof(value)); if (!strcmp("wateralpha", key)) - map_wateralpha = atof(value); + map_fallbackalpha = map_wateralpha = atof(value); if (!strcmp("lavaalpha", key)) map_lavaalpha = atof(value); @@ -396,6 +418,9 @@ void R_NewMap (void) r_viewleaf = NULL; R_ClearParticles (); +#ifdef PSET_SCRIPT + PScript_ClearParticles(); +#endif GL_BuildLightmaps (); GL_BuildBModelVertexBuffer (); @@ -498,7 +523,7 @@ GLint GL_GetUniformLocation (GLuint *programPtr, const char *name) { GLint location; - if (!programPtr) + if (!*programPtr) return -1; location = GL_GetUniformLocationFunc(*programPtr, name); diff --git a/Quake/gl_screen.c b/Quake/gl_screen.c index c7333530..5372d22e 100644 --- a/Quake/gl_screen.c +++ b/Quake/gl_screen.c @@ -134,6 +134,10 @@ float scr_centertime_off; int scr_center_lines; int scr_erase_lines; int scr_erase_center; +#define CPRINT_TYPEWRITER (1u<<0) +#define CPRINT_PERSIST (1u<<1) +#define CPRINT_TALIGN (1u<<2) +unsigned int scr_centerprint_flags; /* ============== @@ -145,10 +149,87 @@ for a few moments */ void SCR_CenterPrint (const char *str) //update centerprint data { + unsigned int flags = 0; + + if (*str != '/' && cl.intermission) + flags |= CPRINT_TYPEWRITER | CPRINT_PERSIST | CPRINT_TALIGN; + + //check for centerprint prefixes/flags + while (*str == '/') + { + if (str[1] == '.') + { //no more + str+=2; + break; + } + else if (str[1] == 'P') + flags |= CPRINT_PERSIST; + else if (str[1] == 'W') //typewriter + flags ^= CPRINT_TYPEWRITER; + else if (str[1] == 'S') //typewriter + flags ^= CPRINT_PERSIST; + else if (str[1] == 'M') //masked background + ; + else if (str[1] == 'O') //obituary print (lower half) + ; + else if (str[1] == 'B') //bottom-align + ; + else if (str[1] == 'B') //top-align + ; + else if (str[1] == 'L') //left-align + ; + else if (str[1] == 'R') //right-align + ; + else if (str[1] == 'F') //alternative 'finale' control + { + str+=2; + if (!cl.intermission) + cl.completed_time = cl.time; + switch(*str++) + { + case 0: + str--; + break; + case 'R': //remove intermission (no other method to do this) + cl.intermission = 0; + break; + case 'I': //regular intermission + case 'S': //show scoreboard + cl.intermission = 1; + break; + case 'F': //like svc_finale + cl.intermission = 2; + break; + default: + break; //any other flag you want + } + vid.recalc_refdef = true; + continue; + } + else if (str[1] == 'I') //title image + { + const char *e; + str+=2; + e = strchr(str, ':'); + if (!e) + e = strchr(str, ' '); //probably an error + if (!e) + e = str+strlen(str)-1; //error + str = e+1; + continue; + } + else + break; + str+=2; + } + strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1); - scr_centertime_off = scr_centertime.value; + scr_centertime_off = (flags&CPRINT_PERSIST)?999999:scr_centertime.value; scr_centertime_start = cl.time; + if (*scr_centerstring && !(flags&CPRINT_PERSIST)) + Con_LogCenterPrint (scr_centerstring); + // count the number of lines for centering scr_center_lines = 1; str = scr_centerstring; @@ -241,8 +322,12 @@ float AdaptFovx (float fov_x, float width, float height) { float a, x; - if (fov_x < 1 || fov_x > 179) - Sys_Error ("Bad fov: %f", fov_x); + if (cl.statsf[STAT_VIEWZOOM]) + fov_x *= cl.statsf[STAT_VIEWZOOM]/255.0; + if (fov_x < 1) + fov_x = 1; + if (fov_x > 179) + fov_x = 179; if (!scr_fov_adapt.value) return fov_x; diff --git a/Quake/gl_sky.c b/Quake/gl_sky.c index 273b8d25..34155e81 100644 --- a/Quake/gl_sky.c +++ b/Quake/gl_sky.c @@ -101,7 +101,7 @@ void Sky_LoadTexture (texture_t *mt) static byte back_data[128*128]; //FIXME: Hunk_Alloc unsigned *rgba; - src = (byte *)mt + mt->offsets[0]; + src = (byte *)(mt+1); // extract back layer and upload for (i=0 ; i<128 ; i++) @@ -155,6 +155,7 @@ void Sky_LoadSkyBox (const char *name) char filename[MAX_OSPATH]; byte *data; qboolean nonefound = true; + qboolean malloced; if (strcmp(skybox_name, name) == 0) return; //no change @@ -179,7 +180,7 @@ void Sky_LoadSkyBox (const char *name) { mark = Hunk_LowMark (); q_snprintf (filename, sizeof(filename), "gfx/env/%s%s", name, suf[i]); - data = Image_LoadImage (filename, &width, &height); + data = Image_LoadImage (filename, &width, &height, &malloced); if (data) { skybox_textures[i] = TexMgr_LoadImage (cl.worldmodel, filename, width, height, SRC_RGBA, data, filename, 0, TEXPREF_NONE); @@ -190,6 +191,8 @@ void Sky_LoadSkyBox (const char *name) Con_Printf ("Couldn't load %s\n", filename); skybox_textures[i] = notexture; } + if (malloced) + free(data); Hunk_FreeToLowMark (mark); } diff --git a/Quake/gl_texmgr.c b/Quake/gl_texmgr.c index 188907d9..6ee21706 100644 --- a/Quake/gl_texmgr.c +++ b/Quake/gl_texmgr.c @@ -810,6 +810,8 @@ TexMgr_AlphaEdgeFix eliminate pink edges on sprites, etc. operates in place on 32bit data + +spike -- small note that would be better to use premultiplied alpha to completely eliminate these skirts without the possibility of misbehaving. =============== */ static void TexMgr_AlphaEdgeFix (byte *data, int width, int height) @@ -1006,6 +1008,23 @@ static byte *TexMgr_PadImageH (byte *in, int width, int height, byte padbyte) return data; } +static byte *TexMgr_PreMultiply32(byte *in, size_t width, size_t height) +{ + size_t pixels = width * height; + byte *out = (byte *) Hunk_Alloc(pixels*4); + byte *result = out; + while (pixels --> 0) + { + out[0] = (in[0]*in[3])>>8; + out[1] = (in[1]*in[3])>>8; + out[2] = (in[2]*in[3])>>8; + out[3] = in[3]; + in += 4; + out += 4; + } + return result; +} + /* ================ TexMgr_LoadImage32 -- handles 32bit source data @@ -1015,6 +1034,10 @@ static void TexMgr_LoadImage32 (gltexture_t *glt, unsigned *data) { int internalformat, miplevel, mipwidth, mipheight, picmip; + //do this before any rescaling + if (glt->flags & TEXPREF_PREMULTIPLY) + data = (unsigned*)TexMgr_PreMultiply32((byte*)data, glt->width, glt->height); + if (!gl_texture_NPOT) { // resample up @@ -1156,7 +1179,7 @@ static void TexMgr_LoadImage8 (gltexture_t *glt, byte *data) data = (byte *)TexMgr_8to32(data, glt->width * glt->height, usepal); // fix edges - if (glt->flags & TEXPREF_ALPHA) + if ((glt->flags & TEXPREF_ALPHA) && !(glt->flags & TEXPREF_PREMULTIPLY)) TexMgr_AlphaEdgeFix (data, glt->width, glt->height); else { @@ -1196,6 +1219,7 @@ gltexture_t *TexMgr_LoadImage (qmodel_t *owner, const char *name, int width, int unsigned short crc; gltexture_t *glt; int mark; + qboolean malloced; if (isDedicated) return NULL; @@ -1212,6 +1236,7 @@ gltexture_t *TexMgr_LoadImage (qmodel_t *owner, const char *name, int width, int case SRC_RGBA: crc = CRC_Block(data, width * height * 4); break; + case SRC_EXTERNAL: default: /* not reachable but avoids compiler warnings */ crc = 0; } @@ -1249,6 +1274,24 @@ gltexture_t *TexMgr_LoadImage (qmodel_t *owner, const char *name, int width, int case SRC_LIGHTMAP: TexMgr_LoadLightmap (glt, data); break; + case SRC_EXTERNAL: + data = Image_LoadImage (glt->source_file, (int *)&glt->source_width, (int *)&glt->source_height, &malloced); //simple file + if (!data) + { + glt->source_width = glt->source_height = 1; + glt->width = glt->source_width; + glt->height = glt->source_height; + TexMgr_LoadImage8 (glt, (byte*)"\x07"); + } + else + { + glt->width = glt->source_width; + glt->height = glt->source_height; + TexMgr_LoadImage32 (glt, (unsigned *)data); + if (malloced) + free(data); + } + break; case SRC_RGBA: TexMgr_LoadImage32 (glt, (unsigned *)data); break; @@ -1277,6 +1320,7 @@ void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants) byte translation[256]; byte *src, *dst, *data = NULL, *translated; int mark, size, i; + qboolean malloced = false; // // get source data // @@ -1302,7 +1346,7 @@ void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants) fclose (f); } else if (glt->source_file[0] && !glt->source_offset) - data = Image_LoadImage (glt->source_file, (int *)&glt->source_width, (int *)&glt->source_height); //simple file + data = Image_LoadImage (glt->source_file, (int *)&glt->source_width, (int *)&glt->source_height, &malloced); //simple file else if (!glt->source_file[0] && glt->source_offset) data = (byte *) glt->source_offset; //image in memory @@ -1382,11 +1426,14 @@ invalid: case SRC_LIGHTMAP: TexMgr_LoadLightmap (glt, data); break; + case SRC_EXTERNAL: case SRC_RGBA: TexMgr_LoadImage32 (glt, (unsigned *)data); break; } + if (malloced) + free(data); Hunk_FreeToLowMark(mark); } diff --git a/Quake/gl_texmgr.h b/Quake/gl_texmgr.h index e58ff035..def7aa54 100644 --- a/Quake/gl_texmgr.h +++ b/Quake/gl_texmgr.h @@ -38,8 +38,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define TEXPREF_NOBRIGHT 0x0200 // use nobright mask palette #define TEXPREF_CONCHARS 0x0400 // use conchars palette #define TEXPREF_WARPIMAGE 0x0800 // resize this texture when warpimagesize changes +#define TEXPREF_PREMULTIPLY 0x1000 // rgb = rgb*a; a=a; -enum srcformat {SRC_INDEXED, SRC_LIGHTMAP, SRC_RGBA}; +enum srcformat {SRC_INDEXED, SRC_LIGHTMAP, SRC_RGBA, SRC_EXTERNAL}; typedef uintptr_t src_offset_t; diff --git a/Quake/gl_vidsdl.c b/Quake/gl_vidsdl.c index f09e3e06..0164efde 100644 --- a/Quake/gl_vidsdl.c +++ b/Quake/gl_vidsdl.c @@ -607,7 +607,7 @@ static qboolean VID_SetMode (int width, int height, int refreshrate, int bpp, qb SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, fsaa > 0 ? 1 : 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fsaa); - q_snprintf(caption, sizeof(caption), "QuakeSpasm " QUAKESPASM_VER_STRING); + q_snprintf(caption, sizeof(caption), ENGINE_NAME_AND_VER); #if defined(USE_SDL2) /* Create the window if needed, hidden */ @@ -617,6 +617,8 @@ static qboolean VID_SetMode (int width, int height, int refreshrate, int bpp, qb if (vid_borderless.value) flags |= SDL_WINDOW_BORDERLESS; + else if (!fullscreen) + flags |= SDL_WINDOW_RESIZABLE; draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); if (!draw_context) { // scale back fsaa @@ -1290,10 +1292,11 @@ static void GL_SetupState (void) glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //johnfitz glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + //spike -- these are invalid as there is no texture bound to receive this state. + //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glDepthRange (0, 1); //johnfitz -- moved here becuase gl_ztrick is gone. glDepthFunc (GL_LEQUAL); //johnfitz -- moved here becuase gl_ztrick is gone. } diff --git a/Quake/glquake.h b/Quake/glquake.h index 315be98b..27511603 100644 --- a/Quake/glquake.h +++ b/Quake/glquake.h @@ -99,6 +99,35 @@ typedef struct particle_s ptype_t type; } particle_t; +#define P_INVALID -1 +#ifdef PSET_SCRIPT + void PScript_InitParticles (void); + void PScript_Shutdown (void); + void PScript_DrawParticles (void); + struct trailstate_s; + int PScript_ParticleTrail (vec3_t startpos, vec3_t end, int type, int dlkey, vec3_t axis[3], struct trailstate_s **tsk); + int PScript_RunParticleEffectState (vec3_t org, vec3_t dir, float count, int typenum, struct trailstate_s **tsk); + void PScript_RunParticleWeather(vec3_t minb, vec3_t maxb, vec3_t dir, float count, int colour, const char *efname); + void PScript_EmitSkyEffectTris(qmodel_t *mod, msurface_t *fa, int ptype); + int PScript_FindParticleType(const char *fullname); + int PScript_RunParticleEffectTypeString (vec3_t org, vec3_t dir, float count, const char *name); + int PScript_EntParticleTrail(vec3_t oldorg, entity_t *ent, const char *name); + int PScript_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count); + void PScript_DelinkTrailstate(struct trailstate_s **tsk); + void PScript_ClearParticles (void); + void PScript_UpdateModelEffects(qmodel_t *mod); + void PScript_ClearSurfaceParticles(qmodel_t *mod); //model is being unloaded. +#else + #define PScript_RunParticleEffectState(o,d,c,t,s) true + #define PScript_RunParticleEffectTypeString(o,d,c,n) true //just unconditionally returns an error + #define PScript_EntParticleTrail(o,e,n) true + #define PScript_ParticleTrail(o,e,t,d,a,s) true + #define PScript_EntParticleTrail(o,e,n) true + #define PScript_RunParticleEffect(o,d,p,c) true + #define PScript_RunParticleWeather(min,max,d,c,p,n) true + #define PScript_ClearSurfaceParticles(m) + #define PScript_DelinkTrailstate(tsp) +#endif //==================================================== @@ -283,7 +312,6 @@ extern devstats_t dev_stats, dev_peakstats; //ohnfitz -- reduce overflow warning spam typedef struct { double packetsize; - double efrags; double beams; double varstring; } overflowtimes_t; @@ -323,6 +351,7 @@ typedef struct glsl_attrib_binding_s { } glsl_attrib_binding_t; extern float map_wateralpha, map_lavaalpha, map_telealpha, map_slimealpha; //ericw +extern float map_fallbackalpha; //spike -- because we might want r_wateralpha to apply to teleporters while water itself wasn't watervised //johnfitz -- fog functions called from outside gl_fog.c void Fog_ParseServerMessage (void); @@ -336,6 +365,7 @@ void Fog_SetupFrame (void); void Fog_NewMap (void); void Fog_Init (void); void Fog_SetupState (void); +const char *Fog_GetFogCommand(void); //for demo recording void R_NewGame (void); @@ -345,7 +375,7 @@ void R_CullSurfaces (void); qboolean R_CullBox (vec3_t emins, vec3_t emaxs); void R_StoreEfrags (efrag_t **ppefrag); qboolean R_CullModelForEntity (entity_t *e); -void R_RotateForEntity (vec3_t origin, vec3_t angles); +void R_RotateForEntity (vec3_t origin, vec3_t angles, unsigned char scale); void R_MarkLights (dlight_t *light, int num, mnode_t *node); void R_InitParticles (void); @@ -370,13 +400,14 @@ void GL_DeleteBModelVertexBuffer (void); void GL_BuildBModelVertexBuffer (void); void GLMesh_LoadVertexBuffers (void); void GLMesh_DeleteVertexBuffers (void); +void GLMesh_LoadVertexBuffer (qmodel_t *m, aliashdr_t *hdr); void R_RebuildAllLightmaps (void); int R_LightPoint (vec3_t p); void GL_SubdivideSurface (msurface_t *fa); -void R_BuildLightMap (msurface_t *surf, byte *dest, int stride); -void R_RenderDynamicLightmaps (msurface_t *fa); +void R_BuildLightMap (qmodel_t *model, msurface_t *surf, byte *dest, int stride); +void R_RenderDynamicLightmaps (qmodel_t *model, msurface_t *fa); void R_UploadLightmaps (void); void R_DrawWorld_ShowTris (void); diff --git a/Quake/host.c b/Quake/host.c index ad60160f..4f5752b3 100644 --- a/Quake/host.c +++ b/Quake/host.c @@ -61,7 +61,7 @@ cvar_t host_framerate = {"host_framerate","0",CVAR_NONE}; // set for slow motion cvar_t host_speeds = {"host_speeds","0",CVAR_NONE}; // set for running times cvar_t host_maxfps = {"host_maxfps", "72", CVAR_ARCHIVE}; //johnfitz cvar_t host_timescale = {"host_timescale", "0", CVAR_NONE}; //johnfitz -cvar_t max_edicts = {"max_edicts", "8192", CVAR_NONE}; //johnfitz //ericw -- changed from 2048 to 8192, removed CVAR_ARCHIVE +cvar_t max_edicts = {"max_edicts", "15000", CVAR_NONE}; //johnfitz //ericw -- changed from 2048 to 8192, removed CVAR_ARCHIVE cvar_t sys_ticrate = {"sys_ticrate","0.05",CVAR_NONE}; // dedicated server cvar_t serverprofile = {"serverprofile","0",CVAR_NONE}; @@ -162,6 +162,8 @@ void Host_Error (const char *error, ...) va_end (argptr); Con_Printf ("Host_Error: %s\n",string); + Con_Redirect(NULL); + if (sv.active) Host_ShutdownServer (false); @@ -434,16 +436,22 @@ void SV_DropClient (qboolean crash) NET_Close (host_client->netconnection); host_client->netconnection = NULL; + SVFTE_DestroyFrames(host_client); //release any delta state + // free the client (the body stays around) host_client->active = false; host_client->name[0] = 0; host_client->old_frags = -999999; net_activeconnections--; + if (host_client->download.file) + fclose(host_client->download.file); + memset(&host_client->download, 0, sizeof(host_client->download)); + // send notification to all clients for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) { - if (!client->active) + if (!client->knowntoqc) continue; MSG_WriteByte (&client->message, svc_updatename); MSG_WriteByte (&client->message, host_client - svs.clients); @@ -488,7 +496,7 @@ void Host_ShutdownServer(qboolean crash) count = 0; for (i=0, host_client = svs.clients ; iactive && host_client->message.cursize) + if (host_client->active && host_client->message.cursize && host_client->netconnection) { if (NET_CanSendMessage (host_client->netconnection)) { @@ -520,6 +528,8 @@ void Host_ShutdownServer(qboolean crash) if (host_client->active) SV_DropClient(crash); + sv.worldmodel = NULL; + // // clear structures // @@ -545,6 +555,9 @@ void Host_ClearMemory (void) Hunk_FreeToLowMark (host_hunklevel); cls.signon = 0; free(sv.edicts); // ericw -- sv.edicts switched to use malloc() + free(cl.static_entities); + free(sv.static_entities); //spike -- this is dynamic too, now + free(sv.ambientsounds); memset (&sv, 0, sizeof(sv)); memset (&cl, 0, sizeof(cl)); } @@ -571,7 +584,7 @@ qboolean Host_FilterTime (float time) //johnfitz -- max fps cvar maxfps = CLAMP (10.0, host_maxfps.value, 1000.0); - if (!cls.timedemo && realtime - oldrealtime < 1.0/maxfps) + if (host_maxfps.value && !cls.timedemo && realtime - oldrealtime < 1.0/maxfps) return false; // framerate is too high //johnfitz @@ -584,7 +597,7 @@ qboolean Host_FilterTime (float time) //johnfitz else if (host_framerate.value > 0) host_frametime = host_framerate.value; - else // don't allow really long or short frames + else if (host_maxfps.value)// don't allow really long or short frames host_frametime = CLAMP (0.001, host_frametime, 0.1); //johnfitz -- use CLAMP return true; @@ -697,6 +710,18 @@ void _Host_Frame (float time) NET_Poll(); + if (cl.sendprespawn) + { + if (CL_CheckDownloads()) + { + cl.sendprespawn = false; + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, "prespawn"); + } + else if (!cls.message.cursize) + MSG_WriteByte (&cls.message, clc_nop); + } + // if running the server locally, make intentions now if (sv.active) CL_SendCmd (); @@ -874,8 +899,13 @@ void Host_Init (void) host_initialized = true; Con_Printf ("\n========= Quake Initialized =========\n\n"); + //spike -- create these aliases, because they're useful. + Cbuf_AddText ("alias startmap_sp \"map start\"\n"); + Cbuf_AddText ("alias startmap_dm \"map start\"\n"); + if (cls.state != ca_dedicated) { + Cbuf_AddText ("cl_warncmd 0\n"); Cbuf_InsertText ("exec quake.rc\n"); // johnfitz -- in case the vid mode was locked during vid_init, we can unlock it now. // note: two leading newlines because the command buffer swallows one of them. @@ -884,11 +914,15 @@ void Host_Init (void) if (cls.state == ca_dedicated) { + Cbuf_AddText ("cl_warncmd 0\n"); + Cbuf_AddText ("exec default.cfg\n"); //spike -- someone decided that quake.rc shouldn't be execed on dedicated servers, but that means you'll get bad defaults + Cbuf_AddText ("cl_warncmd 1\n"); + Cbuf_AddText ("exec server.cfg\n"); //spike -- for people who want things explicit. Cbuf_AddText ("exec autoexec.cfg\n"); Cbuf_AddText ("stuffcmds"); Cbuf_Execute (); if (!sv.active) - Cbuf_AddText ("map start\n"); + Cbuf_AddText ("startmap_dm\n"); } } diff --git a/Quake/host_cmd.c b/Quake/host_cmd.c index 70cc0d8f..962d44af 100644 --- a/Quake/host_cmd.c +++ b/Quake/host_cmd.c @@ -427,7 +427,7 @@ void Host_Status_f (void) int seconds; int minutes; int hours = 0; - int j; + int j, i; if (cmd_source == src_command) { @@ -441,19 +441,43 @@ void Host_Status_f (void) else print_fn = SV_ClientPrintf; - print_fn ("host: %s\n", Cvar_VariableString ("hostname")); - print_fn ("version: %4.2f\n", VERSION); - if (tcpipAvailable) - print_fn ("tcp/ip: %s\n", my_tcpip_address); + print_fn ( "host: %s\n", Cvar_VariableString ("hostname")); + print_fn ( "version: "ENGINE_NAME_AND_VER"\n"); + if (ipv4Available) + print_fn ("tcp/ip: %s\n", my_ipv4_address); //Spike -- FIXME: we should really have ports displayed here or something + if (ipv6Available) + print_fn ("ipv6: %s\n", my_ipv6_address); if (ipxAvailable) print_fn ("ipx: %s\n", my_ipx_address); - print_fn ("map: %s\n", sv.name); - print_fn ("players: %i active (%i max)\n\n", net_activeconnections, svs.maxclients); + print_fn ( "map: %s\n", sv.name); + + for (i = 1,j=0; i < MAX_MODELS; i++) + if (sv.model_precache[i]) + j++; + print_fn ( "models: %i/%i\n", j, MAX_MODELS-1); + for (i = 1,j=0; i < MAX_SOUNDS; i++) + if (sv.sound_precache[i]) + j++; + print_fn ( "sounds: %i/%i\n", j, MAX_SOUNDS-1); + for (i = 0,j=0; i < MAX_PARTICLETYPES; i++) + if (sv.particle_precache[i]) + j++; + if (j) + print_fn ( "effects: %i/%i\n", j, MAX_PARTICLETYPES-1); + for (i = 1,j=1; i < sv.num_edicts; i++) + if (!sv.edicts[i].free) + j++; + print_fn ( "entities:%i/%i\n", j, sv.max_edicts); + + print_fn ( "players: %i active (%i max)\n\n", net_activeconnections, svs.maxclients); for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) { if (!client->active) continue; - seconds = (int)(net_time - NET_QSocketGetTime(client->netconnection)); + if (client->netconnection) + seconds = (int)(net_time - NET_QSocketGetTime(client->netconnection)); + else + seconds = 0; minutes = seconds / 60; if (minutes) { @@ -465,7 +489,10 @@ void Host_Status_f (void) else hours = 0; print_fn ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", j+1, client->name, (int)client->edict->v.frags, hours, minutes, seconds); - print_fn (" %s\n", NET_QSocketGetAddressString(client->netconnection)); + if (cmd_source == src_command) + print_fn (" %s\n", client->netconnection?NET_QSocketGetTrueAddressString(client->netconnection):"botclient"); + else + print_fn (" %s\n", client->netconnection?NET_QSocketGetMaskedAddressString(client->netconnection):"botclient"); } } @@ -752,7 +779,7 @@ void Host_Ping_f (void) SV_ClientPrintf ("Client ping times:\n"); for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) { - if (!client->active) + if (!client->spawned || !client->netconnection) continue; total = 0; for (j = 0; j < NUM_PING_TIMES; j++) @@ -928,11 +955,16 @@ void Host_Restart_f (void) { char mapname[MAX_QPATH]; - if (cls.demoplayback || !sv.active) + if (cls.demoplayback) return; - if (cmd_source != src_command) return; + if (!sv.active) + { + if (*sv.name) + Cmd_ExecuteString(va("map \"%s\"\n", sv.name), src_command); + return; + } q_strlcpy (mapname, sv.name, sizeof(mapname)); // mapname gets cleared in spawnserver SV_SpawnServer (mapname); if (!sv.active) @@ -945,14 +977,28 @@ Host_Reconnect_f This command causes the client to wait for the signon messages again. This is sent just before a server changes levels + +for compatibility with quakeworld et al, we also allow this as a user-command to reconnect to the last server we tried, but we can only reliably do that when we're not already connected ================== */ -void Host_Reconnect_f (void) +void Host_Reconnect_Con_f (void) +{ + CL_Disconnect_f(); + cls.demonum = -1; // stop demo loop in case this fails + if (cls.demoplayback) + { + CL_StopPlayback (); + CL_Disconnect (); + } + CL_EstablishConnection (NULL); +} +void Host_Reconnect_Sv_f (void) { if (cls.demoplayback) // cross-map demo playback fix from Baker return; SCR_BeginLoadingPlaque (); + cl.protocol_dpdownload = false; cls.signon = 0; // need new connection messages } @@ -975,7 +1021,7 @@ void Host_Connect_f (void) } q_strlcpy (name, Cmd_Argv(1), sizeof(name)); CL_EstablishConnection (name); - Host_Reconnect_f (); + Host_Reconnect_Sv_f (); } @@ -1092,7 +1138,7 @@ void Host_Savegame_f (void) // write the light styles - for (i = 0; i < MAX_LIGHTSTYLES; i++) + for (i = 0; i < MAX_LIGHTSTYLES_VANILLA; i++) { if (sv.lightstyles[i]) fprintf (f, "%s\n", sv.lightstyles[i]); @@ -1107,6 +1153,37 @@ void Host_Savegame_f (void) ED_Write (f, EDICT_NUM(i)); fflush (f); } + + //add extra info (lightstyles, precaches, etc) in a way that's supposed to be compatible with DP. + //sidenote - this provides extended lightstyles and support for late precaches + //it does NOT protect against spawnfunc precache changes - we would need to include makestatics here too (and optionally baselines, or just recalculate those). + fprintf(f, "/*\n"); + fprintf(f, "// QuakeSpasm extended savegame\n"); + for (i = MAX_LIGHTSTYLES_VANILLA; i < MAX_LIGHTSTYLES; i++) + { + if (sv.lightstyles[i]) + fprintf (f, "sv.lightstyles %i \"%s\"\n", i, sv.lightstyles[i]); + else + fprintf (f, "sv.lightstyles %i \"\"\n", i); + } + for (i = 1; i < MAX_MODELS; i++) + { + if (sv.model_precache[i]) + fprintf (f, "sv.model_precache %i \"%s\"\n", i, sv.model_precache[i]); + } + for (i = 1; i < MAX_SOUNDS; i++) + { + if (sv.sound_precache[i]) + fprintf (f, "sv.sound_precache %i \"%s\"\n", i, sv.sound_precache[i]); + } + for (i = 1; i < MAX_PARTICLETYPES; i++) + { + if (sv.particle_precache[i]) + fprintf (f, "sv.particle_precache %i \"%s\"\n", i, sv.particle_precache[i]); + } + fprintf(f, "*/\n"); + + fclose (f); Con_Printf ("done.\n"); } @@ -1139,7 +1216,7 @@ void Host_Loadgame_f (void) Con_Printf ("load : load a game\n"); return; } - + if (strstr(Cmd_Argv(1), "..")) { Con_Printf ("Relative pathnames are not allowed.\n"); @@ -1205,7 +1282,7 @@ void Host_Loadgame_f (void) // load the light styles - for (i = 0; i < MAX_LIGHTSTYLES; i++) + for (i = 0; i < MAX_LIGHTSTYLES_VANILLA; i++) { data = COM_ParseStringNewline (data); sv.lightstyles[i] = (const char *)Hunk_Strdup (com_token, "lightstyles"); @@ -1215,7 +1292,69 @@ void Host_Loadgame_f (void) entnum = -1; // -1 is the globals while (*data) { - data = COM_Parse (data); + while (*data == ' ' || *data == '\r' || *data == '\n') + data++; + if (data[0] == '/' && data[1] == '*' && (data[2] == '\r' || data[2] == '\n')) + { //looks like an extended saved game + char *end; + const char *ext; + ext = data+2; + while ((end = strchr(ext, '\n'))) + { + *end = 0; + ext = COM_Parse(ext); + if (!strcmp(com_token, "sv.lightstyles")) + { + int idx; + ext = COM_Parse(ext); + idx = atoi(com_token); + ext = COM_Parse(ext); + if (idx >= 0 && idx < MAX_LIGHTSTYLES) + { + if (*com_token) + sv.lightstyles[idx] = (const char *)Hunk_Strdup (com_token, "lightstyles"); + else + sv.lightstyles[idx] = NULL; + } + } + else if (!strcmp(com_token, "sv.model_precache")) + { + int idx; + ext = COM_Parse(ext); + idx = atoi(com_token); + ext = COM_Parse(ext); + if (idx >= 1 && idx < MAX_MODELS) + { + sv.model_precache[idx] = (const char *)Hunk_Strdup (com_token, "model_precache"); + sv.models[idx] = Mod_ForName (sv.model_precache[idx], idx==1); + //if (idx == 1) + // sv.worldmodel = sv.models[idx]; + } + } + else if (!strcmp(com_token, "sv.sound_precache")) + { + int idx; + ext = COM_Parse(ext); + idx = atoi(com_token); + ext = COM_Parse(ext); + if (idx >= 1 && idx < MAX_MODELS) + sv.sound_precache[idx] = (const char *)Hunk_Strdup (com_token, "sound_precache"); + } + else if (!strcmp(com_token, "sv.particle_precache")) + { + int idx; + ext = COM_Parse(ext); + idx = atoi(com_token); + ext = COM_Parse(ext); + if (idx >= 1 && idx < MAX_PARTICLETYPES) + sv.particle_precache[idx] = (const char *)Hunk_Strdup (com_token, "particle_precache"); + } + *end = '\n'; + ext = end+1; + } + } + + data = COM_Parse(data); if (!com_token[0]) break; // end of file if (strcmp(com_token,"{")) @@ -1259,7 +1398,7 @@ void Host_Loadgame_f (void) if (cls.state != ca_dedicated) { CL_EstablishConnection ("local"); - Host_Reconnect_f (); + Host_Reconnect_Sv_f (); } } @@ -1604,10 +1743,9 @@ void Host_PreSpawn_f (void) return; } - SZ_Write (&host_client->message, sv.signon.data, sv.signon.cursize); - MSG_WriteByte (&host_client->message, svc_signonnum); - MSG_WriteByte (&host_client->message, 2); - host_client->sendsignon = true; + //will start splurging out prespawn data + host_client->sendsignon = 2; + host_client->signonidx = 0; } /* @@ -1633,6 +1771,7 @@ void Host_Spawn_f (void) return; } + host_client->knowntoqc = true; // run the entrance script if (sv.loadgame) { // loaded games are fully inited allready @@ -1670,9 +1809,13 @@ void Host_Spawn_f (void) // send time of update MSG_WriteByte (&host_client->message, svc_time); MSG_WriteFloat (&host_client->message, sv.time); + if (host_client->protocol_pext2 & PEXT2_PREDINFO) + MSG_WriteShort(&host_client->message, (host_client->lastmovemessage&0xffff)); for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) { + if (!client->knowntoqc) + continue; MSG_WriteByte (&host_client->message, svc_updatename); MSG_WriteByte (&host_client->message, i); MSG_WriteString (&host_client->message, client->name); @@ -1687,9 +1830,13 @@ void Host_Spawn_f (void) // send all current light styles for (i = 0; i < MAX_LIGHTSTYLES; i++) { - MSG_WriteByte (&host_client->message, svc_lightstyle); - MSG_WriteByte (&host_client->message, (char)i); - MSG_WriteString (&host_client->message, sv.lightstyles[i]); + //CL_ClearState should have cleared all lightstyles, so don't send irrelevant ones + if (sv.lightstyles[i]) + { + MSG_WriteByte (&host_client->message, svc_lightstyle); + MSG_WriteByte (&host_client->message, (char)i); + MSG_WriteString (&host_client->message, sv.lightstyles[i]); + } } // @@ -1723,7 +1870,8 @@ void Host_Spawn_f (void) MSG_WriteAngle (&host_client->message, ent->v.angles[i], sv.protocolflags ); MSG_WriteAngle (&host_client->message, 0, sv.protocolflags ); - SV_WriteClientdataToMessage (sv_player, &host_client->message); + if (!(host_client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + SV_WriteClientdataToMessage (host_client, &host_client->message); MSG_WriteByte (&host_client->message, svc_signonnum); MSG_WriteByte (&host_client->message, 3); @@ -1881,33 +2029,33 @@ void Host_Give_f (void) // MED 01/04/97 added hipnotic give stuff if (hipnotic) { - if (t[0] == '6') - { - if (t[1] == 'a') - sv_player->v.items = (int)sv_player->v.items | HIT_PROXIMITY_GUN; - else - sv_player->v.items = (int)sv_player->v.items | IT_GRENADE_LAUNCHER; - } - else if (t[0] == '9') - sv_player->v.items = (int)sv_player->v.items | HIT_LASER_CANNON; - else if (t[0] == '0') - sv_player->v.items = (int)sv_player->v.items | HIT_MJOLNIR; - else if (t[0] >= '2') - sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2')); + if (t[0] == '6') + { + if (t[1] == 'a') + sv_player->v.items = (int)sv_player->v.items | HIT_PROXIMITY_GUN; + else + sv_player->v.items = (int)sv_player->v.items | IT_GRENADE_LAUNCHER; + } + else if (t[0] == '9') + sv_player->v.items = (int)sv_player->v.items | HIT_LASER_CANNON; + else if (t[0] == '0') + sv_player->v.items = (int)sv_player->v.items | HIT_MJOLNIR; + else if (t[0] >= '2') + sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2')); } else { - if (t[0] >= '2') - sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2')); + if (t[0] >= '2') + sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2')); } break; case 's': if (rogue) { - val = GetEdictFieldValue(sv_player, "ammo_shells1"); - if (val) - val->_float = v; + val = GetEdictFieldValue(sv_player, ED_FindFieldOffset("ammo_shells1")); + if (val) + val->_float = v; } sv_player->v.ammo_shells = v; break; @@ -1915,60 +2063,60 @@ void Host_Give_f (void) case 'n': if (rogue) { - val = GetEdictFieldValue(sv_player, "ammo_nails1"); - if (val) - { - val->_float = v; - if (sv_player->v.weapon <= IT_LIGHTNING) - sv_player->v.ammo_nails = v; - } + val = GetEdictFieldValue(sv_player, ED_FindFieldOffset("ammo_nails1")); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_nails = v; + } } else { - sv_player->v.ammo_nails = v; + sv_player->v.ammo_nails = v; } break; case 'l': if (rogue) { - val = GetEdictFieldValue(sv_player, "ammo_lava_nails"); - if (val) - { - val->_float = v; - if (sv_player->v.weapon > IT_LIGHTNING) - sv_player->v.ammo_nails = v; - } + val = GetEdictFieldValue(sv_player, ED_FindFieldOffset("ammo_lava_nails")); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_nails = v; + } } break; case 'r': if (rogue) { - val = GetEdictFieldValue(sv_player, "ammo_rockets1"); - if (val) - { - val->_float = v; - if (sv_player->v.weapon <= IT_LIGHTNING) - sv_player->v.ammo_rockets = v; - } + val = GetEdictFieldValue(sv_player, ED_FindFieldOffset("ammo_rockets1")); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_rockets = v; + } } else { - sv_player->v.ammo_rockets = v; + sv_player->v.ammo_rockets = v; } break; case 'm': if (rogue) { - val = GetEdictFieldValue(sv_player, "ammo_multi_rockets"); - if (val) - { - val->_float = v; - if (sv_player->v.weapon > IT_LIGHTNING) - sv_player->v.ammo_rockets = v; - } + val = GetEdictFieldValue(sv_player, ED_FindFieldOffset("ammo_multi_rockets")); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_rockets = v; + } } break; @@ -1979,30 +2127,30 @@ void Host_Give_f (void) case 'c': if (rogue) { - val = GetEdictFieldValue(sv_player, "ammo_cells1"); - if (val) - { - val->_float = v; - if (sv_player->v.weapon <= IT_LIGHTNING) - sv_player->v.ammo_cells = v; - } + val = GetEdictFieldValue(sv_player, ED_FindFieldOffset("ammo_cells1")); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_cells = v; + } } else { - sv_player->v.ammo_cells = v; + sv_player->v.ammo_cells = v; } break; case 'p': if (rogue) { - val = GetEdictFieldValue(sv_player, "ammo_plasma"); - if (val) - { - val->_float = v; - if (sv_player->v.weapon > IT_LIGHTNING) - sv_player->v.ammo_cells = v; - } + val = GetEdictFieldValue(sv_player, ED_FindFieldOffset("ammo_plasma")); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_cells = v; + } } break; @@ -2010,25 +2158,25 @@ void Host_Give_f (void) case 'a': if (v > 150) { - sv_player->v.armortype = 0.8; - sv_player->v.armorvalue = v; - sv_player->v.items = sv_player->v.items - + sv_player->v.armortype = 0.8; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + IT_ARMOR3; } else if (v > 100) { - sv_player->v.armortype = 0.6; - sv_player->v.armorvalue = v; - sv_player->v.items = sv_player->v.items - + sv_player->v.armortype = 0.6; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + IT_ARMOR2; } else if (v >= 0) { - sv_player->v.armortype = 0.3; - sv_player->v.armorvalue = v; - sv_player->v.items = sv_player->v.items - + sv_player->v.armortype = 0.3; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + IT_ARMOR1; } @@ -2280,6 +2428,155 @@ void Host_Stopdemo_f (void) CL_Disconnect (); } +//============================================================================= +//download stuff + +static void Host_Download_f(void) +{ + const char *fname = Cmd_Argv(1); + int fsize; + if (cmd_source == src_command) + { + //FIXME: add some sort of queuing thing +// if (cls.state == ca_connected) +// Cmd_ForwardToServer (); + return; + } + else if (cmd_source == src_client) + { + if (host_client->download.file) + { //abort the current download if the previous didn't terminate properly. + SV_ClientPrintf("cancelling previous download\n"); + MSG_WriteByte (&host_client->message, svc_stufftext); + MSG_WriteString (&host_client->message, "\nstopdownload\n"); + fclose(host_client->download.file); + host_client->download.file = NULL; + } + + host_client->download.size = 0; + host_client->download.started = false; + host_client->download.sendpos = 0; + host_client->download.ackpos = 0; + + fsize = -1; + if (!COM_DownloadNameOkay(fname)) + SV_ClientPrintf("refusing download of %s - restricted filename\n", fname); + else + { + fsize = COM_FOpenFile(fname, &host_client->download.file, NULL); + if (!host_client->download.file) + SV_ClientPrintf("server does not have file %s\n", fname); + else if (file_from_pak) + { + SV_ClientPrintf("refusing download of %s from inside pak\n", fname); + fclose(host_client->download.file); + host_client->download.file = NULL; + } + else if (fsize < 0 || fsize > 50*1024*1024) + { + SV_ClientPrintf("refusing download of large file %s\n", fname); + fclose(host_client->download.file); + host_client->download.file = NULL; + } + } + + host_client->download.size = (unsigned int)fsize; + if (host_client->download.file) + { + host_client->download.startpos = ftell(host_client->download.file); + Con_Printf("downloading %s to %s\n", fname, host_client->name); + MSG_WriteByte (&host_client->message, svc_stufftext); + MSG_WriteString (&host_client->message, va("\ncl_downloadbegin %u \"%s\"\n", host_client->download.size, fname)); + q_strlcpy(host_client->download.name, fname, sizeof(host_client->download.name)); + } + else + { + Con_Printf("refusing download of %s to %s\n", fname, host_client->name); + MSG_WriteByte (&host_client->message, svc_stufftext); + MSG_WriteString (&host_client->message, "\nstopdownload\n"); + } + host_client->sendsignon = true; //override any keepalive issues. + } +} + +static void Host_StartDownload_f(void) +{ + if (cmd_source != src_client) + return; + if (host_client->download.file) + host_client->download.started = true; + else + SV_ClientPrintf("no download started\n"); +} +//just writes download data onto the end of the outgoing unreliable buffer +void Host_AppendDownloadData(client_t *client, sizebuf_t *buf) +{ + if (buf->cursize+7 > buf->maxsize) + return; //no space for anything + if (client->download.file && client->download.started) + { + byte tbuf[1400]; //don't be too aggressive, ethernet mtu is about 1450 + unsigned int size = client->download.size - client->download.sendpos; + //size might be 0 at eof, and that's needed to avoid failure if we drop the last few packets + if (size > sizeof(tbuf)) + size = sizeof(tbuf); + if ((int)size > buf->maxsize-(buf->cursize+7)) + size = (int)(buf->maxsize-(buf->cursize+7)); //don't overflow + + if (size && fread(tbuf, 1, size, host_client->download.file) < size) + client->download.ackpos = client->download.sendpos = client->download.size; //some kind of error... + else + { + MSG_WriteByte(buf, svcdp_downloaddata); + MSG_WriteLong(buf, client->download.sendpos); + MSG_WriteShort(buf, size); + SZ_Write(buf, tbuf, size); + client->download.sendpos += size; + } + } +} +//parses incoming acks from the client, so we know which parts of the file the client actually received. +void Host_DownloadAck(client_t *client) +{ + unsigned int start = MSG_ReadLong(); + unsigned int size = (unsigned short)MSG_ReadShort(); + + if (!client->download.started || !client->download.file) + return; + + if (client->download.ackpos < start) + { + client->download.sendpos = client->download.ackpos;//there was a gap, rewind to the known gap + fseek(client->download.file, host_client->download.startpos+client->download.sendpos, SEEK_SET); + } + else if (client->download.ackpos < start+size) + client->download.ackpos = start+size; //no loss yet. + //else FIXME: build a log of parts known to be acked to avoid resending them later, skip past them in acks + + if (client->download.ackpos == client->download.size) + { + unsigned int hash = 0; + byte *data; + client->download.started = false; + + data = malloc(client->download.size); + if (data) + { + fseek(client->download.file, host_client->download.startpos, SEEK_SET); + fread(data, 1, host_client->download.size, client->download.file); + hash = CRC_Block(data, host_client->download.size); + free(data); + } + fclose(client->download.file); + client->download.file = NULL; + + MSG_WriteByte (&host_client->message, svc_stufftext); + MSG_WriteString (&host_client->message, va("cl_downloadfinished %u %u \"%s\"\n", client->download.size, hash, client->download.name)); + *client->download.name = 0; + host_client->sendsignon = true; //override any keepalive issues. + } +} + //============================================================================= /* @@ -2295,34 +2592,37 @@ void Host_InitCommands (void) Cmd_AddCommand ("mapname", Host_Mapname_f); //johnfitz Cmd_AddCommand ("randmap", Host_Randmap_f); //ericw - Cmd_AddCommand ("status", Host_Status_f); + Cmd_AddCommand_ClientCommand ("status", Host_Status_f); Cmd_AddCommand ("quit", Host_Quit_f); - Cmd_AddCommand ("god", Host_God_f); - Cmd_AddCommand ("notarget", Host_Notarget_f); - Cmd_AddCommand ("fly", Host_Fly_f); + Cmd_AddCommand_ClientCommand ("god", Host_God_f); + Cmd_AddCommand_ClientCommand ("notarget", Host_Notarget_f); + Cmd_AddCommand_ClientCommand ("fly", Host_Fly_f); Cmd_AddCommand ("map", Host_Map_f); Cmd_AddCommand ("restart", Host_Restart_f); Cmd_AddCommand ("changelevel", Host_Changelevel_f); Cmd_AddCommand ("connect", Host_Connect_f); - Cmd_AddCommand ("reconnect", Host_Reconnect_f); - Cmd_AddCommand ("name", Host_Name_f); - Cmd_AddCommand ("noclip", Host_Noclip_f); - Cmd_AddCommand ("setpos", Host_SetPos_f); //QuakeSpasm + Cmd_AddCommand_Console ("reconnect", Host_Reconnect_Con_f); + Cmd_AddCommand_ServerCommand ("reconnect", Host_Reconnect_Sv_f); + Cmd_AddCommand_ClientCommand ("name", Host_Name_f); + Cmd_AddCommand_ClientCommand ("noclip", Host_Noclip_f); + Cmd_AddCommand_ClientCommand ("setpos", Host_SetPos_f); //QuakeSpasm - Cmd_AddCommand ("say", Host_Say_f); - Cmd_AddCommand ("say_team", Host_Say_Team_f); - Cmd_AddCommand ("tell", Host_Tell_f); - Cmd_AddCommand ("color", Host_Color_f); - Cmd_AddCommand ("kill", Host_Kill_f); - Cmd_AddCommand ("pause", Host_Pause_f); - Cmd_AddCommand ("spawn", Host_Spawn_f); - Cmd_AddCommand ("begin", Host_Begin_f); - Cmd_AddCommand ("prespawn", Host_PreSpawn_f); - Cmd_AddCommand ("kick", Host_Kick_f); - Cmd_AddCommand ("ping", Host_Ping_f); + Cmd_AddCommand_ClientCommand ("say", Host_Say_f); + Cmd_AddCommand_ClientCommand ("say_team", Host_Say_Team_f); + Cmd_AddCommand_ClientCommand ("tell", Host_Tell_f); + Cmd_AddCommand_ClientCommand ("color", Host_Color_f); + Cmd_AddCommand_ClientCommand ("kill", Host_Kill_f); + Cmd_AddCommand_ClientCommand ("pause", Host_Pause_f); + Cmd_AddCommand_ClientCommand ("spawn", Host_Spawn_f); + Cmd_AddCommand_ClientCommand ("begin", Host_Begin_f); + Cmd_AddCommand_ClientCommand ("prespawn", Host_PreSpawn_f); + Cmd_AddCommand_ClientCommand ("kick", Host_Kick_f); + Cmd_AddCommand_ClientCommand ("ping", Host_Ping_f); Cmd_AddCommand ("load", Host_Loadgame_f); Cmd_AddCommand ("save", Host_Savegame_f); - Cmd_AddCommand ("give", Host_Give_f); + Cmd_AddCommand_ClientCommand ("give", Host_Give_f); + Cmd_AddCommand_ClientCommand ("download", Host_Download_f); + Cmd_AddCommand_ClientCommand ("sv_startdownload", Host_StartDownload_f); Cmd_AddCommand ("startdemos", Host_Startdemos_f); Cmd_AddCommand ("demos", Host_Demos_f); diff --git a/Quake/image.c b/Quake/image.c index ece0faa3..b242ef13 100644 --- a/Quake/image.c +++ b/Quake/image.c @@ -69,6 +69,54 @@ static inline int Buf_GetC(stdio_buffer_t *buf) return buf->buffer[buf->pos++]; } +/*small function to read files with stb_image - single-file image loader library. +** downloaded from: https://raw.githubusercontent.com/nothings/stb/master/stb_image.h +** only use jpeg+png formats, because tbh there's not much need for the others. +** */ +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_JPEG +#ifdef LODEPNG_NO_COMPILE_DECODER + #define STBI_ONLY_PNG +#endif +#include "stb_image.h" +static byte *Image_LoadSTBI(FILE *f, int *width, int *height) +{ + int bytesPerPixel; + byte *heap = stbi_load_from_file(f, width, height, &bytesPerPixel, 4); + fclose(f); + if (heap) + { //this is silly, but we do it for consistency. + //frankly, most people should be using tga-inside-pk3. + byte *hunk = Hunk_Alloc(*width**height*4); + memcpy(hunk, heap, *width**height*4); + free(heap); + return hunk; + } + return NULL; +} + +byte *Image_LoadPNG(FILE *f, int *width, int *height, qboolean *malloced) +{ +#ifdef LODEPNG_NO_COMPILE_DECODER + return Image_LoadSTBI (f, width, height); +#else + unsigned w, h; + unsigned char *out = NULL, *in; + size_t insize = com_filesize; + + in = malloc(com_filesize); + if (!in) + return NULL; + if (com_filesize == fread(in, 1, com_filesize, f)) + { + *malloced = true; + lodepng_decode32(&out, &w, &h, in, insize); + } + free(in); + return out; +#endif +} + /* ============ Image_LoadImage @@ -78,19 +126,44 @@ returns a pointer to hunk allocated RGBA data TODO: search order: tga png jpg pcx lmp ============ */ -byte *Image_LoadImage (const char *name, int *width, int *height) +byte *Image_LoadImage (const char *name, int *width, int *height, qboolean *malloced) { FILE *f; + char *prefixes[3] = {"", "textures/", "textures/"}; + int i; - q_snprintf (loadfilename, sizeof(loadfilename), "%s.tga", name); - COM_FOpenFile (loadfilename, &f, NULL); - if (f) - return Image_LoadTGA (f, width, height); + *malloced = false; - q_snprintf (loadfilename, sizeof(loadfilename), "%s.pcx", name); - COM_FOpenFile (loadfilename, &f, NULL); - if (f) - return Image_LoadPCX (f, width, height); + for (i = 0; i < sizeof(prefixes)/sizeof(prefixes[0]); i++) + { + if (i == 2) //last resort... + name = COM_SkipPath(name); + + q_snprintf (loadfilename, sizeof(loadfilename), "%s%s.tga", prefixes[i], name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadTGA (f, width, height); + + q_snprintf (loadfilename, sizeof(loadfilename), "%s%s.png", prefixes[i], name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadPNG (f, width, height, malloced); + + q_snprintf (loadfilename, sizeof(loadfilename), "%s%s.jpeg", prefixes[i], name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadSTBI (f, width, height); + + q_snprintf (loadfilename, sizeof(loadfilename), "%s%s.jpg", prefixes[i], name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadSTBI (f, width, height); + + q_snprintf (loadfilename, sizeof(loadfilename), "%s%s.pcx", prefixes[i], name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadPCX (f, width, height); + } return NULL; } diff --git a/Quake/image.h b/Quake/image.h index 0b89abb6..7f519ab8 100644 --- a/Quake/image.h +++ b/Quake/image.h @@ -28,7 +28,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //be sure to free the hunk after using these loading functions byte *Image_LoadTGA (FILE *f, int *width, int *height); byte *Image_LoadPCX (FILE *f, int *width, int *height); -byte *Image_LoadImage (const char *name, int *width, int *height); +byte *Image_LoadImage (const char *name, int *width, int *height, qboolean *malloced); qboolean Image_WriteTGA (const char *name, byte *data, int width, int height, int bpp, qboolean upsidedown); qboolean Image_WritePNG (const char *name, byte *data, int width, int height, int bpp, qboolean upsidedown); diff --git a/Quake/in_sdl.c b/Quake/in_sdl.c index bc8b55bf..7175749e 100644 --- a/Quake/in_sdl.c +++ b/Quake/in_sdl.c @@ -1014,6 +1014,12 @@ void IN_SendKeyEvents (void) S_UnblockSound(); else if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) S_BlockSound(); + else if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + vid.width = event.window.data1; + vid.height = event.window.data2; + Cvar_FindVar("scr_conscale")->callback(NULL); + } break; #else case SDL_ACTIVEEVENT: diff --git a/Quake/keys.c b/Quake/keys.c index 15f77edf..6d1f0c9b 100644 --- a/Quake/keys.c +++ b/Quake/keys.c @@ -31,7 +31,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. char key_lines[CMDLINES][MAXCMDLINE]; int key_linepos; -int key_insert; //johnfitz -- insert key toggle (for editing) +int key_insert = true; //johnfitz -- insert key toggle (for editing) double key_blinktime; //johnfitz -- fudge cursor blinking to make it easier to spot in certain cases int edit_line = 0; diff --git a/Quake/main_sdl.c b/Quake/main_sdl.c index 7ad1095f..7f2b6084 100644 --- a/Quake/main_sdl.c +++ b/Quake/main_sdl.c @@ -136,6 +136,7 @@ int main(int argc, char *argv[]) Sys_Printf("FitzQuake %1.2f (c) John Fitzgibbons\n", FITZQUAKE_VERSION); Sys_Printf("FitzQuake SDL port (c) SleepwalkR, Baker\n"); Sys_Printf("QuakeSpasm " QUAKESPASM_VER_STRING " (c) Ozkan Sezer, Eric Wasylishen & others\n"); + Sys_Printf("QuakeSpasm-Spiked (c) Spike\n"); Sys_Printf("Host_Init\n"); Host_Init(); diff --git a/Quake/mathlib.c b/Quake/mathlib.c index c98008e5..c3bacff7 100644 --- a/Quake/mathlib.c +++ b/Quake/mathlib.c @@ -211,17 +211,46 @@ int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, mplane_t *p) } //johnfitz -- the opposite of AngleVectors. this takes forward and generates pitch yaw roll -//TODO: take right and up vectors to properly set yaw and roll -void VectorAngles (const vec3_t forward, vec3_t angles) +//Spike: take right and up vectors to properly set yaw and roll +void VectorAngles (const vec3_t forward, float *up, vec3_t angles) { - vec3_t temp; + if (forward[0] == 0 && forward[1] == 0) + { //either vertically up or down + if (forward[2] > 0) + { + angles[PITCH] = -90; + angles[YAW] = up ? atan2(-up[1], -up[0]) / M_PI_DIV_180: 0; + } + else + { + angles[PITCH] = 90; + angles[YAW] = up ? atan2(up[1], up[0]) / M_PI_DIV_180: 0; + } + angles[ROLL] = 0; + } + else + { + angles[PITCH] = -atan2(forward[2], sqrt(DotProduct2(forward,forward))); + angles[YAW] = atan2(forward[1], forward[0]); - temp[0] = forward[0]; - temp[1] = forward[1]; - temp[2] = 0; - angles[PITCH] = -atan2(forward[2], VectorLength(temp)) / M_PI_DIV_180; - angles[YAW] = atan2(forward[1], forward[0]) / M_PI_DIV_180; - angles[ROLL] = 0; + if (up) + { + vec_t cp = cos(angles[PITCH]), sp = sin(angles[PITCH]); + vec_t cy = cos(angles[YAW]), sy = sin(angles[YAW]); + vec3_t tleft, tup; + tleft[0] = -sy; + tleft[1] = cy; + tleft[2] = 0; + tup[0] = sp*cy; + tup[1] = sp*sy; + tup[2] = cp; + angles[ROLL] = -atan2(DotProduct(up, tleft), DotProduct(up, tup)) / M_PI_DIV_180; + } + else angles[ROLL] = 0; + + angles[PITCH] /= M_PI_DIV_180; + angles[YAW] /= M_PI_DIV_180; + } } void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) @@ -250,7 +279,7 @@ void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) up[2] = cr*cp; } -int VectorCompare (vec3_t v1, vec3_t v2) +int VectorCompare (const vec3_t v1, const vec3_t v2) { int i; @@ -261,7 +290,7 @@ int VectorCompare (vec3_t v1, vec3_t v2) return 1; } -void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) { vecc[0] = veca[0] + scale*vecb[0]; vecc[1] = veca[1] + scale*vecb[1]; @@ -269,40 +298,40 @@ void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) } -vec_t _DotProduct (vec3_t v1, vec3_t v2) +vec_t _DotProduct (const vec3_t v1, const vec3_t v2) { return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; } -void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +void _VectorSubtract (const vec3_t veca, const vec3_t vecb, vec3_t out) { out[0] = veca[0]-vecb[0]; out[1] = veca[1]-vecb[1]; out[2] = veca[2]-vecb[2]; } -void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +void _VectorAdd (const vec3_t veca, const vec3_t vecb, vec3_t out) { out[0] = veca[0]+vecb[0]; out[1] = veca[1]+vecb[1]; out[2] = veca[2]+vecb[2]; } -void _VectorCopy (vec3_t in, vec3_t out) +void _VectorCopy (const vec3_t in, vec3_t out) { out[0] = in[0]; out[1] = in[1]; out[2] = in[2]; } -void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross) { cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; } -vec_t VectorLength(vec3_t v) +vec_t VectorLength(const vec3_t v) { return sqrt(DotProduct(v,v)); } @@ -332,7 +361,7 @@ void VectorInverse (vec3_t v) v[2] = -v[2]; } -void VectorScale (vec3_t in, vec_t scale, vec3_t out) +void VectorScale (const vec3_t in, vec_t scale, vec3_t out) { out[0] = in[0]*scale; out[1] = in[1]*scale; diff --git a/Quake/mathlib.h b/Quake/mathlib.h index a607905f..5e74adbb 100644 --- a/Quake/mathlib.h +++ b/Quake/mathlib.h @@ -52,6 +52,7 @@ static inline int IS_NAN (float x) { #define Q_rint(x) ((x) > 0 ? (int)((x) + 0.5) : (int)((x) - 0.5)) //johnfitz -- from joequake #define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define DotProduct2(x,y) (x[0]*y[0]+x[1]*y[1]) #define DoublePrecisionDotProduct(x,y) ((double)x[0]*y[0]+(double)x[1]*y[1]+(double)x[2]*y[2]) #define VectorSubtract(a,b,c) {c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];} #define VectorAdd(a,b,c) {c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];} @@ -72,21 +73,21 @@ static inline int IS_NAN (float x) { } void TurnVector (vec3_t out, const vec3_t forward, const vec3_t side, float angle); //johnfitz -void VectorAngles (const vec3_t forward, vec3_t angles); //johnfitz +void VectorAngles (const vec3_t forward, float *up, vec3_t angles); //johnfitz, spike(up is optional) -void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc); -vec_t _DotProduct (vec3_t v1, vec3_t v2); -void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); -void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); -void _VectorCopy (vec3_t in, vec3_t out); +vec_t _DotProduct (const vec3_t v1, const vec3_t v2); +void _VectorSubtract (const vec3_t veca, const vec3_t vecb, vec3_t out); +void _VectorAdd (const vec3_t veca, const vec3_t vecb, vec3_t out); +void _VectorCopy (const vec3_t in, vec3_t out); -int VectorCompare (vec3_t v1, vec3_t v2); -vec_t VectorLength (vec3_t v); -void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +int VectorCompare (const vec3_t v1, const vec3_t v2); +vec_t VectorLength (const vec3_t v); +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross); float VectorNormalize (vec3_t v); // returns vector length void VectorInverse (vec3_t v); -void VectorScale (vec3_t in, vec_t scale, vec3_t out); +void VectorScale (const vec3_t in, vec_t scale, vec3_t out); int Q_log2(int val); void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); diff --git a/Quake/menu.c b/Quake/menu.c index 5548b7e5..97dbdac0 100644 --- a/Quake/menu.c +++ b/Quake/menu.c @@ -38,7 +38,7 @@ void M_Menu_Main_f (void); void M_Menu_Net_f (void); void M_Menu_LanConfig_f (void); void M_Menu_GameOptions_f (void); - void M_Menu_Search_f (void); + void M_Menu_Search_f (enum slistScope_e scope); void M_Menu_ServerList_f (void); void M_Menu_Options_f (void); void M_Menu_Keys_f (void); @@ -398,9 +398,10 @@ void M_SinglePlayer_Key (int key) if (sv.active) Cbuf_AddText ("disconnect\n"); Cbuf_AddText ("maxplayers 1\n"); + Cbuf_AddText ("samelevel 0\n"); //spike -- you'd be amazed how many qw players have this setting breaking their singleplayer experience... Cbuf_AddText ("deathmatch 0\n"); //johnfitz Cbuf_AddText ("coop 0\n"); //johnfitz - Cbuf_AddText ("map start\n"); + Cbuf_AddText ("startmap_sp\n"); break; case 1: @@ -625,7 +626,7 @@ void M_MultiPlayer_Draw (void) M_DrawTransPic (54, 32 + m_multiplayer_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); - if (ipxAvailable || tcpipAvailable) + if (ipxAvailable || ipv4Available || ipv6Available) return; M_PrintWhite ((320/2) - ((27*8)/2), 148, "No Communications Available"); } @@ -659,12 +660,12 @@ void M_MultiPlayer_Key (int key) switch (m_multiplayer_cursor) { case 0: - if (ipxAvailable || tcpipAvailable) + if (ipxAvailable || ipv4Available || ipv6Available) M_Menu_Net_f (); break; case 1: - if (ipxAvailable || tcpipAvailable) + if (ipxAvailable || ipv4Available || ipv6Available) M_Menu_Net_f (); break; @@ -913,7 +914,7 @@ void M_Net_Draw (void) M_DrawTransPic (72, f, p); f += 19; - if (tcpipAvailable) + if (ipv4Available || ipv6Available) p = Draw_CachePic ("gfx/netmen4.lmp"); else p = Draw_CachePic ("gfx/dim_tcp.lmp"); @@ -964,7 +965,7 @@ again: if (m_net_cursor == 0 && !ipxAvailable) goto again; - if (m_net_cursor == 1 && !tcpipAvailable) + if (m_net_cursor == 1 && !(ipv4Available || ipv6Available)) goto again; } @@ -1333,7 +1334,7 @@ void M_Options_Key (int k) //============================================================================= /* KEYS MENU */ -const char *bindnames[][2] = +const char *quakebindnames[][2] = { {"+attack", "attack"}, {"impulse 10", "next weapon"}, @@ -1353,20 +1354,87 @@ const char *bindnames[][2] = {"+mlook", "mouse look"}, {"+klook", "keyboard look"}, {"+moveup", "swim up"}, - {"+movedown", "swim down"} + {"+movedown", "swim down"}, + {"+voip", "Voice Chat"} }; +#define NUMQUAKECOMMANDS (sizeof(quakebindnames)/sizeof(quakebindnames[0])) -#define NUMCOMMANDS (sizeof(bindnames)/sizeof(bindnames[0])) +static struct +{ + char *cmd; + char *desc; +} *bindnames; +static size_t numbindnames; -static int keys_cursor; +static size_t keys_first; +static size_t keys_cursor; static qboolean bind_grab; +void M_Keys_Close (void) +{ + while (numbindnames>0) + { + numbindnames--; + Z_Free(bindnames[numbindnames].cmd); + Z_Free(bindnames[numbindnames].desc); + } + Z_Free(bindnames); + bindnames = NULL; + numbindnames = 0; +} +void M_Keys_Populate(void) +{ + FILE *file; + char line[1024]; + if (numbindnames) + return; + + if (COM_FOpenFile("bindlist.lst", &file, NULL) >= 0) + { + while (fgets(line, sizeof(line), file)) + { + const char *cmd, *desc/*, tip*/; + Cmd_TokenizeString(line); + cmd = Cmd_Argv(0); + desc = Cmd_Argv(1); + /*tip = Cmd_Argv(2); unused in quakespasm*/ + + if (*cmd) + { + bindnames = Z_Realloc(bindnames, sizeof(*bindnames)*(numbindnames+1)); + bindnames[numbindnames].cmd = strcpy(Z_Malloc(strlen(cmd)+1), cmd); + bindnames[numbindnames].desc = strcpy(Z_Malloc(strlen(desc)+1), desc); + numbindnames++; + } + } + fclose(file); + } + + if (!numbindnames) + { + bindnames = Z_Realloc(bindnames, sizeof(*bindnames)*NUMQUAKECOMMANDS); + while(numbindnames < NUMQUAKECOMMANDS) + { + bindnames[numbindnames].cmd = strcpy(Z_Malloc(strlen(quakebindnames[numbindnames][0])+1), quakebindnames[numbindnames][0]); + bindnames[numbindnames].desc = strcpy(Z_Malloc(strlen(quakebindnames[numbindnames][1])+1), quakebindnames[numbindnames][1]); + numbindnames++; + } + } + + //don't start with it on text + keys_first = keys_cursor = 0; + while (keys_cursor < numbindnames-1 && !strcmp(bindnames[keys_cursor].cmd, "-")) + keys_cursor++; +} + void M_Menu_Keys_f (void) { IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_keys; m_entersound = true; + + M_Keys_Populate(); } @@ -1418,10 +1486,12 @@ extern qpic_t *pic_up, *pic_down; void M_Keys_Draw (void) { - int i, x, y; + size_t i; + int x, y; int keys[3]; const char *name; qpic_t *p; + size_t keys_shown; p = Draw_CachePic ("gfx/ttl_cstm.lmp"); M_DrawPic ( (320-p->width)/2, 4, p); @@ -1431,14 +1501,28 @@ void M_Keys_Draw (void) else M_Print (18, 32, "Enter to change, backspace to clear"); + keys_shown = numbindnames; + if (keys_shown > (200-48)/8) + keys_shown = (200-48)/8; + if (keys_first+keys_shown-1 < keys_cursor) + keys_first = keys_cursor-(keys_shown-1); + if (keys_first > keys_cursor) + keys_first = keys_cursor; + // search for known bindings - for (i = 0; i < (int)NUMCOMMANDS; i++) + for (i = keys_first; i < keys_first+keys_shown; i++) { - y = 48 + 8*i; + y = 48 + 8*(i-keys_first); - M_Print (16, y, bindnames[i][1]); + if (!strcmp(bindnames[i].cmd, "-")) + { + M_PrintWhite ((320-strlen(bindnames[i].desc)*8)/2, y, bindnames[i].desc); + continue; + } - M_FindKeysForCommand (bindnames[i][0], keys); + M_Print (16, y, bindnames[i].desc); + + M_FindKeysForCommand (bindnames[i].cmd, keys); if (keys[0] == -1) { @@ -1465,9 +1549,9 @@ void M_Keys_Draw (void) } if (bind_grab) - M_DrawCharacter (130, 48 + keys_cursor*8, '='); + M_DrawCharacter (130, 48 + (keys_cursor-keys_first)*8, '='); else - M_DrawCharacter (130, 48 + keys_cursor*8, 12+((int)(realtime*4)&1)); + M_DrawCharacter (130, 48 + (keys_cursor-keys_first)*8, 12+((int)(realtime*4)&1)); } @@ -1481,7 +1565,7 @@ void M_Keys_Key (int k) S_LocalSound ("misc/menu1.wav"); if ((k != K_ESCAPE) && (k != '`')) { - sprintf (cmd, "bind \"%s\" \"%s\"\n", Key_KeynumToString (k), bindnames[keys_cursor][0]); + sprintf (cmd, "bind \"%s\" \"%s\"\n", Key_KeynumToString (k), bindnames[keys_cursor].cmd); Cbuf_InsertText (cmd); } @@ -1500,26 +1584,43 @@ void M_Keys_Key (int k) case K_LEFTARROW: case K_UPARROW: S_LocalSound ("misc/menu1.wav"); - keys_cursor--; - if (keys_cursor < 0) - keys_cursor = NUMCOMMANDS-1; + do + { + keys_cursor--; + if (keys_cursor >= numbindnames) + { + if (keys_first && strcmp(bindnames[keys_first].cmd, "-")) + { //some weirdness, so the user can re-view any leading titles + keys_cursor = keys_first; + keys_first = 0; + } + else + keys_cursor = numbindnames-1; + break; + } + } while (!strcmp(bindnames[keys_cursor].cmd, "-")); break; case K_DOWNARROW: case K_RIGHTARROW: S_LocalSound ("misc/menu1.wav"); - keys_cursor++; - if (keys_cursor >= (int)NUMCOMMANDS) - keys_cursor = 0; + do + { + keys_cursor++; + if (keys_cursor >= numbindnames) + keys_cursor = keys_first = 0; + else if (keys_cursor == numbindnames-1) + break; + } while (!strcmp(bindnames[keys_cursor].cmd, "-")); break; case K_ENTER: // go into bind mode case K_KP_ENTER: case K_ABUTTON: - M_FindKeysForCommand (bindnames[keys_cursor][0], keys); + M_FindKeysForCommand (bindnames[keys_cursor].cmd, keys); S_LocalSound ("misc/menu2.wav"); if (keys[2] != -1) - M_UnbindCommand (bindnames[keys_cursor][0]); + M_UnbindCommand (bindnames[keys_cursor].cmd); bind_grab = true; IN_Activate(); // activate to allow mouse key binding break; @@ -1527,7 +1628,7 @@ void M_Keys_Key (int k) case K_BACKSPACE: // delete bindings case K_DEL: S_LocalSound ("misc/menu2.wav"); - M_UnbindCommand (bindnames[keys_cursor][0]); + M_UnbindCommand (bindnames[keys_cursor].cmd); break; } } @@ -1696,7 +1797,7 @@ void M_Quit_Draw (void) //johnfitz -- modified for new quit message m_state = m_quit; } - sprintf(msg1, "QuakeSpasm " QUAKESPASM_VER_STRING); + sprintf(msg1, ENGINE_NAME_AND_VER); //okay, this is kind of fucked up. M_DrawTextBox will always act as if //width is even. Also, the width and lines values are for the interior of the box, @@ -1715,8 +1816,8 @@ void M_Quit_Draw (void) //johnfitz -- modified for new quit message /* LAN CONFIG MENU */ int lanConfig_cursor = -1; -int lanConfig_cursor_table [] = {72, 92, 124}; -#define NUM_LANCONFIG_CMDS 3 +int lanConfig_cursor_table [] = {72, 92, 100, 132}; +#define NUM_LANCONFIG_CMDS 4 int lanConfig_port; char lanConfig_portname[6]; @@ -1735,7 +1836,7 @@ void M_Menu_LanConfig_f (void) else lanConfig_cursor = 1; } - if (StartingGame && lanConfig_cursor == 2) + if (StartingGame && lanConfig_cursor >= 2) lanConfig_cursor = 1; lanConfig_port = DEFAULTnet_hostport; sprintf(lanConfig_portname, "%u", lanConfig_port); @@ -1772,7 +1873,20 @@ void M_LanConfig_Draw (void) if (IPXConfig) M_Print (basex+9*8, 52, my_ipx_address); else - M_Print (basex+9*8, 52, my_tcpip_address); + { + if (ipv4Available && ipv6Available) + { + M_Print (basex+9*8, 52-4, my_ipv4_address); + M_Print (basex+9*8, 52+4, my_ipv6_address); + } + else + { + if (ipv4Available) + M_Print (basex+9*8, 52, my_ipv4_address); + if (ipv6Available) + M_Print (basex+9*8, 52, my_ipv6_address); + } + } M_Print (basex, lanConfig_cursor_table[0], "Port"); M_DrawTextBox (basex+8*8, lanConfig_cursor_table[0]-8, 6, 1); @@ -1781,9 +1895,10 @@ void M_LanConfig_Draw (void) if (JoiningGame) { M_Print (basex, lanConfig_cursor_table[1], "Search for local games..."); + M_Print (basex, lanConfig_cursor_table[2], "Search for public games..."); M_Print (basex, 108, "Join game at:"); - M_DrawTextBox (basex+8, lanConfig_cursor_table[2]-8, 22, 1); - M_Print (basex+16, lanConfig_cursor_table[2], lanConfig_joinname); + M_DrawTextBox (basex+8, lanConfig_cursor_table[3]-8, 22, 1); + M_Print (basex+16, lanConfig_cursor_table[3], lanConfig_joinname); } else { @@ -1796,8 +1911,8 @@ void M_LanConfig_Draw (void) if (lanConfig_cursor == 0) M_DrawCharacter (basex+9*8 + 8*strlen(lanConfig_portname), lanConfig_cursor_table [0], 10+((int)(realtime*4)&1)); - if (lanConfig_cursor == 2) - M_DrawCharacter (basex+16 + 8*strlen(lanConfig_joinname), lanConfig_cursor_table [2], 10+((int)(realtime*4)&1)); + if (lanConfig_cursor == 3) + M_DrawCharacter (basex+16 + 8*strlen(lanConfig_joinname), lanConfig_cursor_table [3], 10+((int)(realtime*4)&1)); if (*m_return_reason) M_PrintWhite (basex, 148, m_return_reason); @@ -1839,26 +1954,26 @@ void M_LanConfig_Key (int key) M_ConfigureNetSubsystem (); - if (lanConfig_cursor == 1) + if (StartingGame) { - if (StartingGame) - { + if (lanConfig_cursor == 1) M_Menu_GameOptions_f (); - break; - } - M_Menu_Search_f(); - break; } - - if (lanConfig_cursor == 2) + else { - m_return_state = m_state; - m_return_onerror = true; - IN_Activate(); - key_dest = key_game; - m_state = m_none; - Cbuf_AddText ( va ("connect \"%s\"\n", lanConfig_joinname) ); - break; + if (lanConfig_cursor == 1) + M_Menu_Search_f(SLIST_LAN); + else if (lanConfig_cursor == 2) + M_Menu_Search_f(SLIST_INTERNET); + else if (lanConfig_cursor == 3) + { + m_return_state = m_state; + m_return_onerror = true; + IN_Activate(); + key_dest = key_game; + m_state = m_none; + Cbuf_AddText ( va ("connect \"%s\"\n", lanConfig_joinname) ); + } } break; @@ -1870,7 +1985,7 @@ void M_LanConfig_Key (int key) lanConfig_portname[strlen(lanConfig_portname)-1] = 0; } - if (lanConfig_cursor == 2) + if (lanConfig_cursor == 3) { if (strlen(lanConfig_joinname)) lanConfig_joinname[strlen(lanConfig_joinname)-1] = 0; @@ -1878,7 +1993,7 @@ void M_LanConfig_Key (int key) break; } - if (StartingGame && lanConfig_cursor == 2) + if (StartingGame && lanConfig_cursor >= 2) { if (key == K_UPARROW) lanConfig_cursor = 1; @@ -1911,7 +2026,7 @@ void M_LanConfig_Char (int key) lanConfig_portname[l] = key; } break; - case 2: + case 3: l = strlen(lanConfig_joinname); if (l < 21) { @@ -1925,7 +2040,7 @@ void M_LanConfig_Char (int key) qboolean M_LanConfig_TextEntry (void) { - return (lanConfig_cursor == 0 || lanConfig_cursor == 2); + return (lanConfig_cursor == 0 || lanConfig_cursor == 3); } //============================================================================= @@ -2075,6 +2190,8 @@ episode_t rogueepisodes[] = {"Deathmatch Arena", 16, 1} }; +extern cvar_t sv_public; + int startepisode; int startlevel; int maxplayers; @@ -2094,32 +2211,43 @@ void M_Menu_GameOptions_f (void) } -int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 112, 120}; -#define NUM_GAMEOPTIONS 9 +int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 104, 120, 128}; +#define NUM_GAMEOPTIONS 10 int gameoptions_cursor; void M_GameOptions_Draw (void) { qpic_t *p; int x; + int y = 40; M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); p = Draw_CachePic ("gfx/p_multi.lmp"); M_DrawPic ( (320-p->width)/2, 4, p); - M_DrawTextBox (152, 32, 10, 1); - M_Print (160, 40, "begin game"); + M_DrawTextBox (152, y-8, 10, 1); + M_Print (160, y, "begin game"); + y+=16; - M_Print (0, 56, " Max players"); - M_Print (160, 56, va("%i", maxplayers) ); + M_Print (0, y, " Max players"); + M_Print (160, y, va("%i", maxplayers) ); + y+=8; - M_Print (0, 64, " Game Type"); - if (coop.value) - M_Print (160, 64, "Cooperative"); + M_Print (0, y, " Public"); + if (sv_public.value) + M_Print (160, y, "Yes"); else - M_Print (160, 64, "Deathmatch"); + M_Print (160, y, "No"); + y+=8; - M_Print (0, 72, " Teamplay"); + M_Print (0, y, " Game Type"); + if (coop.value) + M_Print (160, y, "Cooperative"); + else + M_Print (160, y, "Deathmatch"); + y+=8; + + M_Print (0, y, " Teamplay"); if (rogue) { const char *msg; @@ -2134,7 +2262,7 @@ void M_GameOptions_Draw (void) case 6: msg = "Three Team CTF"; break; default: msg = "Off"; break; } - M_Print (160, 72, msg); + M_Print (160, y, msg); } else { @@ -2146,59 +2274,67 @@ void M_GameOptions_Draw (void) case 2: msg = "Friendly Fire"; break; default: msg = "Off"; break; } - M_Print (160, 72, msg); + M_Print (160, y, msg); } + y+=8; - M_Print (0, 80, " Skill"); + M_Print (0, y, " Skill"); if (skill.value == 0) - M_Print (160, 80, "Easy difficulty"); + M_Print (160, y, "Easy difficulty"); else if (skill.value == 1) - M_Print (160, 80, "Normal difficulty"); + M_Print (160, y, "Normal difficulty"); else if (skill.value == 2) - M_Print (160, 80, "Hard difficulty"); + M_Print (160, y, "Hard difficulty"); else - M_Print (160, 80, "Nightmare difficulty"); + M_Print (160, y, "Nightmare difficulty"); + y+=8; - M_Print (0, 88, " Frag Limit"); + M_Print (0, y, " Frag Limit"); if (fraglimit.value == 0) - M_Print (160, 88, "none"); + M_Print (160, y, "none"); else - M_Print (160, 88, va("%i frags", (int)fraglimit.value)); + M_Print (160, y, va("%i frags", (int)fraglimit.value)); + y+=8; - M_Print (0, 96, " Time Limit"); + M_Print (0, y, " Time Limit"); if (timelimit.value == 0) - M_Print (160, 96, "none"); + M_Print (160, y, "none"); else - M_Print (160, 96, va("%i minutes", (int)timelimit.value)); + M_Print (160, y, va("%i minutes", (int)timelimit.value)); + y+=8; - M_Print (0, 112, " Episode"); + y+=8; + + M_Print (0, y, " Episode"); // MED 01/06/97 added hipnotic episodes if (hipnotic) - M_Print (160, 112, hipnoticepisodes[startepisode].description); + M_Print (160, y, hipnoticepisodes[startepisode].description); // PGM 01/07/97 added rogue episodes else if (rogue) - M_Print (160, 112, rogueepisodes[startepisode].description); + M_Print (160, y, rogueepisodes[startepisode].description); else - M_Print (160, 112, episodes[startepisode].description); + M_Print (160, y, episodes[startepisode].description); + y+=8; - M_Print (0, 120, " Level"); + M_Print (0, y, " Level"); // MED 01/06/97 added hipnotic episodes if (hipnotic) { - M_Print (160, 120, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].description); - M_Print (160, 128, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name); + M_Print (160, y, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].description); + M_Print (160, y+8, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name); } // PGM 01/07/97 added rogue episodes else if (rogue) { - M_Print (160, 120, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].description); - M_Print (160, 128, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name); + M_Print (160, y, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].description); + M_Print (160, y+8, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name); } else { - M_Print (160, 120, levels[episodes[startepisode].firstLevel + startlevel].description); - M_Print (160, 128, levels[episodes[startepisode].firstLevel + startlevel].name); + M_Print (160, y, levels[episodes[startepisode].firstLevel + startlevel].description); + M_Print (160, y+8, levels[episodes[startepisode].firstLevel + startlevel].name); } + y+=8; // line cursor M_DrawCharacter (144, gameoptions_cursor_table[gameoptions_cursor], 12+((int)(realtime*4)&1)); @@ -2243,10 +2379,14 @@ void M_NetStart_Change (int dir) break; case 2: - Cvar_Set ("coop", coop.value ? "0" : "1"); + Cvar_SetQuick (&sv_public, sv_public.value ? "0" : "1"); break; case 3: + Cvar_Set ("coop", coop.value ? "0" : "1"); + break; + + case 4: count = (rogue) ? 6 : 2; f = teamplay.value + dir; if (f > count) f = 0; @@ -2254,28 +2394,28 @@ void M_NetStart_Change (int dir) Cvar_SetValue ("teamplay", f); break; - case 4: + case 5: f = skill.value + dir; if (f > 3) f = 0; else if (f < 0) f = 3; Cvar_SetValue ("skill", f); break; - case 5: + case 6: f = fraglimit.value + dir * 10; if (f > 100) f = 0; else if (f < 0) f = 100; Cvar_SetValue ("fraglimit", f); break; - case 6: + case 7: f = timelimit.value + dir * 5; if (f > 60) f = 0; else if (f < 0) f = 60; Cvar_SetValue ("timelimit", f); break; - case 7: + case 8: startepisode += dir; //MED 01/06/97 added hipnotic count if (hipnotic) @@ -2298,7 +2438,7 @@ void M_NetStart_Change (int dir) startlevel = 0; break; - case 8: + case 9: startlevel += dir; //MED 01/06/97 added hipnotic episodes if (hipnotic) @@ -2387,15 +2527,16 @@ void M_GameOptions_Key (int key) qboolean searchComplete = false; double searchCompleteTime; +enum slistScope_e searchLastScope = SLIST_LAN; -void M_Menu_Search_f (void) +void M_Menu_Search_f (enum slistScope_e scope) { IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_search; m_entersound = false; slistSilent = true; - slistLocal = false; + slistScope = searchLastScope = scope; searchComplete = false; NET_Slist_f(); @@ -2446,7 +2587,8 @@ void M_Search_Key (int key) //============================================================================= /* SLIST MENU */ -int slist_cursor; +size_t slist_cursor; +size_t slist_first; qboolean slist_sorted; void M_Menu_ServerList_f (void) @@ -2456,6 +2598,7 @@ void M_Menu_ServerList_f (void) m_state = m_slist; m_entersound = true; slist_cursor = 0; + slist_first = 0; m_return_onerror = false; m_return_reason[0] = 0; slist_sorted = false; @@ -2464,7 +2607,7 @@ void M_Menu_ServerList_f (void) void M_ServerList_Draw (void) { - int n; + size_t n, slist_shown; qpic_t *p; if (!slist_sorted) @@ -2473,11 +2616,19 @@ void M_ServerList_Draw (void) NET_SlistSort (); } + slist_shown = hostCacheCount; + if (slist_shown > (200-32)/8) + slist_shown = (200-32)/8; + if (slist_first+slist_shown-1 < slist_cursor) + slist_first = slist_cursor-(slist_shown-1); + if (slist_first > slist_cursor) + slist_first = slist_cursor; + p = Draw_CachePic ("gfx/p_multi.lmp"); M_DrawPic ( (320-p->width)/2, 4, p); - for (n = 0; n < hostCacheCount; n++) - M_Print (16, 32 + 8*n, NET_SlistPrintServer (n)); - M_DrawCharacter (0, 32 + slist_cursor*8, 12+((int)(realtime*4)&1)); + for (n = 0; n < slist_shown; n++) + M_Print (16, 32 + 8*n, NET_SlistPrintServer (slist_first+n)); + M_DrawCharacter (0, 32 + (slist_cursor-slist_first)*8, 12+((int)(realtime*4)&1)); if (*m_return_reason) M_PrintWhite (16, 148, m_return_reason); @@ -2494,14 +2645,14 @@ void M_ServerList_Key (int k) break; case K_SPACE: - M_Menu_Search_f (); + M_Menu_Search_f (searchLastScope); break; case K_UPARROW: case K_LEFTARROW: S_LocalSound ("misc/menu1.wav"); slist_cursor--; - if (slist_cursor < 0) + if (slist_cursor >= hostCacheCount) slist_cursor = hostCacheCount - 1; break; diff --git a/Quake/net.h b/Quake/net.h index a0784905..ff2061f9 100644 --- a/Quake/net.h +++ b/Quake/net.h @@ -56,12 +56,20 @@ struct qsocket_s *NET_Connect (const char *host); // called by client to connect to a host. Returns -1 if not able to double NET_QSocketGetTime (const struct qsocket_s *sock); -const char *NET_QSocketGetAddressString (const struct qsocket_s *sock); +const char *NET_QSocketGetTrueAddressString (const struct qsocket_s *sock); +const char *NET_QSocketGetMaskedAddressString (const struct qsocket_s *sock); +qboolean NET_QSocketGetProQuakeAngleHack (const struct qsocket_s *sock); +int NET_QSocketGetSequenceIn (const struct qsocket_s *sock); +int NET_QSocketGetSequenceOut (const struct qsocket_s *sock); +void NET_QSocketSetMSS(struct qsocket_s *s, int mss); qboolean NET_CanSendMessage (struct qsocket_s *sock); // Returns true or false if the given qsocket can currently accept a // message to be transmitted. +struct qsocket_s *NET_GetServerMessage(void); +//returns data in net_message, qsocket says which client its from + int NET_GetMessage (struct qsocket_s *sock); // returns data in net_message sizebuf // returns 0 if no data is waiting @@ -94,22 +102,29 @@ void NET_Poll (void); // Server list related globals: extern qboolean slistInProgress; extern qboolean slistSilent; -extern qboolean slistLocal; +extern enum slistScope_e +{ + SLIST_LOOP, + SLIST_LAN, + SLIST_INTERNET +} slistScope; -extern int hostCacheCount; +extern size_t hostCacheCount; void NET_Slist_f (void); void NET_SlistSort (void); -const char *NET_SlistPrintServer (int n); -const char *NET_SlistPrintServerName (int n); +const char *NET_SlistPrintServer (size_t n); +const char *NET_SlistPrintServerName (size_t n); /* FIXME: driver related, but public: */ extern qboolean ipxAvailable; -extern qboolean tcpipAvailable; +extern qboolean ipv4Available; +extern qboolean ipv6Available; extern char my_ipx_address[NET_NAMELEN]; -extern char my_tcpip_address[NET_NAMELEN]; +extern char my_ipv4_address[NET_NAMELEN]; +extern char my_ipv6_address[NET_NAMELEN]; #endif /* _QUAKE_NET_H */ diff --git a/Quake/net_bsd.c b/Quake/net_bsd.c index 59487e7e..a0b7c37a 100644 --- a/Quake/net_bsd.c +++ b/Quake/net_bsd.c @@ -37,6 +37,7 @@ net_driver_t net_drivers[] = Loop_SearchForHosts, Loop_Connect, Loop_CheckNewConnections, + Loop_GetAnyMessage, Loop_GetMessage, Loop_SendMessage, Loop_SendUnreliableMessage, @@ -53,6 +54,7 @@ net_driver_t net_drivers[] = Datagram_SearchForHosts, Datagram_Connect, Datagram_CheckNewConnections, + Datagram_GetAnyMessage, Datagram_GetMessage, Datagram_SendMessage, Datagram_SendUnreliableMessage, @@ -72,21 +74,43 @@ net_landriver_t net_landrivers[] = { "UDP", false, 0, - UDP_Init, - UDP_Shutdown, - UDP_Listen, - UDP_OpenSocket, + UDP4_Init, + UDP4_Shutdown, + UDP4_Listen, + UDP4_OpenSocket, UDP_CloseSocket, UDP_Connect, - UDP_CheckNewConnections, + UDP4_CheckNewConnections, UDP_Read, UDP_Write, - UDP_Broadcast, + UDP4_Broadcast, UDP_AddrToString, - UDP_StringToAddr, + UDP4_StringToAddr, UDP_GetSocketAddr, UDP_GetNameFromAddr, - UDP_GetAddrFromName, + UDP4_GetAddrFromName, + UDP_AddrCompare, + UDP_GetSocketPort, + UDP_SetSocketPort + }, + { "UDP6", + false, + 0, + UDP6_Init, + UDP6_Shutdown, + UDP6_Listen, + UDP6_OpenSocket, + UDP_CloseSocket, + UDP_Connect, + UDP6_CheckNewConnections, + UDP_Read, + UDP_Write, + UDP6_Broadcast, + UDP_AddrToString, + UDP6_StringToAddr, + UDP_GetSocketAddr, + UDP_GetNameFromAddr, + UDP6_GetAddrFromName, UDP_AddrCompare, UDP_GetSocketPort, UDP_SetSocketPort diff --git a/Quake/net_defs.h b/Quake/net_defs.h index 424b85dd..d18e97b0 100644 --- a/Quake/net_defs.h +++ b/Quake/net_defs.h @@ -32,7 +32,7 @@ struct qsockaddr #else short qsa_family; #endif /* BSD, sockaddr */ - unsigned char qsa_data[14]; + unsigned char qsa_data[62]; }; #define NET_HEADERSIZE (2 * sizeof(unsigned int)) @@ -120,12 +120,14 @@ CCREP_RULE_INFO #define CCREQ_SERVER_INFO 0x02 #define CCREQ_PLAYER_INFO 0x03 #define CCREQ_RULE_INFO 0x04 +#define CCREQ_RCON 0x05 #define CCREP_ACCEPT 0x81 #define CCREP_REJECT 0x82 #define CCREP_SERVER_INFO 0x83 #define CCREP_PLAYER_INFO 0x84 #define CCREP_RULE_INFO 0x85 +#define CCREP_RCON 0x86 typedef struct qsocket_s { @@ -134,6 +136,7 @@ typedef struct qsocket_s double lastMessageTime; double lastSendTime; + qboolean isvirtual; //qsocket is emulated by the network layer (closing will not close any system sockets). qboolean disconnected; qboolean canSend; qboolean sendNext; @@ -155,8 +158,12 @@ typedef struct qsocket_s byte receiveMessage [NET_MAXMESSAGE]; struct qsockaddr addr; - char address[NET_NAMELEN]; + char trueaddress[NET_NAMELEN]; //lazy address string + char maskedaddress[NET_NAMELEN]; //addresses for this player that may be displayed publically + qboolean proquake_angle_hack; //1 if we're trying, 2 if the server acked. + int max_datagram; //32000 for local, 1442 for 666, 1024 for 15. this is for reliable fragments. + int pending_max_datagram; //don't change the mtu if we're resending, as that would confuse the peer. } qsocket_t; extern qsocket_t *net_activeSockets; @@ -170,7 +177,7 @@ typedef struct sys_socket_t controlSock; sys_socket_t (*Init) (void); void (*Shutdown) (void); - void (*Listen) (qboolean state); + sys_socket_t (*Listen) (qboolean state); sys_socket_t (*Open_Socket) (int port); int (*Close_Socket) (sys_socket_t socketid); int (*Connect) (sys_socket_t socketid, struct qsockaddr *addr); @@ -178,7 +185,7 @@ typedef struct int (*Read) (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); int (*Write) (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); int (*Broadcast) (sys_socket_t socketid, byte *buf, int len); - const char * (*AddrToString) (struct qsockaddr *addr); + const char * (*AddrToString) (struct qsockaddr *addr, qboolean masked); int (*StringToAddr) (const char *string, struct qsockaddr *addr); int (*GetSocketAddr) (sys_socket_t socketid, struct qsockaddr *addr); int (*GetNameFromAddr) (struct qsockaddr *addr, char *name); @@ -186,6 +193,8 @@ typedef struct int (*AddrCompare) (struct qsockaddr *addr1, struct qsockaddr *addr2); int (*GetSocketPort) (struct qsockaddr *addr); int (*SetSocketPort) (struct qsockaddr *addr, int port); + + sys_socket_t listeningSock; } net_landriver_t; #define MAX_NET_DRIVERS 8 @@ -198,9 +207,10 @@ typedef struct qboolean initialized; int (*Init) (void); void (*Listen) (qboolean state); - void (*SearchForHosts) (qboolean xmit); + qboolean (*SearchForHosts) (qboolean xmit); qsocket_t *(*Connect) (const char *host); qsocket_t *(*CheckNewConnections) (void); + qsocket_t *(*QGetAnyMessage) (void); int (*QGetMessage) (qsocket_t *sock); int (*QSendMessage) (qsocket_t *sock, sizebuf_t *data); int (*SendUnreliableMessage) (qsocket_t *sock, sizebuf_t *data); @@ -228,13 +238,13 @@ void NET_FreeQSocket(qsocket_t *); double SetNetTime(void); -#define HOSTCACHESIZE 8 +#define HOSTCACHESIZE 128 //fixme: make dynamic. typedef struct { char name[16]; char map[16]; - char cname[32]; + char cname[NET_NAMELEN]; int users; int maxusers; int driver; @@ -242,7 +252,7 @@ typedef struct struct qsockaddr addr; } hostcache_t; -extern int hostCacheCount; +extern size_t hostCacheCount; extern hostcache_t hostcache[HOSTCACHESIZE]; diff --git a/Quake/net_dgrm.c b/Quake/net_dgrm.c index 4a4b4f7b..84c7e31f 100644 --- a/Quake/net_dgrm.c +++ b/Quake/net_dgrm.c @@ -43,6 +43,28 @@ static int receivedDuplicateCount = 0; static int shortPacketCount = 0; static int droppedDatagrams; +//cvars controlling dpmaster support: +//our servers might as well claim to be 'FTE-Quake' servers. this means FTE can see us, we can see FTE (when its pretending to be nq). +//we additionally look for 'DarkPlaces-Quake' servers too, because we can, but most of those servers will be using dpp7 and will (safely) not respond to our ccreq_server_info requests. +//we are not visible to DarkPlaces users - dp does not support fitz666 so that's not a viable option, at least by default, feel free to switch the order if you also change sv_protocol back to 15. +cvar_t sv_reportheartbeats = {"sv_reportheartbeats", "0"}; +cvar_t sv_public = {"sv_public", NULL}; +cvar_t com_protocolname = {"com_protocolname", "FTE-Quake DarkPlaces-Quake"}; +cvar_t net_masters[] = +{ + {"net_master1", ""}, + {"net_master2", ""}, + {"net_master3", ""}, + {"net_master4", ""}, + {"net_masterextra1", "ghdigital.com:27950"}, + {"net_masterextra2", "dpmaster.deathmask.net:27950"}, + {"net_masterextra3", "dpmaster.tchr.no:27950"}, + {NULL} +}; +cvar_t rcon_password = {"rcon_password", ""}; +extern cvar_t net_messagetimeout; +extern cvar_t net_connecttimeout; + static struct { unsigned int length; @@ -54,6 +76,7 @@ static int myDriverLevel; extern qboolean m_return_onerror; extern char m_return_reason[32]; +static double heartbeat_time; //when this is reached, send a heartbeat to all masters. static char *StrAddr (struct qsockaddr *addr) @@ -149,14 +172,16 @@ int Datagram_SendMessage (qsocket_t *sock, sizebuf_t *data) Q_memcpy(sock->sendMessage, data->data, data->cursize); sock->sendMessageLength = data->cursize; - if (data->cursize <= MAX_DATAGRAM) + sock->max_datagram = sock->pending_max_datagram; //this can apply only at the start of a reliable, to avoid issues with acks if its resized later. + + if (data->cursize <= sock->max_datagram) { dataLen = data->cursize; eom = NETFLAG_EOM; } else { - dataLen = MAX_DATAGRAM; + dataLen = sock->max_datagram; eom = 0; } packetLen = NET_HEADERSIZE + dataLen; @@ -182,14 +207,14 @@ static int SendMessageNext (qsocket_t *sock) unsigned int dataLen; unsigned int eom; - if (sock->sendMessageLength <= MAX_DATAGRAM) + if (sock->sendMessageLength <= sock->max_datagram) { dataLen = sock->sendMessageLength; eom = NETFLAG_EOM; } else { - dataLen = MAX_DATAGRAM; + dataLen = sock->max_datagram; eom = 0; } packetLen = NET_HEADERSIZE + dataLen; @@ -215,14 +240,14 @@ static int ReSendMessage (qsocket_t *sock) unsigned int dataLen; unsigned int eom; - if (sock->sendMessageLength <= MAX_DATAGRAM) + if (sock->sendMessageLength <= sock->max_datagram) { dataLen = sock->sendMessageLength; eom = NETFLAG_EOM; } else { - dataLen = MAX_DATAGRAM; + dataLen = sock->max_datagram; eom = 0; } packetLen = NET_HEADERSIZE + dataLen; @@ -282,6 +307,219 @@ int Datagram_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) return 1; } +static void _Datagram_ServerControlPacket (sys_socket_t acceptsock, struct qsockaddr *clientaddr, byte *data, unsigned int length); +qboolean Datagram_ProcessPacket(unsigned int length, qsocket_t *sock) +{ + unsigned int flags; + unsigned int sequence; + unsigned int count; + + if (length < NET_HEADERSIZE) + { + shortPacketCount++; + return false; + } + + length = BigLong(packetBuffer.length); + flags = length & (~NETFLAG_LENGTH_MASK); + length &= NETFLAG_LENGTH_MASK; + + if (flags & NETFLAG_CTL) + return false; //should only be for OOB packets. + + sequence = BigLong(packetBuffer.sequence); + packetsReceived++; + + if (flags & NETFLAG_UNRELIABLE) + { + if (sequence < sock->unreliableReceiveSequence) + { + Con_DPrintf("Got a stale datagram\n"); + return false; + } + if (sequence != sock->unreliableReceiveSequence) + { + count = sequence - sock->unreliableReceiveSequence; + droppedDatagrams += count; + Con_DPrintf("Dropped %u datagram(s)\n", count); + } + sock->unreliableReceiveSequence = sequence + 1; + + length -= NET_HEADERSIZE; + + if (length > (unsigned int)net_message.maxsize) + { //is this even possible? maybe it will be in the future! either way, no sys_errors please. + Con_Printf("Over-sized unreliable\n"); + return -1; + } + SZ_Clear (&net_message); + SZ_Write (&net_message, packetBuffer.data, length); + + unreliableMessagesReceived++; + return true; //parse the unreliable + } + + if (flags & NETFLAG_ACK) + { + if (sequence != (sock->sendSequence - 1)) + { + Con_DPrintf("Stale ACK received\n"); + return false; + } + if (sequence == sock->ackSequence) + { + sock->ackSequence++; + if (sock->ackSequence != sock->sendSequence) + Con_DPrintf("ack sequencing error\n"); + } + else + { + Con_DPrintf("Duplicate ACK received\n"); + return false; + } + sock->sendMessageLength -= sock->max_datagram; + if (sock->sendMessageLength > 0) + { + memmove (sock->sendMessage, sock->sendMessage + sock->max_datagram, sock->sendMessageLength); + sock->sendNext = true; + } + else + { + sock->sendMessageLength = 0; + sock->canSend = true; + } + return false; + } + + if (flags & NETFLAG_DATA) + { + packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK); + packetBuffer.sequence = BigLong(sequence); + sfunc.Write (sock->socket, (byte *)&packetBuffer, NET_HEADERSIZE, &sock->addr); + + if (sequence != sock->receiveSequence) + { + receivedDuplicateCount++; + return false; + } + sock->receiveSequence++; + + length -= NET_HEADERSIZE; + + if (flags & NETFLAG_EOM) + { + if (sock->receiveMessageLength + length > (unsigned int)net_message.maxsize) + { + Con_Printf("Over-sized reliable\n"); + return -1; + } + SZ_Clear(&net_message); + SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength); + SZ_Write(&net_message, packetBuffer.data, length); + sock->receiveMessageLength = 0; + + messagesReceived++; + return true; //parse this reliable! + } + + if (sock->receiveMessageLength + length > sizeof(sock->receiveMessage)) + { + Con_Printf("Over-sized reliable\n"); + return -1; + } + Q_memcpy(sock->receiveMessage + sock->receiveMessageLength, packetBuffer.data, length); + sock->receiveMessageLength += length; + return false; //still watiting for the eom + } + //unknown flags + Con_DPrintf("Unknown packet flags\n"); + return false; +} + +qsocket_t *Datagram_GetAnyMessage(void) +{ + qsocket_t *s; + struct qsockaddr addr; + int length; + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + sys_socket_t sock; + if (!dfunc.initialized) + continue; + sock = dfunc.listeningSock; + if (sock == INVALID_SOCKET) + continue; + + while(1) + { + length = dfunc.Read(sock, (byte *)&packetBuffer, NET_DATAGRAMSIZE, &addr); + if (length == -1 || !length) + { + //no more packets, move on to the next. + break; + } + + if (length < 4) + continue; + if (BigLong(packetBuffer.length) & NETFLAG_CTL) + { + _Datagram_ServerControlPacket(sock, &addr, (byte *)&packetBuffer, length); + continue; + } + + //figure out which qsocket it was for + for (s = net_activeSockets; s; s = s->next) + { + if (s->driver != net_driverlevel) + continue; + if (s->disconnected) + continue; + if (!s->isvirtual) + continue; + if (dfunc.AddrCompare(&addr, &s->addr) == 0) + { + //okay, looks like this is us. try to process it, and if there's new data + if (Datagram_ProcessPacket(length, s)) + { + s->lastMessageTime = net_time; + return s; //the server needs to parse that packet. + } + } + } + //stray packet... ignore it and just try the next + } + } + for (s = net_activeSockets; s; s = s->next) + { + if (s->driver != net_driverlevel) + continue; + if (!s->isvirtual) + continue; + + if (!s->canSend) + if ((net_time - s->lastSendTime) > 1.0) + ReSendMessage (s); + if (s->sendNext) + SendMessageNext (s); + + if (net_time - s->lastMessageTime > ((!s->ackSequence)?net_connecttimeout.value:net_messagetimeout.value)) + { //timed out, kick them + //FIXME: add a proper challenge rather than assuming spoofers won't fake acks + int i; + for (i = 0; i < svs.maxclients; i++) + { + if (svs.clients[i].netconnection == s) + { + host_client = &svs.clients[i]; + SV_DropClient(false); + break; + } + } + } + } + + return NULL; +} int Datagram_GetMessage (qsocket_t *sock) { @@ -380,10 +618,10 @@ int Datagram_GetMessage (qsocket_t *sock) Con_DPrintf("Duplicate ACK received\n"); continue; } - sock->sendMessageLength -= MAX_DATAGRAM; + sock->sendMessageLength -= sock->max_datagram; if (sock->sendMessageLength > 0) { - memmove (sock->sendMessage, sock->sendMessage + MAX_DATAGRAM, sock->sendMessageLength); + memmove (sock->sendMessage, sock->sendMessage + sock->max_datagram, sock->sendMessageLength); sock->sendNext = true; } else @@ -411,6 +649,11 @@ int Datagram_GetMessage (qsocket_t *sock) if (flags & NETFLAG_EOM) { + if (sock->receiveMessageLength + length > (unsigned int)net_message.maxsize) + { + Con_Printf("Over-sized reliable\n"); + return -1; + } SZ_Clear(&net_message); SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength); SZ_Write(&net_message, packetBuffer.data, length); @@ -420,6 +663,11 @@ int Datagram_GetMessage (qsocket_t *sock) break; } + if (sock->receiveMessageLength + length > sizeof(sock->receiveMessage)) + { + Con_Printf("Over-sized reliable\n"); + return -1; + } Q_memcpy(sock->receiveMessage + sock->receiveMessageLength, packetBuffer.data, length); sock->receiveMessageLength += length; continue; @@ -469,7 +717,7 @@ static void NET_Stats_f (void) { for (s = net_activeSockets; s; s = s->next) { - if (q_strcasecmp(Cmd_Argv(1), s->address) == 0) + if (q_strcasecmp(Cmd_Argv(1), s->trueaddress) == 0 || q_strcasecmp(Cmd_Argv(1), s->maskedaddress) == 0) break; } @@ -477,7 +725,7 @@ static void NET_Stats_f (void) { for (s = net_freeSockets; s; s = s->next) { - if (q_strcasecmp(Cmd_Argv(1), s->address) == 0) + if (q_strcasecmp(Cmd_Argv(1), s->trueaddress) == 0 || q_strcasecmp(Cmd_Argv(1), s->maskedaddress) == 0) break; } } @@ -503,6 +751,8 @@ static const char *Strip_Port (const char *host) q_strlcpy (noport, host, sizeof(noport)); if ((p = Q_strrchr(noport, ':')) == NULL) return host; + if (strchr(p, ']')) + return host; //[::] should not be considered port 0 *p++ = '\0'; port = Q_atoi (p); if (port > 0 && port < 65536 && port != net_hostport) @@ -581,8 +831,8 @@ static void Test_Poll (void *unused) static void Test_f (void) { const char *host; - int n; - int maxusers = MAX_SCOREBOARD; + size_t n; + size_t maxusers = MAX_SCOREBOARD; struct qsockaddr sendaddr; if (testInProgress) @@ -716,7 +966,7 @@ Done: static void Test2_f (void) { const char *host; - int n; + size_t n; struct qsockaddr sendaddr; if (test2InProgress) @@ -802,6 +1052,7 @@ int Datagram_Init (void) continue; net_landrivers[i].initialized = true; net_landrivers[i].controlSock = csock; + net_landrivers[i].listeningSock = INVALID_SOCKET; num_inited++; } @@ -809,7 +1060,7 @@ int Datagram_Init (void) return -1; #ifdef BAN_TEST - Cmd_AddCommand ("ban", NET_Ban_f); + Cmd_AddCommand_ClientCommand ("ban", NET_Ban_f); #endif Cmd_AddCommand ("test", Test_f); Cmd_AddCommand ("test2", Test2_f); @@ -822,6 +1073,8 @@ void Datagram_Shutdown (void) { int i; + Datagram_Listen(false); + // // shutdown the lan drivers // @@ -838,77 +1091,195 @@ void Datagram_Shutdown (void) void Datagram_Close (qsocket_t *sock) { - sfunc.Close_Socket(sock->socket); + if (sock->isvirtual) + { + sock->isvirtual = false; + sock->socket = INVALID_SOCKET; + } + else + sfunc.Close_Socket(sock->socket); } void Datagram_Listen (qboolean state) { + qsocket_t *s; int i; + qboolean islistening = false; + + heartbeat_time = 0; //reset it for (i = 0; i < net_numlandrivers; i++) { if (net_landrivers[i].initialized) - net_landrivers[i].Listen (state); + { + net_landrivers[i].listeningSock = net_landrivers[i].Listen (state); + if (net_landrivers[i].listeningSock != INVALID_SOCKET) + islistening = true; + + for (s = net_activeSockets; s; s = s->next) + { + if (s->isvirtual) + { + s->isvirtual = false; + s->socket = INVALID_SOCKET; + } + } + } + } + if (state && !islistening) + { + if (isDedicated) + Sys_Error("Unable to open any listening sockets\n"); + Con_Warning("Unable to open any listening sockets\n"); } } - -static qsocket_t *_Datagram_CheckNewConnections (void) +static struct qsockaddr rcon_response_address; +static sys_socket_t rcon_response_socket; +static sys_socket_t rcon_response_landriver; +void Datagram_Rcon_Flush(const char *text) +{ + sizebuf_t msg; + byte buffer[8192]; + msg.data = buffer; + msg.maxsize = sizeof(buffer); + msg.allowoverflow = true; + SZ_Clear(&msg); + // save space for the header, filled in later + MSG_WriteLong(&msg, 0); + MSG_WriteByte(&msg, CCREP_RCON); + MSG_WriteString(&msg, text); + if (msg.overflowed) + return; + *((int *)msg.data) = BigLong(NETFLAG_CTL | (msg.cursize & NETFLAG_LENGTH_MASK)); + net_landrivers[rcon_response_landriver].Write (rcon_response_socket, msg.data, msg.cursize, &rcon_response_address); +} + +static void _Datagram_ServerControlPacket (sys_socket_t acceptsock, struct qsockaddr *clientaddr, byte *data, unsigned int length) { - struct qsockaddr clientaddr; struct qsockaddr newaddr; - sys_socket_t newsock; - sys_socket_t acceptsock; qsocket_t *sock; qsocket_t *s; - int len; int command; int control; int ret; + int plnum; + int mod;//, mod_ver, mod_flags, mod_passwd; //proquake extensions - acceptsock = dfunc.CheckNewConnections(); - if (acceptsock == INVALID_SOCKET) - return NULL; + control = BigLong(*((int *)data)); + if (control == -1) + { + if (!sv_public.value) + return; + data[length] = 0; + Cmd_TokenizeString((char*)data+4); + if (!strcmp(Cmd_Argv(0), "getinfo") || !strcmp(Cmd_Argv(0), "getstatus")) + { //master, as well as other clients, may send us one of these two packets to get our serverinfo data + //masters only really need gamename and player counts. actual clients might want player names too. + qboolean full = !strcmp(Cmd_Argv(0), "getstatus"); + char cookie[128]; + const char *s = Cmd_Args(); + const char *gamedir = COM_GetGameNames(false); + unsigned int numclients = 0, numbots = 0; + int i; + size_t j; + if (!s) s = ""; + q_strlcpy(cookie, s, sizeof(cookie)); + for (i = 0; i < svs.maxclients; i++) + { + if (svs.clients[i].active) + { + numclients++; + if (!svs.clients[i].netconnection) + numbots++; + } + } + + SZ_Clear(&net_message); + MSG_WriteLong(&net_message, -1); + MSG_WriteString(&net_message, full?"statusResponse":"infoResponse\n");net_message.cursize--; + COM_Parse(com_protocolname.string); + if (*com_token) //the master server needs this. This tells the master which game we should be listed as. + {MSG_WriteString(&net_message, va("\\gamename\\%s", com_token));net_message.cursize--;} + MSG_WriteString(&net_message, "\\protocol\\3");net_message.cursize--; //this is stupid + MSG_WriteString(&net_message, "\\ver\\"ENGINE_NAME_AND_VER);net_message.cursize--; + MSG_WriteString(&net_message, va("\\nqprotocol\\%u", sv.protocol));net_message.cursize--; + if (*gamedir) + {MSG_WriteString(&net_message, va("\\modname\\%s", gamedir));net_message.cursize--;} + if (*sv.name) + {MSG_WriteString(&net_message, va("\\mapname\\%s", sv.name));net_message.cursize--;} + if (*deathmatch.string) + {MSG_WriteString(&net_message, va("\\deathmatch\\%s", deathmatch.string));net_message.cursize--;} + if (*teamplay.string) + {MSG_WriteString(&net_message, va("\\teamplay\\%s", teamplay.string));net_message.cursize--;} + if (*hostname.string) + {MSG_WriteString(&net_message, va("\\hostname\\%s", hostname.string));net_message.cursize--;} + MSG_WriteString(&net_message, va("\\clients\\%u", numclients));net_message.cursize--; + if (numbots) + {MSG_WriteString(&net_message, va("\\bots\\%u", numbots));net_message.cursize--;} + MSG_WriteString(&net_message, va("\\sv_maxclients\\%i", svs.maxclients));net_message.cursize--; + if (*cookie) + {MSG_WriteString(&net_message, va("\\challenge\\%s", cookie));net_message.cursize--;} + + if (full) + { + for (i = 0; i < svs.maxclients; i++) + { + if (svs.clients[i].active) + { + float total = 0; + for (j = 0; j < NUM_PING_TIMES; j++) + total+=svs.clients[i].ping_times[j]; + total /= NUM_PING_TIMES; + total *= 1000; //put it in ms + + MSG_WriteString(&net_message, va("\n%i %i %i_%i \"%s\"", + svs.clients[i].old_frags, (int)total, svs.clients[i].colors&15, svs.clients[i].colors>>4, svs.clients[i].name + ));net_message.cursize--; + } + } + } + + dfunc.Write (acceptsock, net_message.data, net_message.cursize, clientaddr); + SZ_Clear(&net_message); + } + return; + } + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + return; + if ((control & NETFLAG_LENGTH_MASK) != length) + return; + + //sigh... FIXME: potentially abusive memcpy SZ_Clear(&net_message); - - len = dfunc.Read (acceptsock, net_message.data, net_message.maxsize, &clientaddr); - if (len < (int) sizeof(int)) - return NULL; - net_message.cursize = len; + SZ_Write(&net_message, data, length); MSG_BeginReading (); - control = BigLong(*((int *)net_message.data)); MSG_ReadLong(); - if (control == -1) - return NULL; - if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) - return NULL; - if ((control & NETFLAG_LENGTH_MASK) != len) - return NULL; command = MSG_ReadByte(); if (command == CCREQ_SERVER_INFO) { if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) - return NULL; + return; SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREP_SERVER_INFO); dfunc.GetSocketAddr(acceptsock, &newaddr); - MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr)); + MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr, false)); MSG_WriteString(&net_message, hostname.string); MSG_WriteString(&net_message, sv.name); MSG_WriteByte(&net_message, net_activeconnections); MSG_WriteByte(&net_message, svs.maxclients); MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); - dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, clientaddr); SZ_Clear(&net_message); - return NULL; + return; } if (command == CCREQ_PLAYER_INFO) @@ -932,7 +1303,7 @@ static qsocket_t *_Datagram_CheckNewConnections (void) } if (clientNumber == svs.maxclients) - return NULL; + return; SZ_Clear(&net_message); // save space for the header, filled in later @@ -942,13 +1313,21 @@ static qsocket_t *_Datagram_CheckNewConnections (void) MSG_WriteString(&net_message, client->name); MSG_WriteLong(&net_message, client->colors); MSG_WriteLong(&net_message, (int)client->edict->v.frags); - MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime)); - MSG_WriteString(&net_message, client->netconnection->address); + if (!client->netconnection) + { + MSG_WriteLong(&net_message, 0); + MSG_WriteString(&net_message, "Bot"); + } + else + { + MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime)); + MSG_WriteString(&net_message, NET_QSocketGetMaskedAddressString(client->netconnection)); + } *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); - dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, clientaddr); SZ_Clear(&net_message); - return NULL; + return; } if (command == CCREQ_RULE_INFO) @@ -971,17 +1350,46 @@ static qsocket_t *_Datagram_CheckNewConnections (void) MSG_WriteString(&net_message, var->string); } *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); - dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, clientaddr); SZ_Clear(&net_message); - return NULL; + return; + } + + if (command == CCREQ_RCON) + { + const char *password = MSG_ReadString(); //FIXME: this really needs crypto + const char *response; + + rcon_response_address = *clientaddr; + rcon_response_socket = acceptsock; + rcon_response_landriver = net_landriverlevel; + + if (!*rcon_password.string) + response = "rcon is not enabled on this server"; + else if (!strcmp(password, rcon_password.string)) + { + Con_Redirect(Datagram_Rcon_Flush); + Cmd_ExecuteString(MSG_ReadString(), src_command); + Con_Redirect(NULL); + return; + } + else if (!strcmp(password, "password")) + response = "What, you really thought that would work? Seriously?"; + else if (!strcmp(password, "thebackdoor")) + response = "Oh look! You found the backdoor. Don't let it slam you in the face on your way out."; + else + response = "Your password is just WRONG dude."; + + Datagram_Rcon_Flush(response); + return; } if (command != CCREQ_CONNECT) - return NULL; + return; if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) - return NULL; + return; if (MSG_ReadByte() != NET_PROTOCOL_VERSION) { @@ -991,17 +1399,34 @@ static qsocket_t *_Datagram_CheckNewConnections (void) MSG_WriteByte(&net_message, CCREP_REJECT); MSG_WriteString(&net_message, "Incompatible version.\n"); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); - dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, clientaddr); SZ_Clear(&net_message); - return NULL; + return; } + //read proquake extensions + mod = MSG_ReadByte(); + if (msg_badread) mod = 0; +#if 0 + mod_ver = MSG_ReadByte(); + if (msg_badread) mod_ver = 0; + mod_flags = MSG_ReadByte(); + if (msg_badread) mod_flags = 0; + mod_passwd = MSG_ReadLong(); + if (msg_badread) mod_passwd = 0; + (void)mod_ver; + (void)mod_flags; + (void)mod_passwd; +#endif + #ifdef BAN_TEST // check for a ban - if (clientaddr.qsa_family == AF_INET) + //fixme: no ipv6 + //fixme: only a single address? someone seriously underestimates tor. + if (clientaddr->qsa_family == AF_INET) { in_addr_t testAddr; - testAddr = ((struct sockaddr_in *)&clientaddr)->sin_addr.s_addr; + testAddr = ((struct sockaddr_in *)clientaddr)->sin_addr.s_addr; if ((testAddr & banMask.s_addr) == banAddr.s_addr) { SZ_Clear(&net_message); @@ -1010,9 +1435,9 @@ static qsocket_t *_Datagram_CheckNewConnections (void) MSG_WriteByte(&net_message, CCREP_REJECT); MSG_WriteString(&net_message, "You have been banned.\n"); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); - dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, clientaddr); SZ_Clear(&net_message); - return NULL; + return; } } #endif @@ -1022,9 +1447,13 @@ static qsocket_t *_Datagram_CheckNewConnections (void) { if (s->driver != net_driverlevel) continue; - ret = dfunc.AddrCompare(&clientaddr, &s->addr); - if (ret >= 0) + if (s->disconnected) + continue; + ret = dfunc.AddrCompare(clientaddr, &s->addr); + if (ret == 0) { + int i; + // is this a duplicate connection reqeust? if (ret == 0 && net_time - s->connecttime < 2.0) { @@ -1035,20 +1464,50 @@ static qsocket_t *_Datagram_CheckNewConnections (void) MSG_WriteByte(&net_message, CCREP_ACCEPT); dfunc.GetSocketAddr(s->socket, &newaddr); MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr)); + if (s->proquake_angle_hack) + { + MSG_WriteByte(&net_message, 1); //proquake + MSG_WriteByte(&net_message, 30);//ver 30 should be safe. 34 screws with our single-server-socket stuff. + MSG_WriteByte(&net_message, 0); //no flags + } *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); - dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, clientaddr); SZ_Clear(&net_message); - return NULL; + return; } // it's somebody coming back in from a crash/disconnect // so close the old qsocket and let their retry get them back in - NET_Close(s); - return NULL; +// NET_Close(s); +// return; + + //FIXME: ideally we would just switch the connection over and restart it with a serverinfo packet. + //warning: there might be packets in-flight which might mess up unreliable sequences. + //so we attempt to ignore the request, and let the user restart. + //FIXME: if this is an issue, it should be possible to reuse the previous connection's outgoing unreliable sequence. reliables should be less of an issue as stray ones will be ignored anyway. + //FIXME: needs challenges, so that other clients can't determine ip's and spoof a reconnect. + for (i = 0; i < svs.maxclients; i++) + { + if (svs.clients[i].netconnection == s) + { + NET_Close(s); //close early, to avoid svc_disconnects confusing things. + host_client = &svs.clients[i]; + SV_DropClient(false); + break; + } + } + return; } } - // allocate a QSocket - sock = NET_NewQSocket (); + //find a free player slot + for (plnum=0 ; plnumproquake_angle_hack = (mod == 1); // everything is allocated, just fill in the details - sock->socket = newsock; + sock->isvirtual = true; + sock->socket = acceptsock; sock->landriver = net_landriverlevel; - sock->addr = clientaddr; - Q_strcpy(sock->address, dfunc.AddrToString(&clientaddr)); + sock->addr = *clientaddr; + Q_strcpy(sock->trueaddress, dfunc.AddrToString(clientaddr, false)); + Q_strcpy(sock->maskedaddress, dfunc.AddrToString(clientaddr, true)); // send him back the info about the server connection he has been allocated SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREP_ACCEPT); - dfunc.GetSocketAddr(newsock, &newaddr); + dfunc.GetSocketAddr(sock->socket, &newaddr); MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr)); // MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr)); + if (sock->proquake_angle_hack) + { + MSG_WriteByte(&net_message, 1); //proquake + MSG_WriteByte(&net_message, 30);//ver 30 should be safe. 34 screws with our single-server-socket stuff. + MSG_WriteByte(&net_message, 0); + } *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); - dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, clientaddr); SZ_Clear(&net_message); - return sock; + //spawn the client. + //FIXME: come up with some challenge mechanism so that we don't go to the expense of spamming serverinfos+modellists+etc until we know that its an actual connection attempt. + svs.clients[plnum].netconnection = sock; + SV_ConnectClient (plnum); } qsocket_t *Datagram_CheckNewConnections (void) { - qsocket_t *ret = NULL; - - for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + //only needs to do master stuff now + if (sv_public.value > 0) { - if (net_landrivers[net_landriverlevel].initialized) + if (Sys_DoubleTime() > heartbeat_time) { - if ((ret = _Datagram_CheckNewConnections ()) != NULL) - break; + //darkplaces here refers to the master server protocol, rather than the game protocol + //(specifies that the server responds to infoRequest packets from the master) + char *str = "\377\377\377\377heartbeat DarkPlaces\n"; + size_t k; + struct qsockaddr addr; + heartbeat_time = Sys_DoubleTime() + 300; + + for (k = 0; net_masters[k].string; k++) + { + if (!*net_masters[k].string) + continue; + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (net_landrivers[net_landriverlevel].initialized && dfunc.listeningSock != INVALID_SOCKET) + { + if (dfunc.GetAddrFromName(net_masters[k].string, &addr) >= 0) + { + if (sv_reportheartbeats.value) + Con_Printf("Sending heartbeat to %s\n", net_masters[k].string); + dfunc.Write(dfunc.listeningSock, (byte*)str, strlen(str), &addr); + } + else + { + if (sv_reportheartbeats.value) + Con_Printf("Unable to resolve %s\n", net_masters[k].string); + } + } + } + } } } - return ret; + + return NULL; +} + +static void _Datagram_SendServerQuery(struct qsockaddr *addr, qboolean master) +{ + SZ_Clear(&net_message); + if (master) //assume false if you want only the protocol 15 servers. + { + MSG_WriteLong(&net_message, ~0); + MSG_WriteString(&net_message, "getinfo"); + } + else + { + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_SERVER_INFO); + MSG_WriteString(&net_message, "QUAKE"); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + } + dfunc.Write(dfunc.controlSock, net_message.data, net_message.cursize, addr); + SZ_Clear(&net_message); +} +static struct +{ + int driver; + qboolean requery; + qboolean master; + struct qsockaddr addr; +} *hostlist; +size_t hostlist_count; +size_t hostlist_max; +static void _Datagram_AddPossibleHost(struct qsockaddr *addr, qboolean master) +{ + size_t u; + for (u = 0; u < hostlist_count; u++) + { + if (!memcmp(&hostlist[u].addr, addr, sizeof(struct qsockaddr)) && hostlist[u].driver == net_landriverlevel) + { //we already know about it. it must have come from some other master. don't respam. + return; + } + } + if (hostlist_count == hostlist_max) + { + hostlist_max = hostlist_count + 16; + hostlist = Z_Realloc(hostlist, sizeof(*hostlist)*hostlist_max); + } + hostlist[hostlist_count].addr = *addr; + hostlist[hostlist_count].requery = true; + hostlist[hostlist_count].master = master; + hostlist[hostlist_count].driver = net_landriverlevel; + hostlist_count++; } -static void _Datagram_SearchForHosts (qboolean xmit) +static void Info_ReadKey(const char *info, const char *key, char *out, size_t outsize) +{ + size_t keylen = strlen(key); + while(*info) + { + if (*info++ != '\\') + break; //error / end-of-string + + if (!strncmp(info, key, keylen) && info[keylen] == '\\') + { + char *o = out, *e = out + outsize - 1; + + //skip the key name + info += keylen+1; + //this is the old value for the key. copy it to the result + while (*info && *info != '\\' && o < e) + *o++ = *info++; + *o++ = 0; + + //success! + return; + } + else + { + //skip the key + while (*info && *info != '\\') + info++; + + //validate that its a value now + if (*info++ != '\\') + break; //error + //skip the value + while (*info && *info != '\\') + info++; + } + } + *out = 0; +} + + +static qboolean _Datagram_SearchForHosts (qboolean xmit) { int ret; - int n; - int i; + size_t n; + size_t i; struct qsockaddr readaddr; struct qsockaddr myaddr; int control; + qboolean sentsomething = false; dfunc.GetSocketAddr (dfunc.controlSock, &myaddr); if (xmit) { + for (i = 0; i < hostlist_count; i++) + hostlist[i].requery = true; + SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); @@ -1137,6 +1715,37 @@ static void _Datagram_SearchForHosts (qboolean xmit) *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); dfunc.Broadcast(dfunc.controlSock, net_message.data, net_message.cursize); SZ_Clear(&net_message); + + if (slistScope == SLIST_INTERNET) + { + struct qsockaddr masteraddr; + char *str; + size_t m; + for (m = 0; net_masters[m].string; m++) + { + if (!*net_masters[m].string) + continue; + if (dfunc.GetAddrFromName(net_masters[m].string, &masteraddr) >= 0) + { + const char *prot = com_protocolname.string; + while (*prot) + { //send a request for each protocol + prot = COM_Parse(prot); + if (!prot) + break; + if (*com_token) + { + if (masteraddr.qsa_family == AF_INET6) + str = va("%c%c%c%cgetserversExt %s %u empty full ipv6"/*\x0A\n"*/, 255, 255, 255, 255, com_token, NET_PROTOCOL_VERSION); + else + str = va("%c%c%c%cgetservers %s %u empty full"/*\x0A\n"*/, 255, 255, 255, 255, com_token, NET_PROTOCOL_VERSION); + dfunc.Write(dfunc.controlSock, (byte*)str, strlen(str), &masteraddr); + } + } + } + } + } + sentsomething = true; } while ((ret = dfunc.Read (dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0) @@ -1146,6 +1755,8 @@ static void _Datagram_SearchForHosts (qboolean xmit) net_message.cursize = ret; // don't answer our own query + //Note: this doesn't really work too well if we're multi-homed. + //we should probably just refuse to respond to serverinfo requests while we're scanning (chances are our server is going to die anyway). if (dfunc.AddrCompare(&readaddr, &myaddr) >= 0) continue; @@ -1157,7 +1768,122 @@ static void _Datagram_SearchForHosts (qboolean xmit) control = BigLong(*((int *)net_message.data)); MSG_ReadLong(); if (control == -1) + { + if (msg_readcount+19 <= net_message.cursize && !strncmp((char*)net_message.data+msg_readcount, "getserversResponse", 18)) + { + struct qsockaddr addr; + int i; + msg_readcount += 18; + for(;;) + { + switch(MSG_ReadByte()) + { + case '\\': + memset(&addr, 0, sizeof(addr)); + addr.qsa_family = AF_INET; + for (i = 0; i < 4; i++) + ((byte*)&((struct sockaddr_in*)&addr)->sin_addr)[i] = MSG_ReadByte(); + ((byte*)&((struct sockaddr_in*)&addr)->sin_port)[0] = MSG_ReadByte(); + ((byte*)&((struct sockaddr_in*)&addr)->sin_port)[1] = MSG_ReadByte(); + if (!((struct sockaddr_in*)&addr)->sin_port) + msg_badread = true; + break; + case '/': + memset(&addr, 0, sizeof(addr)); + addr.qsa_family = AF_INET6; + for (i = 0; i < 16; i++) + ((byte*)&((struct sockaddr_in6*)&addr)->sin6_addr)[i] = MSG_ReadByte(); + ((byte*)&((struct sockaddr_in6*)&addr)->sin6_port)[0] = MSG_ReadByte(); + ((byte*)&((struct sockaddr_in6*)&addr)->sin6_port)[1] = MSG_ReadByte(); + if (!((struct sockaddr_in6*)&addr)->sin6_port) + msg_badread = true; + break; + default: + memset(&addr, 0, sizeof(addr)); + msg_badread = true; + break; + } + if (msg_badread) + break; + _Datagram_AddPossibleHost(&addr, true); + sentsomething = true; + } + } + else if (msg_readcount+13 <= net_message.cursize && !strncmp((char*)net_message.data+msg_readcount, "infoResponse\n", 13)) + { //response from a dpp7 server (or possibly 15, no idea really) + char tmp[1024]; + const char *info = MSG_ReadString()+13; + + // search the cache for this server + for (n = 0; n < hostCacheCount; n++) + { + if (dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0) + break; + } + + // is it already there? + if (n < hostCacheCount) + { + if (*hostcache[n].cname) + continue; + } + else + { + // add it + hostCacheCount++; + } + Info_ReadKey(info, "hostname", hostcache[n].name, sizeof(hostcache[n].name)); + Info_ReadKey(info, "mapname", hostcache[n].map, sizeof(hostcache[n].map)); + Info_ReadKey(info, "modname", tmp, sizeof(tmp)); + if (!COM_GameDirMatches(tmp)) + { + q_strlcpy(hostcache[n].name, va("{%s}", tmp), sizeof(hostcache[n].name)); + q_strlcpy(hostcache[n].map, "", sizeof(hostcache[n].map)); + } + + Info_ReadKey(info, "clients", tmp, sizeof(tmp)); + hostcache[n].users = atoi(tmp); + Info_ReadKey(info, "sv_maxclients", tmp, sizeof(tmp)); + hostcache[n].maxusers = atoi(tmp); + Info_ReadKey(info, "protocol", tmp, sizeof(tmp)); + if (atoi(tmp) != NET_PROTOCOL_VERSION) + { + Q_strcpy(hostcache[n].cname, hostcache[n].name); + Q_strcpy(hostcache[n].name, "*"); + Q_strcat(hostcache[n].name, hostcache[n].cname); + } + Q_memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr)); + hostcache[n].driver = net_driverlevel; + hostcache[n].ldriver = net_landriverlevel; + q_strlcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr, false), sizeof(hostcache[n].cname)); + + // check for a name conflict + for (i = 0; i < hostCacheCount; i++) + { + if (i == n) + continue; + if (q_strcasecmp (hostcache[n].cname, hostcache[i].cname) == 0) + { //this is a dupe. + hostCacheCount--; + break; + } + if (q_strcasecmp (hostcache[n].name, hostcache[i].name) == 0) + { + i = Q_strlen(hostcache[n].name); + if (i < 15 && hostcache[n].name[i-1] > '8') + { + hostcache[n].name[i] = '0'; + hostcache[n].name[i+1] = 0; + } + else + hostcache[n].name[i-1]++; + + i = -1; + } + } + } continue; + } if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) continue; if ((control & NETFLAG_LENGTH_MASK) != ret) @@ -1166,7 +1892,17 @@ static void _Datagram_SearchForHosts (qboolean xmit) if (MSG_ReadByte() != CCREP_SERVER_INFO) continue; - dfunc.GetAddrFromName(MSG_ReadString(), &readaddr); + MSG_ReadString(); + //dfunc.GetAddrFromName(MSG_ReadString(), &peeraddr); + /*if (dfunc.AddrCompare(&readaddr, &peeraddr) != 0) + { + char read[NET_NAMELEN]; + char peer[NET_NAMELEN]; + q_strlcpy(read, dfunc.AddrToString(&readaddr), sizeof(read)); + q_strlcpy(peer, dfunc.AddrToString(&peeraddr), sizeof(peer)); + Con_SafePrintf("Server at %s claimed to be at %s\n", read, peer); + }*/ + // search the cache for this server for (n = 0; n < hostCacheCount; n++) { @@ -1176,12 +1912,17 @@ static void _Datagram_SearchForHosts (qboolean xmit) // is it already there? if (n < hostCacheCount) - continue; - - // add it - hostCacheCount++; - Q_strcpy(hostcache[n].name, MSG_ReadString()); - Q_strcpy(hostcache[n].map, MSG_ReadString()); + { + if (*hostcache[n].cname) + continue; + } + else + { + // add it + hostCacheCount++; + } + q_strlcpy(hostcache[n].name, MSG_ReadString(), sizeof(hostcache[n].name)); + q_strlcpy(hostcache[n].map, MSG_ReadString(), sizeof(hostcache[n].map)); hostcache[n].users = MSG_ReadByte(); hostcache[n].maxusers = MSG_ReadByte(); if (MSG_ReadByte() != NET_PROTOCOL_VERSION) @@ -1194,13 +1935,18 @@ static void _Datagram_SearchForHosts (qboolean xmit) Q_memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr)); hostcache[n].driver = net_driverlevel; hostcache[n].ldriver = net_landriverlevel; - Q_strcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr)); + q_strlcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr, false), sizeof(hostcache[n].cname)); // check for a name conflict for (i = 0; i < hostCacheCount; i++) { if (i == n) continue; + if (q_strcasecmp (hostcache[n].cname, hostcache[i].cname) == 0) + { //this is a dupe. + hostCacheCount--; + break; + } if (q_strcasecmp (hostcache[n].name, hostcache[i].name) == 0) { i = Q_strlen(hostcache[n].name); @@ -1216,23 +1962,42 @@ static void _Datagram_SearchForHosts (qboolean xmit) } } } + + if (!xmit) + { + n = 4; //should be time-based. meh. + for (i = 0; i < hostlist_count; i++) + { + if (hostlist[i].requery && hostlist[i].driver == net_landriverlevel) + { + hostlist[i].requery = false; + _Datagram_SendServerQuery(&hostlist[i].addr, hostlist[i].master); + sentsomething = true; + n--; + if (!n) + break; + } + } + } + return sentsomething; } -void Datagram_SearchForHosts (qboolean xmit) +qboolean Datagram_SearchForHosts (qboolean xmit) { + qboolean ret = false; for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) { if (hostCacheCount == HOSTCACHESIZE) break; if (net_landrivers[net_landriverlevel].initialized) - _Datagram_SearchForHosts (xmit); + ret |= _Datagram_SearchForHosts (xmit); } + return ret; } -static qsocket_t *_Datagram_Connect (const char *host) +static qsocket_t *_Datagram_Connect (struct qsockaddr *serveraddr) { - struct qsockaddr sendaddr; struct qsockaddr readaddr; qsocket_t *sock; sys_socket_t newsock; @@ -1242,13 +2007,6 @@ static qsocket_t *_Datagram_Connect (const char *host) int control; const char *reason; - // see if we can resolve the host name - if (dfunc.GetAddrFromName(host, &sendaddr) == -1) - { - Con_Printf("Could not resolve %s\n", host); - return NULL; - } - newsock = dfunc.Open_Socket (0); if (newsock == INVALID_SOCKET) return NULL; @@ -1260,11 +2018,13 @@ static qsocket_t *_Datagram_Connect (const char *host) sock->landriver = net_landriverlevel; // connect to the host - if (dfunc.Connect (newsock, &sendaddr) == -1) + if (dfunc.Connect (newsock, serveraddr) == -1) goto ErrorReturn; + sock->proquake_angle_hack = true; + // send the connection request - Con_Printf("trying...\n"); + Con_SafePrintf("trying...\n"); SCR_UpdateScreen (); start_time = net_time; @@ -1276,9 +2036,24 @@ static qsocket_t *_Datagram_Connect (const char *host) MSG_WriteByte(&net_message, CCREQ_CONNECT); MSG_WriteString(&net_message, "QUAKE"); MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + if (sock->proquake_angle_hack) + { /*Spike -- proquake compat. if both engines claim to be using mod==1 then 16bit client->server angles can be used. server->client angles remain 16bit*/ + Con_DWarning("Attempting to use ProQuake angle hack\n"); + MSG_WriteByte(&net_message, 1); /*'mod', 1=proquake*/ + MSG_WriteByte(&net_message, 34); /*'mod' version*/ + MSG_WriteByte(&net_message, 0); /*flags*/ + MSG_WriteLong(&net_message, 0);//strtoul(password.string, NULL, 0)); /*password*/ + } *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); - dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr); + dfunc.Write (newsock, net_message.data, net_message.cursize, serveraddr); SZ_Clear(&net_message); + + //for dp compat. DP sends these in addition to the above packet. + //if the (DP) server is running using vanilla protocols, it replies to the above, otherwise to the following, requiring both to be sent. + //(challenges hinder a DOS issue known as smurfing, in that the client must prove that it owns the IP that it might be spoofing before any serious resources are used) + #define DPGETCHALLENGE "\xff\xff\xff\xffgetchallenge\n" + dfunc.Write (newsock, (byte*)DPGETCHALLENGE, strlen(DPGETCHALLENGE), serveraddr); + do { ret = dfunc.Read (newsock, net_message.data, net_message.maxsize, &readaddr); @@ -1286,11 +2061,11 @@ static qsocket_t *_Datagram_Connect (const char *host) if (ret > 0) { // is it from the right place? - if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0) + if (sfunc.AddrCompare(&readaddr, serveraddr) != 0) { - Con_Printf("wrong reply address\n"); - Con_Printf("Expected: %s | %s\n", dfunc.AddrToString (&sendaddr), StrAddr(&sendaddr)); - Con_Printf("Received: %s | %s\n", dfunc.AddrToString (&readaddr), StrAddr(&readaddr)); + Con_SafePrintf("wrong reply address\n"); + Con_SafePrintf("Expected: %s | %s\n", dfunc.AddrToString (serveraddr, false), StrAddr(serveraddr)); + Con_SafePrintf("Received: %s | %s\n", dfunc.AddrToString (&readaddr, false), StrAddr(&readaddr)); SCR_UpdateScreen (); ret = 0; continue; @@ -1309,6 +2084,27 @@ static qsocket_t *_Datagram_Connect (const char *host) MSG_ReadLong(); if (control == -1) { + const char *s = MSG_ReadString(); + if (!strncmp(s, "challenge ", 10)) + { //either a q2 or dp server... + char buf[1024]; + q_snprintf(buf, sizeof(buf), "%c%c%c%cconnect\\protocol\\darkplaces 3\\protocols\\RMQ FITZ DP7 NEHAHRABJP3 QUAKE\\challenge\\%s", 255, 255, 255, 255, s+10); + dfunc.Write (newsock, (byte*)buf, strlen(buf), serveraddr); + } + else if (!strcmp(s, "accept")) + { + Q_memcpy(&sock->addr, serveraddr, sizeof(struct qsockaddr)); + sock->proquake_angle_hack = false; + goto dpserveraccepted; + } + /*else if (!strcmp(s, "reject")) + { + reason = MSG_ReadString(); + Con_Printf("%s\n", reason); + q_strlcpy(m_return_reason, reason, sizeof(m_return_reason)); + goto ErrorReturn; + }*/ + ret = 0; continue; } @@ -1329,7 +2125,7 @@ static qsocket_t *_Datagram_Connect (const char *host) if (ret) break; - Con_Printf("still trying...\n"); + Con_SafePrintf("still trying...\n"); SCR_UpdateScreen (); start_time = SetNetTime(); } @@ -1361,8 +2157,11 @@ static qsocket_t *_Datagram_Connect (const char *host) if (ret == CCREP_ACCEPT) { - Q_memcpy(&sock->addr, &sendaddr, sizeof(struct qsockaddr)); - dfunc.SetSocketPort (&sock->addr, MSG_ReadLong()); + int port; + Q_memcpy(&sock->addr, serveraddr, sizeof(struct qsockaddr)); + port = MSG_ReadLong(); + if (port) //spike --- don't change the remote port if the server doesn't want us to. this allows servers to use port forwarding with less issues, assuming the server uses the same port for all clients. + dfunc.SetSocketPort (&sock->addr, port); } else { @@ -1372,7 +2171,32 @@ static qsocket_t *_Datagram_Connect (const char *host) goto ErrorReturn; } - dfunc.GetNameFromAddr (&sendaddr, sock->address); + if (sock->proquake_angle_hack) + { + byte mod = (msg_readcountproquake_angle_hack = true; + } + else + sock->proquake_angle_hack = false; + } + +dpserveraccepted: + + dfunc.GetNameFromAddr (serveraddr, sock->trueaddress); + dfunc.GetNameFromAddr (serveraddr, sock->maskedaddress); Con_Printf ("Connection accepted\n"); sock->lastMessageTime = SetNetTime(); @@ -1386,6 +2210,31 @@ static qsocket_t *_Datagram_Connect (const char *host) goto ErrorReturn; } + /*Spike's rant about NATs: + We sent a packet to the server's control port. + The server replied from that control port. all is well so far. + The server is now about(or already did, yay resends) to send us a packet from its data port to our address. + The nat will (correctly) see a packet from a different remote address:port. + The local nat has two options here. 1) assume that the wrong port is fine. 2) drop it. Dropping it is far more likely. + The NQ code will not send any unreliables until we have received the serverinfo. There are no reliables that need to be sent either. + Normally we won't send ANYTHING until we get that packet. + Which will never happen because the NAT will never let it through. + So, if we want to get away without fixing everyone else's server (which is also quite messy), + the easy way around this dilema is to just send some (small) useless packet to what we believe to be the server's data port. + A single unreliable clc_nop should do it. There's unlikely to be much packetloss on our local lan (so long as our host buffers outgoing packets on a per-socket basis or something), + so we don't normally need to resend. We don't really care if the server can even read it properly, but its best to avoid warning prints. + With that small outgoing packet, our local nat will think we initiated the request. + HOPEFULLY it'll reuse the same public port+address. Most home routers will, but not all, most hole-punching techniques depend upon such behaviour. + Note that proquake 3.4+ will actually wait for a packet from the new client, which solves that (but makes the nop mandatory, so needs to be reliable). + + the nop is actually sent inside CL_EstablishConnection where it has cleaner access to the client's pending reliable message. + + Note that we do fix our own server. This means that we can easily run on a home nat. the heartbeats to the master will open up a public port with most routers. + And if that doesn't work, then its easy enough to port-forward a single known port instead of having to DMZ the entire network. + I don't really expect that many people will use this, but it'll be nice for the occasional coop game. + (note that this makes the nop redundant, but that's a different can of worms) + */ + m_return_onerror = false; return sock; @@ -1406,16 +2255,26 @@ ErrorReturn2: qsocket_t *Datagram_Connect (const char *host) { qsocket_t *ret = NULL; + qboolean resolved = false; + struct qsockaddr addr; host = Strip_Port (host); for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) { if (net_landrivers[net_landriverlevel].initialized) { - if ((ret = _Datagram_Connect (host)) != NULL) - break; + // see if we can resolve the host name + // Spike -- moved name resolution to here to avoid extraneous 'could not resolves' when using other address families + if (dfunc.GetAddrFromName(host, &addr) != -1) + { + resolved = true; + if ((ret = _Datagram_Connect (&addr)) != NULL) + break; + } } } + if (!resolved) + Con_SafePrintf("Could not resolve %s\n", host); return ret; } diff --git a/Quake/net_dgrm.h b/Quake/net_dgrm.h index 357a829a..e9cd4c88 100644 --- a/Quake/net_dgrm.h +++ b/Quake/net_dgrm.h @@ -24,9 +24,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. int Datagram_Init (void); void Datagram_Listen (qboolean state); -void Datagram_SearchForHosts (qboolean xmit); +qboolean Datagram_SearchForHosts (qboolean xmit); qsocket_t *Datagram_Connect (const char *host); qsocket_t *Datagram_CheckNewConnections (void); +qsocket_t *Datagram_GetAnyMessage (void); int Datagram_GetMessage (qsocket_t *sock); int Datagram_SendMessage (qsocket_t *sock, sizebuf_t *data); int Datagram_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data); diff --git a/Quake/net_loop.c b/Quake/net_loop.c index 8cb22db6..daa64995 100644 --- a/Quake/net_loop.c +++ b/Quake/net_loop.c @@ -48,10 +48,10 @@ void Loop_Listen (qboolean state) } -void Loop_SearchForHosts (qboolean xmit) +qboolean Loop_SearchForHosts (qboolean xmit) { if (!sv.active) - return; + return false; hostCacheCount = 1; if (Q_strcmp(hostname.string, "UNNAMED") == 0) @@ -63,6 +63,7 @@ void Loop_SearchForHosts (qboolean xmit) hostcache[0].maxusers = svs.maxclients; hostcache[0].driver = net_driverlevel; Q_strcpy(hostcache[0].cname, "local"); + return false; } @@ -80,7 +81,8 @@ qsocket_t *Loop_Connect (const char *host) Con_Printf("Loop_Connect: no qsocket available\n"); return NULL; } - Q_strcpy (loop_client->address, "localhost"); + Q_strcpy (loop_client->trueaddress, "localhost"); + Q_strcpy (loop_client->maskedaddress, "localhost"); } loop_client->receiveMessageLength = 0; loop_client->sendMessageLength = 0; @@ -93,7 +95,8 @@ qsocket_t *Loop_Connect (const char *host) Con_Printf("Loop_Connect: no qsocket available\n"); return NULL; } - Q_strcpy (loop_server->address, "LOCAL"); + Q_strcpy (loop_server->trueaddress, "LOCAL"); + Q_strcpy (loop_server->maskedaddress, "LOCAL"); } loop_server->receiveMessageLength = 0; loop_server->sendMessageLength = 0; @@ -102,6 +105,8 @@ qsocket_t *Loop_Connect (const char *host) loop_client->driverdata = (void *)loop_server; loop_server->driverdata = (void *)loop_client; + loop_client->proquake_angle_hack = loop_server->proquake_angle_hack = true; + return loop_client; } @@ -127,7 +132,6 @@ static int IntAlign(int value) return (value + (sizeof(int) - 1)) & (~(sizeof(int) - 1)); } - int Loop_GetMessage (qsocket_t *sock) { int ret; @@ -140,9 +144,19 @@ int Loop_GetMessage (qsocket_t *sock) length = sock->receiveMessage[1] + (sock->receiveMessage[2] << 8); // alignment byte skipped here SZ_Clear (&net_message); - SZ_Write (&net_message, &sock->receiveMessage[4], length); + if (ret == 2) + { //unreliables have sequences that we (now) care about so that clients can ack them. + sock->unreliableReceiveSequence = sock->receiveMessage[4] | (sock->receiveMessage[5]<<8) | (sock->receiveMessage[6]<<16) | (sock->receiveMessage[7]<<16); + sock->unreliableReceiveSequence++; + SZ_Write (&net_message, &sock->receiveMessage[8], length); + length = IntAlign(length + 8); + } + else + { //reliable + SZ_Write (&net_message, &sock->receiveMessage[4], length); + length = IntAlign(length + 4); + } - length = IntAlign(length + 4); sock->receiveMessageLength -= length; if (sock->receiveMessageLength) @@ -154,6 +168,16 @@ int Loop_GetMessage (qsocket_t *sock) return ret; } +qsocket_t *Loop_GetAnyMessage(void) +{ + if (loop_server) + { + if (Loop_GetMessage(loop_server) > 0) + return loop_server; + } + return NULL; +} + int Loop_SendMessage (qsocket_t *sock, sizebuf_t *data) { @@ -193,6 +217,7 @@ int Loop_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) { byte *buffer; int *bufferLength; + int sequence = sock->unreliableSendSequence++; if (!sock->driverdata) return -1; @@ -214,9 +239,14 @@ int Loop_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) // align buffer++; + *buffer++ = (sequence >> 0) & 0xff; + *buffer++ = (sequence >> 8) & 0xff; + *buffer++ = (sequence >> 16) & 0xff; + *buffer++ = (sequence >> 24) & 0xff; + // message Q_memcpy(buffer, data->data, data->cursize); - *bufferLength = IntAlign(*bufferLength + data->cursize + 4); + *bufferLength = IntAlign(*bufferLength + data->cursize + 8); return 1; } diff --git a/Quake/net_loop.h b/Quake/net_loop.h index 267193d9..ea18eef9 100644 --- a/Quake/net_loop.h +++ b/Quake/net_loop.h @@ -25,9 +25,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // net_loop.h int Loop_Init (void); void Loop_Listen (qboolean state); -void Loop_SearchForHosts (qboolean xmit); +qboolean Loop_SearchForHosts (qboolean xmit); qsocket_t *Loop_Connect (const char *host); qsocket_t *Loop_CheckNewConnections (void); +qsocket_t *Loop_GetAnyMessage(void); int Loop_GetMessage (qsocket_t *sock); int Loop_SendMessage (qsocket_t *sock, sizebuf_t *data); int Loop_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data); diff --git a/Quake/net_main.c b/Quake/net_main.c index bbd599af..c5aa666a 100644 --- a/Quake/net_main.c +++ b/Quake/net_main.c @@ -30,20 +30,23 @@ qsocket_t *net_freeSockets = NULL; int net_numsockets = 0; qboolean ipxAvailable = false; -qboolean tcpipAvailable = false; +qboolean ipv4Available = false; +qboolean ipv6Available = false; int net_hostport; int DEFAULTnet_hostport = 26000; char my_ipx_address[NET_NAMELEN]; -char my_tcpip_address[NET_NAMELEN]; +char my_ipv4_address[NET_NAMELEN]; +char my_ipv6_address[NET_NAMELEN]; static qboolean listening = false; qboolean slistInProgress = false; qboolean slistSilent = false; -qboolean slistLocal = true; +enum slistScope_e slistScope = SLIST_LOOP; static double slistStartTime; +static double slistActiveTime; static int slistLastShown; static void Slist_Send (void *); @@ -59,7 +62,8 @@ int messagesReceived = 0; int unreliableMessagesSent = 0; int unreliableMessagesReceived = 0; -static cvar_t net_messagetimeout = {"net_messagetimeout","300",CVAR_NONE}; +cvar_t net_messagetimeout = {"net_messagetimeout","300",CVAR_NONE}; +cvar_t net_connecttimeout = {"net_connecttimeout","10",CVAR_NONE}; //this might be a little brief, but we don't have a way to protect against smurf attacks. cvar_t hostname = {"hostname", "UNNAMED", CVAR_NONE}; // these two macros are to make the code more readable @@ -104,9 +108,11 @@ qsocket_t *NET_NewQSocket (void) sock->next = net_activeSockets; net_activeSockets = sock; + sock->isvirtual = false; sock->disconnected = false; sock->connecttime = net_time; - Q_strcpy (sock->address,"UNSET ADDRESS"); + Q_strcpy (sock->trueaddress,"UNSET ADDRESS"); + Q_strcpy (sock->maskedaddress,"UNSET ADDRESS"); sock->driver = net_driverlevel; sock->socket = 0; sock->driverdata = NULL; @@ -120,6 +126,8 @@ qsocket_t *NET_NewQSocket (void) sock->receiveSequence = 0; sock->unreliableReceiveSequence = 0; sock->receiveMessageLength = 0; + sock->pending_max_datagram = 1024; + sock->proquake_angle_hack = false; return sock; } @@ -154,15 +162,38 @@ void NET_FreeQSocket(qsocket_t *sock) } +int NET_QSocketGetSequenceIn (const qsocket_t *s) +{ //returns the last unreliable sequence that was received + return s->unreliableReceiveSequence-1; +} +int NET_QSocketGetSequenceOut (const qsocket_t *s) +{ //returns the next unreliable sequence that will be sent + return s->unreliableSendSequence; +} double NET_QSocketGetTime (const qsocket_t *s) { return s->connecttime; } -const char *NET_QSocketGetAddressString (const qsocket_t *s) +const char *NET_QSocketGetTrueAddressString (const qsocket_t *s) { - return s->address; + return s->trueaddress; +} +const char *NET_QSocketGetMaskedAddressString (const qsocket_t *s) +{ + return s->maskedaddress; +} +qboolean NET_QSocketGetProQuakeAngleHack(const qsocket_t *s) +{ + if (s && !s->disconnected) + return s->proquake_angle_hack; + else + return false; //happens with demos +} +void NET_QSocketSetMSS(qsocket_t *s, int mss) +{ + s->pending_max_datagram = mss; } @@ -263,7 +294,7 @@ static void PrintSlistHeader(void) static void PrintSlist(void) { - int n; + size_t n; for (n = slistLastShown; n < hostCacheCount; n++) { @@ -297,7 +328,7 @@ void NET_Slist_f (void) } slistInProgress = true; - slistStartTime = Sys_DoubleTime(); + slistActiveTime = slistStartTime = Sys_DoubleTime(); SchedulePollProcedure(&slistSendProcedure, 0.0); SchedulePollProcedure(&slistPollProcedure, 0.1); @@ -310,7 +341,7 @@ void NET_SlistSort (void) { if (hostCacheCount > 1) { - int i, j; + size_t i, j; hostcache_t temp; for (i = 0; i < hostCacheCount; i++) { @@ -328,11 +359,11 @@ void NET_SlistSort (void) } -const char *NET_SlistPrintServer (int idx) +const char *NET_SlistPrintServer (size_t idx) { static char string[64]; - if (idx < 0 || idx >= hostCacheCount) + if (idx >= hostCacheCount) return ""; if (hostcache[idx].maxusers) @@ -351,9 +382,9 @@ const char *NET_SlistPrintServer (int idx) } -const char *NET_SlistPrintServerName (int idx) +const char *NET_SlistPrintServerName (size_t idx) { - if (idx < 0 || idx >= hostCacheCount) + if (idx >= hostCacheCount) return ""; return hostcache[idx].cname; } @@ -363,7 +394,7 @@ static void Slist_Send (void *unused) { for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) { - if (!slistLocal && IS_LOOP_DRIVER(net_driverlevel)) + if (slistScope!=SLIST_LOOP && IS_LOOP_DRIVER(net_driverlevel)) continue; if (net_drivers[net_driverlevel].initialized == false) continue; @@ -379,17 +410,18 @@ static void Slist_Poll (void *unused) { for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) { - if (!slistLocal && IS_LOOP_DRIVER(net_driverlevel)) + if (slistScope!=SLIST_LOOP && IS_LOOP_DRIVER(net_driverlevel)) continue; if (net_drivers[net_driverlevel].initialized == false) continue; - dfunc.SearchForHosts (false); + if (dfunc.SearchForHosts (false)) + slistActiveTime = Sys_DoubleTime(); //something was sent, reset the timer. } if (! slistSilent) PrintSlist(); - if ((Sys_DoubleTime() - slistStartTime) < 1.5) + if ((Sys_DoubleTime() - slistActiveTime) < 1.5) { SchedulePollProcedure(&slistPollProcedure, 0.1); return; @@ -399,7 +431,7 @@ static void Slist_Poll (void *unused) PrintSlistTrailer(); slistInProgress = false; slistSilent = false; - slistLocal = true; + slistScope = SLIST_LOOP; } @@ -409,13 +441,13 @@ NET_Connect =================== */ -int hostCacheCount = 0; +size_t hostCacheCount = 0; hostcache_t hostcache[HOSTCACHESIZE]; qsocket_t *NET_Connect (const char *host) { qsocket_t *ret; - int n; + size_t n; int numdrivers = net_numdrivers; SetNetTime(); @@ -594,6 +626,29 @@ int NET_GetMessage (qsocket_t *sock) return ret; } +/* +================= +NET_GetServerMessage + +If there is a complete message, return it in net_message + +returns the qsocket that the message was meant to be for. +================= +*/ +qsocket_t *NET_GetServerMessage(void) +{ + qsocket_t *s; + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (!net_drivers[net_driverlevel].initialized) + continue; + s = net_drivers[net_driverlevel].QGetAnyMessage(); + if (s) + return s; + } + return NULL; +} + /* ================== @@ -797,6 +852,7 @@ void NET_Init (void) SZ_Alloc (&net_message, NET_MAXMESSAGE); Cvar_RegisterVariable (&net_messagetimeout); + Cvar_RegisterVariable (&net_connecttimeout); Cvar_RegisterVariable (&hostname); Cmd_AddCommand ("slist", NET_Slist_f); @@ -828,9 +884,13 @@ void NET_Init (void) { Con_DPrintf("IPX address %s\n", my_ipx_address); } - if (*my_tcpip_address) + if (*my_ipv4_address) { - Con_DPrintf("TCP/IP address %s\n", my_tcpip_address); + Con_DPrintf("IPv4 address %s\n", my_ipv4_address); + } + if (*my_ipv6_address) + { + Con_DPrintf("IPv6 address %s\n", my_ipv6_address); } } diff --git a/Quake/net_sys.h b/Quake/net_sys.h index 77727e3b..8ea060fd 100644 --- a/Quake/net_sys.h +++ b/Quake/net_sys.h @@ -143,9 +143,10 @@ COMPILE_TIME_ASSERT(sockaddr, offsetof(struct sockaddr, sa_family) == SA_FAM_OFF #if defined(PLATFORM_WINDOWS) /* NOTE: winsock[2].h already includes windows.h */ -#if !defined(_USE_WINSOCK2) +#if 0//!defined(_USE_WINSOCK2) #include #else +//winsock2 has been available since win98, and is available as a separate download for win95, which would be needed for any web browsers anyway. #include #include #endif diff --git a/Quake/net_udp.c b/Quake/net_udp.c index b545851f..60477707 100644 --- a/Quake/net_udp.c +++ b/Quake/net_udp.c @@ -26,18 +26,23 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" #include "net_defs.h" -static sys_socket_t net_acceptsocket = INVALID_SOCKET; // socket for fielding new connections -static sys_socket_t net_controlsocket; -static sys_socket_t net_broadcastsocket = 0; -static struct sockaddr_in broadcastaddr; +static sys_socket_t net_acceptsocket4 = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t net_controlsocket4; +static sys_socket_t net_broadcastsocket4 = INVALID_SOCKET; +static struct sockaddr_in broadcastaddr4; -static in_addr_t myAddr; +static in_addr_t myAddr4; + +static sys_socket_t net_acceptsocket6 = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t net_controlsocket6; + +static struct in6_addr myAddrv6; #include "net_udp.h" //============================================================================= -sys_socket_t UDP_Init (void) +sys_socket_t UDP4_Init (void) { int err; char *tst; @@ -45,15 +50,15 @@ sys_socket_t UDP_Init (void) struct hostent *local; struct qsockaddr addr; - if (COM_CheckParm ("-noudp")) + if (COM_CheckParm ("-noudp") || COM_CheckParm ("-noudp4")) return INVALID_SOCKET; // determine my name & address - myAddr = htonl(INADDR_LOOPBACK); + myAddr4 = htonl(INADDR_LOOPBACK); if (gethostname(buff, MAXHOSTNAMELEN) != 0) { err = SOCKETERRNO; - Con_SafePrintf("UDP_Init: gethostname failed (%s)\n", + Con_SafePrintf("UDP4_Init: gethostname failed (%s)\n", socketerror(err)); } else @@ -72,72 +77,76 @@ sys_socket_t UDP_Init (void) #endif if (!(local = gethostbyname(buff))) { - Con_SafePrintf("UDP_Init: gethostbyname failed (%s)\n", + Con_SafePrintf("UDP4_Init: gethostbyname failed (%s)\n", hstrerror(h_errno)); } else if (local->h_addrtype != AF_INET) { - Con_SafePrintf("UDP_Init: address from gethostbyname not IPv4\n"); + Con_SafePrintf("UDP4_Init: address from gethostbyname not IPv4\n"); } else { - myAddr = *(in_addr_t *)local->h_addr_list[0]; + myAddr4 = *(in_addr_t *)local->h_addr_list[0]; } } - if ((net_controlsocket = UDP_OpenSocket(0)) == INVALID_SOCKET) + if ((net_controlsocket4 = UDP4_OpenSocket(0)) == INVALID_SOCKET) { - Con_SafePrintf("UDP_Init: Unable to open control socket, UDP disabled\n"); + Con_SafePrintf("UDP4_Init: Unable to open control socket, UDP disabled\n"); return INVALID_SOCKET; } - broadcastaddr.sin_family = AF_INET; - broadcastaddr.sin_addr.s_addr = INADDR_BROADCAST; - broadcastaddr.sin_port = htons((unsigned short)net_hostport); + broadcastaddr4.sin_family = AF_INET; + broadcastaddr4.sin_addr.s_addr = INADDR_BROADCAST; + broadcastaddr4.sin_port = htons((unsigned short)net_hostport); - UDP_GetSocketAddr (net_controlsocket, &addr); - strcpy(my_tcpip_address, UDP_AddrToString (&addr)); - tst = strrchr(my_tcpip_address, ':'); + UDP_GetSocketAddr (net_controlsocket4, &addr); + strcpy(my_ipv4_address, UDP_AddrToString (&addr, false)); + tst = strrchr (my_ipv4_address, ':'); if (tst) *tst = 0; - Con_SafePrintf("UDP Initialized\n"); - tcpipAvailable = true; + Con_SafePrintf("UDP4 Initialized\n"); + ipv4Available = true; - return net_controlsocket; + return net_controlsocket4; } //============================================================================= -void UDP_Shutdown (void) +void UDP4_Shutdown (void) { - UDP_Listen (false); - UDP_CloseSocket (net_controlsocket); + UDP4_Listen (false); + UDP_CloseSocket (net_controlsocket4); } //============================================================================= -void UDP_Listen (qboolean state) +sys_socket_t UDP4_Listen (qboolean state) { - // enable listening if (state) { - if (net_acceptsocket != INVALID_SOCKET) - return; - if ((net_acceptsocket = UDP_OpenSocket (net_hostport)) == INVALID_SOCKET) - Sys_Error ("UDP_Listen: Unable to open accept socket"); - return; + // enable listening + if (net_acceptsocket4 == INVALID_SOCKET) + { + if ((net_acceptsocket4 = UDP4_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("UDP4_Listen: Unable to open accept socket"); + } } - - // disable listening - if (net_acceptsocket == INVALID_SOCKET) - return; - UDP_CloseSocket (net_acceptsocket); - net_acceptsocket = INVALID_SOCKET; + else + { + // disable listening + if (net_acceptsocket4 != INVALID_SOCKET) + { + UDP_CloseSocket (net_acceptsocket4); + net_acceptsocket4 = INVALID_SOCKET; + } + } + return net_acceptsocket4; } //============================================================================= -sys_socket_t UDP_OpenSocket (int port) +sys_socket_t UDP4_OpenSocket (int port) { sys_socket_t newsocket; struct sockaddr_in address; @@ -147,7 +156,7 @@ sys_socket_t UDP_OpenSocket (int port) if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) { err = SOCKETERRNO; - Con_SafePrintf("UDP_OpenSocket: %s\n", socketerror(err)); + Con_SafePrintf("UDP4_OpenSocket: %s\n", socketerror(err)); return INVALID_SOCKET; } @@ -163,7 +172,7 @@ sys_socket_t UDP_OpenSocket (int port) ErrorReturn: err = SOCKETERRNO; - Con_SafePrintf("UDP_OpenSocket: %s\n", socketerror(err)); + Con_SafePrintf("UDP4_OpenSocket: %s\n", socketerror(err)); UDP_CloseSocket (newsocket); return INVALID_SOCKET; } @@ -172,8 +181,8 @@ ErrorReturn: int UDP_CloseSocket (sys_socket_t socketid) { - if (socketid == net_broadcastsocket) - net_broadcastsocket = 0; + if (socketid == net_broadcastsocket4) + net_broadcastsocket4 = INVALID_SOCKET; return closesocket (socketid); } @@ -228,7 +237,7 @@ static int PartialIPAddress (const char *in, struct qsockaddr *hostaddr) hostaddr->qsa_family = AF_INET; ((struct sockaddr_in *)hostaddr)->sin_port = htons((unsigned short)port); ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = - (myAddr & htonl(mask)) | htonl(addr); + (myAddr4 & htonl(mask)) | htonl(addr); return 0; } @@ -242,25 +251,25 @@ int UDP_Connect (sys_socket_t socketid, struct qsockaddr *addr) //============================================================================= -sys_socket_t UDP_CheckNewConnections (void) +sys_socket_t UDP4_CheckNewConnections (void) { int available; struct sockaddr_in from; socklen_t fromlen; char buff[1]; - if (net_acceptsocket == INVALID_SOCKET) + if (net_acceptsocket4 == INVALID_SOCKET) return INVALID_SOCKET; - if (ioctl (net_acceptsocket, FIONREAD, &available) == -1) + if (ioctl (net_acceptsocket4, FIONREAD, &available) == -1) { int err = SOCKETERRNO; Sys_Error ("UDP: ioctlsocket (FIONREAD) failed (%s)", socketerror(err)); } if (available) - return net_acceptsocket; + return net_acceptsocket4; // quietly absorb empty packets - recvfrom (net_acceptsocket, buff, 0, 0, (struct sockaddr *) &from, &fromlen); + recvfrom (net_acceptsocket4, buff, 0, 0, (struct sockaddr *) &from, &fromlen); return INVALID_SOCKET; } @@ -284,7 +293,7 @@ int UDP_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) //============================================================================= -static int UDP_MakeSocketBroadcastCapable (sys_socket_t socketid) +static int UDP4_MakeSocketBroadcastCapable (sys_socket_t socketid) { int i = 1; @@ -296,30 +305,30 @@ static int UDP_MakeSocketBroadcastCapable (sys_socket_t socketid) Con_SafePrintf ("UDP, setsockopt: %s\n", socketerror(err)); return -1; } - net_broadcastsocket = socketid; + net_broadcastsocket4 = socketid; return 0; } //============================================================================= -int UDP_Broadcast (sys_socket_t socketid, byte *buf, int len) +int UDP4_Broadcast (sys_socket_t socketid, byte *buf, int len) { int ret; - if (socketid != net_broadcastsocket) + if (socketid != net_broadcastsocket4) { - if (net_broadcastsocket != 0) + if (net_broadcastsocket4 != INVALID_SOCKET) Sys_Error("Attempted to use multiple broadcasts sockets"); - ret = UDP_MakeSocketBroadcastCapable (socketid); + ret = UDP4_MakeSocketBroadcastCapable (socketid); if (ret == -1) { - Con_Printf("Unable to make socket broadcast capable\n"); + Con_SafePrintf("Unable to make socket broadcast capable\n"); return ret; } } - return UDP_Write (socketid, buf, len, (struct qsockaddr *)&broadcastaddr); + return UDP_Write (socketid, buf, len, (struct qsockaddr *)&broadcastaddr4); } //============================================================================= @@ -327,36 +336,86 @@ int UDP_Broadcast (sys_socket_t socketid, byte *buf, int len) int UDP_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) { int ret; + socklen_t addrsize; + if (addr->qsa_family == AF_INET) + addrsize = sizeof(struct sockaddr_in); + else if (addr->qsa_family == AF_INET6) + addrsize = sizeof(struct sockaddr_in6); + else + { + Con_SafePrintf ("UDP_Write: unknown family\n"); + return -1; //some kind of error. a few systems get pissy if the size doesn't exactly match the address family + } - ret = sendto (socketid, buf, len, 0, (struct sockaddr *)addr, - sizeof(struct qsockaddr)); + ret = sendto (socketid, buf, len, 0, (struct sockaddr *)addr, addrsize); + if (!addr->qsa_family) + Con_SafePrintf ("UDP_Write: family was cleared\n"); if (ret == SOCKET_ERROR) { int err = SOCKETERRNO; if (err == NET_EWOULDBLOCK) return 0; - Con_SafePrintf ("UDP_Write, sendto: %s\n", socketerror(err)); + if (err == ENETUNREACH) + Con_SafePrintf ("UDP_Write: %s (%s)\n", socketerror(err), UDP_AddrToString(addr, false)); + else + Con_SafePrintf ("UDP_Write, sendto: %s\n", socketerror(err)); } return ret; } //============================================================================= -const char *UDP_AddrToString (struct qsockaddr *addr) +const char *UDP_AddrToString (struct qsockaddr *addr, qboolean masked) { - static char buffer[22]; - int haddr; + static char buffer[64]; - haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); - q_snprintf (buffer, sizeof(buffer), "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, - (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, - ntohs(((struct sockaddr_in *)addr)->sin_port)); + if (addr->qsa_family == AF_INET) + { + int haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + q_snprintf (buffer, sizeof(buffer), "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, + (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, + ntohs(((struct sockaddr_in *)addr)->sin_port)); + } + else if (addr->qsa_family == AF_INET6) + { + //evil type punning. + unsigned short *s = (unsigned short*)&((struct sockaddr_in6 *)addr)->sin6_addr; + if (((struct sockaddr_in6 *)addr)->sin6_scope_id) + { + q_snprintf(buffer, sizeof(buffer), "[%x:%x:%x:%x:%x:%x:%x:%x%%%i]:%d", + ntohs(s[0]), + ntohs(s[1]), + ntohs(s[2]), + ntohs(s[3]), + ntohs(s[4]), + ntohs(s[5]), + ntohs(s[6]), + ntohs(s[7]), + (int)((struct sockaddr_in6 *)addr)->sin6_scope_id, + ntohs(((struct sockaddr_in6 *)addr)->sin6_port)); + } + else + { + q_snprintf(buffer, sizeof(buffer), "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", + ntohs(s[0]), + ntohs(s[1]), + ntohs(s[2]), + ntohs(s[3]), + ntohs(s[4]), + ntohs(s[5]), + ntohs(s[6]), + ntohs(s[7]), + ntohs(((struct sockaddr_in6 *)addr)->sin6_port)); + } + } + else + strcpy(buffer, "?"); return buffer; } //============================================================================= -int UDP_StringToAddr (const char *string, struct qsockaddr *addr) +int UDP4_StringToAddr (const char *string, struct qsockaddr *addr) { int ha1, ha2, ha3, ha4, hp, ipaddr; @@ -380,9 +439,18 @@ int UDP_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr) if (getsockname(socketid, (struct sockaddr *)addr, &addrlen) != 0) return -1; - a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; - if (a == 0 || a == htonl(INADDR_LOOPBACK)) - ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + if (addr->qsa_family == AF_INET) + { + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == htonl(INADDR_LOOPBACK)) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr4; + } + else if (addr->qsa_family == AF_INET6) + { + static const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; + if (!memcmp(&((struct sockaddr_in6 *)addr)->sin6_addr, &in6addr_any, sizeof(in6addr_any))) + memcpy(&((struct sockaddr_in6 *)addr)->sin6_addr, &myAddrv6, sizeof(((struct sockaddr_in6 *)addr)->sin6_addr)); + } return 0; } @@ -391,35 +459,56 @@ int UDP_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr) int UDP_GetNameFromAddr (struct qsockaddr *addr, char *name) { - struct hostent *hostentry; - - hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, - sizeof(struct in_addr), AF_INET); - if (hostentry) + if (addr->qsa_family == AF_INET) { - strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); - return 0; + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hostentry) + { + strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + } + else if (addr->qsa_family == AF_INET6) + { + //meh, don't bother, its unreliable anyway. } - strcpy (name, UDP_AddrToString (addr)); + strcpy (name, UDP_AddrToString (addr, false)); return 0; } //============================================================================= -int UDP_GetAddrFromName (const char *name, struct qsockaddr *addr) +int UDP4_GetAddrFromName (const char *name, struct qsockaddr *addr) { struct hostent *hostentry; + char *colon; + unsigned short port = net_hostport; if (name[0] >= '0' && name[0] <= '9') return PartialIPAddress (name, addr); - hostentry = gethostbyname (name); - if (!hostentry) + colon = strrchr(name, ':'); + if (colon) + { + char dupe[MAXHOSTNAMELEN]; + if (colon-name+1 > MAXHOSTNAMELEN) + return -1; + memcpy(dupe, name, colon-name); + dupe[colon-name] = 0; + hostentry = gethostbyname (dupe); + port = strtoul(colon+1, NULL, 10); + } + else + hostentry = gethostbyname (name); + if (!hostentry || hostentry->h_addrtype != AF_INET) return -1; addr->qsa_family = AF_INET; - ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)net_hostport); + ((struct sockaddr_in *)addr)->sin_port = htons(port); ((struct sockaddr_in *)addr)->sin_addr.s_addr = *(in_addr_t *)hostentry->h_addr_list[0]; @@ -433,30 +522,322 @@ int UDP_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) if (addr1->qsa_family != addr2->qsa_family) return -1; - if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != - ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + if (addr1->qsa_family == AF_INET) + { + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != + ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != + ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; + } + else if (addr1->qsa_family == AF_INET6) + { + if (memcmp( &((struct sockaddr_in6 *)addr1)->sin6_addr, + &((struct sockaddr_in6 *)addr2)->sin6_addr, + sizeof(((struct sockaddr_in6 *)addr1)->sin6_addr))) + return -1; + + if (((struct sockaddr_in6 *)addr1)->sin6_port != + ((struct sockaddr_in6 *)addr2)->sin6_port) + return 1; + + if (((struct sockaddr_in6 *)addr1)->sin6_scope_id && + ((struct sockaddr_in6 *)addr2)->sin6_scope_id && + ((struct sockaddr_in6 *)addr1)->sin6_scope_id != + ((struct sockaddr_in6 *)addr2)->sin6_scope_id) //the ipv6 scope id is for use with link-local addresses, to identify the specific interface. + return 1; + + return 0; + } + else return -1; - - if (((struct sockaddr_in *)addr1)->sin_port != - ((struct sockaddr_in *)addr2)->sin_port) - return 1; - - return 0; } //============================================================================= int UDP_GetSocketPort (struct qsockaddr *addr) { - return ntohs(((struct sockaddr_in *)addr)->sin_port); + if (addr->qsa_family == AF_INET) + return ntohs(((struct sockaddr_in *)addr)->sin_port); + else if (addr->qsa_family == AF_INET6) + return ntohs(((struct sockaddr_in6 *)addr)->sin6_port); + else + return -1; } int UDP_SetSocketPort (struct qsockaddr *addr, int port) { - ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)port); + if (addr->qsa_family == AF_INET) + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)port); + else if (addr->qsa_family == AF_INET6) + ((struct sockaddr_in6 *)addr)->sin6_port = htons((unsigned short)port); + else + return -1; return 0; } //============================================================================= + + + + + + + + + + +sys_socket_t UDP6_Init (void) +{ + char *colon; + struct qsockaddr addr; + + if (COM_CheckParm ("-noudp") || COM_CheckParm ("-noudp6")) + return INVALID_SOCKET; + + // TODO: determine my name & address + + if ((net_controlsocket6 = UDP6_OpenSocket(0)) == INVALID_SOCKET) + { + Con_SafePrintf("UDP6_Init: Unable to open control socket, UDPv6 disabled\n"); + return INVALID_SOCKET; + } + + UDP_GetSocketAddr (net_controlsocket6, &addr); + strcpy(my_ipv6_address, UDP_AddrToString (&addr, false)); + colon = strrchr (my_ipv6_address, ':'); + if (colon) + *colon = 0; + + Con_SafePrintf("UDPv6 Initialized\n"); + ipv6Available = true; + + return net_controlsocket6; +} + +//============================================================================= + +void UDP6_Shutdown (void) +{ + UDP6_Listen (false); + UDP_CloseSocket (net_controlsocket6); +} + +//============================================================================= + +sys_socket_t UDP6_Listen (qboolean state) +{ + if (state) + { + // enable listening + if (net_acceptsocket6 == INVALID_SOCKET) + { + if ((net_acceptsocket6 = UDP6_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("UDP6_Listen: Unable to open accept socket"); + } + } + else + { + // disable listening + if (net_acceptsocket6 != INVALID_SOCKET) + { + UDP_CloseSocket (net_acceptsocket6); + net_acceptsocket6 = INVALID_SOCKET; + } + } + return net_acceptsocket6; +} + +//============================================================================= + +sys_socket_t UDP6_OpenSocket (int port) +{ + sys_socket_t newsocket; + struct sockaddr_in6 address; + int _true = 1; + int err; + + if ((newsocket = socket (PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + err = SOCKETERRNO; + Con_SafePrintf("UDP6_OpenSocket: %s\n", socketerror(err)); + return INVALID_SOCKET; + } + + setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof(_true)); + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + goto ErrorReturn; + + memset(&address, 0, sizeof(struct sockaddr_in6)); + address.sin6_family = AF_INET6; + memset(&address.sin6_addr, 0, sizeof(address.sin6_addr)); + address.sin6_port = htons((unsigned short)port); + if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) + { + //we don't know if we're the server or not. oh well. + struct ipv6_mreq req; + memset(&req, 0, sizeof(req)); + req.ipv6mr_multiaddr.s6_addr[0] = 0xff; + req.ipv6mr_multiaddr.s6_addr[1] = 0x03; + req.ipv6mr_multiaddr.s6_addr[15] = 0x01; + req.ipv6mr_interface = 0; + setsockopt(newsocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&req, sizeof(req)); + + return newsocket; + } + +ErrorReturn: + err = SOCKETERRNO; + Con_SafePrintf("UDP6_OpenSocket: %s\n", socketerror(err)); + UDP_CloseSocket (newsocket); + return INVALID_SOCKET; +} + +//============================================================================= + +sys_socket_t UDP6_CheckNewConnections (void) +{ + int available; + struct sockaddr_in from; + socklen_t fromlen; + char buff[1]; + + if (net_acceptsocket6 == INVALID_SOCKET) + return INVALID_SOCKET; + + if (ioctl (net_acceptsocket6, FIONREAD, &available) == -1) + { + int err = SOCKETERRNO; + Sys_Error ("UDP6: ioctlsocket (FIONREAD) failed (%s)", socketerror(err)); + } + if (available) + return net_acceptsocket6; + // quietly absorb empty packets + fromlen = sizeof(from); + recvfrom (net_acceptsocket6, buff, 0, 0, (struct sockaddr *) &from, &fromlen); + return INVALID_SOCKET; +} + +//============================================================================= + +int UDP6_Broadcast (sys_socket_t socketid, byte *buf, int len) +{ + struct sockaddr_in6 address; + + memset(&address, 0, sizeof(struct sockaddr_in6)); + address.sin6_family = AF_INET6; + memset(&address.sin6_addr, 0, sizeof(address.sin6_addr)); + address.sin6_addr.s6_addr[0] = 0xff; + address.sin6_addr.s6_addr[1] = 0x03; + address.sin6_addr.s6_addr[15] = 0x1; + address.sin6_port = htons((unsigned short)net_hostport); + + return UDP_Write (socketid, buf, len, (struct qsockaddr *)&address); +} + +//============================================================================= + +int UDP6_StringToAddr (const char *string, struct qsockaddr *addr) +{ //This is never actually called... + Con_SafePrintf("UDP6_StringToAddr: %s\n", string); + return UDP6_GetAddrFromName(string, addr); +} + +//============================================================================= + +int UDP6_GetAddrFromName (const char *name, struct qsockaddr *addr) +{ + struct addrinfo *addrinfo = NULL; + struct addrinfo *pos; + struct addrinfo udp6hint; + int error; + char *port; + char dupbase[256]; + size_t len; + qboolean success = false; + + memset(&udp6hint, 0, sizeof(udp6hint)); + udp6hint.ai_family = 0;//Any... we check for AF_INET6 or 4 + udp6hint.ai_socktype = SOCK_DGRAM; + udp6hint.ai_protocol = IPPROTO_UDP; + + if (*name == '[') + { + port = strstr(name, "]"); + if (!port) + error = EAI_NONAME; + else + { + len = port - (name+1); + if (len >= sizeof(dupbase)) + len = sizeof(dupbase)-1; + strncpy(dupbase, name+1, len); + dupbase[len] = '\0'; + error = getaddrinfo(dupbase, (port[1] == ':')?port+2:NULL, &udp6hint, &addrinfo); + } + } + else + { + port = strrchr(name, ':'); + + if (port) + { + len = port - name; + if (len >= sizeof(dupbase)) + len = sizeof(dupbase)-1; + strncpy(dupbase, name, len); + dupbase[len] = '\0'; + error = getaddrinfo(dupbase, port+1, &udp6hint, &addrinfo); + } + else + error = EAI_NONAME; + if (error) //failed, try string with no port. + error = getaddrinfo(name, NULL, &udp6hint, &addrinfo); //remember, this func will return any address family that could be using the udp protocol... (ip4 or ip6) + } + + if (!error) + { + ((struct sockaddr*)addr)->sa_family = 0; + for (pos = addrinfo; pos; pos = pos->ai_next) + { + if (0)//pos->ai_family == AF_INET) + { + memcpy(addr, pos->ai_addr, pos->ai_addrlen); + success = true; + break; + } + if (pos->ai_family == AF_INET6 && !success) + { + memcpy(addr, pos->ai_addr, pos->ai_addrlen); + success = true; + } + } + freeaddrinfo (addrinfo); + } + + if (success) + { + if (((struct sockaddr*)addr)->sa_family == AF_INET) + { + if (!((struct sockaddr_in *)addr)->sin_port) + ((struct sockaddr_in *)addr)->sin_port = htons(net_hostport); + } + else if (((struct sockaddr*)addr)->sa_family == AF_INET6) + { + if (!((struct sockaddr_in6 *)addr)->sin6_port) + ((struct sockaddr_in6 *)addr)->sin6_port = htons(net_hostport); + } + return 0; + } + return -1; +} + +//============================================================================= + diff --git a/Quake/net_udp.h b/Quake/net_udp.h index 5df71dfe..013315c2 100644 --- a/Quake/net_udp.h +++ b/Quake/net_udp.h @@ -22,21 +22,40 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifndef __net_udp_h #define __net_udp_h -sys_socket_t UDP_Init (void); -void UDP_Shutdown (void); -void UDP_Listen (qboolean state); -sys_socket_t UDP_OpenSocket (int port); +sys_socket_t UDP4_Init (void); +void UDP4_Shutdown (void); +sys_socket_t UDP4_Listen (qboolean state); +sys_socket_t UDP4_OpenSocket (int port); int UDP_CloseSocket (sys_socket_t socketid); int UDP_Connect (sys_socket_t socketid, struct qsockaddr *addr); -sys_socket_t UDP_CheckNewConnections (void); +sys_socket_t UDP4_CheckNewConnections (void); int UDP_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); int UDP_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); -int UDP_Broadcast (sys_socket_t socketid, byte *buf, int len); -const char *UDP_AddrToString (struct qsockaddr *addr); -int UDP_StringToAddr (const char *string, struct qsockaddr *addr); +int UDP4_Broadcast (sys_socket_t socketid, byte *buf, int len); +const char *UDP_AddrToString (struct qsockaddr *addr, qboolean masked); +int UDP4_StringToAddr (const char *string, struct qsockaddr *addr); int UDP_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); int UDP_GetNameFromAddr (struct qsockaddr *addr, char *name); -int UDP_GetAddrFromName (const char *name, struct qsockaddr *addr); +int UDP4_GetAddrFromName (const char *name, struct qsockaddr *addr); +int UDP_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); +int UDP_GetSocketPort (struct qsockaddr *addr); +int UDP_SetSocketPort (struct qsockaddr *addr, int port); + +sys_socket_t UDP6_Init (void); +void UDP6_Shutdown (void); +sys_socket_t UDP6_Listen (qboolean state); +sys_socket_t UDP6_OpenSocket (int port); +int UDP_CloseSocket (sys_socket_t socketid); +int UDP_Connect (sys_socket_t socketid, struct qsockaddr *addr); +sys_socket_t UDP6_CheckNewConnections (void); +int UDP_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int UDP_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int UDP6_Broadcast (sys_socket_t socketid, byte *buf, int len); +const char *UDP_AddrToString (struct qsockaddr *addr, qboolean masked); +int UDP6_StringToAddr (const char *string, struct qsockaddr *addr); +int UDP_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); +int UDP_GetNameFromAddr (struct qsockaddr *addr, char *name); +int UDP6_GetAddrFromName (const char *name, struct qsockaddr *addr); int UDP_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); int UDP_GetSocketPort (struct qsockaddr *addr); int UDP_SetSocketPort (struct qsockaddr *addr, int port); diff --git a/Quake/net_win.c b/Quake/net_win.c index 2d4ec9e2..bef01920 100644 --- a/Quake/net_win.c +++ b/Quake/net_win.c @@ -37,6 +37,7 @@ net_driver_t net_drivers[] = Loop_SearchForHosts, Loop_Connect, Loop_CheckNewConnections, + Loop_GetAnyMessage, Loop_GetMessage, Loop_SendMessage, Loop_SendUnreliableMessage, @@ -53,6 +54,7 @@ net_driver_t net_drivers[] = Datagram_SearchForHosts, Datagram_Connect, Datagram_CheckNewConnections, + Datagram_GetAnyMessage, Datagram_GetMessage, Datagram_SendMessage, Datagram_SendUnreliableMessage, @@ -74,26 +76,49 @@ net_landriver_t net_landrivers[] = { "Winsock TCPIP", false, 0, - WINS_Init, - WINS_Shutdown, - WINS_Listen, - WINS_OpenSocket, + WINIPv4_Init, + WINIPv4_Shutdown, + WINIPv4_Listen, + WINIPv4_OpenSocket, WINS_CloseSocket, WINS_Connect, - WINS_CheckNewConnections, + WINIPv4_CheckNewConnections, WINS_Read, WINS_Write, - WINS_Broadcast, + WINIPv4_Broadcast, WINS_AddrToString, - WINS_StringToAddr, + WINIPv4_StringToAddr, WINS_GetSocketAddr, - WINS_GetNameFromAddr, - WINS_GetAddrFromName, + WINIPv4_GetNameFromAddr, + WINIPv4_GetAddrFromName, WINS_AddrCompare, WINS_GetSocketPort, WINS_SetSocketPort }, - +#ifdef IPPROTO_IPV6 + { "Winsock IPv6", + false, + 0, + WINIPv6_Init, + WINIPv6_Shutdown, + WINIPv6_Listen, + WINIPv6_OpenSocket, + WINS_CloseSocket, + WINS_Connect, + WINIPv6_CheckNewConnections, + WINS_Read, + WINS_Write, + WINIPv6_Broadcast, + WINS_AddrToString, + WINIPv6_StringToAddr, + WINS_GetSocketAddr, + WINIPv6_GetNameFromAddr, + WINIPv6_GetAddrFromName, + WINS_AddrCompare, + WINS_GetSocketPort, + WINS_SetSocketPort + }, +#endif { "Winsock IPX", false, 0, diff --git a/Quake/net_wins.c b/Quake/net_wins.c index f45a522c..e8347608 100644 --- a/Quake/net_wins.c +++ b/Quake/net_wins.c @@ -24,12 +24,30 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" #include "net_defs.h" -static sys_socket_t net_acceptsocket = INVALID_SOCKET; // socket for fielding new connections -static sys_socket_t net_controlsocket; -static sys_socket_t net_broadcastsocket = 0; -static struct sockaddr_in broadcastaddr; +//ipv4 defs +static sys_socket_t netv4_acceptsocket = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t netv4_controlsocket; +static sys_socket_t netv4_broadcastsocket = INVALID_SOCKET; +static struct sockaddr_in broadcastaddrv4; +static in_addr_t myAddrv4, bindAddrv4; //spike --keeping separate bind and detected values. + +//ipv6 defs +#ifdef IPPROTO_IPV6 +typedef struct in_addr6 in_addr6_t; +static sys_socket_t netv6_acceptsocket = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t netv6_controlsocket; +static struct sockaddr_in6 broadcastaddrv6; +static in_addr6_t myAddrv6, bindAddrv6; +#ifndef IPV6_V6ONLY + #define IPV6_V6ONLY 27 +#endif +#endif + +//getaddrinfo was added with winxp, but earlier versions don't do ipv6. +//we don't use hybrid sockets, so things are a little easier when it comes to xp vs vista. +//we don't detect the win2k ipv6 tech preview thing. it has different sized addresses, so lets hope microsoft's code handles that if it ever comes up. +int (WSAAPI *qgetaddrinfo)(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res); -static in_addr_t myAddr; #include "net_wins.h" @@ -70,20 +88,20 @@ static INT_PTR PASCAL FAR BlockingHook (void) #endif /* ! _USE_WINSOCK2 */ -static void WINS_GetLocalAddress (void) +static void WINIPv4_GetLocalAddress (void) { struct hostent *local = NULL; char buff[MAXHOSTNAMELEN]; in_addr_t addr; int err; - if (myAddr != INADDR_ANY) + if (myAddrv4 != INADDR_ANY) return; if (gethostname(buff, MAXHOSTNAMELEN) == SOCKET_ERROR) { err = SOCKETERRNO; - Con_SafePrintf("WINS_GetLocalAddress: gethostname failed (%s)\n", + Con_SafePrintf("WINIPV4_GetLocalAddress: gethostname failed (%s)\n", socketerror(err)); return; } @@ -100,24 +118,24 @@ static void WINS_GetLocalAddress (void) #endif if (local == NULL) { - Con_SafePrintf("WINS_GetLocalAddress: gethostbyname failed (%s)\n", + Con_SafePrintf("WINIPV4_GetLocalAddress: gethostbyname failed (%s)\n", __WSAE_StrError(err)); return; } - myAddr = *(in_addr_t *)local->h_addr_list[0]; + myAddrv4 = *(in_addr_t *)local->h_addr_list[0]; - addr = ntohl(myAddr); - sprintf(my_tcpip_address, "%ld.%ld.%ld.%ld", (addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff); + addr = ntohl(myAddrv4); + sprintf(my_ipv4_address, "%ld.%ld.%ld.%ld", (addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff); } -sys_socket_t WINS_Init (void) +sys_socket_t WINIPv4_Init (void) { int i, err; char buff[MAXHOSTNAMELEN]; - if (COM_CheckParm ("-noudp")) + if (COM_CheckParm ("-noudp") || COM_CheckParm ("-noudp4")) return -1; if (winsock_initialized == 0) @@ -148,10 +166,10 @@ sys_socket_t WINS_Init (void) { if (i < com_argc-1) { - myAddr = inet_addr(com_argv[i+1]); - if (myAddr == INADDR_NONE) + bindAddrv4 = inet_addr(com_argv[i+1]); + if (bindAddrv4 == INADDR_NONE) Sys_Error ("%s is not a valid IP address", com_argv[i+1]); - strcpy(my_tcpip_address, com_argv[i+1]); + strcpy(my_ipv4_address, com_argv[i+1]); } else { @@ -160,11 +178,13 @@ sys_socket_t WINS_Init (void) } else { - myAddr = INADDR_ANY; - strcpy(my_tcpip_address, "INADDR_ANY"); + bindAddrv4 = INADDR_ANY; + strcpy(my_ipv4_address, "INADDR_ANY"); } - if ((net_controlsocket = WINS_OpenSocket(0)) == INVALID_SOCKET) + myAddrv4 = bindAddrv4; + + if ((netv4_controlsocket = WINIPv4_OpenSocket(0)) == INVALID_SOCKET) { Con_SafePrintf("WINS_Init: Unable to open control socket, UDP disabled\n"); if (--winsock_initialized == 0) @@ -172,51 +192,51 @@ sys_socket_t WINS_Init (void) return INVALID_SOCKET; } - broadcastaddr.sin_family = AF_INET; - broadcastaddr.sin_addr.s_addr = INADDR_BROADCAST; - broadcastaddr.sin_port = htons((unsigned short)net_hostport); + broadcastaddrv4.sin_family = AF_INET; + broadcastaddrv4.sin_addr.s_addr = INADDR_BROADCAST; + broadcastaddrv4.sin_port = htons((unsigned short)net_hostport); - Con_SafePrintf("UDP Initialized\n"); - tcpipAvailable = true; + Con_SafePrintf("IPv4 UDP Initialized\n"); + ipv4Available = true; - return net_controlsocket; + return netv4_controlsocket; } //============================================================================= -void WINS_Shutdown (void) +void WINIPv4_Shutdown (void) { - WINS_Listen (false); - WINS_CloseSocket (net_controlsocket); + WINIPv4_Listen (false); + WINS_CloseSocket (netv4_controlsocket); if (--winsock_initialized == 0) WSACleanup (); } //============================================================================= -void WINS_Listen (qboolean state) +sys_socket_t WINIPv4_Listen (qboolean state) { // enable listening if (state) { - if (net_acceptsocket != INVALID_SOCKET) - return; - WINS_GetLocalAddress(); - if ((net_acceptsocket = WINS_OpenSocket (net_hostport)) == INVALID_SOCKET) - Sys_Error ("WINS_Listen: Unable to open accept socket"); - return; + if (netv4_acceptsocket != INVALID_SOCKET) + return netv4_acceptsocket; + WINIPv4_GetLocalAddress(); + netv4_acceptsocket = WINIPv4_OpenSocket (net_hostport); + return netv4_acceptsocket; } // disable listening - if (net_acceptsocket == INVALID_SOCKET) - return; - WINS_CloseSocket (net_acceptsocket); - net_acceptsocket = INVALID_SOCKET; + if (netv4_acceptsocket == INVALID_SOCKET) + return INVALID_SOCKET; + WINS_CloseSocket (netv4_acceptsocket); + netv4_acceptsocket = INVALID_SOCKET; + return INVALID_SOCKET; } //============================================================================= -sys_socket_t WINS_OpenSocket (int port) +sys_socket_t WINIPv4_OpenSocket (int port) { sys_socket_t newsocket; struct sockaddr_in address; @@ -235,16 +255,16 @@ sys_socket_t WINS_OpenSocket (int port) memset(&address, 0, sizeof(struct sockaddr_in)); address.sin_family = AF_INET; - address.sin_addr.s_addr = myAddr; + address.sin_addr.s_addr = bindAddrv4; address.sin_port = htons((unsigned short)port); if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) return newsocket; - if (tcpipAvailable) + if (ipv4Available) { err = SOCKETERRNO; - Sys_Error ("Unable to bind to %s (%s)", - WINS_AddrToString ((struct qsockaddr *) &address), + Con_Warning ("Unable to bind to %s (%s)\n", + WINS_AddrToString ((struct qsockaddr *) &address, false), socketerror(err)); return INVALID_SOCKET; /* not reached */ } @@ -261,8 +281,8 @@ ErrorReturn: int WINS_CloseSocket (sys_socket_t socketid) { - if (socketid == net_broadcastsocket) - net_broadcastsocket = 0; + if (socketid == netv4_broadcastsocket) + netv4_broadcastsocket = INVALID_SOCKET; return closesocket (socketid); } @@ -317,7 +337,7 @@ static int PartialIPAddress (const char *in, struct qsockaddr *hostaddr) hostaddr->qsa_family = AF_INET; ((struct sockaddr_in *)hostaddr)->sin_port = htons((unsigned short)port); ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = - (myAddr & htonl(mask)) | htonl(addr); + (myAddrv4 & htonl(mask)) | htonl(addr); return 0; } @@ -331,17 +351,17 @@ int WINS_Connect (sys_socket_t socketid, struct qsockaddr *addr) //============================================================================= -sys_socket_t WINS_CheckNewConnections (void) +sys_socket_t WINIPv4_CheckNewConnections (void) { char buf[4096]; - if (net_acceptsocket == INVALID_SOCKET) + if (netv4_acceptsocket == INVALID_SOCKET) return INVALID_SOCKET; - if (recvfrom (net_acceptsocket, buf, sizeof(buf), MSG_PEEK, NULL, NULL) + if (recvfrom (netv4_acceptsocket, buf, sizeof(buf), MSG_PEEK, NULL, NULL) != SOCKET_ERROR) { - return net_acceptsocket; + return netv4_acceptsocket; } return INVALID_SOCKET; } @@ -359,7 +379,10 @@ int WINS_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr int err = SOCKETERRNO; if (err == NET_EWOULDBLOCK || err == NET_ECONNREFUSED) return 0; - Con_SafePrintf ("WINS_Read, recvfrom: %s\n", socketerror(err)); + if (err == WSAECONNRESET) + Con_DPrintf ("WINS_Read, recvfrom: %s (%s)\n", socketerror(err), WINS_AddrToString(addr, false)); + else + Con_SafePrintf ("WINS_Read, recvfrom: %s\n", socketerror(err)); } return ret; } @@ -378,31 +401,31 @@ static int WINS_MakeSocketBroadcastCapable (sys_socket_t socketid) Con_SafePrintf ("UDP, setsockopt: %s\n", socketerror(err)); return -1; } - net_broadcastsocket = socketid; + netv4_broadcastsocket = socketid; return 0; } //============================================================================= -int WINS_Broadcast (sys_socket_t socketid, byte *buf, int len) +int WINIPv4_Broadcast (sys_socket_t socketid, byte *buf, int len) { int ret; - if (socketid != net_broadcastsocket) + if (socketid != netv4_broadcastsocket) { - if (net_broadcastsocket != 0) + if (netv4_broadcastsocket != INVALID_SOCKET) Sys_Error("Attempted to use multiple broadcasts sockets"); - WINS_GetLocalAddress(); + WINIPv4_GetLocalAddress(); ret = WINS_MakeSocketBroadcastCapable (socketid); if (ret == -1) { - Con_Printf("Unable to make socket broadcast capable\n"); + Con_SafePrintf("Unable to make socket broadcast capable\n"); return ret; } } - return WINS_Write (socketid, buf, len, (struct qsockaddr *)&broadcastaddr); + return WINS_Write (socketid, buf, len, (struct qsockaddr *)&broadcastaddrv4); } //============================================================================= @@ -425,21 +448,81 @@ int WINS_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *add //============================================================================= -const char *WINS_AddrToString (struct qsockaddr *addr) +unsigned short ntohs_v6word(struct qsockaddr *addr, int wordnum) { - static char buffer[22]; + unsigned char *ptr = ((struct sockaddr_in6 *)addr)->sin6_addr.s6_addr + wordnum*2; + return (unsigned short)(ptr[0]<<8) | ptr[1]; +} + +const char *WINS_AddrToString (struct qsockaddr *addr, qboolean masked) +{ + static char buffer[64]; int haddr; - haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); - sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, - (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, - ntohs(((struct sockaddr_in *)addr)->sin_port)); +#ifdef IPPROTO_IPV6 + if (addr->qsa_family == AF_INET6) + { + if (masked) + { + q_snprintf(buffer, sizeof(buffer), "[%x:%x:%x:%x::]/64", + ntohs_v6word(addr, 0), + ntohs_v6word(addr, 1), + ntohs_v6word(addr, 2), + ntohs_v6word(addr, 3)); + } + else + { + if (((struct sockaddr_in6 *)addr)->sin6_scope_id) + { + q_snprintf(buffer, sizeof(buffer), "[%x:%x:%x:%x:%x:%x:%x:%x%%%i]:%d", + ntohs_v6word(addr, 0), + ntohs_v6word(addr, 1), + ntohs_v6word(addr, 2), + ntohs_v6word(addr, 3), + ntohs_v6word(addr, 4), + ntohs_v6word(addr, 5), + ntohs_v6word(addr, 6), + ntohs_v6word(addr, 7), + (int)((struct sockaddr_in6 *)addr)->sin6_scope_id, + ntohs(((struct sockaddr_in6 *)addr)->sin6_port)); + } + else + { + q_snprintf(buffer, sizeof(buffer), "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", + ntohs_v6word(addr, 0), + ntohs_v6word(addr, 1), + ntohs_v6word(addr, 2), + ntohs_v6word(addr, 3), + ntohs_v6word(addr, 4), + ntohs_v6word(addr, 5), + ntohs_v6word(addr, 6), + ntohs_v6word(addr, 7), + ntohs(((struct sockaddr_in6 *)addr)->sin6_port)); + } + } + } + else +#endif + { + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + if (masked) + { + sprintf(buffer, "%d.%d.%d.0/24", (haddr >> 24) & 0xff, + (haddr >> 16) & 0xff, (haddr >> 8) & 0xff); + } + else + { + sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, + (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, + ntohs(((struct sockaddr_in *)addr)->sin_port)); + } + } return buffer; } //============================================================================= -int WINS_StringToAddr (const char *string, struct qsockaddr *addr) +int WINIPv4_StringToAddr (const char *string, struct qsockaddr *addr) { int ha1, ha2, ha3, ha4, hp, ipaddr; @@ -457,21 +540,31 @@ int WINS_StringToAddr (const char *string, struct qsockaddr *addr) int WINS_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr) { socklen_t addrlen = sizeof(struct qsockaddr); - in_addr_t a; memset(addr, 0, sizeof(struct qsockaddr)); getsockname(socketid, (struct sockaddr *)addr, &addrlen); - a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; - if (a == 0 || a == htonl(INADDR_LOOPBACK)) - ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + if (addr->qsa_family == AF_INET) + { + in_addr_t a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == htonl(INADDR_LOOPBACK)) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddrv4; + } +#ifdef IPPROTO_IPV6 + if (addr->qsa_family == AF_INET6) + { + static const in_addr6_t in6addr_any;// = IN6ADDR_ANY_INIT; + if (!memcmp(&((struct sockaddr_in6 *)addr)->sin6_addr, &in6addr_any, sizeof(in_addr6_t))) + memcpy(&((struct sockaddr_in6 *)addr)->sin6_addr, &myAddrv6, sizeof(struct sockaddr_in6)); + } +#endif return 0; } //============================================================================= -int WINS_GetNameFromAddr (struct qsockaddr *addr, char *name) +int WINIPv4_GetNameFromAddr (struct qsockaddr *addr, char *name) { struct hostent *hostentry; @@ -483,25 +576,41 @@ int WINS_GetNameFromAddr (struct qsockaddr *addr, char *name) return 0; } - Q_strcpy (name, WINS_AddrToString (addr)); + Q_strcpy (name, WINS_AddrToString (addr, false)); return 0; } //============================================================================= -int WINS_GetAddrFromName (const char *name, struct qsockaddr *addr) +int WINIPv4_GetAddrFromName (const char *name, struct qsockaddr *addr) { struct hostent *hostentry; + char *colon; + unsigned short port = net_hostport; if (name[0] >= '0' && name[0] <= '9') return PartialIPAddress (name, addr); - hostentry = gethostbyname (name); + colon = strrchr(name, ':'); + if (colon) + { + char dupe[MAXHOSTNAMELEN]; + if (colon-name+1 > MAXHOSTNAMELEN) + return -1; + memcpy(dupe, name, colon-name); + dupe[colon-name] = 0; + if (strchr(dupe, ':')) + return -1; //don't resolve a name to an ipv4 address if it has multiple colons in it. its probably an ipx or ipv6 address, and I'd rather not block on any screwed dns resolves + hostentry = gethostbyname (dupe); + port = strtoul(colon+1, NULL, 10); + } + else + hostentry = gethostbyname (name); if (!hostentry) return -1; addr->qsa_family = AF_INET; - ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)net_hostport); + ((struct sockaddr_in *)addr)->sin_port = htons(port); ((struct sockaddr_in *)addr)->sin_addr.s_addr = *(in_addr_t *)hostentry->h_addr_list[0]; @@ -515,13 +624,32 @@ int WINS_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) if (addr1->qsa_family != addr2->qsa_family) return -1; - if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != - ((struct sockaddr_in *)addr2)->sin_addr.s_addr) - return -1; +#ifdef IPPROTO_IPV6 + if (addr1->qsa_family == AF_INET6) + { + if (memcmp(&((struct sockaddr_in6 *)addr1)->sin6_addr, &((struct sockaddr_in6 *)addr2)->sin6_addr, sizeof(((struct sockaddr_in6 *)addr2)->sin6_addr))) + return -1; - if (((struct sockaddr_in *)addr1)->sin_port != - ((struct sockaddr_in *)addr2)->sin_port) - return 1; + if (((struct sockaddr_in6 *)addr1)->sin6_port != + ((struct sockaddr_in6 *)addr2)->sin6_port) + return 1; + if (((struct sockaddr_in6 *)addr1)->sin6_scope_id && + ((struct sockaddr_in6 *)addr2)->sin6_scope_id && + ((struct sockaddr_in6 *)addr1)->sin6_scope_id != + ((struct sockaddr_in6 *)addr2)->sin6_scope_id) //the ipv6 scope id is for use with link-local addresses, to identify the specific interface. + return 1; + } + else +#endif + { + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != + ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != + ((struct sockaddr_in *)addr2)->sin_port) + return 1; + } return 0; } @@ -530,15 +658,358 @@ int WINS_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) int WINS_GetSocketPort (struct qsockaddr *addr) { - return ntohs(((struct sockaddr_in *)addr)->sin_port); +#ifdef IPPROTO_IPV6 + if (addr->qsa_family == AF_INET6) + return ntohs(((struct sockaddr_in6 *)addr)->sin6_port); + else +#endif + return ntohs(((struct sockaddr_in *)addr)->sin_port); } int WINS_SetSocketPort (struct qsockaddr *addr, int port) { - ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)port); +#ifdef IPPROTO_IPV6 + if (addr->qsa_family == AF_INET6) + ((struct sockaddr_in6 *)addr)->sin6_port = htons((unsigned short)port); + else +#endif + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)port); return 0; } //============================================================================= + +#ifdef IPPROTO_IPV6 +//winxp (and possibly win2k) is dual stack. +//vista+ has a hybrid stack + +static void WINIPv6_GetLocalAddress (void) +{ + char buff[MAXHOSTNAMELEN]; + int err; + struct addrinfo hints, *local = NULL; + +// if (myAddrv6 != IN6ADDR_ANY) +// return; + + if (gethostname(buff, MAXHOSTNAMELEN) == SOCKET_ERROR) + { + err = SOCKETERRNO; + Con_SafePrintf("WINIPv6_GetLocalAddress: gethostname failed (%s)\n", + socketerror(err)); + return; + } + buff[MAXHOSTNAMELEN - 1] = 0; + +#ifndef _USE_WINSOCK2 + blocktime = Sys_DoubleTime(); + WSASetBlockingHook(BlockingHook); +#endif + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + if (qgetaddrinfo && qgetaddrinfo(buff, NULL, &hints, &local) == 0) + { + size_t l; + q_strlcpy(my_ipv6_address, WINS_AddrToString((struct qsockaddr*)local->ai_addr, false), sizeof(my_ipv6_address)); + l = strlen(my_ipv6_address); + if (l > 2 && !strcmp(my_ipv6_address+l-2, ":0")) + my_ipv6_address[l-2] = 0; + freeaddrinfo(local); + } + err = WSAGetLastError(); +#ifndef _USE_WINSOCK2 + WSAUnhookBlockingHook(); +#endif + + if (local == NULL) + { + Con_SafePrintf("WINIPv6_GetLocalAddress: gethostbyname failed (%s)\n", + __WSAE_StrError(err)); + return; + } +} + +sys_socket_t WINIPv6_Init (void) +{ + int i; + char buff[MAXHOSTNAMELEN]; + + if (COM_CheckParm ("-noudp") || COM_CheckParm ("-noudp6")) + return -1; + + qgetaddrinfo = (void*)GetProcAddress(GetModuleHandle("ws2_32.dll"), "getaddrinfo"); + if (!qgetaddrinfo) + { + Con_SafePrintf("Winsock lacks getaddrinfo, ipv6 support is unavailable.\n"); + return INVALID_SOCKET; + } + + if (winsock_initialized == 0) + { + int err = WSAStartup(MAKEWORD(2,2), &winsockdata); + if (err != 0) + { + Con_SafePrintf("Winsock initialization failed (%s)\n", + socketerror(err)); + return INVALID_SOCKET; + } + } + winsock_initialized++; + + // determine my name & address + if (gethostname(buff, MAXHOSTNAMELEN) != 0) + { + int err = SOCKETERRNO; + Con_SafePrintf("WINIPv6_Init: gethostname failed (%s)\n", + socketerror(err)); + } + else + { + buff[MAXHOSTNAMELEN - 1] = 0; + } + i = COM_CheckParm ("-ip6"); + if (i) + { + if (i < com_argc-1) + { + if (WINIPv6_GetAddrFromName(com_argv[i+1], (struct qsockaddr*)&bindAddrv6)) + Sys_Error ("%s is not a valid IPv6 address", com_argv[i+1]); + if (!*my_ipv6_address) + strcpy(my_ipv6_address, com_argv[i+1]); + } + else + { + Sys_Error ("WINIPv6_Init: you must specify an IP address after -ip"); + } + } + else + { + memset(&bindAddrv6, 0, sizeof(bindAddrv6)); + if (!*my_ipv6_address) + { + strcpy(my_ipv6_address, "[::]"); + WINIPv6_GetLocalAddress(); + } + } + + myAddrv6 = bindAddrv6; + + if ((netv6_controlsocket = WINIPv6_OpenSocket(0)) == INVALID_SOCKET) + { + Con_SafePrintf("WINIPv6_Init: Unable to open control socket, UDP disabled\n"); + if (--winsock_initialized == 0) + WSACleanup (); + return INVALID_SOCKET; + } + + broadcastaddrv6.sin6_family = AF_INET6; + memset(&broadcastaddrv6.sin6_addr, 0, sizeof(broadcastaddrv6.sin6_addr)); + broadcastaddrv6.sin6_addr.s6_addr[0] = 0xff; + broadcastaddrv6.sin6_addr.s6_addr[1] = 0x03; + broadcastaddrv6.sin6_addr.s6_addr[15] = 0x01; + broadcastaddrv6.sin6_port = htons((unsigned short)net_hostport); + + Con_SafePrintf("IPv6 UDP Initialized\n"); + ipv6Available = true; + + return netv6_controlsocket; +} + +sys_socket_t WINIPv6_Listen (qboolean state) +{ + if (state) + { + // enable listening + if (netv6_acceptsocket == INVALID_SOCKET) + netv6_acceptsocket = WINIPv6_OpenSocket (net_hostport); + } + else + { + // disable listening + if (netv6_acceptsocket != INVALID_SOCKET) + { + WINS_CloseSocket (netv6_acceptsocket); + netv6_acceptsocket = INVALID_SOCKET; + } + } + return netv6_acceptsocket; +} +void WINIPv6_Shutdown (void) +{ + WINIPv6_Listen(false); + WINS_CloseSocket (netv6_controlsocket); + if (--winsock_initialized == 0) + WSACleanup (); +} +sys_socket_t WINIPv6_OpenSocket (int port) +{ + sys_socket_t newsocket; + struct sockaddr_in6 address; + u_long _true = 1; + int err; + + if ((newsocket = socket (PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + err = SOCKETERRNO; + Con_SafePrintf("WINS_OpenSocket: %s\n", socketerror(err)); + return INVALID_SOCKET; + } + + setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof(_true)); + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + goto ErrorReturn; + + memset(&address, 0, sizeof(address)); + address.sin6_family = AF_INET6; + address.sin6_addr = bindAddrv6; + address.sin6_port = htons((unsigned short)port); + if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) + { + //we don't know if we're the server or not. oh well. + struct ipv6_mreq req; + req.ipv6mr_multiaddr = broadcastaddrv6.sin6_addr; + req.ipv6mr_interface = 0; + setsockopt(newsocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&req, sizeof(req)); + + + return newsocket; + } + + if (ipv6Available) + { + err = SOCKETERRNO; + Con_Warning ("Unable to bind to %s (%s)\n", + WINS_AddrToString ((struct qsockaddr *) &address, false), + socketerror(err)); + return INVALID_SOCKET; /* not reached */ + } + /* else: we are still in init phase, no need to error */ + +ErrorReturn: + err = SOCKETERRNO; + Con_SafePrintf("WINS_OpenSocket: %s\n", socketerror(err)); + closesocket (newsocket); + return INVALID_SOCKET; +} +sys_socket_t WINIPv6_CheckNewConnections (void) +{ + char buf[4096]; + + if (netv6_acceptsocket == INVALID_SOCKET) + return INVALID_SOCKET; + + if (recvfrom (netv6_acceptsocket, buf, sizeof(buf), MSG_PEEK, NULL, NULL) + != SOCKET_ERROR) + { + return netv6_acceptsocket; + } + return INVALID_SOCKET; +} +int WINIPv6_Broadcast (sys_socket_t socketid, byte *buf, int len) +{ + broadcastaddrv6.sin6_port = htons((unsigned short)net_hostport); + return WINS_Write(socketid, buf, len, (struct qsockaddr*)&broadcastaddrv6); +} +int WINIPv6_StringToAddr (const char *string, struct qsockaddr *addr) +{ //This is never actually called... + return -1; +} +int WINIPv6_GetNameFromAddr (struct qsockaddr *addr, char *name) +{ + //FIXME: should really do a reverse dns lookup. + q_strlcpy(name, WINS_AddrToString(addr, false), NET_NAMELEN); + return 0; +} +int WINIPv6_GetAddrFromName (const char *name, struct qsockaddr *addr) +{ + //ipv6 addresses take form of [::1]:26000 or eg localhost:26000. just ::1 is NOT supported, but localhost as-is is okay. [localhost]:26000 is acceptable, but will fail to resolve as ipv4. + struct addrinfo *addrinfo = NULL; + struct addrinfo *pos; + struct addrinfo udp6hint; + int error; + char *port; + char dupbase[256]; + size_t len; + qboolean success = false; + + memset(&udp6hint, 0, sizeof(udp6hint)); + udp6hint.ai_family = 0;//Any... we check for AF_INET6 or 4 + udp6hint.ai_socktype = SOCK_DGRAM; + udp6hint.ai_protocol = IPPROTO_UDP; + + if (*name == '[') + { + port = strstr(name, "]"); + if (!port) + error = EAI_NONAME; + else + { + len = port - (name+1); + if (len >= sizeof(dupbase)) + len = sizeof(dupbase)-1; + strncpy(dupbase, name+1, len); + dupbase[len] = '\0'; + error = qgetaddrinfo?qgetaddrinfo(dupbase, (port[1] == ':')?port+2:NULL, &udp6hint, &addrinfo):EAI_NONAME; + } + } + else + { + port = strrchr(name, ':'); + + if (port) + { + len = port - name; + if (len >= sizeof(dupbase)) + len = sizeof(dupbase)-1; + strncpy(dupbase, name, len); + dupbase[len] = '\0'; + error = qgetaddrinfo?qgetaddrinfo(dupbase, port+1, &udp6hint, &addrinfo):EAI_NONAME; + } + else + error = EAI_NONAME; + if (error) //failed, try string with no port. + error = qgetaddrinfo?qgetaddrinfo(name, NULL, &udp6hint, &addrinfo):EAI_NONAME; //remember, this func will return any address family that could be using the udp protocol... (ip4 or ip6) + } + + if (!error) + { + ((struct sockaddr*)addr)->sa_family = 0; + for (pos = addrinfo; pos; pos = pos->ai_next) + { + if (0)//pos->ai_family == AF_INET) + { + memcpy(addr, pos->ai_addr, pos->ai_addrlen); + success = true; + break; + } + if (pos->ai_family == AF_INET6 && !success) + { + memcpy(addr, pos->ai_addr, pos->ai_addrlen); + success = true; + } + } + freeaddrinfo (addrinfo); + } + + if (success) + { + if (((struct sockaddr*)addr)->sa_family == AF_INET) + { + if (!((struct sockaddr_in *)addr)->sin_port) + ((struct sockaddr_in *)addr)->sin_port = htons(net_hostport); + } + else if (((struct sockaddr*)addr)->sa_family == AF_INET6) + { + if (!((struct sockaddr_in6 *)addr)->sin6_port) + ((struct sockaddr_in6 *)addr)->sin6_port = htons(net_hostport); + } + return 0; + } + return -1; +} +#endif diff --git a/Quake/net_wins.h b/Quake/net_wins.h index 59abda43..81b92195 100644 --- a/Quake/net_wins.h +++ b/Quake/net_wins.h @@ -22,24 +22,38 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifndef __NET_WINSOCK_H #define __NET_WINSOCK_H -sys_socket_t WINS_Init (void); -void WINS_Shutdown (void); -void WINS_Listen (qboolean state); -sys_socket_t WINS_OpenSocket (int port); int WINS_CloseSocket (sys_socket_t socketid); int WINS_Connect (sys_socket_t socketid, struct qsockaddr *addr); -sys_socket_t WINS_CheckNewConnections (void); int WINS_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); int WINS_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); -int WINS_Broadcast (sys_socket_t socketid, byte *buf, int len); -const char *WINS_AddrToString (struct qsockaddr *addr); -int WINS_StringToAddr (const char *string, struct qsockaddr *addr); +const char *WINS_AddrToString (struct qsockaddr *addr, qboolean masked); int WINS_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); -int WINS_GetNameFromAddr (struct qsockaddr *addr, char *name); -int WINS_GetAddrFromName (const char *name, struct qsockaddr *addr); int WINS_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); int WINS_GetSocketPort (struct qsockaddr *addr); int WINS_SetSocketPort (struct qsockaddr *addr, int port); +sys_socket_t WINIPv4_Init (void); +void WINIPv4_Shutdown (void); +sys_socket_t WINIPv4_Listen (qboolean state); +sys_socket_t WINIPv4_OpenSocket (int port); +sys_socket_t WINIPv4_CheckNewConnections (void); +int WINIPv4_Broadcast (sys_socket_t socketid, byte *buf, int len); +int WINIPv4_StringToAddr (const char *string, struct qsockaddr *addr); +int WINIPv4_GetNameFromAddr (struct qsockaddr *addr, char *name); +int WINIPv4_GetAddrFromName (const char *name, struct qsockaddr *addr); + + +#ifdef IPPROTO_IPV6 +sys_socket_t WINIPv6_Init (void); +void WINIPv6_Shutdown (void); +sys_socket_t WINIPv6_Listen (qboolean state); +sys_socket_t WINIPv6_OpenSocket (int port); +sys_socket_t WINIPv6_CheckNewConnections (void); +int WINIPv6_Broadcast (sys_socket_t socketid, byte *buf, int len); +int WINIPv6_StringToAddr (const char *string, struct qsockaddr *addr); +int WINIPv6_GetNameFromAddr (struct qsockaddr *addr, char *name); +int WINIPv6_GetAddrFromName (const char *name, struct qsockaddr *addr); +#endif + #endif /* __NET_WINSOCK_H */ diff --git a/Quake/net_wipx.c b/Quake/net_wipx.c index 155686ba..64b5e985 100644 --- a/Quake/net_wipx.c +++ b/Quake/net_wipx.c @@ -97,7 +97,7 @@ sys_socket_t WIPX_Init (void) broadcastaddr.sa_socket = htons((unsigned short)net_hostport); WIPX_GetSocketAddr (net_controlsocket, &addr); - Q_strcpy(my_ipx_address, WIPX_AddrToString (&addr)); + Q_strcpy(my_ipx_address, WIPX_AddrToString (&addr, false)); colon = Q_strrchr (my_ipx_address, ':'); if (colon) *colon = 0; @@ -120,23 +120,27 @@ void WIPX_Shutdown (void) //============================================================================= -void WIPX_Listen (qboolean state) +sys_socket_t WIPX_Listen (qboolean state) { - // enable listening if (state) { - if (net_acceptsocket != INVALID_SOCKET) - return; - if ((net_acceptsocket = WIPX_OpenSocket (net_hostport)) == INVALID_SOCKET) - Sys_Error ("WIPX_Listen: Unable to open accept socket"); - return; + // enable listening + if (net_acceptsocket == INVALID_SOCKET) + { + if ((net_acceptsocket = WIPX_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("WIPX_Listen: Unable to open accept socket"); + } } - - // disable listening - if (net_acceptsocket == INVALID_SOCKET) - return; - WIPX_CloseSocket (net_acceptsocket); - net_acceptsocket = INVALID_SOCKET; + else + { + // disable listening + if (net_acceptsocket != INVALID_SOCKET) + { + WIPX_CloseSocket (net_acceptsocket); + net_acceptsocket = INVALID_SOCKET; + } + } + return net_acceptsocket; } //============================================================================= @@ -300,23 +304,36 @@ int WIPX_Write (sys_socket_t handle, byte *buf, int len, struct qsockaddr *addr) //============================================================================= -const char *WIPX_AddrToString (struct qsockaddr *addr) +const char *WIPX_AddrToString (struct qsockaddr *addr, qboolean masked) { static char buf[28]; - sprintf(buf, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%u", - ((struct sockaddr_ipx *)addr)->sa_netnum[0] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_netnum[1] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_netnum[2] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_netnum[3] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_nodenum[0] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_nodenum[1] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_nodenum[2] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_nodenum[3] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_nodenum[4] & 0xff, - ((struct sockaddr_ipx *)addr)->sa_nodenum[5] & 0xff, - ntohs(((struct sockaddr_ipx *)addr)->sa_socket) - ); + if (masked) + { + sprintf(buf, "%02x%02x%02x%02x:??""??""??""??""??""??:%u", + ((struct sockaddr_ipx *)addr)->sa_netnum[0] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[1] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[2] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[3] & 0xff, + ntohs(((struct sockaddr_ipx *)addr)->sa_socket) + ); + } + else + { + sprintf(buf, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%u", + ((struct sockaddr_ipx *)addr)->sa_netnum[0] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[1] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[2] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[3] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[0] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[1] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[2] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[3] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[4] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[5] & 0xff, + ntohs(((struct sockaddr_ipx *)addr)->sa_socket) + ); + } return buf; } @@ -379,7 +396,7 @@ int WIPX_GetSocketAddr (sys_socket_t handle, struct qsockaddr *addr) int WIPX_GetNameFromAddr (struct qsockaddr *addr, char *name) { - Q_strcpy(name, WIPX_AddrToString(addr)); + Q_strcpy(name, WIPX_AddrToString(addr, false)); return 0; } diff --git a/Quake/net_wipx.h b/Quake/net_wipx.h index e8a24d3c..804b0e4d 100644 --- a/Quake/net_wipx.h +++ b/Quake/net_wipx.h @@ -24,7 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. sys_socket_t WIPX_Init (void); void WIPX_Shutdown (void); -void WIPX_Listen (qboolean state); +sys_socket_t WIPX_Listen (qboolean state); sys_socket_t WIPX_OpenSocket (int port); int WIPX_CloseSocket (sys_socket_t socketid); int WIPX_Connect (sys_socket_t socketid, struct qsockaddr *addr); @@ -32,7 +32,7 @@ sys_socket_t WIPX_CheckNewConnections (void); int WIPX_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); int WIPX_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); int WIPX_Broadcast (sys_socket_t socketid, byte *buf, int len); -const char *WIPX_AddrToString (struct qsockaddr *addr); +const char *WIPX_AddrToString (struct qsockaddr *addr, qboolean masked); int WIPX_StringToAddr (const char *string, struct qsockaddr *addr); int WIPX_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); int WIPX_GetNameFromAddr (struct qsockaddr *addr, char *name); diff --git a/Quake/pr_cmds.c b/Quake/pr_cmds.c index b43b6635..7cc591e6 100644 --- a/Quake/pr_cmds.c +++ b/Quake/pr_cmds.c @@ -27,17 +27,19 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. static char pr_string_temp[STRINGTEMP_BUFFERS][STRINGTEMP_LENGTH]; static byte pr_string_tempindex = 0; -static char *PR_GetTempString (void) +char *PR_GetTempString (void) { return pr_string_temp[(STRINGTEMP_BUFFERS-1) & ++pr_string_tempindex]; } #define RETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e)) -#define MSG_BROADCAST 0 // unreliable to all -#define MSG_ONE 1 // reliable to one (msg_entity) -#define MSG_ALL 2 // reliable to all -#define MSG_INIT 3 // write to the init string +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_EXT_MULTICAST 4 // temporary buffer that can be splurged more reliably / with more control. +#define MSG_EXT_ENTITY 5 // for csqc networking. we don't actually support this. I'm just defining it for completeness. /* =============================================================================== @@ -47,7 +49,7 @@ static char *PR_GetTempString (void) =============================================================================== */ -static char *PF_VarString (int first) +char *PF_VarString (int first) { int i; static char out[1024]; @@ -287,7 +289,20 @@ static void PF_setmodel (void) if (!*check) { - PR_RunError ("no precache: %s", m); + if (pr_checkextension.value) + { //Spike: so that func_illusionaries work with custom models even in vanilla. + if (sv.state == ss_loading) + Con_DWarning("PF_setmodel(\"%s\"): Model was not precached\n", m); + else + { +// PR_PrintStatement(pr_statements + pr_xstatement); +// PR_StackTrace(); + Con_Warning("PF_setmodel(\"%s\"): Model was not precached\n", m); + } + i = SV_Precache_Model(m); + } + else + PR_RunError ("no precache: %s", m); } e->v.model = PR_SetEngineString(*check); e->v.modelindex = i; //SV_ModelIndex (m); @@ -511,13 +526,15 @@ PF_Random Returns a number from 0 <= num < 1 random() + +bug: vanilla could return 1, contrary to the (unchanged) comment just above. ================= */ static void PF_random (void) { float num; - num = (rand() & 0x7fff) / ((float)0x7fff); + num = (rand() & 0x7fff) / ((float)0x8000); G_FLOAT(OFS_RETURN) = num; } @@ -554,8 +571,8 @@ static void PF_ambientsound (void) const char *samp, **check; float *pos; float vol, attenuation; - int i, soundnum; - int large = false; //johnfitz -- PROTOCOL_FITZQUAKE + int soundnum; + struct ambientsound_s *st; pos = G_VECTOR (OFS_PARM0); samp = G_STRING(OFS_PARM1); @@ -575,38 +592,22 @@ static void PF_ambientsound (void) return; } - //johnfitz -- PROTOCOL_FITZQUAKE - if (soundnum > 255) + //generate data to splurge on a per-client basis in SV_SendAmbientSounds + if (sv.num_ambients == sv.max_ambients) { - if (sv.protocol == PROTOCOL_NETQUAKE) - return; //don't send any info protocol can't support - else - large = true; + int nm = sv.max_ambients + 128; + struct ambientsound_s *n = (nm*sizeof(*n)origin); + st->soundindex = soundnum; + st->volume = vol; + st->attenuation = attenuation; } /* @@ -638,6 +639,7 @@ static void PF_sound (void) volume = G_FLOAT(OFS_PARM3) * 255; attenuation = G_FLOAT(OFS_PARM4); +/* Spike -- these checks are redundant if (volume < 0 || volume > 255) Host_Error ("SV_StartSound: volume = %i", volume); @@ -646,8 +648,8 @@ static void PF_sound (void) if (channel < 0 || channel > 7) Host_Error ("SV_StartSound: channel = %i", channel); - - SV_StartSound (entity, channel, sample, volume, attenuation); +*/ + SV_StartSound (entity, NULL, channel, sample, volume, attenuation); } /* @@ -1075,29 +1077,70 @@ static void PF_precache_file (void) G_INT(OFS_RETURN) = G_INT(OFS_PARM0); } -static void PF_precache_sound (void) -{ - const char *s; +int SV_Precache_Sound(const char *s) +{ //must be a persistent string. int i; - if (sv.state != ss_loading) - PR_RunError ("PF_Precache_*: Precache can only be done in spawn functions"); - - s = G_STRING(OFS_PARM0); - G_INT(OFS_RETURN) = G_INT(OFS_PARM0); - PR_CheckEmptyString (s); - for (i = 0; i < MAX_SOUNDS; i++) { if (!sv.sound_precache[i]) { + if (sv.state != ss_loading) //spike -- moved this so that there's no actual error any more. + { + Con_Warning("PF_precache_sound(\"%s\"): Precache should only be done in spawn functions\n", s); + //let existing clients know about it + MSG_WriteByte(&sv.reliable_datagram, svcdp_precache); + MSG_WriteShort(&sv.reliable_datagram, i|0x8000); + MSG_WriteString(&sv.reliable_datagram, s); + } sv.sound_precache[i] = s; - return; + return i; } if (!strcmp(sv.sound_precache[i], s)) - return; + { + if (sv.state != ss_loading && !pr_checkextension.value) + Con_Warning("PF_precache_sound(\"%s\"): Precache should only be done in spawn functions\n", s); + return i; + } } - PR_RunError ("PF_precache_sound: overflow"); + return 0; +} + +static void PF_precache_sound (void) +{ + const char *s; + + s = G_STRING(OFS_PARM0); + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); + PR_CheckEmptyString (s); + + if (!SV_Precache_Sound(s)) + PR_RunError ("PF_precache_sound: overflow"); +} + +int SV_Precache_Model(const char *s) +{ + size_t i; + for (i = 0; i < MAX_MODELS; i++) + { + if (!sv.model_precache[i]) + { + if (sv.state != ss_loading) + { + //let existing clients know about it + MSG_WriteByte(&sv.reliable_datagram, svcdp_precache); + MSG_WriteShort(&sv.reliable_datagram, i|0x8000); + MSG_WriteString(&sv.reliable_datagram, s); + } + + sv.model_precache[i] = s; + sv.models[i] = Mod_ForName (s, i==1); + return i; + } + if (!strcmp(sv.model_precache[i], s)) + return i; + } + return 0; } static void PF_precache_model (void) @@ -1105,9 +1148,6 @@ static void PF_precache_model (void) const char *s; int i; - if (sv.state != ss_loading) - PR_RunError ("PF_Precache_*: Precache can only be done in spawn functions"); - s = G_STRING(OFS_PARM0); G_INT(OFS_RETURN) = G_INT(OFS_PARM0); PR_CheckEmptyString (s); @@ -1116,12 +1156,25 @@ static void PF_precache_model (void) { if (!sv.model_precache[i]) { + if (sv.state != ss_loading) + { + Con_Warning ("PF_precache_model(\"%s\"): Precache should only be done in spawn functions\n", s); + //let existing clients know about it + MSG_WriteByte(&sv.reliable_datagram, svcdp_precache); + MSG_WriteShort(&sv.reliable_datagram, i|0x8000); + MSG_WriteString(&sv.reliable_datagram, s); + } + sv.model_precache[i] = s; - sv.models[i] = Mod_ForName (s, true); + sv.models[i] = Mod_ForName (s, i==1); return; } if (!strcmp(sv.model_precache[i], s)) + { + if (sv.state != ss_loading && !pr_checkextension.value) + Con_Warning ("PF_precache_model(\"%s\"): Precache should only be done in spawn functions\n", s); return; + } } PR_RunError ("PF_precache_model: overflow"); } @@ -1475,7 +1528,7 @@ MESSAGE WRITING =============================================================================== */ -static sizebuf_t *WriteDest (void) +sizebuf_t *WriteDest (void) { int entnum; int dest; @@ -1500,6 +1553,9 @@ static sizebuf_t *WriteDest (void) case MSG_INIT: return &sv.signon; + case MSG_EXT_MULTICAST: + return &sv.multicast; + default: PR_RunError ("WriteDest: bad destination"); break; @@ -1543,78 +1599,37 @@ static void PF_WriteString (void) MSG_WriteString (WriteDest(), G_STRING(OFS_PARM1)); } +#define MSG_WriteEntity MSG_WriteShort //fixme - replacement deltas encodes 0x8000+ in 24 bits static void PF_WriteEntity (void) { - MSG_WriteShort (WriteDest(), G_EDICTNUM(OFS_PARM1)); + MSG_WriteEntity (WriteDest(), G_EDICTNUM(OFS_PARM1)); } //============================================================================= static void PF_makestatic (void) { + entity_state_t *st; edict_t *ent; - int i; - int bits = 0; //johnfitz -- PROTOCOL_FITZQUAKE ent = G_EDICT(OFS_PARM0); - //johnfitz -- don't send invisible static entities - if (ent->alpha == ENTALPHA_ZERO) { - ED_Free (ent); - return; - } - //johnfitz - - //johnfitz -- PROTOCOL_FITZQUAKE - if (sv.protocol == PROTOCOL_NETQUAKE) + if (sv.num_statics == sv.max_statics) { - if (SV_ModelIndex(PR_GetString(ent->v.model)) & 0xFF00 || (int)(ent->v.frame) & 0xFF00) - { - ED_Free (ent); - return; //can't display the correct model & frame, so don't show it at all - } + int nm = sv.max_statics + 128; + entity_state_t *n = (nm*sizeof(*n)alpha == ENTALPHA_ZERO) + ; //no point else - { - if (SV_ModelIndex(PR_GetString(ent->v.model)) & 0xFF00) - bits |= B_LARGEMODEL; - if ((int)(ent->v.frame) & 0xFF00) - bits |= B_LARGEFRAME; - if (ent->alpha != ENTALPHA_DEFAULT) - bits |= B_ALPHA; - } - - if (bits) - { - MSG_WriteByte (&sv.signon, svc_spawnstatic2); - MSG_WriteByte (&sv.signon, bits); - } - else - MSG_WriteByte (&sv.signon, svc_spawnstatic); - - if (bits & B_LARGEMODEL) - MSG_WriteShort (&sv.signon, SV_ModelIndex(PR_GetString(ent->v.model))); - else - MSG_WriteByte (&sv.signon, SV_ModelIndex(PR_GetString(ent->v.model))); - - if (bits & B_LARGEFRAME) - MSG_WriteShort (&sv.signon, ent->v.frame); - else - MSG_WriteByte (&sv.signon, ent->v.frame); - //johnfitz - - MSG_WriteByte (&sv.signon, ent->v.colormap); - MSG_WriteByte (&sv.signon, ent->v.skin); - for (i = 0; i < 3; i++) - { - MSG_WriteCoord(&sv.signon, ent->v.origin[i], sv.protocolflags); - MSG_WriteAngle(&sv.signon, ent->v.angles[i], sv.protocolflags); - } - - //johnfitz -- PROTOCOL_FITZQUAKE - if (bits & B_ALPHA) - MSG_WriteByte (&sv.signon, ent->alpha); - //johnfitz + sv.num_statics++; // throw the entity away now ED_Free (ent); @@ -1663,11 +1678,30 @@ static void PF_changelevel (void) Cbuf_AddText (va("changelevel %s\n",s)); } -static void PF_Fixme (void) -{ - PR_RunError ("unimplemented builtin"); -} +void PF_Fixme (void); +//{ +// PR_RunError ("unimplemented builtin"); +//} +void PR_spawnfunc_misc_model(edict_t *self) +{ + eval_t *val; + if (!self->v.model && (val = GetEdictFieldValue(self, ED_FindFieldOffset("mdl")))) + self->v.model = val->string; + if (!*PR_GetString(self->v.model)) //must have a model, because otherwise various things will assume its not valid at all. + self->v.model = PR_SetEngineString("*null"); + + if (self->v.angles[1] < 0) //mimic AD. shame there's no avelocity clientside. + self->v.angles[1] = (rand()*(360.0f/RAND_MAX)); + + //make sure the model is precached, to avoid errors. + G_INT(OFS_PARM0) = self->v.model; + PF_precache_model(); + + //and lets just call makestatic instead of worrying if it'll interfere with the rest of the qc. + G_INT(OFS_PARM0) = EDICT_TO_PROG(self); + PF_makestatic(); +} static builtin_t pr_builtin[] = { diff --git a/Quake/pr_comp.h b/Quake/pr_comp.h index bc6c62e1..e4b29a8b 100644 --- a/Quake/pr_comp.h +++ b/Quake/pr_comp.h @@ -37,7 +37,9 @@ typedef enum ev_entity, ev_field, ev_function, - ev_pointer + ev_pointer, + + ev_ext_integer } etype_t; #define OFS_NULL 0 diff --git a/Quake/pr_edict.c b/Quake/pr_edict.c index c52f7c0e..354a8cf1 100644 --- a/Quake/pr_edict.c +++ b/Quake/pr_edict.c @@ -23,6 +23,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" +struct pr_extfields_s pr_extfields; + dprograms_t *progs; dfunction_t *pr_functions; @@ -31,11 +33,10 @@ static int pr_stringssize; static const char **pr_knownstrings; static int pr_maxknownstrings; static int pr_numknownstrings; -static ddef_t *pr_fielddefs; +static int pr_freeknownstrings; +ddef_t *pr_fielddefs; static ddef_t *pr_globaldefs; -qboolean pr_alpha_supported; //johnfitz - dstatement_t *pr_statements; globalvars_t *pr_global_struct; float *pr_globals; // same as pr_global_struct @@ -55,21 +56,7 @@ int type_size[8] = { }; static ddef_t *ED_FieldAtOfs (int ofs); -static qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s); - -#define MAX_FIELD_LEN 64 -#define GEFV_CACHESIZE 2 - -typedef struct { - ddef_t *pcache; - char field[MAX_FIELD_LEN]; -} gefv_cache; - -static gefv_cache gefvCache[GEFV_CACHESIZE] = -{ - { NULL, "" }, - { NULL, "" } -}; +qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s); cvar_t nomonsters = {"nomonsters", "0", CVAR_NONE}; cvar_t gamecfg = {"gamecfg", "0", CVAR_NONE}; @@ -207,7 +194,7 @@ static ddef_t *ED_FieldAtOfs (int ofs) ED_FindField ============ */ -static ddef_t *ED_FindField (const char *name) +ddef_t *ED_FindField (const char *name) { ddef_t *def; int i; @@ -221,13 +208,22 @@ static ddef_t *ED_FindField (const char *name) return NULL; } +/* +*/ +int ED_FindFieldOffset (const char *name) +{ + ddef_t *def = ED_FindField(name); + if (!def) + return -1; + return def->ofs; +} /* ============ ED_FindGlobal ============ */ -static ddef_t *ED_FindGlobal (const char *name) +ddef_t *ED_FindGlobal (const char *name) { ddef_t *def; int i; @@ -247,7 +243,7 @@ static ddef_t *ED_FindGlobal (const char *name) ED_FindFunction ============ */ -static dfunction_t *ED_FindFunction (const char *fn_name) +dfunction_t *ED_FindFunction (const char *fn_name) { dfunction_t *func; int i; @@ -266,35 +262,12 @@ static dfunction_t *ED_FindFunction (const char *fn_name) GetEdictFieldValue ============ */ -eval_t *GetEdictFieldValue(edict_t *ed, const char *field) +eval_t *GetEdictFieldValue(edict_t *ed, int fldofs) { - ddef_t *def = NULL; - int i; - static int rep = 0; - - for (i = 0; i < GEFV_CACHESIZE; i++) - { - if (!strcmp(field, gefvCache[i].field)) - { - def = gefvCache[i].pcache; - goto Done; - } - } - - def = ED_FindField (field); - - if (strlen(field) < MAX_FIELD_LEN) - { - gefvCache[rep].pcache = def; - strcpy (gefvCache[rep].field, field); - rep ^= 1; - } - -Done: - if (!def) + if (fldofs < 0) return NULL; - return (eval_t *)((char *)&ed->v + def->ofs*4); + return (eval_t *)((char *)&ed->v + fldofs*4); } @@ -336,6 +309,9 @@ static const char *PR_ValueString (int type, eval_t *val) case ev_float: sprintf (line, "%5.1f", val->_float); break; + case ev_ext_integer: + sprintf (line, "%i", val->_int); + break; case ev_vector: sprintf (line, "'%5.1f %5.1f %5.1f'", val->vector[0], val->vector[1], val->vector[2]); break; @@ -359,7 +335,7 @@ Returns a string describing *data in a type specific manner Easier to parse than PR_ValueString ============= */ -static const char *PR_UglyValueString (int type, eval_t *val) +const char *PR_UglyValueString (int type, eval_t *val) { static char line[1024]; ddef_t *def; @@ -389,6 +365,9 @@ static const char *PR_UglyValueString (int type, eval_t *val) case ev_float: q_snprintf (line, sizeof(line), "%f", val->_float); break; + case ev_ext_integer: + sprintf (line, "%i", val->_int); + break; case ev_vector: q_snprintf (line, sizeof(line), "%f %f %f", val->vector[0], val->vector[1], val->vector[2]); break; @@ -554,7 +533,7 @@ void ED_Write (FILE *f, edict_t *ed) } //johnfitz -- save entity alpha manually when progs.dat doesn't know about alpha - if (!pr_alpha_supported && ed->alpha != ENTALPHA_DEFAULT) + if (pr_extfields.alpha<0 && ed->alpha != ENTALPHA_DEFAULT) fprintf (f, "\"alpha\" \"%f\"\n", ENTALPHA_TOSAVE(ed->alpha)); //johnfitz @@ -771,7 +750,7 @@ Can parse either fields or globals returns false if error ============= */ -static qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s) +qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s) { int i; char string[128]; @@ -793,6 +772,10 @@ static qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s) *(float *)d = atof (s); break; + case ev_ext_integer: + *(int *)d = atoi (s); + break; + case ev_vector: q_strlcpy (string, s, sizeof(string)); end = (char *)string + strlen(string); @@ -919,18 +902,54 @@ const char *ED_ParseEdict (const char *data, edict_t *ent) // keynames with a leading underscore are used for utility comments, // and are immediately discarded by quake if (keyname[0] == '_') + { + //spike -- hacks to support func_illusionary with all sorts of mdls, and various particle effects + if (!strcmp(keyname, "_precache_model") && sv.state == ss_loading) + SV_Precache_Model(PR_GetString(ED_NewString(com_token))); + else if (!strcmp(keyname, "_precache_sound") && sv.state == ss_loading) + SV_Precache_Sound(PR_GetString(ED_NewString(com_token))); + //spike continue; + } //johnfitz -- hack to support .alpha even when progs.dat doesn't know about it if (!strcmp(keyname, "alpha")) ent->alpha = ENTALPHA_ENCODE(atof(com_token)); //johnfitz + //spike -- hacks to support func_illusionary/info_notnull with all sorts of mdls, and various particle effects + if (!strcmp(keyname, "modelindex") && sv.state == ss_loading) + { + //"model" "progs/foobar.mdl" + //"modelindex" "progs/foobar.mdl" + //"mins" "-16 -16 -16" + //"maxs" "16 16 16" + char *e; + strtol(com_token, &e, 0); + if (e != com_token && *e) + ent->v.modelindex = SV_Precache_Model(PR_GetString(ED_NewString(com_token))); + } + //spike + key = ED_FindField (keyname); if (!key) { +#ifdef PSET_SCRIPT + eval_t *val; + if (!strcmp(keyname, "traileffect") && sv.state == ss_loading) + { + if ((val = GetEdictFieldValue(ent, pr_extfields.traileffectnum))) + val->_float = PF_SV_ForceParticlePrecache(com_token); + } + else if (!strcmp(keyname, "emiteffect") && sv.state == ss_loading) + { + if ((val = GetEdictFieldValue(ent, pr_extfields.emiteffectnum))) + val->_float = PF_SV_ForceParticlePrecache(com_token); + } //johnfitz -- HACK -- suppress error becuase fog/sky/alpha fields might not be mentioned in defs.qc - if (strncmp(keyname, "sky", 3) && strcmp(keyname, "fog") && strcmp(keyname, "alpha")) + else +#endif + if (strncmp(keyname, "sky", 3) && strcmp(keyname, "fog") && strcmp(keyname, "alpha")) Con_DPrintf ("\"%s\" is not a field\n", keyname); //johnfitz -- was Con_Printf continue; } @@ -973,6 +992,7 @@ void ED_LoadFromFile (const char *data) dfunction_t *func; edict_t *ent = NULL; int inhibit = 0; + int usingspawnfunc = 0; pr_global_struct->time = sv.time; @@ -1023,13 +1043,27 @@ void ED_LoadFromFile (const char *data) } // look for the spawn function - func = ED_FindFunction ( PR_GetString(ent->v.classname) ); + // + func = ED_FindFunction (va("spawnfunc_%s", PR_GetString(ent->v.classname))); + if (func) + { + if (!usingspawnfunc++) + Con_DPrintf2 ("Using DP_SV_SPAWNFUNC_PREFIX\n"); + } + else + func = ED_FindFunction ( PR_GetString(ent->v.classname) ); if (!func) { - Con_SafePrintf ("No spawn function for:\n"); //johnfitz -- was Con_Printf - ED_Print (ent); - ED_Free (ent); + const char *classname = PR_GetString(ent->v.classname); + if (!strcmp(classname, "misc_model")) + PR_spawnfunc_misc_model(ent); + else + { + Con_SafePrintf ("No spawn function for:\n"); //johnfitz -- was Con_Printf + ED_Print (ent); + ED_Free (ent); + } continue; } @@ -1049,10 +1083,9 @@ PR_LoadProgs void PR_LoadProgs (void) { int i; + unsigned int u; - // flush the non-C variable lookup cache - for (i = 0; i < GEFV_CACHESIZE; i++) - gefvCache[i].field[0] = 0; + PR_ShutdownExtensions(); CRC_Init (&pr_crc); @@ -1080,6 +1113,7 @@ void PR_LoadProgs (void) // initialize the strings pr_numknownstrings = 0; + pr_freeknownstrings = 0; pr_maxknownstrings = 0; pr_stringssize = progs->numstrings; if (pr_knownstrings) @@ -1120,7 +1154,8 @@ void PR_LoadProgs (void) pr_globaldefs[i].s_name = LittleLong (pr_globaldefs[i].s_name); } - pr_alpha_supported = false; //johnfitz + for (u = 0; u < sizeof(pr_extfields)/sizeof(int); u++) + ((int*)&pr_extfields)[u] = -1; for (i = 0; i < progs->numfielddefs; i++) { @@ -1129,22 +1164,46 @@ void PR_LoadProgs (void) Host_Error ("PR_LoadProgs: pr_fielddefs[i].type & DEF_SAVEGLOBAL"); pr_fielddefs[i].ofs = LittleShort (pr_fielddefs[i].ofs); pr_fielddefs[i].s_name = LittleLong (pr_fielddefs[i].s_name); - - //johnfitz -- detect alpha support in progs.dat - if (!strcmp(pr_strings + pr_fielddefs[i].s_name,"alpha")) - pr_alpha_supported = true; - //johnfitz } for (i = 0; i < progs->numglobals; i++) ((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]); - pr_edict_size = progs->entityfields * 4 + sizeof(edict_t) - sizeof(entvars_t); + //spike: detect extended fields from progs + pr_extfields.items2 = ED_FindFieldOffset("items2"); + pr_extfields.gravity = ED_FindFieldOffset("gravity"); + pr_extfields.alpha = ED_FindFieldOffset("alpha"); + pr_extfields.movement = ED_FindFieldOffset("movement"); + pr_extfields.traileffectnum = ED_FindFieldOffset("traileffectnum"); + pr_extfields.emiteffectnum = ED_FindFieldOffset("emiteffectnum"); + pr_extfields.viewmodelforclient = ED_FindFieldOffset("viewmodelforclient"); + pr_extfields.scale = ED_FindFieldOffset("scale"); + pr_extfields.colormod = ED_FindFieldOffset("colormod"); + pr_extfields.tag_entity = ED_FindFieldOffset("tag_entity"); + pr_extfields.tag_index = ED_FindFieldOffset("tag_index"); + pr_extfields.button3 = ED_FindFieldOffset("button3"); + pr_extfields.button4 = ED_FindFieldOffset("button4"); + pr_extfields.button5 = ED_FindFieldOffset("button5"); + pr_extfields.button6 = ED_FindFieldOffset("button6"); + pr_extfields.button7 = ED_FindFieldOffset("button7"); + pr_extfields.button8 = ED_FindFieldOffset("button8"); + pr_extfields.viewzoom = ED_FindFieldOffset("viewzoom"); + pr_extfields.modelflags = ED_FindFieldOffset("modelflags"); + + i = progs->entityfields; + if (pr_extfields.emiteffectnum < 0) + pr_extfields.emiteffectnum = i++; + if (pr_extfields.traileffectnum < 0) + pr_extfields.traileffectnum = i++; + + pr_edict_size = i * 4 + sizeof(edict_t) - sizeof(entvars_t); // round off to next highest whole word address (esp for Alpha) // this ensures that pointers in the engine data area are always // properly aligned pr_edict_size += sizeof(void *) - 1; pr_edict_size &= ~(sizeof(void *) - 1); + + PR_EnableExtensions(pr_globaldefs); } @@ -1159,6 +1218,7 @@ void PR_Init (void) Cmd_AddCommand ("edicts", ED_PrintEdicts); Cmd_AddCommand ("edictcount", ED_Count); Cmd_AddCommand ("profile", PR_Profile_f); + Cmd_AddCommand ("pr_dumpplatform", PR_DumpPlatform_f); Cvar_RegisterVariable (&nomonsters); Cvar_RegisterVariable (&gamecfg); Cvar_RegisterVariable (&scratch1); @@ -1219,11 +1279,23 @@ const char *PR_GetString (int num) } else { + return pr_strings; Host_Error("PR_GetString: invalid string offset %d\n", num); return ""; } } +void PR_ClearEngineString(int num) +{ + if (num < 0 && num >= -pr_numknownstrings) + { + num = -1 - num; + pr_knownstrings[num] = NULL; + if (pr_freeknownstrings > num) + pr_freeknownstrings = num; + } +} + int PR_SetEngineString (const char *s) { int i; @@ -1244,19 +1316,22 @@ int PR_SetEngineString (const char *s) } // new unknown engine string //Con_DPrintf ("PR_SetEngineString: new engine string %p\n", s); -#if 0 - for (i = 0; i < pr_numknownstrings; i++) + for (i = pr_freeknownstrings; ; i++) { - if (!pr_knownstrings[i]) - break; + if (i < pr_numknownstrings) + { + if (pr_knownstrings[i]) + continue; + } + else + { + if (i >= pr_maxknownstrings) + PR_AllocStringSlots(); + pr_numknownstrings++; + } + break; } -#endif -// if (i >= pr_numknownstrings) -// { - if (i >= pr_maxknownstrings) - PR_AllocStringSlots(); - pr_numknownstrings++; -// } + pr_freeknownstrings = i+1; pr_knownstrings[i] = s; return -1 - i; } diff --git a/Quake/pr_exec.c b/Quake/pr_exec.c index ad9503e1..540c579f 100644 --- a/Quake/pr_exec.c +++ b/Quake/pr_exec.c @@ -371,6 +371,8 @@ void PR_ExecuteProgram (func_t fnum) f = &pr_functions[fnum]; + //FIXME: if this is a builtin, then we're going to crash. + pr_trace = false; // make a stack frame @@ -383,7 +385,7 @@ void PR_ExecuteProgram (func_t fnum) { st++; /* next statement */ - if (++profile > 100000) + if (++profile > 10000000) //spike -- was 100000 { pr_xstatement = st - pr_statements; PR_RunError("runaway loop error"); @@ -612,7 +614,7 @@ void PR_ExecuteProgram (func_t fnum) { // Built-in function int i = -newf->first_statement; if (i >= pr_numbuiltins) - PR_RunError("Bad builtin call number %d", i); + i = 0; //just invoke the fixme builtin. pr_builtins[i](); break; } diff --git a/Quake/pr_ext.c b/Quake/pr_ext.c new file mode 100755 index 00000000..213971af --- /dev/null +++ b/Quake/pr_ext.c @@ -0,0 +1,4651 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2016 Spike + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "q_ctype.h" + +//there's a few different aproaches to tempstrings... +//the lame way is to just have a single one (vanilla). +//the only slightly less lame way is to just cycle between 16 or so (most engines). +//one funky way is to allocate a single large buffer and just concatenate it for more tempstring space. don't forget to resize (dp). +//alternatively, just allocate them persistently and purge them only when there appear to be no more references to it (fte). makes strzone redundant. + +cvar_t pr_checkextension = {"pr_checkextension", "1", CVAR_NONE}; //spike - enables qc extensions. if 0 then they're ALL BLOCKED! MWAHAHAHA! *cough* *splutter* +struct pr_extfuncs_s pr_extfuncs; +int pr_ext_warned_particleeffectnum; //so these only spam once per map + +void SV_CheckVelocity (edict_t *ent); + +typedef enum multicast_e +{ + MULTICAST_ALL_U, + MULTICAST_PHS_U, + MULTICAST_PVS_U, + MULTICAST_ALL_R, + MULTICAST_PHS_R, + MULTICAST_PVS_R, + + MULTICAST_ONE_U, + MULTICAST_ONE_R, + MULTICAST_INIT +} multicast_t; +static void SV_Multicast(multicast_t to, float *org, int msg_entity, unsigned int requireext2); + +#define Z_StrDup(s) strcpy(Z_Malloc(strlen(s)+1), s) +#define RETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e)) + +//provides a few convienience extensions, primarily builtins, but also autocvars. +//Also note the set+seta features. + +#define D(typestr,desc) typestr,desc + +//#define fixme + +//maths stuff +static void PF_Sin(void) +{ + G_FLOAT(OFS_RETURN) = sin(G_FLOAT(OFS_PARM0)); +} +static void PF_asin(void) +{ + G_FLOAT(OFS_RETURN) = asin(G_FLOAT(OFS_PARM0)); +} +static void PF_Cos(void) +{ + G_FLOAT(OFS_RETURN) = cos(G_FLOAT(OFS_PARM0)); +} +static void PF_acos(void) +{ + G_FLOAT(OFS_RETURN) = acos(G_FLOAT(OFS_PARM0)); +} +static void PF_tan(void) +{ + G_FLOAT(OFS_RETURN) = tan(G_FLOAT(OFS_PARM0)); +} +static void PF_atan(void) +{ + G_FLOAT(OFS_RETURN) = atan(G_FLOAT(OFS_PARM0)); +} +static void PF_atan2(void) +{ + G_FLOAT(OFS_RETURN) = atan2(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1)); +} +static void PF_Sqrt(void) +{ + G_FLOAT(OFS_RETURN) = sqrt(G_FLOAT(OFS_PARM0)); +} +static void PF_pow(void) +{ + G_FLOAT(OFS_RETURN) = pow(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1)); +} +static void PF_Logarithm(void) +{ + //log2(v) = ln(v)/ln(2) + double r; + r = log(G_FLOAT(OFS_PARM0)); + if (pr_argc > 1) + r /= log(G_FLOAT(OFS_PARM1)); + G_FLOAT(OFS_RETURN) = r; +} +static void PF_mod(void) +{ + float a = G_FLOAT(OFS_PARM0); + float n = G_FLOAT(OFS_PARM1); + + if (n == 0) + { + Con_DWarning("PF_mod: mod by zero\n"); + G_FLOAT(OFS_RETURN) = 0; + } + else + { + //because QC is inherantly floaty, lets use floats. + G_FLOAT(OFS_RETURN) = a - (n * (int)(a/n)); + } +} +static void PF_min(void) +{ + float r = G_FLOAT(OFS_PARM0); + int i; + for (i = 1; i < pr_argc; i++) + { + if (r > G_FLOAT(OFS_PARM0 + i*3)) + r = G_FLOAT(OFS_PARM0 + i*3); + } + G_FLOAT(OFS_RETURN) = r; +} +static void PF_max(void) +{ + float r = G_FLOAT(OFS_PARM0); + int i; + for (i = 1; i < pr_argc; i++) + { + if (r < G_FLOAT(OFS_PARM0 + i*3)) + r = G_FLOAT(OFS_PARM0 + i*3); + } + G_FLOAT(OFS_RETURN) = r; +} +static void PF_bound(void) +{ + float minval = G_FLOAT(OFS_PARM0); + float curval = G_FLOAT(OFS_PARM1); + float maxval = G_FLOAT(OFS_PARM2); + if (curval > maxval) + curval = maxval; + if (curval < minval) + curval = minval; + G_FLOAT(OFS_RETURN) = curval; +} +static void PF_anglemod(void) +{ + float v = G_FLOAT(OFS_PARM0); + + while (v >= 360) + v = v - 360; + while (v < 0) + v = v + 360; + + G_FLOAT(OFS_RETURN) = v; +} +static void PF_bitshift(void) +{ + int bitmask = G_FLOAT(OFS_PARM0); + int shift = G_FLOAT(OFS_PARM1); + if (shift < 0) + bitmask >>= -shift; + else + bitmask <<= shift; + G_FLOAT(OFS_RETURN) = bitmask; +} +static void PF_crossproduct(void) +{ + CrossProduct(G_VECTOR(OFS_PARM0), G_VECTOR(OFS_PARM1), G_VECTOR(OFS_RETURN)); +} +static void PF_vectorvectors(void) +{ + VectorCopy(G_VECTOR(OFS_PARM0), pr_global_struct->v_forward); + VectorNormalize(pr_global_struct->v_forward); + if (!pr_global_struct->v_forward[0] && !pr_global_struct->v_forward[1]) + { + if (pr_global_struct->v_forward[2]) + pr_global_struct->v_right[1] = -1; + else + pr_global_struct->v_right[1] = 0; + pr_global_struct->v_right[0] = pr_global_struct->v_right[2] = 0; + } + else + { + pr_global_struct->v_right[0] = pr_global_struct->v_forward[1]; + pr_global_struct->v_right[1] = -pr_global_struct->v_forward[0]; + pr_global_struct->v_right[2] = 0; + VectorNormalize(pr_global_struct->v_right); + } + CrossProduct(pr_global_struct->v_right, pr_global_struct->v_forward, pr_global_struct->v_up); +} +static void PF_ext_vectoangles(void) +{ //alternative version of the original builtin, that can deal with roll angles too, by accepting an optional second argument for 'up'. + float *value1, *up; + + value1 = G_VECTOR(OFS_PARM0); + if (pr_argc >= 2) + up = G_VECTOR(OFS_PARM1); + else + up = NULL; + + VectorAngles(value1, up, G_VECTOR(OFS_RETURN)); + G_VECTOR(OFS_RETURN)[PITCH] *= -1; //this builtin is for use with models. models have an inverted pitch. consistency with makevectors would never do! +} + +//string stuff +static void PF_strlen(void) +{ //FIXME: doesn't try to handle utf-8 + const char *s = G_STRING(OFS_PARM0); + G_FLOAT(OFS_RETURN) = strlen(s); +} +static void PF_strcat(void) +{ + int i; + char *out = PR_GetTempString(); + size_t s; + + out[0] = 0; + s = 0; + for (i = 0; i < pr_argc; i++) + { + s = q_strlcat(out, G_STRING((OFS_PARM0+i*3)), STRINGTEMP_LENGTH); + if (s >= STRINGTEMP_LENGTH) + { + Con_Warning("PF_strcat: overflow (string truncated)\n"); + break; + } + } + + G_INT(OFS_RETURN) = PR_SetEngineString(out); +} +static void PF_substring(void) +{ + int start, length, slen; + const char *s; + char *string; + + s = G_STRING(OFS_PARM0); + start = G_FLOAT(OFS_PARM1); + length = G_FLOAT(OFS_PARM2); + + slen = strlen(s); //utf-8 should use chars, not bytes. + + if (start < 0) + start = slen+start; + if (length < 0) + length = slen-start+(length+1); + if (start < 0) + { + // length += start; + start = 0; + } + + if (start >= slen || length<=0) + { + G_INT(OFS_RETURN) = PR_SetEngineString(""); + return; + } + + slen -= start; + if (length > slen) + length = slen; + //utf-8 should switch to bytes now. + s += start; + + if (length >= STRINGTEMP_LENGTH) + { + length = STRINGTEMP_LENGTH-1; + Con_Warning("PF_substring: truncation\n"); + } + + string = PR_GetTempString(); + memcpy(string, s, length); + string[length] = '\0'; + G_INT(OFS_RETURN) = PR_SetEngineString(string); +} +/*our zoned strings implementation is somewhat specific to quakespasm, so good luck porting*/ +unsigned char *knownzone; +size_t knownzonesize; +static void PF_strzone(void) +{ + char *buf; + size_t len = 0; + const char *s[8]; + size_t l[8]; + int i; + size_t id; + for (i = 0; i < pr_argc; i++) + { + s[i] = G_STRING(OFS_PARM0+i*3); + l[i] = strlen(s[i]); + len += l[i]; + } + len++; /*for the null*/ + + buf = Z_Malloc(len); + G_INT(OFS_RETURN) = PR_SetEngineString(buf); + id = -1-G_INT(OFS_RETURN); + if (id >= knownzonesize) + { + knownzonesize = (id+32)&~7; + knownzone = Z_Realloc(knownzone, knownzonesize>>3); + } + knownzone[id>>3] |= 1u<<(id&7); + + len = 0; + for (i = 0; i < pr_argc; i++) + { + memcpy(buf, s[i], l[i]); + buf += l[i]; + } + *buf = '\0'; +} +static void PF_strunzone(void) +{ + size_t id; + const char *foo = G_STRING(OFS_PARM0); + if (!G_INT(OFS_PARM0)) + return; //don't bug out if they gave a null string + id = -1-G_INT(OFS_PARM0); + if (id < knownzonesize && (knownzone[id>>3] & (1u<<(id&7)))) + { + knownzone[id>>3] &= ~(1u<<(id&7)); + PR_ClearEngineString(G_INT(OFS_PARM0)); + Z_Free((void*)foo); + } + else + Con_Warning("PF_strunzone: string wasn't strzoned\n"); +} +static void PR_UnzoneAll(void) +{ //called to clean up all zoned strings. + while (knownzonesize --> 0) + { + size_t id = knownzonesize; + if (knownzone[id>>3] & (1u<<(id&7))) + { + string_t s = -1-(int)id; + char *ptr = (char*)PR_GetString(s); + PR_ClearEngineString(s); + Z_Free(ptr); + } + } + if (knownzone) + Z_Free(knownzone); + knownzonesize = 0; + knownzone = NULL; +} +static void PF_str2chr(void) +{ + const char *instr = G_STRING(OFS_PARM0); + int ofs = (pr_argc>1)?G_FLOAT(OFS_PARM1):0; + + if (ofs < 0) + ofs = strlen(instr)+ofs; + + if (ofs && (ofs < 0 || ofs > (int)strlen(instr))) + G_FLOAT(OFS_RETURN) = '\0'; + else + G_FLOAT(OFS_RETURN) = (unsigned char)instr[ofs]; +} +static void PF_chr2str(void) +{ + char *ret = PR_GetTempString(), *out; + int i; + for (i = 0, out=ret; i < pr_argc; i++) + *out++ = G_FLOAT(OFS_PARM0 + i*3); + *out = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(ret); +} +//part of PF_strconv +static int chrconv_number(int i, int base, int conv) +{ + i -= base; + switch (conv) + { + default: + case 5: + case 6: + case 0: + break; + case 1: + base = '0'; + break; + case 2: + base = '0'+128; + break; + case 3: + base = '0'-30; + break; + case 4: + base = '0'+128-30; + break; + } + return i + base; +} +//part of PF_strconv +static int chrconv_punct(int i, int base, int conv) +{ + i -= base; + switch (conv) + { + default: + case 0: + break; + case 1: + base = 0; + break; + case 2: + base = 128; + break; + } + return i + base; +} +//part of PF_strconv +static int chrchar_alpha(int i, int basec, int baset, int convc, int convt, int charnum) +{ + //convert case and colour seperatly... + + i -= baset + basec; + switch (convt) + { + default: + case 0: + break; + case 1: + baset = 0; + break; + case 2: + baset = 128; + break; + + case 5: + case 6: + baset = 128*((charnum&1) == (convt-5)); + break; + } + + switch (convc) + { + default: + case 0: + break; + case 1: + basec = 'a'; + break; + case 2: + basec = 'A'; + break; + } + return i + basec + baset; +} +//FTE_STRINGS +//bulk convert a string. change case or colouring. +static void PF_strconv (void) +{ + int ccase = G_FLOAT(OFS_PARM0); //0 same, 1 lower, 2 upper + int redalpha = G_FLOAT(OFS_PARM1); //0 same, 1 white, 2 red, 5 alternate, 6 alternate-alternate + int rednum = G_FLOAT(OFS_PARM2); //0 same, 1 white, 2 red, 3 redspecial, 4 whitespecial, 5 alternate, 6 alternate-alternate + const unsigned char *string = (const unsigned char*)PF_VarString(3); + int len = strlen((const char*)string); + int i; + unsigned char *resbuf = (unsigned char*)PR_GetTempString(); + unsigned char *result = resbuf; + + //UTF-8-FIXME: cope with utf+^U etc + + if (len >= STRINGTEMP_LENGTH) + len = STRINGTEMP_LENGTH-1; + + for (i = 0; i < len; i++, string++, result++) //should this be done backwards? + { + if (*string >= '0' && *string <= '9') //normal numbers... + *result = chrconv_number(*string, '0', rednum); + else if (*string >= '0'+128 && *string <= '9'+128) + *result = chrconv_number(*string, '0'+128, rednum); + else if (*string >= '0'+128-30 && *string <= '9'+128-30) + *result = chrconv_number(*string, '0'+128-30, rednum); + else if (*string >= '0'-30 && *string <= '9'-30) + *result = chrconv_number(*string, '0'-30, rednum); + + else if (*string >= 'a' && *string <= 'z') //normal numbers... + *result = chrchar_alpha(*string, 'a', 0, ccase, redalpha, i); + else if (*string >= 'A' && *string <= 'Z') //normal numbers... + *result = chrchar_alpha(*string, 'A', 0, ccase, redalpha, i); + else if (*string >= 'a'+128 && *string <= 'z'+128) //normal numbers... + *result = chrchar_alpha(*string, 'a', 128, ccase, redalpha, i); + else if (*string >= 'A'+128 && *string <= 'Z'+128) //normal numbers... + *result = chrchar_alpha(*string, 'A', 128, ccase, redalpha, i); + + else if ((*string & 127) < 16 || !redalpha) //special chars.. + *result = *string; + else if (*string < 128) + *result = chrconv_punct(*string, 0, redalpha); + else + *result = chrconv_punct(*string, 128, redalpha); + } + *result = '\0'; + + G_INT(OFS_RETURN) = PR_SetEngineString((char*)resbuf); +} +static void PF_strpad(void) +{ + char *destbuf = PR_GetTempString(); + char *dest = destbuf; + int pad = G_FLOAT(OFS_PARM0); + const char *src = PF_VarString(1); + + //UTF-8-FIXME: pad is chars not bytes... + + if (pad < 0) + { //pad left + pad = -pad - strlen(src); + if (pad>=STRINGTEMP_LENGTH) + pad = STRINGTEMP_LENGTH-1; + if (pad < 0) + pad = 0; + + q_strlcpy(dest+pad, src, STRINGTEMP_LENGTH-pad); + while(pad) + { + dest[--pad] = ' '; + } + } + else + { //pad right + if (pad>=STRINGTEMP_LENGTH) + pad = STRINGTEMP_LENGTH-1; + pad -= strlen(src); + if (pad < 0) + pad = 0; + + q_strlcpy(dest, src, STRINGTEMP_LENGTH); + dest+=strlen(dest); + + while(pad-->0) + *dest++ = ' '; + *dest = '\0'; + } + + G_INT(OFS_RETURN) = PR_SetEngineString(destbuf); +} +static void PF_infoadd(void) +{ + const char *info = G_STRING(OFS_PARM0); + const char *key = G_STRING(OFS_PARM1); + const char *value = PF_VarString(2); + char *destbuf = PR_GetTempString(), *o = destbuf, *e = destbuf + STRINGTEMP_LENGTH - 1; + + size_t keylen = strlen(key); + size_t valuelen = strlen(value); + if (!*key) + { //error + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); + return; + } + + //copy the string to the output, stripping the named key + while(*info) + { + const char *l = info; + if (*info++ != '\\') + break; //error / end-of-string + + if (!strncmp(info, key, keylen) && info[keylen] == '\\') + { + //skip the key name + info += keylen+1; + //this is the old value for the key. skip over it + while (*info && *info != '\\') + info++; + } + else + { + //skip the key + while (*info && *info != '\\') + info++; + + //validate that its a value now + if (*info++ != '\\') + break; //error + //skip the value + while (*info && *info != '\\') + info++; + + //copy them over + if (o + (info-l) >= e) + break; //exceeds maximum length + while (l < info) + *o++ = *l++; + } + } + + if (*info) + Con_Warning("PF_infoadd: invalid source info\n"); + else if (!*value) + ; //nothing needed + else if (!*key || strchr(key, '\\') || strchr(value, '\\')) + Con_Warning("PF_infoadd: invalid key/value\n"); + else if (o + 2 + keylen + valuelen >= e) + Con_Warning("PF_infoadd: length exceeds max\n"); + else + { + *o++ = '\\'; + memcpy(o, key, keylen); + o += keylen; + *o++ = '\\'; + memcpy(o, value, valuelen); + o += keylen; + } + + *o = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(destbuf); +} +static void PF_infoget(void) +{ + const char *info = G_STRING(OFS_PARM0); + const char *key = G_STRING(OFS_PARM1); + size_t keylen = strlen(key); + while(*info) + { + if (*info++ != '\\') + break; //error / end-of-string + + if (!strncmp(info, key, keylen) && info[keylen] == '\\') + { + char *destbuf = PR_GetTempString(), *o = destbuf, *e = destbuf + STRINGTEMP_LENGTH - 1; + + //skip the key name + info += keylen+1; + //this is the old value for the key. copy it to the result + while (*info && *info != '\\' && o < e) + *o++ = *info++; + *o++ = 0; + + //success! + G_INT(OFS_RETURN) = PR_SetEngineString(destbuf); + return; + } + else + { + //skip the key + while (*info && *info != '\\') + info++; + + //validate that its a value now + if (*info++ != '\\') + break; //error + //skip the value + while (*info && *info != '\\') + info++; + } + } + G_INT(OFS_RETURN) = 0; + +} +static void PF_strncmp(void) +{ + const char *a = G_STRING(OFS_PARM0); + const char *b = G_STRING(OFS_PARM1); + + if (pr_argc > 2) + { + int len = G_FLOAT(OFS_PARM2); + int aofs = pr_argc>3?G_FLOAT(OFS_PARM3):0; + int bofs = pr_argc>4?G_FLOAT(OFS_PARM4):0; + if (aofs < 0 || (aofs && aofs > (int)strlen(a))) + aofs = strlen(a); + if (bofs < 0 || (bofs && bofs > (int)strlen(b))) + bofs = strlen(b); + G_FLOAT(OFS_RETURN) = Q_strncmp(a + aofs, b, len); + } + else + G_FLOAT(OFS_RETURN) = Q_strcmp(a, b); +} +static void PF_strncasecmp(void) +{ + const char *a = G_STRING(OFS_PARM0); + const char *b = G_STRING(OFS_PARM1); + + if (pr_argc > 2) + { + int len = G_FLOAT(OFS_PARM2); + int aofs = pr_argc>3?G_FLOAT(OFS_PARM3):0; + int bofs = pr_argc>4?G_FLOAT(OFS_PARM4):0; + if (aofs < 0 || (aofs && aofs > (int)strlen(a))) + aofs = strlen(a); + if (bofs < 0 || (bofs && bofs > (int)strlen(b))) + bofs = strlen(b); + G_FLOAT(OFS_RETURN) = q_strncasecmp(a + aofs, b, len); + } + else + G_FLOAT(OFS_RETURN) = q_strcasecmp(a, b); +} +static void PF_strstrofs(void) +{ + const char *instr = G_STRING(OFS_PARM0); + const char *match = G_STRING(OFS_PARM1); + int firstofs = (pr_argc>2)?G_FLOAT(OFS_PARM2):0; + + if (firstofs && (firstofs < 0 || firstofs > (int)strlen(instr))) + { + G_FLOAT(OFS_RETURN) = -1; + return; + } + + match = strstr(instr+firstofs, match); + if (!match) + G_FLOAT(OFS_RETURN) = -1; + else + G_FLOAT(OFS_RETURN) = match - instr; +} +static void PF_strtrim(void) +{ + const char *str = G_STRING(OFS_PARM0); + const char *end; + char *news; + size_t len; + + //figure out the new start + while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r') + str++; + + //figure out the new end. + end = str + strlen(str); + while(end > str && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r')) + end--; + + //copy that substring into a tempstring. + len = end - str; + if (len >= STRINGTEMP_LENGTH) + len = STRINGTEMP_LENGTH-1; + + news = PR_GetTempString(); + memcpy(news, str, len); + news[len] = 0; + + G_INT(OFS_RETURN) = PR_SetEngineString(news); +} +static void PF_strreplace(void) +{ + char *resultbuf = PR_GetTempString(); + char *result = resultbuf; + const char *search = G_STRING(OFS_PARM0); + const char *replace = G_STRING(OFS_PARM1); + const char *subject = G_STRING(OFS_PARM2); + int searchlen = strlen(search); + int replacelen = strlen(replace); + + if (searchlen) + { + while (*subject && result < resultbuf + sizeof(resultbuf) - replacelen - 2) + { + if (!strncmp(subject, search, searchlen)) + { + subject += searchlen; + memcpy(result, replace, replacelen); + result += replacelen; + } + else + *result++ = *subject++; + } + *result = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(resultbuf); + } + else + G_INT(OFS_RETURN) = PR_SetEngineString(subject); +} +static void PF_strireplace(void) +{ + char *resultbuf = PR_GetTempString(); + char *result = resultbuf; + const char *search = G_STRING(OFS_PARM0); + const char *replace = G_STRING(OFS_PARM1); + const char *subject = G_STRING(OFS_PARM2); + int searchlen = strlen(search); + int replacelen = strlen(replace); + + if (searchlen) + { + while (*subject && result < resultbuf + sizeof(resultbuf) - replacelen - 2) + { + //UTF-8-FIXME: case insensitivity is awkward... + if (!q_strncasecmp(subject, search, searchlen)) + { + subject += searchlen; + memcpy(result, replace, replacelen); + result += replacelen; + } + else + *result++ = *subject++; + } + *result = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(resultbuf); + } + else + G_INT(OFS_RETURN) = PR_SetEngineString(subject); +} + + +static void PF_sprintf_internal (const char *s, int firstarg, char *outbuf, int outbuflen) +{ + const char *s0; + char *o = outbuf, *end = outbuf + outbuflen, *err; + int width, precision, thisarg, flags; + char formatbuf[16]; + char *f; + int argpos = firstarg; + int isfloat; + static int dummyivec[3] = {0, 0, 0}; + static float dummyvec[3] = {0, 0, 0}; + +#define PRINTF_ALTERNATE 1 +#define PRINTF_ZEROPAD 2 +#define PRINTF_LEFT 4 +#define PRINTF_SPACEPOSITIVE 8 +#define PRINTF_SIGNPOSITIVE 16 + + formatbuf[0] = '%'; + +#define GETARG_FLOAT(a) (((a)>=firstarg && (a)=firstarg && (a)=firstarg && (a)=firstarg && (a)=firstarg && (a)= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err) + { + Con_Warning("PF_sprintf: bad format string: %s\n", s0); + goto finished; + } + if(*err == '$') + { + thisarg = width + (firstarg-1); + width = -1; + s = err + 1; + } + else + { + if(*s == '0') + { + flags |= PRINTF_ZEROPAD; + if(width == 0) + width = -1; // it was just a flag + } + s = err; + } + } + + if(width < 0) + { + for(;;) + { + switch(*s) + { + case '#': flags |= PRINTF_ALTERNATE; break; + case '0': flags |= PRINTF_ZEROPAD; break; + case '-': flags |= PRINTF_LEFT; break; + case ' ': flags |= PRINTF_SPACEPOSITIVE; break; + case '+': flags |= PRINTF_SIGNPOSITIVE; break; + default: + goto noflags; + } + ++s; + } +noflags: + if(*s == '*') + { + ++s; + if(*s >= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err || *err != '$') + { + Con_Warning("PF_sprintf: invalid format string: %s\n", s0); + goto finished; + } + s = err + 1; + } + else + width = argpos++; + width = GETARG_FLOAT(width); + if(width < 0) + { + flags |= PRINTF_LEFT; + width = -width; + } + } + else if(*s >= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err) + { + Con_Warning("PF_sprintf: invalid format string: %s\n", s0); + goto finished; + } + s = err; + if(width < 0) + { + flags |= PRINTF_LEFT; + width = -width; + } + } + // otherwise width stays -1 + } + + if(*s == '.') + { + ++s; + if(*s == '*') + { + ++s; + if(*s >= '0' && *s <= '9') + { + precision = strtol(s, &err, 10); + if(!err || *err != '$') + { + Con_Warning("PF_sprintf: invalid format string: %s\n", s0); + goto finished; + } + s = err + 1; + } + else + precision = argpos++; + precision = GETARG_FLOAT(precision); + } + else if(*s >= '0' && *s <= '9') + { + precision = strtol(s, &err, 10); + if(!err) + { + Con_Warning("PF_sprintf: invalid format string: %s\n", s0); + goto finished; + } + s = err; + } + else + { + Con_Warning("PF_sprintf: invalid format string: %s\n", s0); + goto finished; + } + } + + for(;;) + { + switch(*s) + { + case 'h': isfloat = 1; break; + case 'l': isfloat = 0; break; + case 'L': isfloat = 0; break; + case 'j': break; + case 'z': break; + case 't': break; + default: + goto nolength; + } + ++s; + } +nolength: + + // now s points to the final directive char and is no longer changed + if (*s == 'p' || *s == 'P') + { + //%p is slightly different from %x. + //always 8-bytes wide with 0 padding, always ints. + flags |= PRINTF_ZEROPAD; + if (width < 0) width = 8; + if (isfloat < 0) isfloat = 0; + } + else if (*s == 'i') + { + //%i defaults to ints, not floats. + if(isfloat < 0) isfloat = 0; + } + + //assume floats, not ints. + if(isfloat < 0) + isfloat = 1; + + if(thisarg < 0) + thisarg = argpos++; + + if(o < end - 1) + { + f = &formatbuf[1]; + if(*s != 's' && *s != 'c') + if(flags & PRINTF_ALTERNATE) *f++ = '#'; + if(flags & PRINTF_ZEROPAD) *f++ = '0'; + if(flags & PRINTF_LEFT) *f++ = '-'; + if(flags & PRINTF_SPACEPOSITIVE) *f++ = ' '; + if(flags & PRINTF_SIGNPOSITIVE) *f++ = '+'; + *f++ = '*'; + if(precision >= 0) + { + *f++ = '.'; + *f++ = '*'; + } + if (*s == 'p') + *f++ = 'x'; + else if (*s == 'P') + *f++ = 'X'; + else + *f++ = *s; + *f++ = 0; + + if(width < 0) // not set + width = 0; + + switch(*s) + { + case 'd': case 'i': + if(precision < 0) // not set + q_snprintf(o, end - o, formatbuf, width, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg))); + else + q_snprintf(o, end - o, formatbuf, width, precision, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg))); + o += strlen(o); + break; + case 'o': case 'u': case 'x': case 'X': case 'p': case 'P': + if(precision < 0) // not set + q_snprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + else + q_snprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + o += strlen(o); + break; + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + if(precision < 0) // not set + q_snprintf(o, end - o, formatbuf, width, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); + else + q_snprintf(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); + o += strlen(o); + break; + case 'v': case 'V': + f[-2] += 'g' - 'v'; + if(precision < 0) // not set + q_snprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) + ); + else + q_snprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) + ); + o += strlen(o); + break; + case 'c': + //UTF-8-FIXME: figure it out yourself +// if(flags & PRINTF_ALTERNATE) + { + if(precision < 0) // not set + q_snprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + else + q_snprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + o += strlen(o); + } +/* else + { + unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)); + char charbuf16[16]; + const char *buf = u8_encodech(c, NULL, charbuf16); + if(!buf) + buf = ""; + if(precision < 0) // not set + precision = end - o - 1; + o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision); + } +*/ break; + case 's': + //UTF-8-FIXME: figure it out yourself +// if(flags & PRINTF_ALTERNATE) + { + if(precision < 0) // not set + q_snprintf(o, end - o, formatbuf, width, GETARG_STRING(thisarg)); + else + q_snprintf(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg)); + o += strlen(o); + } +/* else + { + if(precision < 0) // not set + precision = end - o - 1; + o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision); + } +*/ break; + default: + Con_Warning("PF_sprintf: invalid format string: %s\n", s0); + goto finished; + } + } + ++s; + break; + default: +verbatim: + if(o < end - 1) + *o++ = *s; + s++; + break; + } + } +finished: + *o = 0; +} + +static void PF_sprintf(void) +{ + char *outbuf = PR_GetTempString(); + PF_sprintf_internal(G_STRING(OFS_PARM0), 1, outbuf, STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(outbuf); +} + +//string tokenizing (gah) +#define MAXQCTOKENS 64 +static struct { + char *token; + unsigned int start; + unsigned int end; +} qctoken[MAXQCTOKENS]; +unsigned int qctoken_count; + +static void tokenize_flush(void) +{ + while(qctoken_count > 0) + { + qctoken_count--; + free(qctoken[qctoken_count].token); + } + qctoken_count = 0; +} + +static void PF_ArgC(void) +{ + G_FLOAT(OFS_RETURN) = qctoken_count; +} + +static int tokenizeqc(const char *str, qboolean dpfuckage) +{ + //FIXME: if dpfuckage, then we should handle punctuation specially, as well as /*. + const char *start = str; + while(qctoken_count > 0) + { + qctoken_count--; + free(qctoken[qctoken_count].token); + } + qctoken_count = 0; + while (qctoken_count < MAXQCTOKENS) + { + /*skip whitespace here so the token's start is accurate*/ + while (*str && *(unsigned char*)str <= ' ') + str++; + + if (!*str) + break; + + qctoken[qctoken_count].start = str - start; + str = COM_Parse(str); + if (!str) + break; + + qctoken[qctoken_count].token = strdup(com_token); + + qctoken[qctoken_count].end = str - start; + qctoken_count++; + } + return qctoken_count; +} + +/*KRIMZON_SV_PARSECLIENTCOMMAND added these two - note that for compatibility with DP, this tokenize builtin is veeery vauge and doesn't match the console*/ +static void PF_Tokenize(void) +{ + G_FLOAT(OFS_RETURN) = tokenizeqc(G_STRING(OFS_PARM0), true); +} + +static void PF_tokenize_console(void) +{ + G_FLOAT(OFS_RETURN) = tokenizeqc(G_STRING(OFS_PARM0), false); +} + +static void PF_tokenizebyseparator(void) +{ + const char *str = G_STRING(OFS_PARM0); + const char *sep[7]; + int seplen[7]; + int seps = 0, s; + const char *start = str; + int tlen; + qboolean found = true; + + while (seps < pr_argc - 1 && seps < 7) + { + sep[seps] = G_STRING(OFS_PARM1 + seps*3); + seplen[seps] = strlen(sep[seps]); + seps++; + } + + tokenize_flush(); + + qctoken[qctoken_count].start = 0; + if (*str) + for(;;) + { + found = false; + /*see if its a separator*/ + if (!*str) + { + qctoken[qctoken_count].end = str - start; + found = true; + } + else + { + for (s = 0; s < seps; s++) + { + if (!strncmp(str, sep[s], seplen[s])) + { + qctoken[qctoken_count].end = str - start; + str += seplen[s]; + found = true; + break; + } + } + } + /*it was, split it out*/ + if (found) + { + tlen = qctoken[qctoken_count].end - qctoken[qctoken_count].start; + qctoken[qctoken_count].token = malloc(tlen + 1); + memcpy(qctoken[qctoken_count].token, start + qctoken[qctoken_count].start, tlen); + qctoken[qctoken_count].token[tlen] = 0; + + qctoken_count++; + + if (*str && qctoken_count < MAXQCTOKENS) + qctoken[qctoken_count].start = str - start; + else + break; + } + str++; + } + G_FLOAT(OFS_RETURN) = qctoken_count; +} + +static void PF_argv_start_index(void) +{ + int idx = G_FLOAT(OFS_PARM0); + + /*negative indexes are relative to the end*/ + if (idx < 0) + idx += qctoken_count; + + if ((unsigned int)idx >= qctoken_count) + G_FLOAT(OFS_RETURN) = -1; + else + G_FLOAT(OFS_RETURN) = qctoken[idx].start; +} + +static void PF_argv_end_index(void) +{ + int idx = G_FLOAT(OFS_PARM0); + + /*negative indexes are relative to the end*/ + if (idx < 0) + idx += qctoken_count; + + if ((unsigned int)idx >= qctoken_count) + G_FLOAT(OFS_RETURN) = -1; + else + G_FLOAT(OFS_RETURN) = qctoken[idx].end; +} + +static void PF_ArgV(void) +{ + int idx = G_FLOAT(OFS_PARM0); + + /*negative indexes are relative to the end*/ + if (idx < 0) + idx += qctoken_count; + + if ((unsigned int)idx >= qctoken_count) + G_INT(OFS_RETURN) = 0; + else + { + char *ret = PR_GetTempString(); + q_strlcpy(ret, qctoken[idx].token, STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(ret); + } +} + +//conversions (mostly string) +static void PF_strtoupper(void) +{ + const char *in = G_STRING(OFS_PARM0); + char *out, *result = PR_GetTempString(); + for (out = result; *in && out < result+STRINGTEMP_LENGTH-1;) + *out++ = q_toupper(*in++); + *out = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(result); +} +static void PF_strtolower(void) +{ + const char *in = G_STRING(OFS_PARM0); + char *out, *result = PR_GetTempString(); + for (out = result; *in && out < result+STRINGTEMP_LENGTH-1;) + *out++ = q_tolower(*in++); + *out = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(result); +} +#include +static void PF_strftime(void) +{ + const char *in = G_STRING(OFS_PARM1); + char *result = PR_GetTempString(); + + time_t ctime; + struct tm *tm; + + ctime = time(NULL); + + if (G_FLOAT(OFS_PARM0)) + tm = localtime(&ctime); + else + tm = gmtime(&ctime); + +#ifdef _WIN32 + //msvc sucks. this is a crappy workaround. + if (!strcmp(in, "%R")) + in = "%H:%M"; + else if (!strcmp(in, "%F")) + in = "%Y-%m-%d"; +#endif + + strftime(result, STRINGTEMP_LENGTH, in, tm); + + G_INT(OFS_RETURN) = PR_SetEngineString(result); +} +static void PF_stof(void) +{ + G_FLOAT(OFS_RETURN) = atoi(G_STRING(OFS_PARM0)); +} +static void PF_stov(void) +{ + const char *s = G_STRING(OFS_PARM0); + s = COM_Parse(s); + G_VECTOR(OFS_RETURN)[0] = atof(com_token); + s = COM_Parse(s); + G_VECTOR(OFS_RETURN)[1] = atof(com_token); + s = COM_Parse(s); + G_VECTOR(OFS_RETURN)[2] = atof(com_token); +} +static void PF_stoi(void) +{ + G_INT(OFS_RETURN) = atoi(G_STRING(OFS_PARM0)); +} +static void PF_itos(void) +{ + char *result = PR_GetTempString(); + q_snprintf(result, STRINGTEMP_LENGTH, "%i", G_INT(OFS_PARM0)); + G_INT(OFS_RETURN) = PR_SetEngineString(result); +} +static void PF_etos(void) +{ //yes, this is lame + char *result = PR_GetTempString(); + q_snprintf(result, STRINGTEMP_LENGTH, "entity %i", G_EDICTNUM(OFS_PARM0)); + G_INT(OFS_RETURN) = PR_SetEngineString(result); +} +static void PF_stoh(void) +{ + G_INT(OFS_RETURN) = strtoul(G_STRING(OFS_PARM0), NULL, 16); +} +static void PF_htos(void) +{ + char *result = PR_GetTempString(); + q_snprintf(result, STRINGTEMP_LENGTH, "%x", G_INT(OFS_PARM0)); + G_INT(OFS_RETURN) = PR_SetEngineString(result); +} +static void PF_ftoi(void) +{ + G_INT(OFS_RETURN) = G_FLOAT(OFS_PARM0); +} +static void PF_itof(void) +{ + G_FLOAT(OFS_RETURN) = G_INT(OFS_PARM0); +} + +//collision stuff +static void PF_tracebox(void) +{ //alternative version of traceline that just passes on two extra args. trivial really. + float *v1, *mins, *maxs, *v2; + trace_t trace; + int nomonsters; + edict_t *ent; + + v1 = G_VECTOR(OFS_PARM0); + mins = G_VECTOR(OFS_PARM1); + maxs = G_VECTOR(OFS_PARM2); + v2 = G_VECTOR(OFS_PARM3); + nomonsters = G_FLOAT(OFS_PARM4); + ent = G_EDICT(OFS_PARM5); + + /* FIXME FIXME FIXME: Why do we hit this with certain progs.dat ?? */ + if (developer.value) { + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || + IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) { + Con_Warning ("NAN in traceline:\nv1(%f %f %f) v2(%f %f %f)\nentity %d\n", + v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], NUM_FOR_EDICT(ent)); + } + } + + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2])) + v1[0] = v1[1] = v1[2] = 0; + if (IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) + v2[0] = v2[1] = v2[2] = 0; + + trace = SV_Move (v1, mins, maxs, v2, nomonsters, ent); + + pr_global_struct->trace_allsolid = trace.allsolid; + pr_global_struct->trace_startsolid = trace.startsolid; + pr_global_struct->trace_fraction = trace.fraction; + pr_global_struct->trace_inwater = trace.inwater; + pr_global_struct->trace_inopen = trace.inopen; + VectorCopy (trace.endpos, pr_global_struct->trace_endpos); + VectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal); + pr_global_struct->trace_plane_dist = trace.plane.dist; + if (trace.ent) + pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent); + else + pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); +} +static void PF_TraceToss(void) +{ + extern cvar_t sv_maxvelocity, sv_gravity; + int i; + float gravity; + vec3_t move, end; + trace_t trace; + eval_t *val; + + vec3_t origin, velocity; + + edict_t *tossent, *ignore; + tossent = G_EDICT(OFS_PARM0); + if (tossent == sv.edicts) + Con_Warning("tracetoss: can not use world entity\n"); + ignore = G_EDICT(OFS_PARM1); + + val = GetEdictFieldValue(tossent, pr_extfields.gravity); + if (val && val->_float) + gravity = val->_float; + else + gravity = 1; + gravity *= sv_gravity.value * 0.05; + + VectorCopy (tossent->v.origin, origin); + VectorCopy (tossent->v.velocity, velocity); + + SV_CheckVelocity (tossent); + + for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds + { + velocity[2] -= gravity; + VectorScale (velocity, 0.05, move); + VectorAdd (origin, move, end); + trace = SV_Move (origin, tossent->v.mins, tossent->v.maxs, end, MOVE_NORMAL, tossent); + VectorCopy (trace.endpos, origin); + + if (trace.fraction < 1 && trace.ent && trace.ent != ignore) + break; + + if (VectorLength(velocity) > sv_maxvelocity.value) + { +// Con_DPrintf("Slowing %s\n", PR_GetString(w->progs, tossent->v->classname)); + VectorScale (velocity, sv_maxvelocity.value/VectorLength(velocity), velocity); + } + } + + trace.fraction = 0; // not relevant + + //and return those as globals. + pr_global_struct->trace_allsolid = trace.allsolid; + pr_global_struct->trace_startsolid = trace.startsolid; + pr_global_struct->trace_fraction = trace.fraction; + pr_global_struct->trace_inwater = trace.inwater; + pr_global_struct->trace_inopen = trace.inopen; + VectorCopy (trace.endpos, pr_global_struct->trace_endpos); + VectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal); + pr_global_struct->trace_plane_dist = trace.plane.dist; + if (trace.ent) + pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent); + else + pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); +} + +//model stuff +static void PF_frameforname(void) +{ + unsigned int modelindex = G_FLOAT(OFS_PARM0); + const char *framename = G_STRING(OFS_PARM1); + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + aliashdr_t *alias; + + G_FLOAT(OFS_RETURN) = -1; + if (mod && mod->type == mod_alias && (alias = Mod_Extradata(mod))) + { + int i; + for (i = 0; i < alias->numframes; i++) + { + if (!strcmp(alias->frames[i].name, framename)) + { + G_FLOAT(OFS_RETURN) = i; + break; + } + } + } +} +static void PF_frametoname(void) +{ + unsigned int modelindex = G_FLOAT(OFS_PARM0); + unsigned int framenum = G_FLOAT(OFS_PARM1); + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + aliashdr_t *alias; + + if (mod && mod->type == mod_alias && (alias = Mod_Extradata(mod)) && framenum < (unsigned int)alias->numframes) + G_INT(OFS_RETURN) = PR_SetEngineString(alias->frames[framenum].name); + else + G_INT(OFS_RETURN) = 0; +} +static void PF_frameduration(void) +{ + unsigned int modelindex = G_FLOAT(OFS_PARM0); + unsigned int framenum = G_FLOAT(OFS_PARM1); + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + aliashdr_t *alias; + + if (mod && mod->type == mod_alias && (alias = Mod_Extradata(mod)) && framenum < (unsigned int)alias->numframes) + G_FLOAT(OFS_RETURN) = alias->frames[framenum].numposes * alias->frames[framenum].interval; +} +static void PF_getsurfacenumpoints(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int surfidx = G_FLOAT(OFS_PARM1); + unsigned int modelindex = ed->v.modelindex; + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + + if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces) + { + surfidx += mod->firstmodelsurface; + G_FLOAT(OFS_RETURN) = mod->surfaces[surfidx].numedges; + } + else + G_FLOAT(OFS_RETURN) = 0; +} +static mvertex_t *PF_getsurfacevertex(qmodel_t *mod, msurface_t *surf, unsigned int vert) +{ + signed int edge = mod->surfedges[vert+surf->firstedge]; + if (edge >= 0) + return &mod->vertexes[mod->edges[edge].v[0]]; + else + return &mod->vertexes[mod->edges[-edge].v[1]]; +} +static void PF_getsurfacepoint(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int surfidx = G_FLOAT(OFS_PARM1); + unsigned int point = G_FLOAT(OFS_PARM2); + unsigned int modelindex = ed->v.modelindex; + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + + if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && point < (unsigned int)mod->surfaces[surfidx].numedges) + { + mvertex_t *v = PF_getsurfacevertex(mod, &mod->surfaces[surfidx+mod->firstmodelsurface], point); + VectorCopy(v->position, G_VECTOR(OFS_RETURN)); + } + else + { + G_FLOAT(OFS_RETURN+0) = 0; + G_FLOAT(OFS_RETURN+1) = 0; + G_FLOAT(OFS_RETURN+2) = 0; + } +} +static void PF_getsurfacenumtriangles(void) +{ //for q3bsp compat (which this engine doesn't support, so its fairly simple) + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int surfidx = G_FLOAT(OFS_PARM1); + unsigned int modelindex = ed->v.modelindex; + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + + if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces) + G_FLOAT(OFS_RETURN) = (mod->surfaces[surfidx+mod->firstmodelsurface].numedges-2); //q1bsp is only triangle fans + else + G_FLOAT(OFS_RETURN) = 0; +} +static void PF_getsurfacetriangle(void) +{ //for q3bsp compat (which this engine doesn't support, so its fairly simple) + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int surfidx = G_FLOAT(OFS_PARM1); + unsigned int triangleidx= G_FLOAT(OFS_PARM2); + unsigned int modelindex = ed->v.modelindex; + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + + if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && triangleidx < (unsigned int)mod->surfaces[surfidx].numedges-2) + { + G_FLOAT(OFS_RETURN+0) = 0; + G_FLOAT(OFS_RETURN+1) = triangleidx+1; + G_FLOAT(OFS_RETURN+2) = triangleidx+2; + } + else + { + G_FLOAT(OFS_RETURN+0) = 0; + G_FLOAT(OFS_RETURN+1) = 0; + G_FLOAT(OFS_RETURN+2) = 0; + } +} +static void PF_getsurfacenormal(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int surfidx = G_FLOAT(OFS_PARM1); + unsigned int modelindex = ed->v.modelindex; + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + + if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces) + { + surfidx += mod->firstmodelsurface; + VectorCopy(mod->surfaces[surfidx].plane->normal, G_VECTOR(OFS_RETURN)); + if (mod->surfaces[surfidx].flags & SURF_PLANEBACK) + VectorInverse(G_VECTOR(OFS_RETURN)); + } + else + G_FLOAT(OFS_RETURN) = 0; +} +static void PF_getsurfacetexture(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int surfidx = G_FLOAT(OFS_PARM1); + unsigned int modelindex = ed->v.modelindex; + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + + if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces) + { + surfidx += mod->firstmodelsurface; + G_INT(OFS_RETURN) = PR_SetEngineString(mod->surfaces[surfidx].texinfo->texture->name); + } + else + G_INT(OFS_RETURN) = 0; +} + +#define TriangleNormal(a,b,c,n) ( \ + (n)[0] = ((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1]), \ + (n)[1] = ((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2]), \ + (n)[2] = ((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0]) \ + ) +static float getsurface_clippointpoly(qmodel_t *model, msurface_t *surf, vec3_t point, vec3_t bestcpoint, float bestdist) +{ + int e, edge; + vec3_t edgedir, edgenormal, cpoint, temp; + mvertex_t *v1, *v2; + float dist = DotProduct(point, surf->plane->normal) - surf->plane->dist; + //don't care about SURF_PLANEBACK, the maths works out the same. + + if (dist*dist < bestdist) + { //within a specific range + //make sure it's within the poly + VectorMA(point, dist, surf->plane->normal, cpoint); + for (e = surf->firstedge+surf->numedges; e > surf->firstedge; edge++) + { + edge = model->surfedges[--e]; + if (edge < 0) + { + v1 = &model->vertexes[model->edges[-edge].v[0]]; + v2 = &model->vertexes[model->edges[-edge].v[1]]; + } + else + { + v2 = &model->vertexes[model->edges[edge].v[0]]; + v1 = &model->vertexes[model->edges[edge].v[1]]; + } + + VectorSubtract(v1->position, v2->position, edgedir); + CrossProduct(edgedir, surf->plane->normal, edgenormal); + if (!(surf->flags & SURF_PLANEBACK)) + { + VectorSubtract(vec3_origin, edgenormal, edgenormal); + } + VectorNormalize(edgenormal); + + dist = DotProduct(v1->position, edgenormal) - DotProduct(cpoint, edgenormal); + if (dist < 0) + VectorMA(cpoint, dist, edgenormal, cpoint); + } + + VectorSubtract(cpoint, point, temp); + dist = DotProduct(temp, temp); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(cpoint, bestcpoint); + } + } + return bestdist; +} + +// #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE) +static void PF_getsurfacenearpoint(void) +{ + qmodel_t *model; + edict_t *ent; + msurface_t *surf; + int i; + float *point; + unsigned int u; + + vec3_t cpoint = {0,0,0}; + float bestdist = 0x7fffffff, dist; + int bestsurf = -1; + + ent = G_EDICT(OFS_PARM0); + point = G_VECTOR(OFS_PARM1); + + G_FLOAT(OFS_RETURN) = -1; + + u = ent->v.modelindex; + model = (u>=MAX_MODELS)?NULL:sv.models[u]; + + if (!model || model->type != mod_brush || model->needload) + return; + + bestdist = 256; + + //all polies, we can skip parts. special case. + surf = model->surfaces + model->firstmodelsurface; + for (i = 0; i < model->nummodelsurfaces; i++, surf++) + { + dist = getsurface_clippointpoly(model, surf, point, cpoint, bestdist); + if (dist < bestdist) + { + bestdist = dist; + bestsurf = i; + } + } + G_FLOAT(OFS_RETURN) = bestsurf; +} + +// #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE) +static void PF_getsurfaceclippedpoint(void) +{ + qmodel_t *model; + edict_t *ent; + msurface_t *surf; + float *point; + int surfnum; + unsigned int u; + + float *result = G_VECTOR(OFS_RETURN); + + ent = G_EDICT(OFS_PARM0); + surfnum = G_FLOAT(OFS_PARM1); + point = G_VECTOR(OFS_PARM2); + + VectorCopy(point, result); + + u = ent->v.modelindex; + model = (u>=MAX_MODELS)?NULL:sv.models[u]; + + if (!model || model->type != mod_brush || model->needload) + return; + if (surfnum >= model->nummodelsurfaces) + return; + + //all polies, we can skip parts. special case. + surf = model->surfaces + model->firstmodelsurface + surfnum; + getsurface_clippointpoly(model, surf, point, result, 0x7fffffff); +} + +static void PF_getsurfacepointattribute(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int surfidx = G_FLOAT(OFS_PARM1); + unsigned int point = G_FLOAT(OFS_PARM2); + unsigned int attribute = G_FLOAT(OFS_PARM3); + + unsigned int modelindex = ed->v.modelindex; + qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + + if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && point < (unsigned int)mod->surfaces[mod->firstmodelsurface+surfidx].numedges) + { + msurface_t *fa = &mod->surfaces[surfidx+mod->firstmodelsurface]; + mvertex_t *v = PF_getsurfacevertex(mod, fa, point); + switch(attribute) + { + default: + Con_Warning("PF_getsurfacepointattribute: attribute %u not supported\n", attribute); + G_FLOAT(OFS_RETURN+0) = 0; + G_FLOAT(OFS_RETURN+1) = 0; + G_FLOAT(OFS_RETURN+2) = 0; + break; + case 0: //xyz coord + VectorCopy(v->position, G_VECTOR(OFS_RETURN)); + break; + case 1: //s dir + case 2: //t dir + { + //figure out how similar to the normal it is, and negate any influence, so that its perpendicular + float sc = -DotProduct(fa->plane->normal, fa->texinfo->vecs[attribute-1]); + VectorMA(fa->texinfo->vecs[attribute-1], sc, fa->plane->normal, G_VECTOR(OFS_RETURN)); + VectorNormalize(G_VECTOR(OFS_RETURN)); + } + break; + case 3: //normal + VectorCopy(fa->plane->normal, G_VECTOR(OFS_RETURN)); + if (fa->flags & SURF_PLANEBACK) + VectorInverse(G_VECTOR(OFS_RETURN)); + break; + case 4: //st coord + G_FLOAT(OFS_RETURN+0) = (DotProduct(v->position, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3]) / fa->texinfo->texture->width; + G_FLOAT(OFS_RETURN+1) = (DotProduct(v->position, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3]) / fa->texinfo->texture->height; + G_FLOAT(OFS_RETURN+2) = 0; + break; + case 5: //lmst coord, not actually very useful +#define BLOCK_WIDTH 128 +#define BLOCK_HEIGHT 128 + G_FLOAT(OFS_RETURN+0) = (DotProduct(v->position, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3] - fa->texturemins[0] + fa->light_s*16+8) / (BLOCK_WIDTH*16); + G_FLOAT(OFS_RETURN+1) = (DotProduct(v->position, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3] - fa->texturemins[1] + fa->light_t*16+8) / (BLOCK_HEIGHT*16); + G_FLOAT(OFS_RETURN+2) = 0; + break; + case 6: //colour + G_FLOAT(OFS_RETURN+0) = 1; + G_FLOAT(OFS_RETURN+1) = 1; + G_FLOAT(OFS_RETURN+2) = 1; + break; + } + } + else + { + G_FLOAT(OFS_RETURN+0) = 0; + G_FLOAT(OFS_RETURN+1) = 0; + G_FLOAT(OFS_RETURN+2) = 0; + } +} +static void PF_sv_getlight(void) +{ + float *point = G_VECTOR(OFS_PARM0); + //FIXME: seems like quakespasm doesn't do lits for model lighting, so we won't either. + G_FLOAT(OFS_RETURN+0) = G_FLOAT(OFS_RETURN+1) = G_FLOAT(OFS_RETURN+2) = R_LightPoint(point) / 255.0; +} + +//server/client stuff +static void PF_checkcommand(void) +{ + const char *name = G_STRING(OFS_PARM0); + if (Cmd_Exists(name)) + G_FLOAT(OFS_RETURN) = 1; + else if (Cmd_AliasExists(name)) + G_FLOAT(OFS_RETURN) = 2; + else if (Cvar_FindVar(name)) + G_FLOAT(OFS_RETURN) = 3; + else + G_FLOAT(OFS_RETURN) = 0; +} +static void PF_setcolors(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + int newcol = G_FLOAT(OFS_PARM1); + unsigned int i = NUM_FOR_EDICT(ed)-1; + if (i >= (unsigned int)svs.maxclients) + { + Con_Printf ("tried to setcolor a non-client\n"); + return; + } + //FIXME: should we allow this for inactive players? + +//update it + svs.clients[i].colors = newcol; + svs.clients[i].edict->v.team = (newcol&15) + 1; + +// send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteByte (&sv.reliable_datagram, newcol); +} +static void PF_clientcommand(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + const char *str = G_STRING(OFS_PARM1); + unsigned int i = NUM_FOR_EDICT(ed)-1; + if (i < (unsigned int)svs.maxclients && svs.clients[i].active) + { + client_t *ohc = host_client; + host_client = &svs.clients[i]; + Cmd_ExecuteString (str, src_client); + host_client = ohc; + } + else + Con_Printf("PF_clientcommand: not a client\n"); +} +static void PF_clienttype(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int i = NUM_FOR_EDICT(ed)-1; + if (i >= (unsigned int)svs.maxclients) + { + G_FLOAT(OFS_RETURN) = 3; //CLIENTTYPE_NOTACLIENT + return; + } + if (svs.clients[i].active) + { + if (svs.clients[i].netconnection) + G_FLOAT(OFS_RETURN) = 1;//CLIENTTYPE_REAL; + else + G_FLOAT(OFS_RETURN) = 2;//CLIENTTYPE_BOT; + } + else + G_FLOAT(OFS_RETURN) = 0;//CLIENTTYPE_DISCONNECTED; +} +static void PF_spawnclient(void) +{ + edict_t *ent; + unsigned int i; + if (svs.maxclients) + for (i = svs.maxclients; i --> 0; ) + { + if (!svs.clients[i].active) + { + svs.clients[i].netconnection = NULL; //botclients have no net connection, obviously. + SV_ConnectClient(i); + svs.clients[i].spawned = true; + ent = svs.clients[i].edict; + memset (&ent->v, 0, progs->entityfields * 4); + ent->v.colormap = NUM_FOR_EDICT(ent); + ent->v.team = (svs.clients[i].colors & 15) + 1; + ent->v.netname = PR_SetEngineString(svs.clients[i].name); + RETURN_EDICT(ent); + return; + } + } + RETURN_EDICT(sv.edicts); +} +static void PF_dropclient(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + unsigned int i = NUM_FOR_EDICT(ed)-1; + if (i < (unsigned int)svs.maxclients && svs.clients[i].active) + { //FIXME: should really set a flag or something, to avoid recursion issues. + client_t *ohc = host_client; + host_client = &svs.clients[i]; + SV_DropClient (false); + host_client = ohc; + } +} + +//console/cvar stuff +static void PF_print(void) +{ + int i; + for (i = 0; i < pr_argc; i++) + Con_Printf("%s", G_STRING(OFS_PARM0 + i*3)); +} +static void PF_cvar_string(void) +{ + const char *name = G_STRING(OFS_PARM0); + cvar_t *var = Cvar_FindVar(name); + if (var && var->string) + { + //cvars can easily change values. + //this would result in leaks/exploits/slowdowns if the qc spams calls to cvar_string+changes. + //so keep performance consistent, even if this is going to be slower. + char *temp = PR_GetTempString(); + q_strlcpy(temp, var->string, STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(temp); + } + else + G_INT(OFS_RETURN) = 0; +} +static void PF_cvar_defstring(void) +{ + const char *name = G_STRING(OFS_PARM0); + cvar_t *var = Cvar_FindVar(name); + if (var && var->default_string) + G_INT(OFS_RETURN) = PR_SetEngineString(var->default_string); + else + G_INT(OFS_RETURN) = 0; +} +static void PF_cvar_type(void) +{ + const char *str = G_STRING(OFS_PARM0); + int ret = 0; + cvar_t *v; + + v = Cvar_FindVar(str); + if (v) + { + ret |= 1; // CVAR_EXISTS + if(v->flags & CVAR_ARCHIVE) + ret |= 2; // CVAR_TYPE_SAVED +// if(v->flags & CVAR_NOTFROMSERVER) +// ret |= 4; // CVAR_TYPE_PRIVATE + if(!(v->flags & CVAR_USERDEFINED)) + ret |= 8; // CVAR_TYPE_ENGINE +// if (v->description) +// ret |= 16; // CVAR_TYPE_HASDESCRIPTION + } + G_FLOAT(OFS_RETURN) = ret; +} +static void PF_cvar_description(void) +{ //quakespasm does not support cvar descriptions. we provide this stub to avoid crashes. + G_INT(OFS_RETURN) = 0; +} +static void PF_registercvar(void) +{ + const char *name = G_STRING(OFS_PARM0); + const char *value = (pr_argc>1)?G_STRING(OFS_PARM0):""; + Cvar_Create(name, value); +} + +//temp entities + networking +static void PF_WriteString2(void) +{ //writes a string without the null. a poor-man's strcat. + const char *string = G_STRING(OFS_PARM0); + SZ_Write (WriteDest(), string, Q_strlen(string)); +} +static void PF_WriteFloat(void) +{ //curiously, this was missing in vanilla. + MSG_WriteFloat(WriteDest(), G_FLOAT(OFS_PARM0)); +} +static void PF_sv_te_blooddp(void) +{ //blood is common enough that we should emulate it for when engines do actually support it. + float *org = G_VECTOR(OFS_PARM0); + float *dir = G_VECTOR(OFS_PARM1); + float color = 73; + float count = G_FLOAT(OFS_PARM2); + SV_StartParticle (org, dir, color, count); +} +static void PF_sv_te_bloodqw(void) +{ //qw tried to strip a lot. + float *org = G_VECTOR(OFS_PARM0); + float *dir = vec3_origin; + float color = 73; + float count = G_FLOAT(OFS_PARM1)*20; + SV_StartParticle (org, dir, color, count); +} +static void PF_sv_te_lightningblood(void) +{ //a qw builtin, to replace particle. + float *org = G_VECTOR(OFS_PARM0); + vec3_t dir = {0, 0, -100}; + float color = 20; + float count = 225; + SV_StartParticle (org, dir, color, count); +} +static void PF_sv_te_spike(void) +{ + float *org = G_VECTOR(OFS_PARM0); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SPIKE); + MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PVS_U, org, 0, 0); +} +static void PF_sv_te_superspike(void) +{ + float *org = G_VECTOR(OFS_PARM0); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SUPERSPIKE); + MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PVS_U, org, 0, 0); +} +static void PF_sv_te_gunshot(void) +{ + float *org = G_VECTOR(OFS_PARM0); + //float count = G_FLOAT(OFS_PARM1)*20; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_GUNSHOT); + MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PVS_U, org, 0, 0); +} +static void PF_sv_te_explosion(void) +{ + float *org = G_VECTOR(OFS_PARM0); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSION); + MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, org, 0, 0); +} +static void PF_sv_te_tarexplosion(void) +{ + float *org = G_VECTOR(OFS_PARM0); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_TAREXPLOSION); + MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, org, 0, 0); +} +static void PF_sv_te_lightning1(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + float *start = G_VECTOR(OFS_PARM1); + float *end = G_VECTOR(OFS_PARM2); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING1); + MSG_WriteShort(&sv.datagram, NUM_FOR_EDICT(ed)); + MSG_WriteCoord(&sv.datagram, start[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, start[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, start[2], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, start, 0, 0); +} +static void PF_sv_te_lightning2(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + float *start = G_VECTOR(OFS_PARM1); + float *end = G_VECTOR(OFS_PARM2); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING2); + MSG_WriteShort(&sv.datagram, NUM_FOR_EDICT(ed)); + MSG_WriteCoord(&sv.datagram, start[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, start[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, start[2], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, start, 0, 0); +} +static void PF_sv_te_wizspike(void) +{ + float *org = G_VECTOR(OFS_PARM0); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_WIZSPIKE); + MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, org, 0, 0); +} +static void PF_sv_te_knightspike(void) +{ + float *org = G_VECTOR(OFS_PARM0); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_KNIGHTSPIKE); + MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, org, 0, 0); +} +static void PF_sv_te_lightning3(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + float *start = G_VECTOR(OFS_PARM1); + float *end = G_VECTOR(OFS_PARM2); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING3); + MSG_WriteShort(&sv.datagram, NUM_FOR_EDICT(ed)); + MSG_WriteCoord(&sv.datagram, start[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, start[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, start[2], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, end[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, start, 0, 0); +} +static void PF_sv_te_lavasplash(void) +{ + float *org = G_VECTOR(OFS_PARM0); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LAVASPLASH); + MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, org, 0, 0); +} +static void PF_sv_te_teleport(void) +{ + float *org = G_VECTOR(OFS_PARM0); + MSG_WriteByte(&sv.multicast, svc_temp_entity); + MSG_WriteByte(&sv.multicast, TE_TELEPORT); + MSG_WriteCoord(&sv.multicast, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, org[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, org, 0, 0); +} +static void PF_sv_te_explosion2(void) +{ + float *org = G_VECTOR(OFS_PARM0); + int palstart = G_FLOAT(OFS_PARM1); + int palcount = G_FLOAT(OFS_PARM1); + MSG_WriteByte(&sv.multicast, svc_temp_entity); + MSG_WriteByte(&sv.multicast, TE_EXPLOSION2); + MSG_WriteCoord(&sv.multicast, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, org[2], sv.protocolflags); + MSG_WriteByte(&sv.multicast, palstart); + MSG_WriteByte(&sv.multicast, palcount); + SV_Multicast(MULTICAST_PHS_U, org, 0, 0); +} +static void PF_sv_te_beam(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + float *start = G_VECTOR(OFS_PARM1); + float *end = G_VECTOR(OFS_PARM2); + MSG_WriteByte(&sv.multicast, svc_temp_entity); + MSG_WriteByte(&sv.multicast, TE_BEAM); + MSG_WriteShort(&sv.multicast, NUM_FOR_EDICT(ed)); + MSG_WriteCoord(&sv.multicast, start[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, start[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, start[2], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, end[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, end[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, end[2], sv.protocolflags); + SV_Multicast(MULTICAST_PHS_U, start, 0, 0); +} +#ifdef PSET_SCRIPT +static void PF_sv_te_particlerain(void) +{ + float *min = G_VECTOR(OFS_PARM0); + float *max = G_VECTOR(OFS_PARM1); + float *velocity = G_VECTOR(OFS_PARM2); + float count = G_FLOAT(OFS_PARM3); + float colour = G_FLOAT(OFS_PARM4); + + if (count < 1) + return; + if (count > 65535) + count = 65535; + + MSG_WriteByte(&sv.multicast, svc_temp_entity); + MSG_WriteByte(&sv.multicast, TEDP_PARTICLERAIN); + MSG_WriteCoord(&sv.multicast, min[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, min[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, min[2], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, max[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, max[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, max[2], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, velocity[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, velocity[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, velocity[2], sv.protocolflags); + MSG_WriteShort(&sv.multicast, count); + MSG_WriteByte(&sv.multicast, colour); + + SV_Multicast (MULTICAST_ALL_U, NULL, 0, PEXT2_REPLACEMENTDELTAS); +} +static void PF_sv_te_particlesnow(void) +{ + float *min = G_VECTOR(OFS_PARM0); + float *max = G_VECTOR(OFS_PARM1); + float *velocity = G_VECTOR(OFS_PARM2); + float count = G_FLOAT(OFS_PARM3); + float colour = G_FLOAT(OFS_PARM4); + + if (count < 1) + return; + if (count > 65535) + count = 65535; + + MSG_WriteByte(&sv.multicast, svc_temp_entity); + MSG_WriteByte(&sv.multicast, TEDP_PARTICLESNOW); + MSG_WriteCoord(&sv.multicast, min[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, min[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, min[2], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, max[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, max[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, max[2], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, velocity[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, velocity[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, velocity[2], sv.protocolflags); + MSG_WriteShort(&sv.multicast, count); + MSG_WriteByte(&sv.multicast, colour); + + SV_Multicast (MULTICAST_ALL_U, NULL, 0, PEXT2_REPLACEMENTDELTAS); +} +#else +#define PF_sv_te_particlerain PF_void_stub +#define PF_sv_te_particlesnow PF_void_stub +#endif +#define PF_sv_te_bloodshower PF_void_stub +#define PF_sv_te_explosionrgb PF_void_stub +#define PF_sv_te_particlecube PF_void_stub +#define PF_sv_te_spark PF_void_stub +#define PF_sv_te_gunshotquad PF_sv_te_gunshot +#define PF_sv_te_spikequad PF_sv_te_spike +#define PF_sv_te_superspikequad PF_sv_te_superspike +#define PF_sv_te_explosionquad PF_sv_te_explosion +#define PF_sv_te_smallflash PF_void_stub +#define PF_sv_te_customflash PF_void_stub +#define PF_sv_te_plasmaburn PF_sv_te_tarexplosion +#define PF_sv_effect PF_void_stub + +static void PF_sv_pointsound(void) +{ + float *origin = G_VECTOR(OFS_PARM0); + const char *sample = G_STRING(OFS_PARM1); + float volume = G_FLOAT(OFS_PARM2); + float attenuation = G_FLOAT(OFS_PARM3); + SV_StartSound (sv.edicts, origin, 0, sample, volume, attenuation); +} + +//file stuff + +//returns false if the file is denied. +//fallbackread can be NULL, if the qc is not allowed to read that (original) file at all. +static qboolean QC_FixFileName(const char *name, const char **result, const char **fallbackread) +{ + if (!*name || //blank names are bad + strchr(name, ':') || //dos/win absolute path, ntfs ADS, amiga drives. reject them all. + strchr(name, '\\') || //windows-only paths. + *name == '/' || //absolute path was given - reject + strstr(name, "..")) //someone tried to be clever. + { + return false; + } + + *fallbackread = name; + //if its a user config, ban any fallback locations so that csqc can't read passwords or whatever. + if ((!strchr(name, '/') || q_strncasecmp(name, "configs/", 8)) + && !q_strcasecmp(COM_FileGetExtension(name), "cfg") + && q_strncasecmp(name, "particles/", 10) && q_strncasecmp(name, "huds/", 5) && q_strncasecmp(name, "models/", 7)) + *fallbackread = NULL; + *result = va("data/%s", name); + return true; +} + +//small note on access modes: +//when reading, we fopen files inside paks, for compat with (crappy non-zip-compatible) filesystem code +//when writing, we directly fopen the file such that it can never be inside a pak. +//this means that we need to take care when reading in order to detect EOF properly. +//writing doesn't need anything like that, so it can just dump stuff out, but we do need to ensure that the modes don't get mixed up, because trying to read from a writable file will not do what you would expect. +//even libc mandates a seek between reading+writing, so no great loss there. +static struct qcfile_s +{ + char cache[1024]; + int cacheoffset, cachesize; + FILE *file; + int fileoffset; + int filesize; + int filebase; //the offset of the file inside a pak + int mode; +} *qcfiles; +static size_t qcfiles_max; +#define QC_FILE_BASE 1 +static void PF_fopen(void) +{ + const char *fname = G_STRING(OFS_PARM0); + int fmode = G_FLOAT(OFS_PARM1); + const char *fallback; + FILE *file; + size_t i; + char name[MAX_OSPATH]; + int filesize = 0; + + G_FLOAT(OFS_RETURN) = -1; //assume failure + + if (!QC_FixFileName(fname, &fname, &fallback)) + { + Con_Printf("qcfopen: Access denied: %s\n", fname); + return; + } + //if we were told to use 'foo.txt' + //fname is now 'data/foo.txt' + //fallback is now 'foo.txt', and must ONLY be read. + + switch(fmode) + { + case 0: //read + filesize = COM_FOpenFile (fname, &file, NULL); + if (!file && fallback) + filesize = COM_FOpenFile (fallback, &file, NULL); + break; + case 1: //append + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, fname); + Sys_mkdir (name); + file = fopen(name, "w+b"); + if (file) + fseek(file, 0, SEEK_END); + break; + case 2: //write + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, fname); + Sys_mkdir (name); + file = fopen(name, "wb"); + break; + } + if (!file) + return; + + for (i = 0; ; i++) + { + if (i == qcfiles_max) + { + qcfiles_max++; + qcfiles = Z_Realloc(qcfiles, sizeof(*qcfiles)*qcfiles_max); + } + if (!qcfiles[i].file) + break; + } + qcfiles[i].filebase = ftell(file); + qcfiles[i].file = file; + qcfiles[i].mode = fmode; + //reading needs size info + qcfiles[i].filesize = filesize; + //clear the read cache. + qcfiles[i].fileoffset = qcfiles[i].cacheoffset = qcfiles[i].cachesize = 0; + + G_FLOAT(OFS_RETURN) = i+QC_FILE_BASE; +} +static void PF_fgets(void) +{ + size_t fileid = G_FLOAT(OFS_PARM0) - QC_FILE_BASE; + G_INT(OFS_RETURN) = 0; + if (fileid >= qcfiles_max) + Con_Warning("PF_fgets: invalid file handle\n"); + else if (!qcfiles[fileid].file) + Con_Warning("PF_fgets: file not open\n"); + else if (qcfiles[fileid].mode != 0) + Con_Warning("PF_fgets: file not open for reading\n"); + else + { + struct qcfile_s *f = &qcfiles[fileid]; + char *ret = PR_GetTempString(); + char *s = ret; + char *end = ret+STRINGTEMP_LENGTH; + for (;;) + { + if (!f->cachesize) + { + //figure out how much we can try to cache. + int sz = f->filesize - f->fileoffset; + if (sz < 0 || f->fileoffset < 0) //... maybe we shouldn't have implemented seek support. + sz = 0; + else if ((size_t)sz > sizeof(f->cache)) + sz = sizeof(f->cache); + //read a chunk + f->cacheoffset = 0; + f->cachesize = fread(f->cache, 1, sz, f->file); + f->fileoffset += f->cachesize; + if (!f->cachesize) + { + //classic eof... + break; + } + } + *s = f->cache[f->cacheoffset++]; + if (*s == '\n') //new line, yay! + break; + s++; + if (s == end) + s--; //rewind if we're overflowing, such that we truncate the string. + } + if (s > ret && s[-1] == '\r') + s--; //terminate it on the \r of a \r\n pair. + *s = 0; //terminate it + G_INT(OFS_RETURN) = PR_SetEngineString(ret); + } +} +static void PF_fputs(void) +{ + size_t fileid = G_FLOAT(OFS_PARM0) - QC_FILE_BASE; + const char *str = PF_VarString(1); + if (fileid >= qcfiles_max) + Con_Warning("PF_fputs: invalid file handle\n"); + else if (!qcfiles[fileid].file) + Con_Warning("PF_fputs: file not open\n"); + else if (qcfiles[fileid].mode == 0) + Con_Warning("PF_fgets: file not open for writing\n"); + else + fputs(str, qcfiles[fileid].file); +} +static void PF_fclose(void) +{ + size_t fileid = G_FLOAT(OFS_PARM0) - QC_FILE_BASE; + if (fileid >= qcfiles_max) + Con_Warning("PF_fclose: invalid file handle\n"); + else if (!qcfiles[fileid].file) + Con_Warning("PF_fclose: file not open\n"); + else + { + fclose(qcfiles[fileid].file); + qcfiles[fileid].file = NULL; + } +} +static void PF_fseek(void) +{ //returns current position. or changes that position. + size_t fileid = G_FLOAT(OFS_PARM0) - QC_FILE_BASE; + G_INT(OFS_RETURN) = 0; + if (fileid >= qcfiles_max) + Con_Warning("PF_fread: invalid file handle\n"); + else if (!qcfiles[fileid].file) + Con_Warning("PF_fread: file not open\n"); + else + { + if (qcfiles[fileid].mode == 0) + G_INT(OFS_RETURN) = qcfiles[fileid].fileoffset; //when we're reading, use the cached read offset + else + G_INT(OFS_RETURN) = ftell(qcfiles[fileid].file)-qcfiles[fileid].filebase; + if (pr_argc>1) + { + qcfiles[fileid].fileoffset = G_INT(OFS_PARM1); + fseek(qcfiles[fileid].file, qcfiles[fileid].filebase+qcfiles[fileid].fileoffset, SEEK_SET); + qcfiles[fileid].cachesize = qcfiles[fileid].cacheoffset = 0; + } + } +} +#if 0 +static void PF_fread(void) +{ + size_t fileid = G_FLOAT(OFS_PARM0) - QC_FILE_BASE; + int qcptr = G_INT(OFS_PARM1); + size_t size = G_INT(OFS_PARM2); + + //FIXME: validate. make usable. + char *nativeptr = (char*)sv.edicts + qcptr; + + G_INT(OFS_RETURN) = 0; + if (fileid >= maxfiles) + Con_Warning("PF_fread: invalid file handle\n"); + else if (!qcfiles[fileid].file) + Con_Warning("PF_fread: file not open\n"); + else + G_INT(OFS_RETURN) = fread(nativeptr, 1, size, qcfiles[fileid].file); +} +static void PF_fwrite(void) +{ + size_t fileid = G_FLOAT(OFS_PARM0) - QC_FILE_BASE; + int qcptr = G_INT(OFS_PARM1); + size_t size = G_INT(OFS_PARM2); + + //FIXME: validate. make usable. + const char *nativeptr = (const char*)sv.edicts + qcptr; + + G_INT(OFS_RETURN) = 0; + if (fileid >= maxfiles) + Con_Warning("PF_fwrite: invalid file handle\n"); + else if (!qcfiles[fileid].file) + Con_Warning("PF_fwrite: file not open\n"); + else + G_INT(OFS_RETURN) = fwrite(nativeptr, 1, size, qcfiles[fileid].file); +} +#ifdef _WIN32 +#include +#else +#include +#endif +static void PF_fsize(void) +{ + size_t fileid = G_FLOAT(OFS_PARM0) - QC_FILE_BASE; + G_INT(OFS_RETURN) = 0; + if (fileid >= maxfiles) + Con_Warning("PF_fread: invalid file handle\n"); + else if (!qcfiles[fileid].file) + Con_Warning("PF_fread: file not open\n"); + else if (qcfiles[fileid].mode == 0) + { + G_INT(OFS_RETURN) = qcfiles[fileid].filesize; + //can't truncate if we're reading. + } + else + { + long curpos = ftell(qcfiles[fileid].file); + fseek(qcfiles[fileid].file, 0, SEEK_END); + G_INT(OFS_RETURN) = ftell(qcfiles[fileid].file); + fseek(qcfiles[fileid].file, curpos, SEEK_SET); + + if (pr_argc>1) + { + //specifically resize. or maybe extend. +#ifdef _WIN32 + _chsize(fileno(qcfiles[fileid].file), G_INT(OFS_PARM1)); +#else + ftruncate(fileno(qcfiles[fileid].file), G_INT(OFS_PARM1)); +#endif + } + } +} +#endif + + +static void PF_search_begin(void) +{ +// const char *pattern = G_STRING(OFS_PARM0); +// qboolean caseinsensitive = !!G_FLOAT(OFS_PARM0); +// qboolaen quiet = !!G_FLOAT(OFS_PARM0); + + G_FLOAT(OFS_RETURN) = -1; +} +static void PF_search_end(void) +{ +// int handle = G_FLOAT(OFS_PARM0); +} +static void PF_search_getsize(void) +{ +// int handle = G_FLOAT(OFS_PARM0); + G_FLOAT(OFS_RETURN) = 0; +} +static void PF_search_getfilename(void) +{ +// int handle = G_FLOAT(OFS_PARM0); +// int index = G_FLOAT(OFS_PARM1); + G_INT(OFS_RETURN) = PR_SetEngineString(""); +} +static void PF_search_getfilesize(void) +{ +// int handle = G_FLOAT(OFS_PARM0); +// int index = G_FLOAT(OFS_PARM1); + G_FLOAT(OFS_RETURN) = 0; +} +static void PF_search_getfilemtime(void) +{ +// int handle = G_FLOAT(OFS_PARM0); +// int index = G_FLOAT(OFS_PARM1); + G_INT(OFS_RETURN) = PR_SetEngineString(""); +} + +static void PF_whichpack(void) +{ + const char *fname = G_STRING(OFS_PARM0); //uses native paths, as this isn't actually reading anything. + unsigned int path_id; + if (COM_FileExists(fname, &path_id)) + { + //FIXME: quakespasm reports which gamedir the file is in, but paks are hidden. + //I'm too lazy to rewrite COM_FindFile, so I'm just going to hack something small to get the gamedir, just not the paks + + searchpath_t *path; + for (path = com_searchpaths; path; path = path->next) + if (!path->pack && path->path_id == path_id) + break; //okay, this one looks like one we can report + + //sandbox it by stripping the basedir + fname = path->filename; + if (!strncmp(fname, com_basedir, strlen(com_basedir))) + fname += strlen(com_basedir); + else + fname = "?"; //no idea where this came from. something is screwing with us. + while (*fname == '/' || *fname == '\\') + fname++; //small cleanup, just in case + G_INT(OFS_RETURN) = PR_SetEngineString(fname); + } + else + G_INT(OFS_RETURN) = 0; +} + +//string buffers + +struct strbuf { + qboolean isactive; + char **strings; + unsigned int used; + unsigned int allocated; +}; + +#define BUFSTRBASE 1 +#define NUMSTRINGBUFS 64u +struct strbuf strbuflist[NUMSTRINGBUFS]; + +static void PF_buf_shutdown(void) +{ + unsigned int i, bufno; + + for (bufno = 0; bufno < NUMSTRINGBUFS; bufno++) + { + if (!strbuflist[bufno].isactive) + continue; + + for (i = 0; i < strbuflist[bufno].used; i++) + Z_Free(strbuflist[bufno].strings[i]); + Z_Free(strbuflist[bufno].strings); + + strbuflist[bufno].strings = NULL; + strbuflist[bufno].used = 0; + strbuflist[bufno].allocated = 0; + } +} + +// #440 float() buf_create (DP_QC_STRINGBUFFERS) +static void PF_buf_create(void) +{ + unsigned int i; + + const char *type = ((pr_argc>0)?G_STRING(OFS_PARM0):"string"); +// unsigned int flags = ((pr_argc>1)?G_FLOAT(OFS_PARM1):1); + + if (!q_strcasecmp(type, "string")) + ; + else + { + G_FLOAT(OFS_RETURN) = -1; + return; + } + + //flags&1 == saved. apparently. + + for (i = 0; i < NUMSTRINGBUFS; i++) + { + if (!strbuflist[i].isactive) + { + strbuflist[i].isactive = true; + strbuflist[i].used = 0; + strbuflist[i].allocated = 0; + strbuflist[i].strings = NULL; + G_FLOAT(OFS_RETURN) = i+BUFSTRBASE; + return; + } + } + G_FLOAT(OFS_RETURN) = -1; +} +// #441 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) +static void PF_buf_del(void) +{ + unsigned int i; + unsigned int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + + if (bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + for (i = 0; i < strbuflist[bufno].used; i++) + Z_Free(strbuflist[bufno].strings[i]); + Z_Free(strbuflist[bufno].strings); + + strbuflist[bufno].strings = NULL; + strbuflist[bufno].used = 0; + strbuflist[bufno].allocated = 0; + + strbuflist[bufno].isactive = false; +} +// #442 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) +static void PF_buf_getsize(void) +{ + int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + G_FLOAT(OFS_RETURN) = strbuflist[bufno].used; +} +// #443 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) +static void PF_buf_copy(void) +{ + unsigned int buffrom = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + unsigned int bufto = G_FLOAT(OFS_PARM1)-BUFSTRBASE; + unsigned int i; + + if (bufto == buffrom) //err... + return; + if (buffrom >= NUMSTRINGBUFS) + return; + if (!strbuflist[buffrom].isactive) + return; + if (bufto >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufto].isactive) + return; + + //obliterate any and all existing data. + for (i = 0; i < strbuflist[bufto].used; i++) + Z_Free(strbuflist[bufto].strings[i]); + Z_Free(strbuflist[bufto].strings); + + //copy new data over. + strbuflist[bufto].used = strbuflist[bufto].allocated = strbuflist[buffrom].used; + strbuflist[bufto].strings = Z_Malloc(strbuflist[buffrom].used * sizeof(char*)); + for (i = 0; i < strbuflist[buffrom].used; i++) + strbuflist[bufto].strings[i] = strbuflist[buffrom].strings[i]?Z_StrDup(strbuflist[buffrom].strings[i]):NULL; +} +static int PF_buf_sort_sortprefixlen; +static int PF_buf_sort_ascending(const void *a, const void *b) +{ + return strncmp(*(char**)a, *(char**)b, PF_buf_sort_sortprefixlen); +} +static int PF_buf_sort_descending(const void *b, const void *a) +{ + return strncmp(*(char**)a, *(char**)b, PF_buf_sort_sortprefixlen); +} +// #444 void(float bufhandle, float sortprefixlen, float backward) buf_sort (DP_QC_STRINGBUFFERS) +static void PF_buf_sort(void) +{ + int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + int sortprefixlen = G_FLOAT(OFS_PARM1); + int backwards = G_FLOAT(OFS_PARM2); + unsigned int s,d; + char **strings; + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + if (sortprefixlen <= 0) + sortprefixlen = 0x7fffffff; + + //take out the nulls first, to avoid weird/crashy sorting + for (s = 0, d = 0, strings = strbuflist[bufno].strings; s < strbuflist[bufno].used; ) + { + if (!strings[s]) + { + s++; + continue; + } + strings[d++] = strings[s++]; + } + strbuflist[bufno].used = d; + + //no nulls now, sort it. + PF_buf_sort_sortprefixlen = sortprefixlen; //eww, a global. burn in hell. + if (backwards) //z first + qsort(strings, strbuflist[bufno].used, sizeof(char*), PF_buf_sort_descending); + else //a first + qsort(strings, strbuflist[bufno].used, sizeof(char*), PF_buf_sort_ascending); +} +// #445 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) +static void PF_buf_implode(void) +{ + int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + const char *glue = G_STRING(OFS_PARM1); + unsigned int gluelen = strlen(glue); + unsigned int retlen, l, i; + char **strings; + char *ret; + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + //count neededlength + strings = strbuflist[bufno].strings; + /* + for (i = 0, retlen = 0; i < strbuflist[bufno].used; i++) + { + if (strings[i]) + { + if (retlen) + retlen += gluelen; + retlen += strlen(strings[i]); + } + } + ret = malloc(retlen+1);*/ + + //generate the output + ret = PR_GetTempString(); + for (i = 0, retlen = 0; i < strbuflist[bufno].used; i++) + { + if (strings[i]) + { + if (retlen) + { + if (retlen+gluelen+1 > STRINGTEMP_LENGTH) + { + Con_Printf("PF_buf_implode: tempstring overflow\n"); + break; + } + memcpy(ret+retlen, glue, gluelen); + retlen += gluelen; + } + l = strlen(strings[i]); + if (retlen+l+1 > STRINGTEMP_LENGTH) + { + Con_Printf("PF_buf_implode: tempstring overflow\n"); + break; + } + memcpy(ret+retlen, strings[i], l); + retlen += l; + } + } + + //add the null and return + ret[retlen] = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(ret); +} +// #446 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) +static void PF_bufstr_get(void) +{ + unsigned int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + unsigned int index = G_FLOAT(OFS_PARM1); + char *ret; + + if (bufno >= NUMSTRINGBUFS) + { + G_INT(OFS_RETURN) = 0; + return; + } + if (!strbuflist[bufno].isactive) + { + G_INT(OFS_RETURN) = 0; + return; + } + + if (index >= strbuflist[bufno].used) + { + G_INT(OFS_RETURN) = 0; + return; + } + + ret = PR_GetTempString(); + q_strlcpy(ret, strbuflist[bufno].strings[index], STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(ret); +} +// #447 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) +static void PF_bufstr_set(void) +{ + unsigned int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + unsigned int index = G_FLOAT(OFS_PARM1); + const char *string = G_STRING(OFS_PARM2); + unsigned int oldcount; + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + if (index >= strbuflist[bufno].allocated) + { + oldcount = strbuflist[bufno].allocated; + strbuflist[bufno].allocated = (index + 256); + strbuflist[bufno].strings = Z_Realloc(strbuflist[bufno].strings, strbuflist[bufno].allocated*sizeof(char*)); + memset(strbuflist[bufno].strings+oldcount, 0, (strbuflist[bufno].allocated - oldcount) * sizeof(char*)); + } + if (strbuflist[bufno].strings[index]) + Z_Free(strbuflist[bufno].strings[index]); + strbuflist[bufno].strings[index] = Z_Malloc(strlen(string)+1); + strcpy(strbuflist[bufno].strings[index], string); + + if (index >= strbuflist[bufno].used) + strbuflist[bufno].used = index+1; +} + +static int PF_bufstr_add_internal(unsigned int bufno, const char *string, int appendonend) +{ + unsigned int index; + if (appendonend) + { + //add on end + index = strbuflist[bufno].used; + } + else + { + //find a hole + for (index = 0; index < strbuflist[bufno].used; index++) + if (!strbuflist[bufno].strings[index]) + break; + } + + //expand it if needed + if (index >= strbuflist[bufno].allocated) + { + unsigned int oldcount; + oldcount = strbuflist[bufno].allocated; + strbuflist[bufno].allocated = (index + 256); + strbuflist[bufno].strings = Z_Realloc(strbuflist[bufno].strings, strbuflist[bufno].allocated*sizeof(char*)); + memset(strbuflist[bufno].strings+oldcount, 0, (strbuflist[bufno].allocated - oldcount) * sizeof(char*)); + } + + //add in the new string. + if (strbuflist[bufno].strings[index]) + Z_Free(strbuflist[bufno].strings[index]); + strbuflist[bufno].strings[index] = Z_Malloc(strlen(string)+1); + strcpy(strbuflist[bufno].strings[index], string); + + if (index >= strbuflist[bufno].used) + strbuflist[bufno].used = index+1; + + return index; +} + +// #448 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) +static void PF_bufstr_add(void) +{ + size_t bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + const char *string = G_STRING(OFS_PARM1); + qboolean ordered = G_FLOAT(OFS_PARM2); + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + G_FLOAT(OFS_RETURN) = PF_bufstr_add_internal(bufno, string, ordered); +} +// #449 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) +static void PF_bufstr_free(void) +{ + size_t bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + size_t index = G_FLOAT(OFS_PARM1); + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + if (index >= strbuflist[bufno].used) + return; //not valid anyway. + + if (strbuflist[bufno].strings[index]) + Z_Free(strbuflist[bufno].strings[index]); + strbuflist[bufno].strings[index] = NULL; +} + +/*static void PF_buf_cvarlist(void) +{ + size_t bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE; + const char *pattern = G_STRING(OFS_PARM1); + const char *antipattern = G_STRING(OFS_PARM2); + int i; + cvar_t *var; + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + //obliterate any and all existing data. + for (i = 0; i < strbuflist[bufno].used; i++) + Z_Free(strbuflist[bufno].strings[i]); + Z_Free(strbuflist[bufno].strings); + strbuflist[bufno].used = strbuflist[bufno].allocated = 0; + + //ignore name2, no point listing it twice. + for (var=Cvar_FindVarAfter ("", CVAR_NONE) ; var ; var=var->next) + { + if (pattern && wildcmp(pattern, var->name)) + continue; + if (antipattern && !wildcmp(antipattern, var->name)) + continue; + + PF_bufstr_add_internal(bufno, var->name, true); + } +}*/ + +//directly reads a file into a stringbuffer +static void PF_buf_loadfile(void) +{ + const char *fname = G_STRING(OFS_PARM0); + size_t bufno = G_FLOAT(OFS_PARM1)-BUFSTRBASE; + char *line, *nl; + const char *fallback; + + G_FLOAT(OFS_RETURN) = 0; + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + if (!QC_FixFileName(fname, &fname, &fallback)) + { + Con_Printf("qcfopen: Access denied: %s\n", fname); + return; + } + line = (char*)COM_LoadTempFile(fname, NULL); + if (!line && fallback) + line = (char*)COM_LoadTempFile(fallback, NULL); + if (!line) + return; + + while(line) + { + nl = strchr(line, '\n'); + if (nl) + *nl++ = 0; + PF_bufstr_add_internal(bufno, line, true); + line = nl; + } + + G_FLOAT(OFS_RETURN) = true; +} + +static void PF_buf_writefile(void) +{ + size_t fnum = G_FLOAT(OFS_PARM0) - QC_FILE_BASE; + size_t bufno = G_FLOAT(OFS_PARM1)-BUFSTRBASE; + char **strings; + unsigned int idx, midx; + + G_FLOAT(OFS_RETURN) = 0; + + if ((unsigned int)bufno >= NUMSTRINGBUFS) + return; + if (!strbuflist[bufno].isactive) + return; + + if (fnum >= qcfiles_max) + return; + if (!qcfiles[fnum].file) + return; + + if (pr_argc >= 3) + { + if (G_FLOAT(OFS_PARM2) <= 0) + idx = 0; + else + idx = G_FLOAT(OFS_PARM2); + } + else + idx = 0; + if (pr_argc >= 4) + midx = idx + G_FLOAT(OFS_PARM3); + else + midx = strbuflist[bufno].used - idx; + if (idx > strbuflist[bufno].used) + idx = strbuflist[bufno].used; + if (midx > strbuflist[bufno].used) + midx = strbuflist[bufno].used; + for(strings = strbuflist[bufno].strings; idx < midx; idx++) + { + if (strings[idx]) + fprintf(qcfiles[fnum].file, "%s\n", strings[idx]); + } + G_FLOAT(OFS_RETURN) = 1; +} + +//entity stuff +static void PF_WasFreed(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + G_FLOAT(OFS_RETURN) = ed->free; +} +static void PF_copyentity(void) +{ + edict_t *src = G_EDICT(OFS_PARM0); + edict_t *dst = G_EDICT(OFS_PARM1); + if (src->free || dst->free) + Con_Printf("PF_copyentity: entity is free\n"); + memcpy(&dst->v, &src->v, pr_edict_size - sizeof(edict_t)); + dst->alpha = src->alpha; + dst->sendinterval = src->sendinterval; + SV_LinkEdict(dst, false); +} +static void PF_edict_for_num(void) +{ + G_INT(OFS_RETURN) = EDICT_TO_PROG(EDICT_NUM(G_FLOAT(OFS_PARM0))); +} +static void PF_num_for_edict(void) +{ + G_FLOAT(OFS_RETURN) = G_EDICTNUM(OFS_PARM0); +} +static void PF_sv_findchain(void) +{ + edict_t *ent, *chain; + int i, f; + const char *s, *t; + + chain = (edict_t *)sv.edicts; + + f = G_INT(OFS_PARM0); + s = G_STRING(OFS_PARM1); + + ent = NEXT_EDICT(sv.edicts); + for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + { + if (ent->free) + continue; + t = E_STRING(ent,f); + if (strcmp(s, t)) + continue; + ent->v.chain = EDICT_TO_PROG(chain); + chain = ent; + } + + RETURN_EDICT(chain); +} +static void PF_sv_findfloat(void) +{ + int e; + int f; + float s, t; + edict_t *ed; + + e = G_EDICTNUM(OFS_PARM0); + f = G_INT(OFS_PARM1); + s = G_FLOAT(OFS_PARM2); + + for (e++ ; e < sv.num_edicts ; e++) + { + ed = EDICT_NUM(e); + if (ed->free) + continue; + t = E_FLOAT(ed,f); + if (t == s) + { + RETURN_EDICT(ed); + return; + } + } + + RETURN_EDICT(sv.edicts); +} +static void PF_sv_findchainfloat(void) +{ + edict_t *ent, *chain; + int i, f; + float s, t; + + chain = (edict_t *)sv.edicts; + + f = G_INT(OFS_PARM0); + s = G_FLOAT(OFS_PARM1); + + ent = NEXT_EDICT(sv.edicts); + for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + { + if (ent->free) + continue; + t = E_FLOAT(ent,f); + if (s != t) + continue; + ent->v.chain = EDICT_TO_PROG(chain); + chain = ent; + } + + RETURN_EDICT(chain); +} +static void PF_sv_findflags(void) +{ + int e; + int f; + int s, t; + edict_t *ed; + + e = G_EDICTNUM(OFS_PARM0); + f = G_INT(OFS_PARM1); + s = G_FLOAT(OFS_PARM2); + + for (e++ ; e < sv.num_edicts ; e++) + { + ed = EDICT_NUM(e); + if (ed->free) + continue; + t = E_FLOAT(ed,f); + if (t & s) + { + RETURN_EDICT(ed); + return; + } + } + + RETURN_EDICT(sv.edicts); +} +static void PF_sv_findchainflags(void) +{ + edict_t *ent, *chain; + int i, f; + int s, t; + + chain = (edict_t *)sv.edicts; + + f = G_INT(OFS_PARM0); + s = G_FLOAT(OFS_PARM1); + + ent = NEXT_EDICT(sv.edicts); + for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + { + if (ent->free) + continue; + t = E_FLOAT(ent,f); + if (!(s & t)) + continue; + ent->v.chain = EDICT_TO_PROG(chain); + chain = ent; + } + + RETURN_EDICT(chain); +} +static void PF_numentityfields(void) +{ + G_FLOAT(OFS_RETURN) = progs->numfielddefs; +} +static void PF_findentityfield(void) +{ + ddef_t *fld = ED_FindField(G_STRING(OFS_PARM0)); + if (fld) + G_FLOAT(OFS_RETURN) = fld - pr_fielddefs; + else + G_FLOAT(OFS_RETURN) = 0; //the first field is meant to be some dummy placeholder. or it could be modelindex... +} +static void PF_entityfieldref(void) +{ + unsigned int fldidx = G_FLOAT(OFS_PARM0); + if (fldidx >= (unsigned int)progs->numfielddefs) + G_INT(OFS_RETURN) = 0; + else + G_INT(OFS_RETURN) = pr_fielddefs[fldidx].ofs; +} +static void PF_entityfieldname(void) +{ + unsigned int fldidx = G_FLOAT(OFS_PARM0); + if (fldidx < (unsigned int)progs->numfielddefs) + G_INT(OFS_RETURN) = pr_fielddefs[fldidx].s_name; + else + G_INT(OFS_RETURN) = 0; +} +static void PF_entityfieldtype(void) +{ + unsigned int fldidx = G_FLOAT(OFS_PARM0); + if (fldidx >= (unsigned int)progs->numfielddefs) + G_INT(OFS_RETURN) = ev_void; + else + G_INT(OFS_RETURN) = pr_fielddefs[fldidx].type; +} +static void PF_getentityfieldstring(void) +{ + unsigned int fldidx = G_FLOAT(OFS_PARM0); + edict_t *ent = G_EDICT(OFS_PARM1); + if (fldidx < (unsigned int)progs->numfielddefs) + { + char *ret = PR_GetTempString(); + const char *val = PR_UglyValueString (pr_fielddefs[fldidx].type, (eval_t*)((float*)&ent->v + pr_fielddefs[fldidx].ofs)); + q_strlcpy(ret, val, STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(ret); + } + else + G_INT(OFS_RETURN) = 0; +} +static void PF_putentityfieldstring(void) +{ + unsigned int fldidx = G_FLOAT(OFS_PARM0); + edict_t *ent = G_EDICT(OFS_PARM1); + const char *value = G_STRING(OFS_PARM2); + if (fldidx < (unsigned int)progs->numfielddefs) + G_FLOAT(OFS_RETURN) = ED_ParseEpair ((void *)&ent->v, pr_fielddefs+fldidx, value); + else + G_FLOAT(OFS_RETURN) = false; +} +//static void PF_loadfromdata(void) +//{ +//fixme; +//} +//static void PF_loadfromfile(void) +//{ +//fixme; +//} + +static void PF_parseentitydata(void) +{ + edict_t *ed = G_EDICT(OFS_PARM0); + const char *data = G_STRING(OFS_PARM1), *end; + unsigned int offset = (pr_argc>2)?G_FLOAT(OFS_PARM2):0; + if (offset) + { + unsigned int len = strlen(data); + if (offset > len) + offset = len; + } + if (!data[offset]) + G_FLOAT(OFS_RETURN) = 0; + else + { + end = ED_ParseEdict(data+offset, ed); + G_FLOAT(OFS_RETURN) = end - data; + } +} +static void PF_callfunction(void) +{ + dfunction_t *fnc; + const char *fname; + if (!pr_argc) + return; + pr_argc--; + fname = G_STRING(OFS_PARM0 + pr_argc*3); + fnc = ED_FindFunction(fname); + if (fnc && fnc->first_statement > 0) + { + PR_ExecuteProgram(fnc - pr_functions); + } +} +static void PF_isfunction(void) +{ + const char *fname = G_STRING(OFS_PARM0); + G_FLOAT(OFS_RETURN) = ED_FindFunction(fname)?true:false; +} + +//other stuff +static void PF_gettime (void) +{ + int timer = (pr_argc > 0)?G_FLOAT(OFS_PARM0):0; + switch(timer) + { + default: + Con_DPrintf("PF_gettime: unsupported timer %i\n", timer); + case 0: //cached time at start of frame + G_FLOAT(OFS_RETURN) = realtime; + break; + case 1: //actual time + G_FLOAT(OFS_RETURN) = Sys_DoubleTime(); + break; + //case 2: //highres.. looks like time into the frame. no idea + //case 3: //uptime + //case 4: //cd track + //case 5: //client simtime + } +} +#define STRINGIFY2(x) #x +#define STRINGIFY(x) STRINGIFY2(x) +static void PF_infokey_internal(qboolean returnfloat) +{ + unsigned int ent = G_EDICTNUM(OFS_PARM0); + const char *key = G_STRING(OFS_PARM1); + const char *r; + char buf[64]; + if (!ent) + { //nq doesn't really do serverinfo. it just has some cvars. + if (!strcmp(key, "*version")) + { + q_snprintf(buf, sizeof(buf), ENGINE_NAME_AND_VER); + r = buf; + } + else + { + cvar_t *var = Cvar_FindVar(key); + if (var && (var->flags & CVAR_SERVERINFO)) + r = var->string; + else + r = NULL; + } + } + else if (ent <= (unsigned int)svs.maxclients && svs.clients[ent-1].active) + { + ent--; + r = buf; + if (!strcmp(key, "ip")) + r = NET_QSocketGetTrueAddressString(svs.clients[ent].netconnection); + else if (!strcmp(key, "ping")) + { + float total = 0; + unsigned int j; + for (j = 0; j < NUM_PING_TIMES; j++) + total+=svs.clients[ent].ping_times[j]; + total /= NUM_PING_TIMES; + q_snprintf(buf, sizeof(buf), "%f", total); + } + else if (!strcmp(key, "protocol")) + { + switch(sv.protocol) + { + case PROTOCOL_NETQUAKE: + r = "quake"; + break; + case PROTOCOL_FITZQUAKE: + r = "fitz666"; + break; + case PROTOCOL_RMQ: + r = "rmq999"; + break; + default: + r = ""; + break; + } + } + else if (!strcmp(key, "name")) + r = svs.clients[ent].name; + else if (!strcmp(key, "topcolor")) + q_snprintf(buf, sizeof(buf), "%u", svs.clients[ent].colors>>4); + else if (!strcmp(key, "bottomcolor")) + q_snprintf(buf, sizeof(buf), "%u", svs.clients[ent].colors&15); + else if (!strcmp(key, "team")) //nq doesn't really do teams. qw does though. yay compat? + q_snprintf(buf, sizeof(buf), "t%u", (svs.clients[ent].colors&15)+1); + else if (!strcmp(key, "*VIP")) + r = ""; + else if (!strcmp(key, "*spectator")) + r = ""; + else if (!strcmp(key, "skin")) + r = ""; + else if (!strcmp(key, "csqcactive")) + r = ""; + else if (!strcmp(key, "rate")) + r = "0"; + else + r = NULL; + } + else r = NULL; + + if (returnfloat) + { + if (r) + G_FLOAT(OFS_RETURN) = atof(r); + else + G_FLOAT(OFS_RETURN) = 0; + } + else + { + if (r) + { + char *temp = PR_GetTempString(); + q_strlcpy(temp, r, STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(temp); + } + else + G_INT(OFS_RETURN) = 0; + } +} +static void PF_infokey_s(void) +{ + PF_infokey_internal(false); +} +static void PF_infokey_f(void) +{ + PF_infokey_internal(true); +} + +static void PF_multicast_internal(qboolean reliable, byte *pvs, unsigned int requireext2) +{ + unsigned int i; + int cluster; + mleaf_t *playerleaf; + if (!pvs) + { + if (!requireext2) + SZ_Write((reliable?&sv.reliable_datagram:&sv.datagram), sv.multicast.data, sv.multicast.cursize); + else + { + for (i = 0; i < (unsigned int)svs.maxclients; i++) + { + if (!svs.clients[i].active) + continue; + if (!(svs.clients[i].protocol_pext2 & requireext2)) + continue; + SZ_Write((reliable?&svs.clients[i].message:&svs.clients[i].datagram), sv.multicast.data, sv.multicast.cursize); + } + } + } + else + { + for (i = 0; i < (unsigned int)svs.maxclients; i++) + { + if (!svs.clients[i].active) + continue; + + if (requireext2 && !(svs.clients[i].protocol_pext2 & requireext2)) + continue; + + //figure out which cluster (read: pvs index) to use. + playerleaf = Mod_PointInLeaf(svs.clients[i].edict->v.origin, sv.worldmodel); + cluster = playerleaf - sv.worldmodel->leafs; + cluster--; //pvs is 1-based, leaf 0 is discarded. + if (cluster < 0 || (pvs[cluster>>3] & (1<<(cluster&7)))) + { + //they can see it. add it in to whichever buffer is appropriate. + if (reliable) + SZ_Write(&svs.clients[i].message, sv.multicast.data, sv.multicast.cursize); + else + SZ_Write(&svs.clients[i].datagram, sv.multicast.data, sv.multicast.cursize); + } + } + } +} +//FIXME: shouldn't really be using pext2, but we don't track the earlier extensions, and it should be safe enough. +static void SV_Multicast(multicast_t to, float *org, int msg_entity, unsigned int requireext2) +{ + unsigned int i; + + if (to == MULTICAST_INIT && sv.state != ss_loading) + { + SZ_Write (&sv.signon, sv.multicast.data, sv.multicast.cursize); + to = MULTICAST_ALL_R; //and send to players that are already on + } + + switch(to) + { + case MULTICAST_INIT: + SZ_Write (&sv.signon, sv.multicast.data, sv.multicast.cursize); + break; + case MULTICAST_ALL_R: + case MULTICAST_ALL_U: + PF_multicast_internal(to==MULTICAST_PHS_R, NULL, requireext2); + break; + case MULTICAST_PHS_R: + case MULTICAST_PHS_U: + PF_multicast_internal(to==MULTICAST_PHS_R, NULL/*Mod_LeafPHS(Mod_PointInLeaf(org, sv.worldmodel), sv.worldmodel)*/, requireext2); //we don't support phs, that would require lots of pvs decompression+merging stuff, and many q1bsps have a LOT of leafs. + break; + case MULTICAST_PVS_R: + case MULTICAST_PVS_U: + PF_multicast_internal(to==MULTICAST_PVS_R, Mod_LeafPVS(Mod_PointInLeaf(org, sv.worldmodel), sv.worldmodel), requireext2); + break; + case MULTICAST_ONE_R: + case MULTICAST_ONE_U: + i = msg_entity-1; + if (i >= (unsigned int)svs.maxclients) + break; + //a unicast, which ignores pvs. + //(unlike vanilla this allows unicast unreliables, so woo) + if (svs.clients[i].active) + { + SZ_Write(((to==MULTICAST_ONE_R)?&svs.clients[i].message:&svs.clients[i].datagram), sv.multicast.data, sv.multicast.cursize); + } + break; + default: + break; + } + SZ_Clear(&sv.multicast); +} +static void PF_multicast(void) +{ + float *org = G_VECTOR(OFS_PARM0); + multicast_t to = G_FLOAT(OFS_PARM1); + SV_Multicast(to, org, 0, 0); +} +static void PF_randomvector(void) +{ + vec3_t temp; + do + { + temp[0] = (rand() & 32767) * (2.0 / 32767.0) - 1.0; + temp[1] = (rand() & 32767) * (2.0 / 32767.0) - 1.0; + temp[2] = (rand() & 32767) * (2.0 / 32767.0) - 1.0; + } while (DotProduct(temp, temp) >= 1); + VectorCopy (temp, G_VECTOR(OFS_RETURN)); +} +static void PF_checkextension(void); +static void PF_checkbuiltin(void); +static void PF_builtinsupported(void); + +static void PF_uri_escape(void) +{ + static const char *hex = "0123456789ABCDEF"; + + char *result = PR_GetTempString(); + char *o = result; + const unsigned char *s = (const unsigned char*)G_STRING(OFS_PARM0); + *result = 0; + while (*s && o < result+STRINGTEMP_LENGTH-4) + { + if ((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9') + || *s == '.' || *s == '-' || *s == '_') + *o++ = *s++; + else + { + *o++ = '%'; + *o++ = hex[*s>>4]; + *o++ = hex[*s&0xf]; + s++; + } + } + *o = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(result); +} +static void PF_uri_unescape(void) +{ + const char *s = G_STRING(OFS_PARM0), *i; + char *resultbuf = PR_GetTempString(), *o; + unsigned char hex; + i = s; o = resultbuf; + while (*i && o < resultbuf+STRINGTEMP_LENGTH-2) + { + if (*i == '%') + { + hex = 0; + if (i[1] >= 'A' && i[1] <= 'F') + hex += i[1]-'A'+10; + else if (i[1] >= 'a' && i[1] <= 'f') + hex += i[1]-'a'+10; + else if (i[1] >= '0' && i[1] <= '9') + hex += i[1]-'0'; + else + { + *o++ = *i++; + continue; + } + hex <<= 4; + if (i[2] >= 'A' && i[2] <= 'F') + hex += i[2]-'A'+10; + else if (i[2] >= 'a' && i[2] <= 'f') + hex += i[2]-'a'+10; + else if (i[2] >= '0' && i[2] <= '9') + hex += i[2]-'0'; + else + { + *o++ = *i++; + continue; + } + *o++ = hex; + i += 3; + } + else + *o++ = *i++; + } + *o = 0; + G_INT(OFS_RETURN) = PR_SetEngineString(resultbuf); +} +static void PF_crc16(void) +{ + qboolean insens = G_FLOAT(OFS_PARM0); + const char *str = PF_VarString(1); + size_t len = strlen(str); + + if (insens) + { + unsigned short crc; + + CRC_Init (&crc); + while (len--) + CRC_ProcessByte(&crc, q_tolower(*str++)); + G_FLOAT(OFS_RETURN) = crc; + } + else + G_FLOAT(OFS_RETURN) = CRC_Block((byte*)str, len); +} + +static void PF_strlennocol(void) +{ + //quakespasm doesn't support colour codes. that makes this a bit of a no-op. + //there's no single set either, so this stuff is a bit awkward in ssqc. at least nothing will crash. + G_FLOAT(OFS_RETURN) = strlen(G_STRING(OFS_PARM0)); +} +static void PF_strdecolorize(void) +{ + //quakespasm doesn't support colour codes. that makes this a bit of a no-op. + //there's no single set either, so this stuff is a bit awkward in ssqc. at least nothing will crash. + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); +} +static void PF_setattachment(void) +{ + edict_t *ent = G_EDICT(OFS_PARM0); + edict_t *tagent = G_EDICT(OFS_PARM1); + const char *tagname = G_STRING(OFS_PARM2); + eval_t *val; + + if (*tagname) + { + //we don't support md3s, or any skeletal formats, so all tag names are logically invalid for us. + Con_DWarning("PF_setattachment: tag %s not found\n", tagname); + } + + if ((val = GetEdictFieldValue(ent, pr_extfields.tag_entity))) + val->edict = EDICT_TO_PROG(tagent); + if ((val = GetEdictFieldValue(ent, pr_extfields.tag_index))) + val->_float = 0; +} +static void PF_void_stub(void) +{ + G_FLOAT(OFS_RETURN) = 0; +} + +#ifdef PSET_SCRIPT +int PF_SV_ForceParticlePrecache(const char *s) +{ + unsigned int i; + for (i = 1; i < MAX_PARTICLETYPES; i++) + { + if (!sv.particle_precache[i]) + { + if (sv.state != ss_loading) + { + MSG_WriteByte(&sv.multicast, svcdp_precache); + MSG_WriteShort(&sv.multicast, i|0x4000); + MSG_WriteString(&sv.multicast, s); + SV_Multicast(MULTICAST_ALL_R, NULL, 0, PEXT2_REPLACEMENTDELTAS); //FIXME + } + + sv.particle_precache[i] = strcpy(Hunk_Alloc(strlen(s)+1), s); //weirdness to avoid issues with tempstrings + return i; + } + if (!strcmp(sv.particle_precache[i], s)) + return i; + } + return 0; +} +static void PF_sv_particleeffectnum(void) +{ + const char *s; + unsigned int i; +#ifdef PSET_SCRIPT + extern cvar_t r_particledesc; +#endif + + s = G_STRING(OFS_PARM0); + G_FLOAT(OFS_RETURN) = 0; +// PR_CheckEmptyString (s); + + if (!*s) + return; + +#ifdef PSET_SCRIPT + if (!sv.particle_precache[1] && (!strncmp(s, "effectinfo.", 11) || strstr(r_particledesc.string, "effectinfo"))) + COM_Effectinfo_Enumerate(PF_SV_ForceParticlePrecache); +#endif + + for (i = 1; i < MAX_PARTICLETYPES; i++) + { + if (!sv.particle_precache[i]) + { + if (sv.state != ss_loading) + { + if (pr_ext_warned_particleeffectnum++ < 3) + Con_Warning ("PF_sv_particleeffectnum(%s): Precache should only be done in spawn functions\n", s); + + MSG_WriteByte(&sv.multicast, svcdp_precache); + MSG_WriteShort(&sv.multicast, i|0x4000); + MSG_WriteString(&sv.multicast, s); + SV_Multicast(MULTICAST_ALL_R, NULL, 0, PEXT2_REPLACEMENTDELTAS); + } + + sv.particle_precache[i] = strcpy(Hunk_Alloc(strlen(s)+1), s); //weirdness to avoid issues with tempstrings + G_FLOAT(OFS_RETURN) = i; + return; + } + if (!strcmp(sv.particle_precache[i], s)) + { + if (sv.state != ss_loading && !pr_checkextension.value) + { + if (pr_ext_warned_particleeffectnum++ < 3) + Con_Warning ("PF_sv_particleeffectnum(%s): Precache should only be done in spawn functions\n", s); + } + G_FLOAT(OFS_RETURN) = i; + return; + } + } + PR_RunError ("PF_sv_particleeffectnum: overflow"); +} +static void PF_sv_trailparticles(void) +{ + int efnum; + int ednum; + float *start = G_VECTOR(OFS_PARM2); + float *end = G_VECTOR(OFS_PARM3); + + /*DP gets this wrong, lets try to be compatible*/ + if (G_INT(OFS_PARM1) >= MAX_EDICTS) + { + ednum = G_EDICTNUM(OFS_PARM0); + efnum = G_FLOAT(OFS_PARM1); + } + else + { + efnum = G_FLOAT(OFS_PARM0); + ednum = G_EDICTNUM(OFS_PARM1); + } + + if (efnum <= 0) + return; + + MSG_WriteByte(&sv.multicast, svcdp_trailparticles); + MSG_WriteShort(&sv.multicast, ednum); + MSG_WriteShort(&sv.multicast, efnum); + MSG_WriteCoord(&sv.multicast, start[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, start[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, start[2], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, end[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, end[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, end[2], sv.protocolflags); + + SV_Multicast(MULTICAST_PHS_U, start, 0, PEXT2_REPLACEMENTDELTAS); +} +static void PF_sv_pointparticles(void) +{ + int efnum = G_FLOAT(OFS_PARM0); + float *org = G_VECTOR(OFS_PARM1); + float *vel = (pr_argc < 3)?vec3_origin:G_VECTOR(OFS_PARM2); + int count = (pr_argc < 4)?1:G_FLOAT(OFS_PARM3); + + if (efnum <= 0) + return; + if (count > 65535) + count = 65535; + if (count < 1) + return; + + if (count == 1 && !vel[0] && !vel[1] && !vel[2]) + { + MSG_WriteByte(&sv.multicast, svcdp_pointparticles1); + MSG_WriteShort(&sv.multicast, efnum); + MSG_WriteCoord(&sv.multicast, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, org[2], sv.protocolflags); + } + else + { + MSG_WriteByte(&sv.multicast, svcdp_pointparticles); + MSG_WriteShort(&sv.multicast, efnum); + MSG_WriteCoord(&sv.multicast, org[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, org[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, org[2], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, vel[0], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, vel[1], sv.protocolflags); + MSG_WriteCoord(&sv.multicast, vel[2], sv.protocolflags); + MSG_WriteShort(&sv.multicast, count); + } + + SV_Multicast(MULTICAST_PHS_U, org, 0, PEXT2_REPLACEMENTDELTAS); +} +#else +#define PF_sv_particleeffectnum PF_void_stub +#define PF_sv_trailparticles PF_void_stub +#define PF_sv_pointparticles PF_void_stub +#endif + +//A quick note on number ranges. +//0: automatically assigned. more complicated, but no conflicts over numbers, just names... +// NOTE: #0 is potentially ambiguous - vanilla will interpret it as instruction 0 (which is normally reserved) rather than a builtin. +// if such functions were actually used, this would cause any 64bit engines that switched to unsigned types to crash due to an underflow. +// we do some sneaky hacks to avoid changes to the vm... because we're evil. +//0-199: free for all. +//200-299: fte's random crap +//300-399: csqc's random crap +//400+: dp's random crap +static struct +{ + const char *name; + builtin_t func; + int documentednumber; + const char *typestr; + const char *desc; + int number; +} extensionbuiltins[] = +{ + {"vectoangles2", PF_ext_vectoangles, 51, D("vector(vector fwd, optional vector up)", "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.")}, + + {"sin", PF_Sin, 60, "float(float angle)"}, //60 + {"cos", PF_Cos, 61, "float(float angle)"}, //61 + {"sqrt", PF_Sqrt, 62, "float(float value)"}, //62 + {"tracetoss", PF_TraceToss, 64, "void(entity ent, entity ignore)"}, + {"etos", PF_etos, 65, "string(entity ent)"}, + + + + {"infokey", PF_infokey_s, 80, D("string(entity e, string key)", "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.")}, //80 + {"infokeyf", PF_infokey_f, 0, D("float(entity e, string key)", "Identical to regular infokey, but returns it as a float instead of creating new tempstrings.")}, //80 + {"stof", PF_stof, 81, "float(string)"}, //81 + {"multicast", PF_multicast, 82, D("#define unicast(pl,reli) do{msg_entity = pl; multicast('0 0 0', reli?MULITCAST_ONE_R:MULTICAST_ONE);}while(0)\n" + "void(vector where, float set)", "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.")}, //82 + {"tracebox", PF_tracebox, 90, D("void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent)", "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.")}, + {"randomvec", PF_randomvector, 91, D("vector()", "Returns a vector with random values. Each axis is independantly a value between -1 and 1 inclusive.")}, + {"getlight", PF_sv_getlight, 92, "vector(vector org)"},// (DP_QC_GETLIGHT), + {"registercvar", PF_registercvar, 93, D("float(string cvarname, string defaultvalue)", "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.\nThis 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.\nIn engines that support it, you will generally find the autocvar feature easier and more efficient to use.")}, + {"min", PF_min, 94, D("float(float a, float b, ...)", "Returns the lowest value of its arguments.")},// (DP_QC_MINMAXBOUND) + {"max", PF_max, 95, D("float(float a, float b, ...)", "Returns the highest value of its arguments.")},// (DP_QC_MINMAXBOUND) + {"bound", PF_bound, 96, D("float(float minimum, float val, float maximum)", "Returns val, unless minimum is higher, or maximum is less.")},// (DP_QC_MINMAXBOUND) + {"pow", PF_pow, 97, "float(float value, float exp)"}, + {"findfloat", PF_sv_findfloat, 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, 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, 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,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, 102,"float(float value)"}, + + {"fopen", PF_fopen, 110, D("filestream(string filename, float mode, optional float mmapminsize)", "Opens a file, typically prefixed with \"data/\", for either read or write access.")}, // (FRIK_FILE) + {"fclose", PF_fclose, 111, "void(filestream fhandle)"}, // (FRIK_FILE) + {"fgets", PF_fgets, 112, D("string(filestream fhandle)", "Reads a single line out of the file. The new line character is not returned as part of the string. Returns the null string on EOF (use if not(string) to easily test for this, which distinguishes it from the empty string which is returned if the line being read is blank")}, // (FRIK_FILE) + {"fputs", PF_fputs, 113, D("void(filestream fhandle, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7)", "Writes the given string(s) into the file. For compatibility with fgets, you should ensure that the string is terminated with a \\n - this will not otherwise be done for you. It is up to the engine whether dos or unix line endings are actually written.")}, // (FRIK_FILE) +// {"fread", PF_fread, 0, D("int(filestream fhandle, void *ptr, int size)", "Reads binary data out of the file. Returns truncated lengths if the read exceeds the length of the file.")}, +// {"fwrite", PF_fwrite, 0, D("int(filestream fhandle, void *ptr, int size)", "Writes binary data out of the file.")}, + {"fseek", PF_fseek, 0, D("#define ftell fseek //c-compat\nint(filestream fhandle, optional int newoffset)", "Changes the current position of the file, if specified. Returns prior position, in bytes.")}, +// {"fsize", PF_fsize, 0, D("int(filestream fhandle, optional int newsize)", "Reports the total size of the file, in bytes. Can also be used to truncate/extend the file")}, + {"strlen", PF_strlen, 114, "float(string s)"}, // (FRIK_FILE) + {"strcat", PF_strcat, 115, "string(string s1, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8)"}, // (FRIK_FILE) + {"substring", PF_substring, 116, "string(string s, float start, float length)"}, // (FRIK_FILE) + {"stov", PF_stov, 117, "vector(string s)"}, // (FRIK_FILE) + {"strzone", PF_strzone, 118, D("string(string s, ...)", "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).")}, // (FRIK_FILE) + {"strunzone", PF_strunzone, 119, D("void(string s)", "Destroys a string that was allocated by strunzone. Further references to the string MAY crash the game.")}, // (FRIK_FILE) + + {"bitshift", PF_bitshift, 218, "float(float number, float quantity)"}, + {"te_lightningblood",PF_sv_te_lightningblood,219,"void(vector org)"}, + {"strstrofs", PF_strstrofs, 221, D("float(string s1, string sub, optional float startidx)", "Returns the 0-based offset of sub within the s1 string, or -1 if sub is not in s1.\nIf startidx is set, this builtin will ignore matches before that 0-based offset.")}, + {"str2chr", PF_str2chr, 222, D("float(string str, float index)", "Retrieves the character value at offset 'index'.")}, + {"chr2str", PF_chr2str, 223, D("string(float chr, ...)", "The input floats are considered character values, and are concatenated.")}, + {"strconv", PF_strconv, 224, D("string(float ccase, float redalpha, float redchars, string str, ...)", "Converts quake chars in the input string amongst different representations.\nccase specifies the new case for letters.\n 0: not changed.\n 1: forced to lower case.\n 2: forced to upper case.\nredalpha and redchars switch between colour ranges.\n 0: no change.\n 1: Forced white.\n 2: Forced red.\n 3: Forced gold(low) (numbers only).\n 4: Forced gold (high) (numbers only).\n 5+6: Forced to white and red alternately.\nYou should not use this builtin in combination with UTF-8.")}, + {"strpad", PF_strpad, 225, D("string(float pad, string str1, ...)", "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.")}, //will be moved + {"infoadd", PF_infoadd, 226, D("string(infostring old, string key, string value)", "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.")}, + {"infoget", PF_infoget, 227, D("string(infostring info, string key)", "Reads a named value from an infostring. The returned value is a tempstring")}, +// {"strcmp", PF_strncmp, 228, D("float(string s1, string s2)", "Compares the two strings exactly. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher.")}, + {"strncmp", PF_strncmp, 228, D("#define strcmp strncmp\nfloat(string s1, string s2, optional float len, optional float s1ofs, optional float s2ofs)", "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.\nReturns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher.")}, + {"strcasecmp", PF_strncasecmp, 229, D("float(string s1, string s2)", "Compares the two strings without case sensitivity.\nReturns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon.")}, + {"strncasecmp", PF_strncasecmp, 230, D("float(string s1, string s2, float len, optional float s1ofs, optional float s2ofs)", "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.\nReturns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon.")}, + {"strtrim", PF_strtrim, 0, D("string(string s)", "Trims the whitespace from the start+end of the string.")}, + {"te_bloodqw", PF_sv_te_bloodqw, 239, "void(vector org, float count)"}, + {"mod", PF_mod, 245, "float(float a, float n)"}, + {"stoi", PF_stoi, 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, 260, D("string(int)", "Converts the passed true integer into a base10 string.")}, + {"stoh", PF_stoh, 261, D("int(string)", "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")}, + {"htos", PF_htos, 262, D("string(int)", "Formats an integer as a base16 string, with leading 0s and no prefix. Always returns 8 characters.")}, + {"ftoi", PF_ftoi, 0, D("int(float)", "Converts the given float into a true integer without depending on extended qcvm instructions.")}, + {"itof", PF_itof, 0, D("float(int)", "Converts the given true integer into a float without depending on extended qcvm instructions.")}, + {"crossproduct", PF_crossproduct, 0, D("#define dotproduct(v1,v2) ((vector)(v1)*(vector)(v2))\nvector(vector v1, vector v2)", "Small helper function to calculate the crossproduct of two vectors.")}, + {"frameforname", PF_frameforname, 276, D("float(float modidx, string framename)", "Looks up a framegroup from a model by name, avoiding the need for hardcoding. Returns -1 on error.")},// (FTE_CSQC_SKELETONOBJECTS) + {"frameduration", PF_frameduration, 277, D("float(float modidx, float framenum)", "Retrieves the duration (in seconds) of the specified framegroup.")},// (FTE_CSQC_SKELETONOBJECTS) + {"WriteFloat", PF_WriteFloat, 280, "void(float buf, float fl)"}, + {"frametoname", PF_frametoname, 284, "string(float modidx, float framenum)"}, + {"checkcommand", PF_checkcommand, 294, D("float(string name)", "Checks to see if the supplied name is a valid command, cvar, or alias. Returns 0 if it does not exist.")}, + {"particleeffectnum",PF_sv_particleeffectnum,335,D("float(string effectname)", "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.\nDifferent engines will have different particle systems, this specifies the QC API only.")},// (EXT_CSQC) + {"trailparticles", PF_sv_trailparticles,336, D("void(float effectnum, entity ent, vector start, vector end)", "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.")},// (EXT_CSQC), + {"pointparticles", PF_sv_pointparticles,337, D("void(float effectnum, vector origin, optional vector dir, optional float count)", "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.")},// (EXT_CSQC) + {"print", PF_print, 339, D("void(string s, ...)", "Unconditionally print on the local system's console, even in ssqc (doesn't care about the value of the developer cvar).")},//(EXT_CSQC) + {"wasfreed", PF_WasFreed, 353, D("float(entity ent)", "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.")},//(EXT_CSQC) (should be availabe on server too) + + {"copyentity", PF_copyentity, 400, D("entity(entity from, optional entity to)", "Copies all fields from one entity to another.")},// (DP_QC_COPYENTITY) + {"setcolors", PF_setcolors, 401, D("void(entity ent, float colours)", "Changes a player's colours. The bits 0-3 are the lower/trouser colour, bits 4-7 are the upper/shirt colours.")},//DP_SV_SETCOLOR + {"findchain", PF_sv_findchain, 402, "entity(.string field, string match)"},// (DP_QC_FINDCHAIN) + {"findchainfloat", PF_sv_findchainfloat,403, "entity(.float fld, float match)"},// (DP_QC_FINDCHAINFLOAT) + {"effect", PF_sv_effect, 404, D("void(vector org, string modelname, float startframe, float endframe, float framerate)", "stub. Spawns a self-animating sprite")},// (DP_SV_EFFECT) + {"te_blood", PF_sv_te_blooddp, 405, "void(vector org, vector dir, float count)"},// #405 te_blood + {"te_bloodshower", PF_sv_te_bloodshower, 406, "void(vector mincorner, vector maxcorner, float explosionspeed, float howmany)", "stub."},// (DP_TE_BLOODSHOWER) + {"te_explosionrgb", PF_sv_te_explosionrgb, 407, "void(vector org, vector color)", "stub."},// (DP_TE_EXPLOSIONRGB) + {"te_particlecube", PF_sv_te_particlecube, 408, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter)", "stub."},// (DP_TE_PARTICLECUBE) + {"te_particlerain", PF_sv_te_particlerain, 409, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color)"},// (DP_TE_PARTICLERAIN) + {"te_particlesnow", PF_sv_te_particlesnow, 410, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color)"},// (DP_TE_PARTICLESNOW) + {"te_spark", PF_sv_te_spark, 411, "void(vector org, vector vel, float howmany)", "stub."},// (DP_TE_SPARK) + {"te_gunshotquad", PF_sv_te_gunshotquad, 412, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) + {"te_spikequad", PF_sv_te_spikequad, 413, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) + {"te_superspikequad",PF_sv_te_superspikequad,414, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) + {"te_explosionquad",PF_sv_te_explosionquad,415, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) + {"te_smallflash", PF_sv_te_smallflash, 416, "void(vector org)", "stub."},// (DP_TE_SMALLFLASH) + {"te_customflash", PF_sv_te_customflash, 417, "void(vector org, float radius, float lifetime, vector color)", "stub."},// (DP_TE_CUSTOMFLASH) + {"te_gunshot", PF_sv_te_gunshot, 418, "void(vector org, optional float count)"},// #418 te_gunshot + {"te_spike", PF_sv_te_spike, 419, "void(vector org)"},// #419 te_spike + {"te_superspike", PF_sv_te_superspike,420, "void(vector org)"},// #420 te_superspike + {"te_explosion", PF_sv_te_explosion, 421, "void(vector org)"},// #421 te_explosion + {"te_tarexplosion", PF_sv_te_tarexplosion,422, "void(vector org)"},// #422 te_tarexplosion + {"te_wizspike", PF_sv_te_wizspike, 423, "void(vector org)"},// #423 te_wizspike + {"te_knightspike", PF_sv_te_knightspike,424, "void(vector org)"},// #424 te_knightspike + {"te_lavasplash", PF_sv_te_lavasplash,425, "void(vector org)"},// #425 te_lavasplash + {"te_teleport", PF_sv_te_teleport, 426, "void(vector org)"},// #426 te_teleport + {"te_explosion2", PF_sv_te_explosion2,427, "void(vector org, float color, float colorlength)"},// #427 te_explosion2 + {"te_lightning1", PF_sv_te_lightning1,428, "void(entity own, vector start, vector end)"},// #428 te_lightning1 + {"te_lightning2", PF_sv_te_lightning2,429, "void(entity own, vector start, vector end)"},// #429 te_lightning2 + {"te_lightning3", PF_sv_te_lightning3,430, "void(entity own, vector start, vector end)"},// #430 te_lightning3 + {"te_beam", PF_sv_te_beam, 431, "void(entity own, vector start, vector end)"},// #431 te_beam + {"vectorvectors", PF_vectorvectors, 432, "void(vector dir)"},// (DP_QC_VECTORVECTORS) + {"te_plasmaburn", PF_sv_te_plasmaburn,433, "void(vector org)", "stub."},// (DP_TE_PLASMABURN) + {"getsurfacenumpoints",PF_getsurfacenumpoints,434, "float(entity e, float s)"},// (DP_QC_GETSURFACE) + {"getsurfacepoint",PF_getsurfacepoint, 435, "vector(entity e, float s, float n)"},// (DP_QC_GETSURFACE) + {"getsurfacenormal",PF_getsurfacenormal,436, "vector(entity e, float s)"},// (DP_QC_GETSURFACE) + {"getsurfacetexture",PF_getsurfacetexture,437, "string(entity e, float s)"},// (DP_QC_GETSURFACE) + {"getsurfacenearpoint",PF_getsurfacenearpoint,438, "float(entity e, vector p)"},// (DP_QC_GETSURFACE) + {"getsurfaceclippedpoint",PF_getsurfaceclippedpoint,439, "vector(entity e, float s, vector p)"},// (DP_QC_GETSURFACE) + {"clientcommand", PF_clientcommand, 440, "void(entity e, string s)"},// (KRIMZON_SV_PARSECLIENTCOMMAND) + {"tokenize", PF_Tokenize, 441, "float(string s)"},// (KRIMZON_SV_PARSECLIENTCOMMAND) + {"argv", PF_ArgV, 442, "string(float n)"},// (KRIMZON_SV_PARSECLIENTCOMMAND + {"argc", PF_ArgC, 0, "float()"}, + {"setattachment", PF_setattachment, 443, "void(entity e, entity tagentity, string tagname)", ""},// (DP_GFX_QUAKE3MODELTAGS) + {"search_begin", PF_search_begin, 444, "searchhandle(string pattern, optional float caseinsensitive, optional float quiet)", "stub. initiate a filesystem scan based upon filenames. Be sure to call search_end on the returned handle."}, + {"search_end", PF_search_end, 445, "void(searchhandle handle)", "stub."}, + {"search_getsize", PF_search_getsize, 446, "float(searchhandle handle)", "stub. Retrieves the number of files that were found."}, + {"search_getfilename", PF_search_getfilename, 447, "string(searchhandle handle, float num)", "stub. Retrieves name of one of the files that was found by the initial search."}, + {"search_getfilesize", PF_search_getfilesize, 0, "float(searchhandle handle, float num)", "stub. Retrieves the size of one of the files that was found by the initial search."}, + {"search_getfilemtime", PF_search_getfilemtime, 0, "string(searchhandle handle, float num)", "stub. Retrieves modification time of one of the files in %Y-%m-%d %H:%M:%S format."}, + {"cvar_string", PF_cvar_string, 448, "string(string cvarname)"},//DP_QC_CVAR_STRING + {"findflags", PF_sv_findflags, 449, "entity(entity start, .float fld, float match)"},//DP_QC_FINDFLAGS + {"findchainflags", PF_sv_findchainflags,450, "entity(.float fld, float match)"},//DP_QC_FINDCHAINFLAGS + {"dropclient", PF_dropclient, 453, "void(entity player)"},//DP_SV_BOTCLIENT + {"spawnclient", PF_spawnclient, 454, "entity()", "Spawns a dummy player entity.\nNote that such dummy players will be carried from one map to the next.\nWarning: DP_SV_CLIENTCOLORS DP_SV_CLIENTNAME are not implemented in quakespasm, so use KRIMZON_SV_PARSECLIENTCOMMAND's clientcommand builtin to change the bot's name/colours/skin/team/etc, in the same way that clients would ask."},//DP_SV_BOTCLIENT + {"clienttype", PF_clienttype, 455, "float(entity client)"},//botclient + {"WriteUnterminatedString",PF_WriteString2,456, "void(float target, string str)"}, //writestring but without the null terminator. makes things a little nicer. + {"edict_num", PF_edict_for_num, 459, "entity(float entnum)"},//DP_QC_EDICT_NUM + {"buf_create", PF_buf_create, 460, "strbuf()"},//DP_QC_STRINGBUFFERS + {"buf_del", PF_buf_del, 461, "void(strbuf bufhandle)"},//DP_QC_STRINGBUFFERS + {"buf_getsize", PF_buf_getsize, 462, "float(strbuf bufhandle)"},//DP_QC_STRINGBUFFERS + {"buf_copy", PF_buf_copy, 463, "void(strbuf bufhandle_from, strbuf bufhandle_to)"},//DP_QC_STRINGBUFFERS + {"buf_sort", PF_buf_sort, 464, "void(strbuf bufhandle, float sortprefixlen, float backward)"},//DP_QC_STRINGBUFFERS + {"buf_implode", PF_buf_implode, 465, "string(strbuf bufhandle, string glue)"},//DP_QC_STRINGBUFFERS + {"bufstr_get", PF_bufstr_get, 466, "string(strbuf bufhandle, float string_index)"},//DP_QC_STRINGBUFFERS + {"bufstr_set", PF_bufstr_set, 467, "void(strbuf bufhandle, float string_index, string str)"},//DP_QC_STRINGBUFFERS + {"bufstr_add", PF_bufstr_add, 468, "float(strbuf bufhandle, string str, float order)"},//DP_QC_STRINGBUFFERS + {"bufstr_free", PF_bufstr_free, 469, "void(strbuf bufhandle, float string_index)"},//DP_QC_STRINGBUFFERS + + + + + + + {"asin", PF_asin, 471, "float(float s)"},//DP_QC_ASINACOSATANATAN2TAN + {"acos", PF_acos, 472, "float(float c)"},//DP_QC_ASINACOSATANATAN2TAN + {"atan", PF_atan, 473, "float(float t)"},//DP_QC_ASINACOSATANATAN2TAN + {"atan2", PF_atan2, 474, "float(float c, float s)"},//DP_QC_ASINACOSATANATAN2TAN + {"tan", PF_tan, 475, "float(float a)"},//DP_QC_ASINACOSATANATAN2TAN + {"strlennocol", PF_strlennocol, 476, D("float(string s)", "stub. Returns the number of characters in the string after any colour codes or other markup has been parsed.")},//DP_QC_STRINGCOLORFUNCTIONS + {"strdecolorize", PF_strdecolorize, 477, D("string(string s)", "stub. Flattens any markup/colours, removing them from the string.")},//DP_QC_STRINGCOLORFUNCTIONS + {"strftime", PF_strftime, 478, "string(float uselocaltime, string format, ...)"}, //DP_QC_STRFTIME + {"tokenizebyseparator",PF_tokenizebyseparator,479, "float(string s, string separator1, ...)"}, //DP_QC_TOKENIZEBYSEPARATOR + {"strtolower", PF_strtolower, 480, "string(string s)"}, //DP_QC_STRING_CASE_FUNCTIONS + {"strtoupper", PF_strtoupper, 481, "string(string s)"}, //DP_QC_STRING_CASE_FUNCTIONS + {"cvar_defstring", PF_cvar_defstring, 482, "string(string s)"}, //DP_QC_CVAR_DEFSTRING + {"pointsound", PF_sv_pointsound, 483, "void(vector origin, string sample, float volume, float attenuation)"},//DP_SV_POINTSOUND + {"strreplace", PF_strreplace, 484, "string(string search, string replace, string subject)"},//DP_QC_STRREPLACE + {"strireplace", PF_strireplace, 485, "string(string search, string replace, string subject)"},//DP_QC_STRREPLACE + {"getsurfacepointattribute",PF_getsurfacepointattribute,486, "vector(entity e, float s, float n, float a)"},//DP_QC_GETSURFACEPOINTATTRIBUTE + + {"crc16", PF_crc16, 494, "float(float caseinsensitive, string s, ...)"},//DP_QC_CRC16 + {"cvar_type", PF_cvar_type, 495, "float(string name)"},//DP_QC_CVAR_TYPE + {"numentityfields", PF_numentityfields, 496, D("float()", "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).")},//DP_QC_ENTITYDATA + {"findentityfield", PF_findentityfield, 0, D("float(string fieldname)", "Find a field index by name.")}, + {"entityfieldref", PF_entityfieldref, 0, D("typedef .__variant field_t;\nfield_t(float fieldnum)", "Returns a field value that can be directly used to read entity fields. Be sure to validate the type with entityfieldtype before using.")},//DP_QC_ENTITYDATA + {"entityfieldname", PF_entityfieldname, 497, D("string(float fieldnum)", "Retrieves the name of the given entity field.")},//DP_QC_ENTITYDATA + {"entityfieldtype", PF_entityfieldtype, 498, D("float(float fieldnum)", "Provides information about the type of the field specified by the field num. Returns one of the EV_ values.")},//DP_QC_ENTITYDATA + {"getentityfieldstring",PF_getentityfieldstring,499, "string(float fieldnum, entity ent)"},//DP_QC_ENTITYDATA + {"putentityfieldstring",PF_putentityfieldstring,500, "float(float fieldnum, entity ent, string s)"},//DP_QC_ENTITYDATA + {"whichpack", PF_whichpack, 503, D("string(string filename, optional float makereferenced)", "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.")},//DP_QC_WHICHPACK + {"uri_escape", PF_uri_escape, 510, "string(string in)"},//DP_QC_URI_ESCAPE + {"uri_unescape", PF_uri_unescape, 511, "string(string in)"},//DP_QC_URI_ESCAPE + {"num_for_edict", PF_num_for_edict, 512, "float(entity ent)"},//DP_QC_NUM_FOR_EDICT + {"tokenize_console",PF_tokenize_console,514, D("float(string str)", "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.")}, + {"argv_start_index",PF_argv_start_index,515, D("float(float idx)", "Returns the character index that the tokenized arg started at.")}, + {"argv_end_index", PF_argv_end_index, 516, D("float(float idx)", "Returns the character index that the tokenized arg stopped at.")}, +// {"buf_cvarlist", PF_buf_cvarlist, 517, "void(strbuf strbuf, string pattern, string antipattern)"}, + {"cvar_description",PF_cvar_description,518, D("string(string cvarname)", "Retrieves the description of a cvar, which might be useful for tooltips or help files. This may still not be useful.")}, + {"gettime", PF_gettime, 519, "float(optional float timetype)"}, +// {"loadfromdata", PF_loadfromdata, 529, D("void(string s)", "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.")}, +// {"loadfromfile", PF_loadfromfile, 530, D("void(string s)", "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.")}, + {"log", PF_Logarithm, 532, 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.")}, + {"buf_loadfile", PF_buf_loadfile, 535, D("float(string filename, strbuf bufhandle)", "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.")}, + {"buf_writefile", PF_buf_writefile, 536, D("float(filestream filehandle, strbuf bufhandle, optional float startpos, optional float numstrings)", "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.")}, + {"callfunction", PF_callfunction, 605, D("void(.../*, string funcname*/)", "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")}, + {"isfunction", PF_isfunction, 607, D("float(string s)", "Returns true if the named function exists and can be called with the callfunction builtin.")}, + {"parseentitydata", PF_parseentitydata, 613, D("float(entity e, string s, optional float offset)", "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\"}. Returns <=0 on failure, otherwise returns the offset in the string that was read to.")}, +// {"generateentitydata",PF_generateentitydata,0, D("string(entity e)", "Dumps the entities fields into a string which can later be parsed with parseentitydata."}), + {"sprintf", PF_sprintf, 627, "string(string fmt, ...)"}, + {"getsurfacenumtriangles",PF_getsurfacenumtriangles,628,"float(entity e, float s)"}, + {"getsurfacetriangle",PF_getsurfacetriangle,629,"vector(entity e, float s, float n)"}, +// {"digest_hex", PF_digest_hex, 639, "string(string digest, string data, ...)"}, +}; + +static const char *extnames[] = +{ + "DP_CON_SET", + "DP_CON_SETA", + "DP_EF_NOSHADOW", + "DP_ENT_ALPHA", //already in quakespasm, supposedly. + "DP_ENT_COLORMOD", + "DP_ENT_SCALE", + "DP_ENT_TRAILEFFECTNUM", + //"DP_GFX_QUAKE3MODELTAGS", //we support attachments but no md3/iqm/tags, so we can't really advertise this (although the builtin is complete if you ignore the lack of md3/iqms/tags) + "DP_INPUTBUTTONS", + "DP_QC_AUTOCVARS", //they won't update on changes + "DP_QC_ASINACOSATANATAN2TAN", + "DP_QC_COPYENTITY", + "DP_QC_CRC16", + //"DP_QC_DIGEST", + "DP_QC_CVAR_DEFSTRING", + "DP_QC_CVAR_STRING", + "DP_QC_CVAR_TYPE", + "DP_QC_EDICT_NUM", + "DP_QC_ENTITYDATA", + "DP_QC_ETOS", + "DP_QC_FINDCHAIN", + "DP_QC_FINDCHAINFLAGS", + "DP_QC_FINDCHAINFLOAT", + "DP_QC_FINDFLAGS", + "DP_QC_FINDFLOAT", + "DP_QC_GETLIGHT", + "DP_QC_GETSURFACE", + "DP_QC_GETSURFACETRIANGLE", + "DP_QC_GETSURFACEPOINTATTRIBUTE", + "DP_QC_MINMAXBOUND", + "DP_QC_MULTIPLETEMPSTRINGS", + "DP_QC_RANDOMVEC", + "DP_QC_SINCOSSQRTPOW", + "DP_QC_SPRINTF", + "DP_QC_STRFTIME", + "DP_QC_STRING_CASE_FUNCTIONS", + "DP_QC_STRINGBUFFERS", +// "DP_QC_STRINGCOLORFUNCTIONS", //the functions are provided only as stubs. the client has absolutely no support. + "DP_QC_STRREPLACE", + "DP_QC_TOKENIZEBYSEPARATOR", + "DP_QC_TRACEBOX", + "DP_QC_TRACETOSS", + "DP_QC_TRACE_MOVETYPES", + "DP_QC_URI_ESCAPE", + "DP_QC_VECTOANGLES_WITH_ROLL", + "DP_QC_VECTORVECTORS", + "DP_QC_WHICHPACK", + "DP_VIEWZOOM", + "DP_REGISTERCVAR", + "DP_SV_BOTCLIENT", + "DP_SV_DROPCLIENT", +// "DP_SV_POINTPARTICLES", //can't enable this, because certain mods then assume that we're DP and all the particles break. + "DP_SV_POINTSOUND", + "DP_SV_PRINT", + "DP_SV_SETCOLOR", + "DP_SV_SPAWNFUNC_PREFIX", + "DP_SV_WRITEUNTERMINATEDSTRING", +// "DP_TE_BLOOD", +#ifdef PSET_SCRIPT + "DP_TE_PARTICLERAIN", + "DP_TE_PARTICLESNOW", +#endif + "DP_TE_STANDARDEFFECTBUILTINS", + "EXT_BITSHIFT", + "FRIK_FILE", //lacks the file part, but does have the strings part. +#ifdef PSET_SCRIPT + "FTE_PART_SCRIPT", + "FTE_PART_NAMESPACES", +#ifdef PSET_SCRIPT_EFFECTINFO + "FTE_PART_NAMESPACE_EFFECTINFO", +#endif +#endif + "FTE_QC_CHECKCOMMAND", + "FTE_QC_CROSSPRODUCT", + "FTE_QC_INFOKEY", + "FTE_QC_INTCONV", + "FTE_QC_MULTICAST", + "FTE_STRINGS", +#ifdef PSET_SCRIPT + "FTE_SV_POINTPARTICLES", +#endif + "KRIMZON_SV_PARSECLIENTCOMMAND", + "ZQ_QC_STRINGS", + +}; + +static builtin_t extbuiltins[1024]; + +static void PF_checkextension(void) +{ + const char *extname = G_STRING(OFS_PARM0); + unsigned int i; + for (i = 0; i < sizeof(extnames)/sizeof(extnames[0]); i++) + { + if (!strcmp(extname, extnames[i])) + { + if (!pr_checkextension.value) + Con_Printf("Mod found extension %s\n", extname); + G_FLOAT(OFS_RETURN) = true; + return; + } + } + if (!pr_checkextension.value) + Con_DPrintf("Mod tried extension %s\n", extname); + G_FLOAT(OFS_RETURN) = false; +} + +static void PF_EnableExtensionBuiltins(void) +{ + if (pr_builtins != extbuiltins) + { //first time we're using an extension! woo, everything is new!... + memcpy(extbuiltins, pr_builtins, sizeof(pr_builtins[0])*pr_numbuiltins); + pr_builtins = extbuiltins; + for (; (unsigned int)pr_numbuiltins < sizeof(extbuiltins)/sizeof(extbuiltins[0]); ) + extbuiltins[pr_numbuiltins++] = PF_Fixme; + } +} + +static void PF_builtinsupported(void) +{ + const char *biname = G_STRING(OFS_PARM0); + unsigned int i; + for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++) + { + if (!strcmp(extensionbuiltins[i].name, biname)) + { + G_FLOAT(OFS_RETURN) = extensionbuiltins[i].number; + return; + } + } + G_FLOAT(OFS_RETURN) = 0; +} + + +static void PF_checkbuiltin (void) +{ + func_t funcref = G_INT(OFS_PARM0); + if ((unsigned int)funcref < (unsigned int)progs->numfunctions) + { + dfunction_t *fnc = &pr_functions[(unsigned int)funcref]; +// const char *funcname = PR_GetString(fnc->s_name); + int binum = -fnc->first_statement; + unsigned int i; + + //qc defines the function at least. nothing weird there... + if (binum > 0 && binum < pr_numbuiltins) + { + if (pr_builtins[binum] == PF_Fixme) + { + G_FLOAT(OFS_RETURN) = false; //the builtin with that number isn't defined. + for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++) + { + if (extensionbuiltins[i].number == binum) + { //but it will be defined if its actually executed. + if (extensionbuiltins[i].desc && !strncmp(extensionbuiltins[i].desc, "stub.", 5)) + G_FLOAT(OFS_RETURN) = false; //pretend it won't work if it probably won't be useful + else + G_FLOAT(OFS_RETURN) = true; + break; + } + } + } + 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 at load, even if the builtin is activated then) + } + else + { //not valid somehow. + G_FLOAT(OFS_RETURN) = false; + } +} + +void PF_Fixme (void) +{ + //interrogate the vm to try to figure out exactly which builtin they just tried to execute. + dstatement_t *st = &pr_statements[pr_xstatement]; + eval_t *glob = (eval_t*)&pr_globals[st->a]; + if ((unsigned int)glob->function < (unsigned int)progs->numfunctions) + { + dfunction_t *fnc = &pr_functions[(unsigned int)glob->function]; + const char *funcname = PR_GetString(fnc->s_name); + int binum = -fnc->first_statement; + unsigned int i; + if (binum >= 0) + { + //find an extension with the matching number + for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++) + { + if (extensionbuiltins[i].number == binum) + { //set it up so we're faster next time + PF_EnableExtensionBuiltins(); + if (!pr_checkextension.value || (extensionbuiltins[i].desc && !strncmp(extensionbuiltins[i].desc, "stub.", 5))) + Con_Warning("Mod is using builtin #%u - %s\n", extensionbuiltins[i].documentednumber, extensionbuiltins[i].name); + else + Con_DPrintf2("Mod uses builtin #%u - %s\n", extensionbuiltins[i].documentednumber, extensionbuiltins[i].name); + extbuiltins[binum] = extensionbuiltins[i].func; + extensionbuiltins[i].func(); + return; + } + } + + PR_RunError ("unimplemented builtin #%i - %s", binum, funcname); + } + } + PR_RunError ("PF_Fixme: not a builtin..."); +} + + +//called at map end +void PR_ShutdownExtensions(void) +{ + PR_UnzoneAll(); + PF_buf_shutdown(); + tokenize_flush(); + + pr_ext_warned_particleeffectnum = 0; +} + +static func_t PR_FindExtFunction(const char *entryname) +{ //depends on 0 being an invalid function, + dfunction_t *func = ED_FindFunction(entryname); + if (func) + return func - pr_functions; + return 0; +} + +void PR_AutoCvarChanged(cvar_t *var) +{ + char *n; + ddef_t *glob; + + if (!sv.active) + return; //someone flushed our globals!.. + + n = va("autocvar_%s", var->name); + glob = ED_FindGlobal(n); + if (glob) + { + if (!ED_ParseEpair ((void *)pr_globals, glob, var->string)) + Con_Warning("EXT: Unable to configure %s\n", n); + } +} + +//called at map start +void PR_EnableExtensions(ddef_t *pr_globaldefs) +{ + unsigned int i, j; + unsigned int numautocvars = 0; + + static builtin_t *stdbuiltins; + static int stdnumbuiltins; + if (!stdbuiltins) + { + stdbuiltins = pr_builtins; + stdnumbuiltins = pr_numbuiltins; + + //this also only needs to be done once. because we're evil. + //it should help slightly with the 'documentation' above at least. + j = sizeof(extbuiltins)/sizeof(extbuiltins[0]); + for (i = 1; i < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); i++) + { + if (extensionbuiltins[i].documentednumber) + extensionbuiltins[i].number = extensionbuiltins[i].documentednumber; + else + extensionbuiltins[i].number = --j; + } + } + pr_builtins = stdbuiltins; + pr_numbuiltins = stdnumbuiltins; + + memset(&pr_extfuncs, 0, sizeof(pr_extfuncs)); + + PR_ShutdownExtensions(); //just in case. + + if (!pr_checkextension.value) + { + Con_DPrintf("not enabling qc extensions\n"); + return; + } + + PF_EnableExtensionBuiltins(); + extbuiltins[51] = PF_ext_vectoangles; + + pr_extfuncs.parseclientcommand = PR_FindExtFunction("SV_ParseClientCommand"); + pr_extfuncs.endframe = PR_FindExtFunction("EndFrame"); + + //any #0 functions are remapped to their builtins here, so we don't have to tweak the VM in an obscure potentially-breaking way. + for (i = 0; i < (unsigned int)progs->numfunctions; i++) + { + if (pr_functions[i].first_statement == 0 && pr_functions[i].s_name && !pr_functions[i].parm_start && !pr_functions[i].locals) + { + const char *name = PR_GetString(pr_functions[i].s_name); + for (j = 0; j < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); j++) + { + if (!strcmp(extensionbuiltins[j].name, name)) + { //okay, map it + pr_functions[i].first_statement = -extensionbuiltins[j].number; + break; + } + } + } + } + + //autocvars + for (i = 0; i < (unsigned int)progs->numglobaldefs; i++) + { + const char *n = PR_GetString(pr_globaldefs[i].s_name); + if (!strncmp(n, "autocvar_", 9)) + { + //really crappy approach + cvar_t *var = Cvar_Create(n + 9, PR_UglyValueString (pr_globaldefs[i].type, (eval_t*)(pr_globals + pr_globaldefs[i].ofs))); + numautocvars++; + if (!var) + continue; //name conflicts with a command? + + if (!ED_ParseEpair ((void *)pr_globals, &pr_globaldefs[i], var->string)) + Con_Warning("EXT: Unable to configure %s\n", n); + var->flags |= CVAR_AUTOCVAR; + } + } + if (numautocvars) + Con_DPrintf2("Found %i autocvars\n", numautocvars); +} + +void PR_DumpPlatform_f(void) +{ + char name[MAX_OSPATH]; + FILE *f; + const char *outname = "qsextensions"; + unsigned int i, j; + for (i = 1; i < (unsigned)Cmd_Argc(); ) + { + const char *arg = Cmd_Argv(i++); + if (!strcmp(arg, "-O")) + { + if (arg[2]) + outname = arg+2; + else + outname = Cmd_Argv(i++); + } + else + { + Con_Printf("%s: Unknown argument\n", Cmd_Argv(0)); + return; + } + } + + if (strstr(outname, "..")) + return; + q_snprintf (name, sizeof(name), "%s/src/%s", com_gamedir, outname); + COM_AddExtension (name, ".qc", sizeof(name)); + + f = fopen (name, "w"); + if (!f) + { + Con_Printf("%s: Couldn't write %s\n", Cmd_Argv(0), name); + return; + } + fprintf(f, + "/*\n" + "Extensions file for "ENGINE_NAME_AND_VER"\n" + "This file is auto-generated by %s %s.\n" + "You will probably need to use FTEQCC to compile this.\n" + "*/\n" + ,Cmd_Argv(0), Cmd_Args()?Cmd_Args():"with no args"); + + fprintf(f, + "\n\n//QuakeSpasm only supports ssqc, so including this file in some other situation is a user error\n" + "#if defined(QUAKEWORLD) || defined(CSQC) || defined(MENU)\n" + "#error Mixed up module defs\n" + "#endif\n" + ); + + fprintf(f, "\n\n//List of advertised extensions\n"); + for (i = 0; i < sizeof(extnames)/sizeof(extnames[0]); i++) + fprintf(f, "//%s\n", extnames[i]); + + fprintf(f, "\n\n//Explicitly flag this stuff as probably-not-referenced, meaning fteqcc will shut up about it and silently strip what it can.\n"); + fprintf(f, "#pragma noref 1\n"); + + fprintf(f, "\n\n//Some custom types (that might be redefined as accessors by fteextensions.qc, although we don't define any methods here)\n"); + fprintf(f, "#ifdef _ACCESSORS\n"); + fprintf(f, "accessor strbuf:float;\n"); + fprintf(f, "accessor searchhandle:float;\n"); + fprintf(f, "accessor hashtable:float;\n"); + fprintf(f, "accessor infostring:string;\n"); + fprintf(f, "accessor filestream:float;\n"); + fprintf(f, "#else\n"); + fprintf(f, "#define strbuf float\n"); + fprintf(f, "#define searchhandle float\n"); + fprintf(f, "#define hashtable float\n"); + fprintf(f, "#define infostring string\n"); + fprintf(f, "#define filestream float\n"); + fprintf(f, "#endif\n"); + + //extra fields + fprintf(f, "\n\n//Supported Extension fields\n"); + fprintf(f, ".float gravity;\n"); //used by hipnotic + fprintf(f, "//.float items2; /*if defined, overrides serverflags for displaying runes on the hud*/\n"); //used by both mission packs. *REPLACES* serverflags if defined, so lets try not to define it. + fprintf(f, ".float traileffectnum; /*can also be set with 'traileffect' from a map editor*/\n"); + fprintf(f, ".float emiteffectnum; /*can also be set with 'traileffect' from a map editor*/\n"); + fprintf(f, ".vector movement; /*describes which forward/right/up keys the player is holidng*/\n"); + fprintf(f, ".entity viewmodelforclient; /*attaches this entity to the specified player's view. invisible to other players*/\n"); + fprintf(f, ".float scale; /*rescales the etntiy*/\n"); + fprintf(f, ".float alpha; /*entity opacity*/\n"); //entity alpha. woot. + fprintf(f, ".vector colormod; /*tints the entity's colours*/\n"); + fprintf(f, ".entity tag_entity;\n"); + fprintf(f, ".float tag_index;\n"); + fprintf(f, ".float button3;\n"); + fprintf(f, ".float button4;\n"); + fprintf(f, ".float button5;\n"); + fprintf(f, ".float button6;\n"); + fprintf(f, ".float button7;\n"); + fprintf(f, ".float button8;\n"); + fprintf(f, ".float viewzoom; /*rescales the user's fov*/\n"); + fprintf(f, ".float modelflags; /*provides additional modelflags to use (effects&EF_NOMODELFLAGS to replace the original model's)*/\n"); + + //extra constants + fprintf(f, "\n\n//Supported Extension Constants\n"); + fprintf(f, "const float MOVETYPE_FOLLOW = "STRINGIFY(MOVETYPE_EXT_FOLLOW)";\n"); + fprintf(f, "const float SOLID_CORPSE = "STRINGIFY(SOLID_EXT_CORPSE)";\n"); + + fprintf(f, "const float FILE_READ = "STRINGIFY(0)";\n"); + fprintf(f, "const float FILE_APPEND = "STRINGIFY(1)";\n"); + fprintf(f, "const float FILE_WRITE = "STRINGIFY(2)";\n"); + + fprintf(f, "const float CLIENTTYPE_DISCONNECT = "STRINGIFY(0)";\n"); + fprintf(f, "const float CLIENTTYPE_REAL = "STRINGIFY(1)";\n"); + fprintf(f, "const float CLIENTTYPE_BOT = "STRINGIFY(2)";\n"); + fprintf(f, "const float CLIENTTYPE_NOTCLIENT = "STRINGIFY(3)";\n"); + + fprintf(f, "const float EF_NOSHADOW = %#x;\n", EF_NOSHADOW); + fprintf(f, "const float EF_NOMODELFLAGS = %#x; /*the standard modelflags from the model are ignored*/\n", EF_NOMODELFLAGS); + + fprintf(f, "const float MF_ROCKET = %#x;\n", EF_ROCKET); + fprintf(f, "const float MF_GRENADE = %#x;\n", EF_GRENADE); + fprintf(f, "const float MF_GIB = %#x;\n", EF_GIB); + fprintf(f, "const float MF_ROTATE = %#x;\n", EF_ROTATE); + fprintf(f, "const float MF_TRACER = %#x;\n", EF_TRACER); + fprintf(f, "const float MF_ZOMGIB = %#x;\n", EF_ZOMGIB); + fprintf(f, "const float MF_TRACER2 = %#x;\n", EF_TRACER2); + fprintf(f, "const float MF_TRACER3 = %#x;\n", EF_TRACER3); + + fprintf(f, "const float MSG_MULTICAST = "STRINGIFY(4)";\n"); + fprintf(f, "const float MULTICAST_ALL = "STRINGIFY(MULTICAST_ALL_U)";\n"); +// fprintf(f, "const float MULTICAST_PHS = "STRINGIFY(MULTICAST_PHS_U)";\n"); + fprintf(f, "const float MULTICAST_PVS = "STRINGIFY(MULTICAST_PVS_U)";\n"); + fprintf(f, "const float MULTICAST_ONE = "STRINGIFY(MULTICAST_ONE_U)";\n"); + fprintf(f, "const float MULTICAST_ALL_R = "STRINGIFY(MULTICAST_ALL_R)";\n"); +// fprintf(f, "const float MULTICAST_PHS_R = "STRINGIFY(MULTICAST_PHS_R)";\n"); + fprintf(f, "const float MULTICAST_PVS_R = "STRINGIFY(MULTICAST_PVS_R)";\n"); + fprintf(f, "const float MULTICAST_ONE_R = "STRINGIFY(MULTICAST_ONE_R)";\n"); + fprintf(f, "const float MULTICAST_INIT = "STRINGIFY(MULTICAST_INIT)";\n"); + + for (j = 0; j < 2; j++) + { + if (j) + fprintf(f, "\n\n//Builtin Stubs List (these are present for simpler compatibility, but not properly supported in QuakeSpasm at this time).\n/*\n"); + else + fprintf(f, "\n\n//Builtin list\n"); + for (i = 0; i < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); i++) + { + if (j != (extensionbuiltins[i].desc?!strncmp(extensionbuiltins[i].desc, "stub.", 5):0)) + continue; + fprintf(f, "%s %s = #%i;", extensionbuiltins[i].typestr, extensionbuiltins[i].name, extensionbuiltins[i].documentednumber); + if (extensionbuiltins[i].desc && !j) + { + const char *line = extensionbuiltins[i].desc; + const char *term; + fprintf(f, " /*"); + while(*line) + { + fprintf(f, "\n\t\t"); + term = line; + while(*term && *term != '\n') + term++; + fwrite(line, 1, term - line, f); + if (*term == '\n') + { + term++; + } + line = term; + } + fprintf(f, " */\n\n"); + } + else + fprintf(f, "\n"); + } + if (j) + fprintf(f, "*/\n"); + } + + fprintf(f, "\n\n//Reset this back to normal.\n"); + fprintf(f, "#pragma noref 0\n"); + fclose(f); +} diff --git a/Quake/progs.h b/Quake/progs.h index c0e8066d..61977587 100644 --- a/Quake/progs.h +++ b/Quake/progs.h @@ -42,7 +42,7 @@ typedef struct edict_s qboolean free; link_t area; /* linked to a division node or leaf */ - int num_leafs; + unsigned int num_leafs; int leafnums[MAX_ENT_LEAFS]; entity_state_t baseline; @@ -64,6 +64,7 @@ extern dfunction_t *pr_functions; extern dstatement_t *pr_statements; extern globalvars_t *pr_global_struct; extern float *pr_globals; /* same as pr_global_struct */ +extern ddef_t *pr_fielddefs; //yay reflection. extern int pr_edict_size; /* in bytes */ @@ -73,9 +74,28 @@ void PR_Init (void); void PR_ExecuteProgram (func_t fnum); void PR_LoadProgs (void); +//from pr_ext.c +void PR_EnableExtensions(ddef_t *pr_globaldefs); //adds in the extra builtins etc +void PR_AutoCvarChanged(cvar_t *var); //updates the autocvar_ globals when their cvar is changed +void PR_ShutdownExtensions(void); //nooooes! +void PR_DumpPlatform_f(void); //console command: writes out a qsextensions.qc file +//special hacks... +int PF_SV_ForceParticlePrecache(const char *s); +int SV_Precache_Model(const char *s); +int SV_Precache_Sound(const char *s); +void PR_spawnfunc_misc_model(edict_t *self); + +//from pr_edict, for pr_ext. reflection is messy. +qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s); +const char *PR_UglyValueString (int type, eval_t *val); +ddef_t *ED_FindField (const char *name); +ddef_t *ED_FindGlobal (const char *name); +dfunction_t *ED_FindFunction (const char *fn_name); + const char *PR_GetString (int num); int PR_SetEngineString (const char *s); int PR_AllocString (int bufferlength, char **ptr); +void PR_ClearEngineString(int num); void PR_Profile_f (void); @@ -138,7 +158,49 @@ FUNC_NORETURN void PR_RunError (const char *error, ...) FUNC_PRINTF(1,2); void ED_PrintEdicts (void); void ED_PrintNum (int ent); -eval_t *GetEdictFieldValue(edict_t *ed, const char *field); +eval_t *GetEdictFieldValue(edict_t *ed, int fldofs); //handles invalid offsets with a null +int ED_FindFieldOffset (const char *name); + + +//from pr_cmds, no longer static so that pr_ext can use them. +sizebuf_t *WriteDest (void); +char *PR_GetTempString (void); +char *PF_VarString (int first); +#define STRINGTEMP_BUFFERS 16 +#define STRINGTEMP_LENGTH 1024 +void PF_Fixme(void); //the 'unimplemented' builtin. woot. + +extern struct pr_extfuncs_s +{ //various global qc entry points that might be called by the engine, if set. + func_t endframe; + func_t parseclientcommand; +} pr_extfuncs; +extern cvar_t pr_checkextension; //if 0, extensions are disabled (unless they'd be fatal, but they're still spammy) + +extern struct pr_extfields_s +{ //various fields that might be wanted by the engine. -1 == invalid + //I should probably use preprocessor magic for this list or something + int items2; //float + int gravity; //float + int alpha; //float + int movement; //vector + int viewmodelforclient; //entity + int traileffectnum; //float + int emiteffectnum; //float + int scale; //float + int colormod; //vector + int tag_entity; //entity + int tag_index; //float + int button3; //float + int button4; //float + int button5; //float + int button6; //float + int button7; //float + int button8; //float + int viewzoom; //float + int modelflags; //float, the upper 8 bits of .effects + //REMEMBER TO ADD THESE TO qsextensions.qc AND pr_edict.c +} pr_extfields; #endif /* _QUAKE_PROGS_H */ diff --git a/Quake/protocol.h b/Quake/protocol.h index 81c7e966..c8461360 100644 --- a/Quake/protocol.h +++ b/Quake/protocol.h @@ -25,9 +25,19 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // protocol.h -- communications protocols -#define PROTOCOL_NETQUAKE 15 //johnfitz -- standard quake protocol -#define PROTOCOL_FITZQUAKE 666 //johnfitz -- added new protocol for fitzquake 0.85 -#define PROTOCOL_RMQ 999 +#define PROTOCOL_NETQUAKE 15 //johnfitz -- standard quake protocol +//#define PROTOCOL_VERSION_H2 19 +//#define PROTOCOL_VERSION_NEHD 250 +#define PROTOCOL_FITZQUAKE 666 //johnfitz -- added new protocol for fitzquake 0.85 +#define PROTOCOL_RMQ 999 +//#define PROTOCOL_VERSION_DP5 3502 +//#define PROTOCOL_VERSION_DP6 3503 +#define PROTOCOL_VERSION_DP7 3504 +//#define PROTOCOL_VERSION_BJP1 10000 +//#define PROTOCOL_VERSION_BJP2 10001 +#define PROTOCOL_VERSION_BJP3 10002 //spike, note that this protocol is intentionally flawed to work around mods+writebytes - svc_staticsound is limited to 8bit indexes. +//#define PROTOCOL_FTE_PEXT1 (('F'<<0) + ('T'<<8) + ('E'<<16) + ('X' << 24)) //fte extensions, provides extensions to the underlying base protocol (like 666 or even 15). +#define PROTOCOL_FTE_PEXT2 (('F'<<0) + ('T'<<8) + ('E'<<16) + ('2' << 24)) //fte extensions, provides extensions to the underlying base protocol (like 666 or even 15). // PROTOCOL_RMQ protocol flags #define PRFL_SHORTANGLE (1 << 1) @@ -39,6 +49,21 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define PRFL_INT32COORD (1 << 7) #define PRFL_MOREFLAGS (1 << 31) // not supported +// PROTOCOL_FTE_PEXT(1) flags +//mostly uninteresting, mostly superseeded by PEXT2_REPLACEMENTDELTAS... + +// PROTOCOL_FTE_PEXT2 flags +#define PEXT2_PRYDONCURSOR 0x00000001 //a mouse cursor exposed to ssqc +#define PEXT2_VOICECHAT 0x00000002 //+voip or cl_voip_send 1; requires opus dll, and others to also have that same dll. +#define PEXT2_SETANGLEDELTA 0x00000004 //less annoying when teleporting. +#define PEXT2_REPLACEMENTDELTAS 0x00000008 //more compact entity deltas (can also be split across multiple packets) +#define PEXT2_MAXPLAYERS 0x00000010 //up to 255 player slots +#define PEXT2_PREDINFO 0x00000020 //provides input acks and reworks stats such that clc_clientdata becomes redundant. +#define PEXT2_NEWSIZEENCODING 0x00000040 //richer size encoding, for more precise bboxes. +#define PEXT2_ACCEPTED_CLIENT (PEXT2_SUPPORTED_CLIENT|PEXT2_NEWSIZEENCODING|PEXT2_PRYDONCURSOR) //pext2 flags that we can parse, but don't want to advertise +#define PEXT2_SUPPORTED_CLIENT (PEXT2_SETANGLEDELTA|PEXT2_VOICECHAT|PEXT2_REPLACEMENTDELTAS|PEXT2_MAXPLAYERS|PEXT2_PREDINFO) //pext2 flags that we understand+support +#define PEXT2_SUPPORTED_SERVER ( PEXT2_VOICECHAT|PEXT2_REPLACEMENTDELTAS |PEXT2_PREDINFO) + // if the high bit of the servercmd is set, the low bits are fast update flags: #define U_MOREBITS (1<<0) #define U_ORIGIN1 (1<<1) @@ -73,6 +98,59 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define U_TRANS (1<<15) //johnfitz + +//spike -- FTE Replacement Deltas +//first byte contains the stuff that's most likely to change constantly +#define UF_FRAME (1u<<0) +#define UF_ORIGINXY (1u<<1) +#define UF_ORIGINZ (1u<<2) +#define UF_ANGLESXZ (1u<<3) +#define UF_ANGLESY (1u<<4) +#define UF_EFFECTS (1u<<5) +#define UF_PREDINFO (1u<<6) /*ent is predicted, probably a player*/ +#define UF_EXTEND1 (1u<<7) + +/*stuff which is common on ent spawning*/ +#define UF_RESET (1u<<8) +#define UF_16BIT (1u<<9) /*within this update, frame/skin/model is 16bit, not part of the deltaing itself*/ +#define UF_MODEL (1u<<10) +#define UF_SKIN (1u<<11) +#define UF_COLORMAP (1u<<12) +#define UF_SOLID (1u<<13) /*encodes the size of the entity, so prediction can bump against it*/ +#define UF_FLAGS (1u<<14) /*some extra flags like viewmodelforclient*/ +#define UF_EXTEND2 (1u<<15) + +/*the rest is optional extensions*/ +#define UF_ALPHA (1u<<16) /*transparency*/ +#define UF_SCALE (1u<<17) /*rescaling stuff, 1/16th*/ +#define UF_BONEDATA (1u<<18) /*for ssqc control over skeletal models*/ +#define UF_DRAWFLAGS (1u<<19) /*scale offsets and things*/ +#define UF_TAGINFO (1u<<20) /*simple entity attachments, generally needs either md3s or skeletal models*/ +#define UF_LIGHT (1u<<21) /*attaching rtlights to dynamic entities from ssqc*/ +#define UF_TRAILEFFECT (1u<<22) /*attaches custom particle trails to entities, woo.*/ +#define UF_EXTEND3 (1u<<23) + +#define UF_COLORMOD (1u<<24) /*rgb tints. 1/16th*/ +#define UF_GLOW (1u<<25) /*tbh only useful as an extra 'renderable' field for csqc...*/ +#define UF_FATNESS (1u<<26) /*push the entity's normals out by this distance*/ +#define UF_MODELINDEX2 (1u<<27) /*for lame visible weapon models, like q2. just adds a second ent at the same point*/ +#define UF_GRAVITYDIR (1u<<28) /*yay prediction*/ +#define UF_EFFECTS2 (1u<<29) /*effects is 16bit, or if both effects flags are set then 32bit*/ +#define UF_UNUSED2 (1u<<30) +#define UF_UNUSED1 (1u<<31) + +/*these flags are generally not deltaed as they're changing constantly*/ +#define UFP_FORWARD (1u<<0) +#define UFP_SIDE (1u<<1) +#define UFP_UP (1u<<2) +#define UFP_MOVETYPE (1u<<3) /*deltaed*/ +#define UFP_VELOCITYXY (1u<<4) +#define UFP_VELOCITYZ (1u<<5) +#define UFP_MSEC (1u<<6) +#define UFP_WEAPONFRAME_OLD (1u<<7) //no longer used. just a stat now that I rewrote stat deltas. +#define UFP_VIEWANGLE (1u<<7) +//spike + #define SU_VIEWHEIGHT (1<<0) #define SU_IDEALPITCH (1<<1) #define SU_PUNCH1 (1<<2) @@ -107,19 +185,32 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define SU_UNUSED30 (1<<30) #define SU_EXTEND3 (1<<31) // another byte to follow, future expansion //johnfitz +//spike dp crap +#define DPSU_PUNCHVEC1 (1<<16) +#define DPSU_PUNCHVEC2 (1<<17) +#define DPSU_PUNCHVEC3 (1<<18) +#define DPSU_VIEWZOOM (1<<19) // byte factor (0 = 0.0 (not valid), 255 = 1.0) +//spike // a sound with no channel is a local only sound #define SND_VOLUME (1<<0) // a byte #define SND_ATTENUATION (1<<1) // a byte -#define SND_LOOPING (1<<2) // a long +//#define SND_LOOPING (1<<2) // a long (unused in vanilla) #define DEFAULT_SOUND_PACKET_VOLUME 255 #define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 //johnfitz -- PROTOCOL_FITZQUAKE -- new bits -#define SND_LARGEENTITY (1<<3) // a short + byte (instead of just a short) -#define SND_LARGESOUND (1<<4) // a short soundindex (instead of a byte) +#define SND_LARGEENTITY (1<<3) // a short + byte (instead of just a short) +#define SND_LARGESOUND (1<<4) // a short soundindex (instead of a byte) //johnfitz +//spike -- parsing, but not using at this time +#define SND_FTE_MOREFLAGS (1<<2) // a byte, for channel flags +#define SND_DP_PITCH (1<<5) //dp uses this for pitch... +#define SND_FTE_TIMEOFS (1<<6) //signed short, in milliseconds. +#define SND_FTE_PITCHADJ (1<<7) //a byte (speed percent (0=100%)) +#define SND_FTE_VELOCITY (1<<8) //3 shorts (1/8th), for doppler or whatever. +//spike //johnfitz -- PROTOCOL_FITZQUAKE -- flags for entity baseline messages #define B_LARGEMODEL (1<<0) // modelindex is short instead of byte @@ -178,6 +269,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define svc_damage 19 #define svc_spawnstatic 20 //#define svc_spawnbinary 21 +#define svcfte_spawnstatic2 21 #define svc_spawnbaseline 22 #define svc_temp_entity 23 #define svc_setpause 24 // [byte] on / off @@ -201,6 +293,31 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define svc_spawnstaticsound2 44 // [coord3] [short] samp [byte] vol [byte] aten //johnfitz +//spike -- some extensions for particles. +//some extra stuff for fte's pext2_replacementdeltas, including stats +//fte reuses the dp svcs for nq (instead of qw-specific ones), at least where the protocol is identical. this should make dpp7 support a little easier if you ever want to implement that. +//dp has a tendancy to use the svcs even when told to use protocol 15, so supporting them helps there too. +#define svcdp_downloaddata 50 +#define svcdp_updatestatbyte 51 +#define svcdp_effect 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate +#define svcdp_effect2 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate +#define svcdp_precache 54 // [short] precacheindex [string] filename. index&0x8000 = sound, 0x4000 = particle, 0xc000 = reserved (probably to reclaim these bits eventually), otherwise model. +#define svcdp_spawnbaseline2 55 +#define svcdp_spawnstatic2 56 +#define svcdp_entities 57 +#define svcdp_csqcentities 58 +#define svcdp_spawnstaticsound2 59 // [coord3] [short] samp [byte] vol [byte] aten +#define svcdp_trailparticles 60 // [short] entnum [short] effectnum [vector] start [vector] end +#define svcdp_pointparticles 61 // [short] effectnum [vector] start [vector] velocity [short] count +#define svcdp_pointparticles1 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 +#define svcfte_spawnbaseline2 66 +#define svcfte_updatestatstring 78 +#define svcfte_updatestatfloat 79 +#define svcfte_voicechat 84 +#define svcfte_setangledelta 85 +#define svcfte_updateentities 86 +//spike -- end + // // client to server // @@ -209,6 +326,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define clc_disconnect 2 #define clc_move 3 // [usercmd_t] #define clc_stringcmd 4 // [string] message +#define clcdp_ackframe 50 // [long] frame sequence. reused by fte replacement deltas +#define clcdp_ackdownloaddata 51 +#define clcfte_voicechat 83 /*spike*/ // // temp entity events @@ -231,17 +351,93 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define TE_BEAM 13 // PGM 01/21/97 -typedef struct +//spike, these are from nehahra. misc servers might expect us to support them +#define TENEH_EXPLOSION3 16 // [vector] origin [coord] red [coord] green [coord] blue +#define TENEH_LIGHTNING4 17 // [string] model [entity] entity [vector] start [vector] end +//spike, quakeworld mods have different explosion+gunshot behaviour, which we might be expected to support +#define TEFTE_EXPLOSION_SPRITE 20 //for compat with qw mods +#define TEFTE_GUNSHOT_COUNT 21 //for compat with qw mods +//spike, these are the various te effects from DP. ideally people would just use pointparticles so these are somewhat considered legacy. +#define TEDP_BLOOD 50 +#define TEDP_SPARK 51 +#define TEDP_BLOODSHOWER 52 +#define TEDP_EXPLOSIONRGB 53 +#define TEDP_PARTICLECUBE 54 +#define TEDP_PARTICLERAIN 55 // [vector] min [vector] max [vector] dir [short] count [byte] color +#define TEDP_PARTICLESNOW 56 // [vector] min [vector] max [vector] dir [short] count [byte] color +#define TEDP_GUNSHOTQUAD 57 // [vector] origin +#define TEDP_SPIKEQUAD 58 // [vector] origin +#define TEDP_SUPERSPIKEQUAD 59 // [vector] origin +#define TEDP_EXPLOSIONQUAD 70 // [vector] origin +#define TEDP_SMALLFLASH 72 // [vector] origin +#define TEDP_CUSTOMFLASH 73 +#define TEDP_FLAMEJET 74 +#define TEDP_PLASMABURN 75 +#define TEDP_TEI_G3 76 +#define TEDP_SMOKE 77 +#define TEDP_TEI_BIGEXPLOSION 78 +#define TEDP_TEI_PLASMAHIT 79 +//end spike + +// entity effects + +#define EF_BRIGHTFIELD 1 +#define EF_MUZZLEFLASH 2 +#define EF_BRIGHTLIGHT 4 +#define EF_DIMLIGHT 8 +//#define EF_NODRAW 16 +//#define EF_ADDITIVE 32 +//#define EF_BLUE 64 +//#define EF_RED 128 +//#define EFDP_NOGUNBOB (1u<<8) +#define EF_FULLBRIGHT (1u<<9) +//#define EFDP_PART_FLAME (1u<<10) +//#define EFDP_PART_STARDUST (1u<<11) +#define EF_NOSHADOW (1u<<12) +//#define EF_NODEPTHTEST (1u<<13) +//#define EFDP_SELECTABLE (1u<<14) +//#define EFDP_DOUBLESIDED (1u<<15) +//#define EFDP_NOSELFSHADOW (1u<<16) +//#define EFDP_DYNAMICMODELLIGHT (1u<<17) +//#define EF_GREEN (1u<<18) +//#define EF_UNUSED (1u<<19) +//#define EF_RESTARTANIM_BIT (1u<<20) //reset model lerps over toggles +//#define EF_TELEPORT_BIT (1u<<21) //reset origin lerps over toggles +//#define EFDP_LOWPRECISION (1u<<22) +#define EF_NOMODELFLAGS (1u<<23) +//24-31 are used for modelflag overrides (rotate, legacy trails, etc) + +typedef struct entity_state_s { vec3_t origin; vec3_t angles; unsigned short modelindex; //johnfitz -- was int unsigned short frame; //johnfitz -- was int - unsigned char colormap; //johnfitz -- was int - unsigned char skin; //johnfitz -- was int + unsigned int effects; + unsigned char colormap; //johnfitz -- was int + unsigned char skin; //johnfitz -- was int + unsigned char scale; //spike -- *16 + unsigned char pmovetype; //spike + unsigned short traileffectnum; //spike -- for qc-defined particle trails. typically evilly used for things that are not trails. + unsigned short emiteffectnum; //spike -- for qc-defined particle trails. typically evilly used for things that are not trails. + short velocity[3]; //spike -- the player's velocity. + unsigned char eflags; + unsigned char tagindex; + unsigned short tagentity; +// unsigned short pad; + unsigned char colormod[3]; //spike -- entity tints, *32 unsigned char alpha; //johnfitz -- added - int effects; } entity_state_t; +#define EFLAGS_STEP 1 +//#define EFLAGS_GLOWTRAIL 2 +#define EFLAGS_VIEWMODEL 4 //does not appear in reflections/third person. attached to the view. +#define EFLAGS_EXTERIORMODEL 8 //only appears in reflections/third person +//#define EFLAGS_ 16 +//#define EFLAGS_COLOURMAPPED 32 //.colormap=1024|(top<<4)|bottom), instead of a player number +//#define EFLAGS_ 64 +#define EFLAGS_ONGROUND 128 //for bobbing more than anything else. *sigh*. + +extern entity_state_t nullentitystate; //note: not all null. typedef struct { diff --git a/Quake/q_sound.h b/Quake/q_sound.h index ded7b5da..ea29180b 100644 --- a/Quake/q_sound.h +++ b/Quake/q_sound.h @@ -105,9 +105,6 @@ void S_UnblockSound (void); sfx_t *S_PrecacheSound (const char *sample); void S_TouchSound (const char *sample); -void S_ClearPrecache (void); -void S_BeginPrecaching (void); -void S_EndPrecaching (void); void S_PaintChannels (int endtime); void S_InitPaintChannels (void); @@ -147,10 +144,10 @@ void SNDDMA_UnblockSound(void); * ==================================================================== */ -#define MAX_CHANNELS 1024 // ericw -- was 512 /* johnfitz -- was 128 */ +//#define MAX_CHANNELS 1024 // spike -- made this obsolete. ericw -- was 512 /* johnfitz -- was 128 */ #define MAX_DYNAMIC_CHANNELS 128 /* johnfitz -- was 8 */ -extern channel_t snd_channels[MAX_CHANNELS]; +extern channel_t *snd_channels; /* 0 to MAX_DYNAMIC_CHANNELS-1 = normal entity sounds * MAX_DYNAMIC_CHANNELS to MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS -1 = water, etc * MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS to total_channels = static sounds @@ -158,11 +155,14 @@ extern channel_t snd_channels[MAX_CHANNELS]; extern volatile dma_t *shm; +extern int max_channels; extern int total_channels; extern int soundtime; extern int paintedtime; extern int s_rawend; +extern float voicevolumescale; + extern vec3_t listener_origin; extern vec3_t listener_forward; extern vec3_t listener_right; diff --git a/Quake/quakedef.h b/Quake/quakedef.h index 85a7e7ba..75f8ca7b 100644 --- a/Quake/quakedef.h +++ b/Quake/quakedef.h @@ -48,10 +48,16 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // combined version string like "0.92.1-beta1" #define QUAKESPASM_VER_STRING QS_STRINGIFY(QUAKESPASM_VERSION) "." QS_STRINGIFY(QUAKESPASM_VER_PATCH) QUAKESPASM_VER_SUFFIX +#define ENGINE_NAME_AND_VER "QSS" " " QUAKESPASM_VER_STRING + //define PARANOID // speed sapping error checking #define GAMENAME "id1" // directory to look in by default +#define PSET_SCRIPT //enable the scriptable particle system (poorly ported from FTE) +#define PSET_SCRIPT_EFFECTINFO //scripted particle system can load dp's effects + + #include "q_stdinc.h" // !!! if this is changed, it must be changed in d_ifacea.h too !!! @@ -88,15 +94,17 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // per-level limits // -#define MIN_EDICTS 256 // johnfitz -- lowest allowed value for max_edicts cvar -#define MAX_EDICTS 32000 // johnfitz -- highest allowed value for max_edicts cvar - // ents past 8192 can't play sounds in the standard protocol -#define MAX_LIGHTSTYLES 64 -#define MAX_MODELS 2048 // johnfitz -- was 256 -#define MAX_SOUNDS 2048 // johnfitz -- was 256 +#define MIN_EDICTS 256 // johnfitz -- lowest allowed value for max_edicts cvar +#define MAX_EDICTS 32000 // johnfitz -- highest allowed value for max_edicts cvar + // ents past 8192 can't play sounds in the standard protocol +#define MAX_LIGHTSTYLES 255 //spike -- file format max of 255, increasing will break saved games. +#define MAX_MODELS 4096 // johnfitz -- was 256 +#define MAX_SOUNDS 2048 // johnfitz -- was 256 +#define MAX_PARTICLETYPES 2048 #define SAVEGAME_COMMENT_LENGTH 39 +#define MAX_LIGHTSTYLES_VANILLA 64 #define MAX_STYLESTRING 64 // @@ -119,6 +127,17 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define STAT_SECRETS 13 // bumped on client side by svc_foundsecret #define STAT_MONSTERS 14 // bumped by svc_killedmonster +#define STAT_ITEMS 15 //replaces clc_clientdata info +#define STAT_VIEWHEIGHT 16 //replaces clc_clientdata info +//#define STAT_TIME 17 //zquake, redundant for nq. +//#define STAT_MATCHSTARTTIME 18 +//#define STAT_VIEW2 20 +#define STAT_VIEWZOOM 21 // DP +#define STAT_IDEALPITCH 25 //nq-emu +#define STAT_PUNCHANGLE_X 26 //nq-emu +#define STAT_PUNCHANGLE_Y 27 //nq-emu +#define STAT_PUNCHANGLE_Z 28 //nq-emu + // stock defines // #define IT_SHOTGUN 1 @@ -186,7 +205,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //=========================================== -#define MAX_SCOREBOARD 16 +#define MAX_SCOREBOARD 255 #define MAX_SCOREBOARDNAME 32 #define SOUND_CHANNELS 8 @@ -219,6 +238,7 @@ typedef struct #include "cmd.h" #include "crc.h" +#include "snd_voip.h" #include "progs.h" #include "server.h" @@ -316,6 +336,9 @@ void Host_ClientCommands (const char *fmt, ...) FUNC_PRINTF(1,2); void Host_ShutdownServer (qboolean crash); void Host_WriteConfiguration (void); +void Host_AppendDownloadData(client_t *client, sizebuf_t *buf); +void Host_DownloadAck(client_t *client); + void ExtraMaps_Init (void); void Modlist_Init (void); void DemoList_Init (void); diff --git a/Quake/r_alias.c b/Quake/r_alias.c index 1f1f1e1b..58f9c233 100644 --- a/Quake/r_alias.c +++ b/Quake/r_alias.c @@ -94,10 +94,15 @@ Returns the offset of the first vertex's meshxyz_t.xyz in the vbo for the given model and pose. ============= */ -static void *GLARB_GetXYZOffset (aliashdr_t *hdr, int pose) +static void *GLARB_GetXYZOffset_MDL (aliashdr_t *hdr, int pose) { - const int xyzoffs = offsetof (meshxyz_t, xyz); - return (void *) (currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + xyzoffs); + const size_t xyzoffs = offsetof (meshxyz_mdl_t, xyz); + return currententity->model->meshvboptr+(hdr->vbovertofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_mdl_t)) + xyzoffs); +} +static void *GLARB_GetXYZOffset_MD3 (aliashdr_t *hdr, int pose) +{ + const size_t xyzoffs = offsetof (meshxyz_md3_t, xyz); + return currententity->model->meshvboptr+(hdr->vbovertofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_md3_t)) + xyzoffs); } /* @@ -108,10 +113,15 @@ Returns the offset of the first vertex's meshxyz_t.normal in the vbo for the given model and pose. ============= */ -static void *GLARB_GetNormalOffset (aliashdr_t *hdr, int pose) +static void *GLARB_GetNormalOffset_MDL (aliashdr_t *hdr, int pose) { - const int normaloffs = offsetof (meshxyz_t, normal); - return (void *)(currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + normaloffs); + const size_t normaloffs = offsetof (meshxyz_mdl_t, normal); + return currententity->model->meshvboptr+(hdr->vbovertofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_mdl_t)) + normaloffs); +} +static void *GLARB_GetNormalOffset_MD3 (aliashdr_t *hdr, int pose) +{ + const size_t normaloffs = offsetof (meshxyz_md3_t, normal); + return currententity->model->meshvboptr+(hdr->vbovertofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_md3_t)) + normaloffs); } /* @@ -249,12 +259,23 @@ void GL_DrawAliasFrame_GLSL (aliashdr_t *paliashdr, lerpdata_t lerpdata, gltextu GL_EnableVertexAttribArrayFunc (pose1NormalAttrIndex); GL_EnableVertexAttribArrayFunc (pose2NormalAttrIndex); - GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, 0, (void *)(intptr_t)currententity->model->vbostofs); - GL_VertexAttribPointerFunc (pose1VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_t), GLARB_GetXYZOffset (paliashdr, lerpdata.pose1)); - GL_VertexAttribPointerFunc (pose2VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_t), GLARB_GetXYZOffset (paliashdr, lerpdata.pose2)); -// GL_TRUE to normalize the signed bytes to [-1 .. 1] - GL_VertexAttribPointerFunc (pose1NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_t), GLARB_GetNormalOffset (paliashdr, lerpdata.pose1)); - GL_VertexAttribPointerFunc (pose2NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_t), GLARB_GetNormalOffset (paliashdr, lerpdata.pose2)); + GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, 0, currententity->model->meshvboptr+paliashdr->vbostofs); + if (paliashdr->posevertssize == 1) + { + GL_VertexAttribPointerFunc (pose1VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_mdl_t), GLARB_GetXYZOffset_MDL (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_mdl_t), GLARB_GetXYZOffset_MDL (paliashdr, lerpdata.pose2)); + // GL_TRUE to normalize the signed bytes to [-1 .. 1] + GL_VertexAttribPointerFunc (pose1NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_mdl_t), GLARB_GetNormalOffset_MDL (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_mdl_t), GLARB_GetNormalOffset_MDL (paliashdr, lerpdata.pose2)); + } + else if (paliashdr->posevertssize == 2) + { + GL_VertexAttribPointerFunc (pose1VertexAttrIndex, 4, GL_SHORT, GL_FALSE, sizeof (meshxyz_md3_t), GLARB_GetXYZOffset_MD3 (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2VertexAttrIndex, 4, GL_SHORT, GL_FALSE, sizeof (meshxyz_md3_t), GLARB_GetXYZOffset_MD3 (paliashdr, lerpdata.pose2)); + // GL_TRUE to normalize the signed bytes to [-1 .. 1] + GL_VertexAttribPointerFunc (pose1NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_md3_t), GLARB_GetNormalOffset_MD3 (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_md3_t), GLARB_GetNormalOffset_MD3 (paliashdr, lerpdata.pose2)); + } // set uniforms GL_Uniform1fFunc (blendLoc, blend); @@ -277,7 +298,7 @@ void GL_DrawAliasFrame_GLSL (aliashdr_t *paliashdr, lerpdata_t lerpdata, gltextu } // draw - glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, (void *)(intptr_t)currententity->model->vboindexofs); + glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, currententity->model->meshindexesvboptr+paliashdr->eboofs); // clean up GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex); @@ -294,111 +315,215 @@ void GL_DrawAliasFrame_GLSL (aliashdr_t *paliashdr, lerpdata_t lerpdata, gltextu /* ============= -GL_DrawAliasFrame -- johnfitz -- rewritten to support colored light, lerping, entalpha, multitexture, and r_drawflat +GL_DrawAliasFrame +-- johnfitz -- rewritten to support colored light, lerping, entalpha, multitexture, and r_drawflat +-- spike -- rewritten to use vertex arrays, which should be slightly faster thanks to less branches+gl calls (note that this requires gl1.1, which we depend on anyway for texture objects, and is pretty much universal. ============= */ void GL_DrawAliasFrame (aliashdr_t *paliashdr, lerpdata_t lerpdata) { - float vertcolor[4]; - trivertx_t *verts1, *verts2; - int *commands; - int count; - float u,v; + static vec3_t vpos[65536]; + static vec4_t vc[65536]; + int i; float blend, iblend; - qboolean lerping; if (lerpdata.pose1 != lerpdata.pose2) { - lerping = true; - verts1 = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata); - verts2 = verts1; - verts1 += lerpdata.pose1 * paliashdr->poseverts; - verts2 += lerpdata.pose2 * paliashdr->poseverts; blend = lerpdata.blend; - iblend = 1.0f - blend; + iblend = 1.0-blend; } else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled { - lerping = false; - verts1 = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata); - verts2 = verts1; // avoid bogus compiler warning - verts1 += lerpdata.pose1 * paliashdr->poseverts; - blend = iblend = 0; // avoid bogus compiler warning + blend = 1; + iblend = 0; } - commands = (int *)((byte *)paliashdr + paliashdr->commands); + //pose1*iblend + pose2*blend - vertcolor[3] = entalpha; //never changes, so there's no need to put this inside the loop - - while (1) + if (shading && r_drawflat_cheatsafe) { - // get the vertex count and primitive type - count = *commands++; - if (!count) - break; // done + shading = false; + glColor4f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0, entalpha); + } - if (count < 0) - { - count = -count; - glBegin (GL_TRIANGLE_FAN); - } - else - glBegin (GL_TRIANGLE_STRIP); + glEnableClientState(GL_VERTEX_ARRAY); + if (paliashdr->posevertssize == 1) + { + trivertx_t *verts1 = (trivertx_t*)((byte *)paliashdr + paliashdr->vertexes) + lerpdata.pose1 * paliashdr->numverts_vbo; + trivertx_t *verts2 = (trivertx_t*)((byte *)paliashdr + paliashdr->vertexes) + lerpdata.pose2 * paliashdr->numverts_vbo; - do + if (iblend) { - u = ((float *)commands)[0]; - v = ((float *)commands)[1]; - if (mtexenabled) + for (i = 0; i < paliashdr->numverts_vbo; i++) { - GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, u, v); - GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, u, v); + vpos[i][0] = verts1[i].v[0] * iblend + blend * verts2[i].v[0]; + vpos[i][1] = verts1[i].v[1] * iblend + blend * verts2[i].v[1]; + vpos[i][2] = verts1[i].v[2] * iblend + blend * verts2[i].v[2]; } - else - glTexCoord2f (u, v); - - commands += 2; + GL_BindBuffer (GL_ARRAY_BUFFER, 0); + glVertexPointer(3, GL_FLOAT, sizeof (vpos[0]), vpos); if (shading) { - if (r_drawflat_cheatsafe) + for (i = 0; i < paliashdr->numverts_vbo; i++) { - srand(count * (unsigned int)(src_offset_t)commands); - glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + vc[i][0] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[0]; + vc[i][1] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[1]; + vc[i][2] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[2]; + vc[i][3] = entalpha; } - else if (lerping) + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_FLOAT, sizeof(*vc), vc); + } + } + else + { + if (shading) + { + for (i = 0; i < paliashdr->numverts_vbo; i++) { - vertcolor[0] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[0]; - vertcolor[1] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[1]; - vertcolor[2] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[2]; - glColor4fv (vertcolor); - } - else - { - vertcolor[0] = shadedots[verts1->lightnormalindex] * lightcolor[0]; - vertcolor[1] = shadedots[verts1->lightnormalindex] * lightcolor[1]; - vertcolor[2] = shadedots[verts1->lightnormalindex] * lightcolor[2]; - glColor4fv (vertcolor); + vc[i][0] = shadedots[verts2->lightnormalindex] * lightcolor[0]; + vc[i][1] = shadedots[verts2->lightnormalindex] * lightcolor[1]; + vc[i][2] = shadedots[verts2->lightnormalindex] * lightcolor[2]; + vc[i][3] = entalpha; } + glEnableClientState(GL_COLOR_ARRAY); + GL_BindBuffer (GL_ARRAY_BUFFER, 0); + glColorPointer(4, GL_FLOAT, 0, vc); } - if (lerping) + //glVertexPointer may not take GL_UNSIGNED_BYTE, which means we can't use our vbos. attribute 0 MAY be vertex coords, but I don't want to depend on that. + for (i = 0; i < paliashdr->numverts_vbo; i++) { - glVertex3f (verts1->v[0]*iblend + verts2->v[0]*blend, - verts1->v[1]*iblend + verts2->v[1]*blend, - verts1->v[2]*iblend + verts2->v[2]*blend); - verts1++; - verts2++; + vpos[i][0] = verts2[i].v[0]; + vpos[i][1] = verts2[i].v[1]; + vpos[i][2] = verts2[i].v[2]; } - else - { - glVertex3f (verts1->v[0], verts1->v[1], verts1->v[2]); - verts1++; - } - } while (--count); - - glEnd (); + GL_BindBuffer (GL_ARRAY_BUFFER, 0); + glVertexPointer(3, GL_FLOAT, sizeof (vpos[0]), vpos); + } } + else if (paliashdr->posevertssize == 2) + { + md3XyzNormal_t *verts1 = (md3XyzNormal_t*)((byte *)paliashdr + paliashdr->vertexes) + lerpdata.pose1 * paliashdr->numverts_vbo; + md3XyzNormal_t *verts2 = (md3XyzNormal_t*)((byte *)paliashdr + paliashdr->vertexes) + lerpdata.pose2 * paliashdr->numverts_vbo; + + if (iblend) + { + for (i = 0; i < paliashdr->numverts_vbo; i++) + { + vpos[i][0] = verts1[i].xyz[0] * iblend + blend * verts2[i].xyz[0]; + vpos[i][1] = verts1[i].xyz[1] * iblend + blend * verts2[i].xyz[1]; + vpos[i][2] = verts1[i].xyz[2] * iblend + blend * verts2[i].xyz[2]; + } + GL_BindBuffer (GL_ARRAY_BUFFER, 0); + glVertexPointer(3, GL_FLOAT, sizeof (vpos[0]), vpos); + + if (shading) + { + for (i = 0; i < paliashdr->numverts_vbo; i++) + { + vec3_t n; + float dot; + // map the normal coordinates in [-1..1] to [-127..127] and store in an unsigned char. + // this introduces some error (less than 0.004), but the normals were very coarse + // to begin with + //this should be a table. + float lat = (float)verts2[i].latlong[0] * (2 * M_PI)*(1.0 / 255.0); + float lng = (float)verts2[i].latlong[1] * (2 * M_PI)*(1.0 / 255.0); + n[0] = blend * cos ( lng ) * sin ( lat ); + n[1] = blend * sin ( lng ) * sin ( lat ); + n[2] = blend * cos ( lat ); + lat = (float)verts1[i].latlong[0] * (2 * M_PI)*(1.0 / 255.0); + lng = (float)verts1[i].latlong[1] * (2 * M_PI)*(1.0 / 255.0); + n[0] += iblend * cos ( lng ) * sin ( lat ); + n[1] += iblend * sin ( lng ) * sin ( lat ); + n[2] += iblend * cos ( lat ); + dot = DotProduct(n, shadevector); + if (dot < 0.0) //bizzare maths guessed by mh + dot = 1.0 + dot * (13.0 / 44.0); + else + dot = 1.0 + dot; + vc[i][0] = dot * lightcolor[0]; + vc[i][1] = dot * lightcolor[1]; + vc[i][2] = dot * lightcolor[2]; + vc[i][3] = entalpha; + } + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_FLOAT, 0, vc); + } + } + else + { + if (shading) + { + for (i = 0; i < paliashdr->numverts_vbo; i++) + { + vec3_t n; + float dot; + // map the normal coordinates in [-1..1] to [-127..127] and store in an unsigned char. + // this introduces some error (less than 0.004), but the normals were very coarse + // to begin with + //this should be a table. + float lat = (float)verts2[i].latlong[0] * (2 * M_PI)*(1.0 / 255.0); + float lng = (float)verts2[i].latlong[1] * (2 * M_PI)*(1.0 / 255.0); + n[0] = cos ( lng ) * sin ( lat ); + n[1] = sin ( lng ) * sin ( lat ); + n[2] = cos ( lat ); + dot = DotProduct(n, shadevector); + if (dot < 0.0) //bizzare maths guessed by mh + dot = 1.0 + dot * (13.0 / 44.0); + else + dot = 1.0 + dot; + vc[i][0] = dot * lightcolor[0]; + vc[i][1] = dot * lightcolor[1]; + vc[i][2] = dot * lightcolor[2]; + vc[i][3] = entalpha; + } + glEnableClientState(GL_COLOR_ARRAY); + GL_BindBuffer (GL_ARRAY_BUFFER, 0); + glColorPointer(4, GL_FLOAT, 0, vc); + } + GL_BindBuffer (GL_ARRAY_BUFFER, currententity->model->meshvbo); + glVertexPointer(3, GL_SHORT, sizeof (meshxyz_md3_t), GLARB_GetXYZOffset_MD3 (paliashdr, lerpdata.pose2)); + } + } + +// set textures + GL_BindBuffer (GL_ARRAY_BUFFER, currententity->model->meshvbo); + if (mtexenabled) + { + GL_ClientActiveTextureFunc (GL_TEXTURE0); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 0, currententity->model->meshvboptr+paliashdr->vbostofs); + + GL_ClientActiveTextureFunc (GL_TEXTURE1); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 0, currententity->model->meshvboptr+paliashdr->vbostofs); + } + else + { + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 0, currententity->model->meshvboptr+paliashdr->vbostofs); + } + +// draw + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, currententity->model->meshindexesvbo); + glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, currententity->model->meshindexesvboptr + paliashdr->eboofs); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); + + GL_BindBuffer (GL_ARRAY_BUFFER, 0); + +// clean up + if (mtexenabled) + { + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + GL_ClientActiveTextureFunc (GL_TEXTURE0); + } + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + rs_aliaspasses += paliashdr->numtris; } @@ -546,15 +671,18 @@ void R_SetupAliasLighting (entity_t *e) int i; int quantizedangle; float radiansangle; + float *origin = e->origin; - R_LightPoint (e->origin); + if (e->netstate.eflags & EFLAGS_VIEWMODEL) + origin = r_refdef.vieworg; + R_LightPoint (origin); //add dlights for (i=0 ; i= cl.time) { - VectorSubtract (currententity->origin, cl_dlights[i].origin, dist); + VectorSubtract (origin, cl_dlights[i].origin, dist); add = cl_dlights[i].radius - VectorLength(dist); if (add > 0) VectorMA (lightcolor, add, cl_dlights[i].color, lightcolor); @@ -574,7 +702,7 @@ void R_SetupAliasLighting (entity_t *e) } // minimum light value on players (8) - if (currententity > cl_entities && currententity <= cl_entities + cl.maxclients) + if (e > cl.entities && e <= cl.entities + cl.maxclients) { add = 24.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]); if (add > 0.0f) @@ -615,6 +743,10 @@ void R_SetupAliasLighting (entity_t *e) shadedots = r_avertexnormal_dots[quantizedangle]; VectorScale (lightcolor, 1.0f / 200.0f, lightcolor); + + lightcolor[0] *= e->netstate.colormod[0] / 32.0; + lightcolor[1] *= e->netstate.colormod[1] / 32.0; + lightcolor[2] *= e->netstate.colormod[2] / 32.0; } /* @@ -629,6 +761,7 @@ void R_DrawAliasModel (entity_t *e) gltexture_t *tx, *fb; lerpdata_t lerpdata; qboolean alphatest = !!(e->model->flags & MF_HOLEY); + int surf; // // setup pose/lerp data -- do it first so we don't miss updates due to culling @@ -637,17 +770,30 @@ void R_DrawAliasModel (entity_t *e) R_SetupAliasFrame (paliashdr, e->frame, &lerpdata); R_SetupEntityTransform (e, &lerpdata); - // - // cull it - // - if (R_CullModelForEntity(e)) - return; + if (e->netstate.eflags & EFLAGS_VIEWMODEL) + { + //transform it relative to the view, by rebuilding the modelview matrix without the view position. + glPushMatrix (); + glLoadIdentity(); + glRotatef (-90, 1, 0, 0); // put Z going up + glRotatef (90, 0, 0, 1); // put Z going up - // - // transform it - // - glPushMatrix (); - R_RotateForEntity (lerpdata.origin, lerpdata.angles); + glDepthRange (0, 0.3); + } + else + { + // + // cull it + // + if (R_CullModelForEntity(e)) + return; + + // + // transform it + // + glPushMatrix (); + } + R_RotateForEntity (lerpdata.origin, lerpdata.angles, e->netstate.scale); glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]); glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]); @@ -682,110 +828,59 @@ void R_DrawAliasModel (entity_t *e) // // set up lighting // - rs_aliaspolys += paliashdr->numtris; R_SetupAliasLighting (e); - // - // set up textures - // - GL_DisableMultitexture(); - anim = (int)(cl.time*10) & 3; - skinnum = e->skinnum; - if ((skinnum >= paliashdr->numskins) || (skinnum < 0)) + for(surf=0;;surf++) { - Con_DPrintf ("R_DrawAliasModel: no such skin # %d for '%s'\n", skinnum, e->model->name); - // ericw -- display skin 0 for winquake compatibility - skinnum = 0; - } - tx = paliashdr->gltextures[skinnum][anim]; - fb = paliashdr->fbtextures[skinnum][anim]; - if (e->colormap != vid.colormap && !gl_nocolors.value) - { - i = e - cl_entities; - if (i >= 1 && i<=cl.maxclients /* && !strcmp (currententity->model->name, "progs/player.mdl") */) - tx = playertextures[i - 1]; - } - if (!gl_fullbrights.value) - fb = NULL; + rs_aliaspolys += paliashdr->numtris; - // - // draw it - // - if (r_drawflat_cheatsafe) - { - glDisable (GL_TEXTURE_2D); - GL_DrawAliasFrame (paliashdr, lerpdata); - glEnable (GL_TEXTURE_2D); - srand((int) (cl.time * 1000)); //restore randomness - } - else if (r_fullbright_cheatsafe) - { - GL_Bind (tx); - shading = false; - glColor4f(1,1,1,entalpha); - GL_DrawAliasFrame (paliashdr, lerpdata); - if (fb) + // + // set up textures + // + GL_DisableMultitexture(); + anim = (int)(cl.time*10) & 3; + skinnum = e->skinnum; + if ((skinnum >= paliashdr->numskins) || (skinnum < 0)) { - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - GL_Bind(fb); - glEnable(GL_BLEND); - glBlendFunc (GL_ONE, GL_ONE); - glDepthMask(GL_FALSE); - glColor3f(entalpha,entalpha,entalpha); - Fog_StartAdditive (); - GL_DrawAliasFrame (paliashdr, lerpdata); - Fog_StopAdditive (); - glDepthMask(GL_TRUE); - glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_BLEND); + Con_DPrintf ("R_DrawAliasModel: no such skin # %d for '%s'\n", skinnum, e->model->name); + // ericw -- display skin 0 for winquake compatibility + skinnum = 0; } - } - else if (r_lightmap_cheatsafe) - { - glDisable (GL_TEXTURE_2D); - shading = false; - glColor3f(1,1,1); - GL_DrawAliasFrame (paliashdr, lerpdata); - glEnable (GL_TEXTURE_2D); - } -// call fast path if possible. if the shader compliation failed for some reason, -// r_alias_program will be 0. - else if (r_alias_program != 0) - { - GL_DrawAliasFrame_GLSL (paliashdr, lerpdata, tx, fb); - } - else if (overbright) - { - if (gl_texture_env_combine && gl_mtexable && gl_texture_env_add && fb) //case 1: everything in one pass + if (paliashdr->numskins <= 0) + { + tx = NULL; // NULL will give the checkerboard texture + fb = NULL; + } + else + { + tx = paliashdr->gltextures[skinnum][anim]; + fb = paliashdr->fbtextures[skinnum][anim]; + } + if (e->colormap != vid.colormap && !gl_nocolors.value) + { + i = e - cl.entities; + if (i >= 1 && i<=cl.maxclients /* && !strcmp (currententity->model->name, "progs/player.mdl") */) + tx = playertextures[i - 1]; + } + if (!gl_fullbrights.value) + fb = NULL; + + // + // draw it + // + if (r_drawflat_cheatsafe) + { + glDisable (GL_TEXTURE_2D); + GL_DrawAliasFrame (paliashdr, lerpdata); + glEnable (GL_TEXTURE_2D); + srand((int) (cl.time * 1000)); //restore randomness + } + else if (r_fullbright_cheatsafe) { GL_Bind (tx); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); - glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); - GL_EnableMultitexture(); // selects TEXTURE1 - GL_Bind (fb); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); - glEnable(GL_BLEND); + shading = false; + glColor4f(1,1,1,entalpha); GL_DrawAliasFrame (paliashdr, lerpdata); - glDisable(GL_BLEND); - GL_DisableMultitexture(); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - } - else if (gl_texture_env_combine) //case 2: overbright in one pass, then fullbright pass - { - // first pass - GL_Bind(tx); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); - glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); - GL_DrawAliasFrame (paliashdr, lerpdata); - glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - // second pass if (fb) { glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); @@ -793,7 +888,6 @@ void R_DrawAliasModel (entity_t *e) glEnable(GL_BLEND); glBlendFunc (GL_ONE, GL_ONE); glDepthMask(GL_FALSE); - shading = false; glColor3f(entalpha,entalpha,entalpha); Fog_StartAdditive (); GL_DrawAliasFrame (paliashdr, lerpdata); @@ -801,85 +895,152 @@ void R_DrawAliasModel (entity_t *e) glDepthMask(GL_TRUE); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_BLEND); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); } } - else //case 3: overbright in two passes, then fullbright pass + else if (r_lightmap_cheatsafe) { - // first pass - GL_Bind(tx); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glDisable (GL_TEXTURE_2D); + shading = false; + glColor3f(1,1,1); GL_DrawAliasFrame (paliashdr, lerpdata); - // second pass -- additive with black fog, to double the object colors but not the fog color - glEnable(GL_BLEND); - glBlendFunc (GL_ONE, GL_ONE); - glDepthMask(GL_FALSE); - Fog_StartAdditive (); - GL_DrawAliasFrame (paliashdr, lerpdata); - Fog_StopAdditive (); - glDepthMask(GL_TRUE); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_BLEND); - // third pass - if (fb) + glEnable (GL_TEXTURE_2D); + } + // call fast path if possible. if the shader compliation failed for some reason, + // r_alias_program will be 0. + else if (r_alias_program != 0) + { + GL_DrawAliasFrame_GLSL (paliashdr, lerpdata, tx, fb); + } + else if (overbright) + { + if (gl_texture_env_combine && gl_mtexable && gl_texture_env_add && fb) //case 1: everything in one pass { + GL_Bind (tx); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + GL_EnableMultitexture(); // selects TEXTURE1 + GL_Bind (fb); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); +// glEnable(GL_BLEND); + GL_DrawAliasFrame (paliashdr, lerpdata); +// glDisable(GL_BLEND); + GL_DisableMultitexture(); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else if (gl_texture_env_combine) //case 2: overbright in one pass, then fullbright pass + { + // first pass + GL_Bind(tx); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + GL_DrawAliasFrame (paliashdr, lerpdata); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + // second pass + if (fb) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + } + else //case 3: overbright in two passes, then fullbright pass + { + // first pass + GL_Bind(tx); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - GL_Bind(fb); + GL_DrawAliasFrame (paliashdr, lerpdata); + // second pass -- additive with black fog, to double the object colors but not the fog color glEnable(GL_BLEND); glBlendFunc (GL_ONE, GL_ONE); glDepthMask(GL_FALSE); - shading = false; - glColor3f(entalpha,entalpha,entalpha); Fog_StartAdditive (); GL_DrawAliasFrame (paliashdr, lerpdata); Fog_StopAdditive (); glDepthMask(GL_TRUE); - glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_BLEND); + // third pass + if (fb) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + } + } + else + { + if (gl_mtexable && gl_texture_env_add && fb) //case 4: fullbright mask using multitexture + { + GL_DisableMultitexture(); // selects TEXTURE0 + GL_Bind (tx); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_EnableMultitexture(); // selects TEXTURE1 + GL_Bind (fb); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); + glEnable(GL_BLEND); + GL_DrawAliasFrame (paliashdr, lerpdata); + glDisable(GL_BLEND); + GL_DisableMultitexture(); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); } - } - } - else - { - if (gl_mtexable && gl_texture_env_add && fb) //case 4: fullbright mask using multitexture - { - GL_DisableMultitexture(); // selects TEXTURE0 - GL_Bind (tx); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - GL_EnableMultitexture(); // selects TEXTURE1 - GL_Bind (fb); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); - glEnable(GL_BLEND); - GL_DrawAliasFrame (paliashdr, lerpdata); - glDisable(GL_BLEND); - GL_DisableMultitexture(); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - } - else //case 5: fullbright mask without multitexture - { - // first pass - GL_Bind(tx); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - GL_DrawAliasFrame (paliashdr, lerpdata); - // second pass - if (fb) + else //case 5: fullbright mask without multitexture { - GL_Bind(fb); - glEnable(GL_BLEND); - glBlendFunc (GL_ONE, GL_ONE); - glDepthMask(GL_FALSE); - shading = false; - glColor3f(entalpha,entalpha,entalpha); - Fog_StartAdditive (); + // first pass + GL_Bind(tx); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); GL_DrawAliasFrame (paliashdr, lerpdata); - Fog_StopAdditive (); - glDepthMask(GL_TRUE); - glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_BLEND); + // second pass + if (fb) + { + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + } } } + if (!paliashdr->nextsurface) + break; + paliashdr = (aliashdr_t*)((byte*)paliashdr + paliashdr->nextsurface); } cleanup: @@ -891,6 +1052,8 @@ cleanup: if (alphatest) glDisable (GL_ALPHA_TEST); glColor3f(1,1,1); + if (e->netstate.eflags & EFLAGS_VIEWMODEL) + glDepthRange (0, 1); glPopMatrix (); } @@ -921,7 +1084,7 @@ void GL_DrawAliasShadow (entity_t *e) if (R_CullModelForEntity(e)) return; - if (e == &cl.viewent || e->model->flags & MOD_NOSHADOW) + if (e == &cl.viewent || e->effects & EF_NOSHADOW || e->model->flags & MOD_NOSHADOW) return; entalpha = ENTALPHA_DECODE(e->alpha); @@ -979,7 +1142,7 @@ void R_DrawAliasModel_ShowTris (entity_t *e) R_SetupEntityTransform (e, &lerpdata); glPushMatrix (); - R_RotateForEntity (lerpdata.origin,lerpdata.angles); + R_RotateForEntity (lerpdata.origin,lerpdata.angles, e->netstate.scale); glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]); glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]); diff --git a/Quake/r_brush.c b/Quake/r_brush.c index 14999089..186d8f5d 100644 --- a/Quake/r_brush.c +++ b/Quake/r_brush.c @@ -549,7 +549,7 @@ void R_DrawBrushModel (entity_t *e) e->origin[1] -= DIST_EPSILON; e->origin[2] -= DIST_EPSILON; } - R_RotateForEntity (e->origin, e->angles); + R_RotateForEntity (e->origin, e->angles, e->netstate.scale); if (gl_zfix.value) { e->origin[0] += DIST_EPSILON; @@ -614,7 +614,7 @@ void R_DrawBrushModel_ShowTris (entity_t *e) glPushMatrix (); e->angles[0] = -e->angles[0]; // stupid quake bug - R_RotateForEntity (e->origin, e->angles); + R_RotateForEntity (e->origin, e->angles, e->netstate.scale); e->angles[0] = -e->angles[0]; // stupid quake bug // @@ -652,7 +652,7 @@ R_RenderDynamicLightmaps called during rendering ================ */ -void R_RenderDynamicLightmaps (msurface_t *fa) +void R_RenderDynamicLightmaps (qmodel_t *model, msurface_t *fa) { byte *base; int maps; @@ -698,7 +698,7 @@ dynamic: theRect->h = (fa->light_t-theRect->t)+tmax; base = lm->data; base += fa->light_t * LMBLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes; - R_BuildLightMap (fa, base, LMBLOCK_WIDTH*lightmap_bytes); + R_BuildLightMap (model, fa, base, LMBLOCK_WIDTH*lightmap_bytes); } } } @@ -777,7 +777,7 @@ int nColinElim; GL_CreateSurfaceLightmap ======================== */ -void GL_CreateSurfaceLightmap (msurface_t *surf) +void GL_CreateSurfaceLightmap (qmodel_t *model, msurface_t *surf) { int smax, tmax; byte *base; @@ -788,7 +788,7 @@ void GL_CreateSurfaceLightmap (msurface_t *surf) surf->lightmaptexturenum = AllocBlock (smax, tmax, &surf->light_s, &surf->light_t); base = lightmap[surf->lightmaptexturenum].data; base += (surf->light_t * LMBLOCK_WIDTH + surf->light_s) * lightmap_bytes; - R_BuildLightMap (surf, base, LMBLOCK_WIDTH*lightmap_bytes); + R_BuildLightMap (model, surf, base, LMBLOCK_WIDTH*lightmap_bytes); } /* @@ -917,7 +917,7 @@ void GL_BuildLightmaps (void) //johnfitz -- rewritten to use SURF_DRAWTILED instead of the sky/water flags if (m->surfaces[i].flags & SURF_DRAWTILED) continue; - GL_CreateSurfaceLightmap (m->surfaces + i); + GL_CreateSurfaceLightmap (m, m->surfaces + i); BuildSurfaceDisplayList (m->surfaces + i); //johnfitz } @@ -1130,7 +1130,7 @@ R_BuildLightMap -- johnfitz -- revised for lit support via lordhavoc Combine and scale multiple lightmaps into the 8.8 format in blocklights =============== */ -void R_BuildLightMap (msurface_t *surf, byte *dest, int stride) +void R_BuildLightMap (qmodel_t *model, msurface_t *surf, byte *dest, int stride) { int smax, tmax; int r,g,b; @@ -1147,7 +1147,7 @@ void R_BuildLightMap (msurface_t *surf, byte *dest, int stride) size = smax*tmax; lightmap = surf->samples; - if (cl.worldmodel->lightdata) + if (model->lightdata) { // clear to no light memset (&blocklights[0], 0, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc @@ -1310,7 +1310,7 @@ void R_RebuildAllLightmaps (void) continue; base = lightmap[fa->lightmaptexturenum].data; base += fa->light_t * LMBLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes; - R_BuildLightMap (fa, base, LMBLOCK_WIDTH*lightmap_bytes); + R_BuildLightMap (mod, fa, base, LMBLOCK_WIDTH*lightmap_bytes); } } diff --git a/Quake/r_part.c b/Quake/r_part.c index a72c4d5b..541c8753 100644 --- a/Quake/r_part.c +++ b/Quake/r_part.c @@ -337,9 +337,19 @@ void R_ParseParticleEffect (void) color = MSG_ReadByte (); if (msgcount == 255) - count = 1024; + { + if (!PScript_RunParticleEffectTypeString(org, dir, 1, "te_explosion")) + count = 0; + else + count = 1024; + } else - count = msgcount; + { + if (!PScript_RunParticleEffect(org, dir, color, msgcount)) + count = 0; + else + count = msgcount; + } R_RunParticleEffect (org, dir, color, count); } diff --git a/Quake/r_part_fte.c b/Quake/r_part_fte.c new file mode 100755 index 00000000..4dbd68ac --- /dev/null +++ b/Quake/r_part_fte.c @@ -0,0 +1,7787 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2016 Spike + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* +The aim of this particle system is to have as much as possible configurable. +Some parts still fail here, and are marked FIXME +Effects are flushed on new maps. +The engine has a few builtins. +*/ + +#include "quakedef.h" + +#ifdef PSET_SCRIPT +#define USE_DECALS +#define Con_Printf Con_SafePrintf + +#ifdef GLQUAKE +#include "glquake.h"//hack +#endif + +#define frandom() (rand()*(1.0f/RAND_MAX)) +#define crandom() (rand()*(2.0f/RAND_MAX)-1.0f) +#define hrandom() (rand()*(1.0f/RAND_MAX)-0.5f) +#define particle_s fparticle_s +#define particle_t fparticle_t +typedef vec_t vec2_t[2]; +#define FloatInterpolate(a, bness, b, c) ((c) = (a) + (b - a)*bness) +#define Vector2Copy(a,b) do{(b)[0]=(a)[0];(b)[1]=(a)[1];}while(0) +#define Vector2Set(r,x,y) do{(r)[0] = x; (r)[1] = y;}while(0) +#define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0) +#define VectorInterpolate(a, bness, b, c) FloatInterpolate((a)[0], bness, (b)[0], (c)[0]),FloatInterpolate((a)[1], bness, (b)[1], (c)[1]),FloatInterpolate((a)[2], bness, (b)[2], (c)[2]) +#define VectorSet(r,x,y,z) do{(r)[0] = x; (r)[1] = y;(r)[2] = z;}while(0) +#define Vector4Clear(a) ((a)[0]=(a)[1]=(a)[2]=(a)[3]=0) +#define Vector4Copy(a,b) do{(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];(b)[3]=(a)[3];}while(0) +#define Vector4Scale(in,scale,out) ((out)[0]=(in)[0]*scale,(out)[1]=(in)[1]*scale,(out)[2]=(in)[2]*scale,(out)[3]=(in)[3]*scale) +vec_t VectorNormalize2 (const vec3_t v, vec3_t out) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + + if (length) + { + length = sqrt (length); // FIXME + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } + else + { + VectorClear (out); + } + + return length; +} +void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up) +{ + if (!forward[0] && !forward[1]) + { + if (forward[2]) + right[1] = -1; + else + right[1] = 0; + right[0] = right[2] = 0; + } + else + { + right[0] = forward[1]; + right[1] = -forward[0]; + right[2] = 0; + VectorNormalize(right); + } + CrossProduct(right, forward, up); +} +typedef enum { BM_BLEND/*SRC_ALPHA ONE_MINUS_SRC_ALPHA*/, BM_BLENDCOLOUR/*SRC_COLOR ONE_MINUS_SRC_COLOR*/, BM_ADDA/*SRC_ALPHA ONE*/, BM_ADDC/*GL_SRC_COLOR GL_ONE*/, BM_SUBTRACT/*SRC_ALPHA ONE_MINUS_SRC_COLOR*/, BM_INVMODA/*ZERO ONE_MINUS_SRC_ALPHA*/, BM_INVMODC/*ZERO ONE_MINUS_SRC_COLOR*/, BM_PREMUL/*ONE ONE_MINUS_SRC_ALPHA*/} blendmode_t; +typedef struct trailstate_s { + struct trailstate_s **key; // key to check if ts has been overwriten + struct trailstate_s *assoc; // assoc linked trail + struct beamseg_s *lastbeam; // last beam pointer (flagged with BS_LASTSEG) + union { + float lastdist; // last distance used with particle effect + float statetime; // time to emit effect again (used by spawntime field) + } state1; + union { + float laststop; // last stopping point for particle effect + float emittime; // used by r_effect emitters + } state2; +} trailstate_t; +#define CON_WARNING "Warning: " +entity_t *CL_EntityNum (int num); +#define BEF_LINES 1 + +#define Z_Malloc malloc +#define Z_Free free +#define Z_Realloc realloc + +extern int PClassic_PointFile(int c, vec3_t point); +//#define FALLBACKBIAS 0x1000000 + +#define PART_VALID(part) ((part) >= 0 && (part) < numparticletypes) + +static int pe_default = P_INVALID; +static int pe_size2 = P_INVALID; +static int pe_size3 = P_INVALID; +static int pe_defaulttrail = P_INVALID; + +static float psintable[256]; + +int PScript_RunParticleEffectState (vec3_t org, vec3_t dir, float count, int typenum, trailstate_t **tsk); +int PScript_ParticleTrail (vec3_t startpos, vec3_t end, int type, int dlkey, vec3_t axis[3], trailstate_t **tsk); +static qboolean P_LoadParticleSet(char *name, qboolean implicit, qboolean showwarning); +static void R_Particles_KillAllEffects(void); + +static void buildsintable(void) +{ + int i; + for (i = 0; i < 256; i++) + psintable[i] = sin((i*M_PI)/128); +} +#define sin(x) (psintable[(int)((x)*(128/M_PI)) & 255]) +#define cos(x) (psintable[((int)((x)*(128/M_PI)) + 64) & 255]) + +typedef struct particle_s +{ + struct particle_s *next; + float die; + +// driver-usable fields + vec3_t org; + vec4_t rgba; + float scale; + float s1, t1, s2, t2; + + vec3_t oldorg; //to throttle traces + vec3_t vel; //renderer uses for sparks + float angle; + union { + float nextemit; + trailstate_t *trailstate; + } state; +// drivers never touch the following fields + float rotationspeed; +} particle_t; + +typedef struct clippeddecal_s +{ + struct clippeddecal_s *next; + float die; + + int entity; //>0 is a lerpentity, <0 is a csqc ent. 0 is world. woot. + qmodel_t *model; //just for paranoia + + vec3_t vertex[3]; + vec2_t texcoords[3]; + float valpha[3]; + + vec4_t rgba; +} clippeddecal_t; + +#define BS_LASTSEG 0x1 // no draw to next, no delete +#define BS_DEAD 0x2 // segment is dead +#define BS_NODRAW 0x4 // only used for lerp switching + +typedef struct beamseg_s +{ + struct beamseg_s *next; // next in beamseg list + + particle_t *p; + int flags; // flags for beamseg + vec3_t dir; + + float texture_s; +} beamseg_t; + + + +typedef struct skytris_s { + struct skytris_s *next; + vec3_t org; + vec3_t x; + vec3_t y; + float area; + double nexttime; + int ptype; + struct msurface_s *face; +} skytris_t; + +typedef struct skytriblock_s +{ + struct skytriblock_s *next; + unsigned int count; + skytris_t tris[1024]; +} skytriblock_t; + +//this is the required render state for each particle +//dynamic per-particle stuff isn't important. only static state. +typedef struct { + enum {PT_NORMAL, PT_SPARK, PT_SPARKFAN, PT_TEXTUREDSPARK, PT_BEAM, PT_CDECAL, PT_UDECAL, PT_INVISIBLE} type; + + blendmode_t blendmode; + gltexture_t *texture; + qboolean nearest; + + float scalefactor; + float invscalefactor; + float stretch; + float minstretch; //limits the particle's length to a multiple of its width. + int premul; //0: direct rgba. 1: rgb*a,a (blend). 2: rgb*a,0 (add). +} plooks_t; + +//these could be deltas or absolutes depending on ramping mode. +typedef struct { + vec3_t rgb; + float alpha; + float scale; + float rotation; +} ramp_t; +#if UNSUPPORTED +typedef struct { + char name[MAX_QPATH]; + qmodel_t *model; + float framestart; + float framecount; + float framerate; + float alpha; + float scalemin, scalemax; + int skin; + int traileffect; + unsigned int rflags; +#define RF_USEORIENTATION Q2RF_CUSTOMSKIN //private flag +} partmodels_t; +#endif +typedef struct { + char name[MAX_QPATH]; + float vol; + float atten; + float delay; + float pitch; + float weight; +} partsounds_t; +// TODO: merge in alpha with rgb to gain benefit of vector opts +typedef struct part_type_s { + char name[MAX_QPATH]; + char config[MAX_QPATH]; + char texname[MAX_QPATH]; + +#if UNSUPPORTED + int nummodels; + partmodels_t *models; +#endif + + int numsounds; + partsounds_t *sounds; + + vec3_t rgb; //initial colour + float alpha; + vec3_t rgbchange; //colour delta (per second) + float alphachange; + vec3_t rgbrand; //random rgb colour to start with + float alpharand; + int colorindex; //get colour from a palette + int colorrand; //and add up to this amount + float rgbchangetime;//colour stops changing at this time + vec3_t rgbrandsync; //like rgbrand, but a single random value instead of separate (can mix) + float scale; //initial scale + float scalerand; //with up to this much extra + float die, randdie; //how long it lasts (plus some rand) + float veladd, randomveladd; //scale the incoming velocity by this much + float orgadd, randomorgadd; //spawn the particle this far along its velocity direction + float spawnvel, spawnvelvert; //spawn the particle with a velocity based upon its spawn type (generally so it flies outwards) + vec3_t orgbias; //static 3d world-coord bias + vec3_t velbias; + vec3_t orgwrand; //3d world-coord randomisation without relation to spawn mode + vec3_t velwrand; //3d world-coord randomisation without relation to spawn mode + float viewspacefrac; + float flurry; + int surfflagmatch; //this decal only spawns on these surfaces + int surfflagmask; //this decal only spawns on these surfaces + + float s1, t1, s2, t2; //texture coords + float texsstride; //addition for s for each random slot. + int randsmax; //max times the stride can be added + + plooks_t *slooks; //shared looks, so state switches don't apply between particles so much. + plooks_t looks; // + + float spawntime; //time limit for trails + float spawnchance; //if < 0, particles might not spawn so many + + float rotationstartmin, rotationstartrand; + float rotationmin, rotationrand; + + float scaledelta; + float countextra; + float count; + float countrand; + float rainfrequency; //surface emitter multiplier + + int assoc; + int cliptype; + int inwater; + float clipcount; + int emit; + float emittime; + float emitrand; + float emitstart; + + float areaspread; + float areaspreadvert; + + float spawnparam1; + float spawnparam2; +/* float spawnparam3; */ + + enum { + SM_BOX, //box = even spread within the area + SM_CIRCLE, //circle = around edge of a circle + SM_BALL, //ball = filled sphere + SM_SPIRAL, //spiral = spiral trail + SM_TRACER, //tracer = tracer trail + SM_TELEBOX, //telebox = q1-style telebox + SM_LAVASPLASH, //lavasplash = q1-style lavasplash + SM_UNICIRCLE, //unicircle = uniform circle + SM_FIELD, //field = synced field (brightfield, etc) + SM_DISTBALL, // uneven distributed ball + SM_MESHSURFACE //distributed roughly evenly over the surface of the mesh + } spawnmode; + + float gravity; + vec3_t friction; + float clipbounce; + float stainonimpact; + + vec3_t dl_rgb; + float dl_radius[2]; + float dl_time; + vec4_t dl_decay; + float dl_corona_intensity; + float dl_corona_scale; + vec3_t dl_scales; + //PT_NODLSHADOW + int dl_cubemapnum; +#if UNSUPPORTED + vec3_t stain_rgb; + float stain_radius; +#endif + + enum {RAMP_NONE, RAMP_DELTA, RAMP_NEAREST, RAMP_LERP} rampmode; + int rampindexes; + ramp_t *ramp; + + int loaded; //0 if not loaded, 1 if automatically loaded, 2 if user loaded + particle_t *particles; + clippeddecal_t *clippeddecals; + beamseg_t *beams; + struct part_type_s *nexttorun; + + unsigned int flags; +#define PT_VELOCITY 0x0001 // has velocity modifiers +#define PT_FRICTION 0x0002 // has friction modifiers +#define PT_CHANGESCOLOUR 0x0004 +#define PT_CITRACER 0x0008 // Q1-style tracer behavior for colorindex +#define PT_INVFRAMETIME 0x0010 // apply inverse frametime to count (causes emits to be per frame) +#define PT_AVERAGETRAIL 0x0020 // average trail points from start to end, useful with t_lightning, etc +#define PT_NOSTATE 0x0040 // don't use trailstate for this emitter (careful with assoc...) +#define PT_NOSPREADFIRST 0x0080 // don't randomize org/vel for first generated particle +#define PT_NOSPREADLAST 0x0100 // don't randomize org/vel for last generated particle +#define PT_TROVERWATER 0x0200 // don't spawn if underwater +#define PT_TRUNDERWATER 0x0400 // don't spawn if overwater +#define PT_NODLSHADOW 0x0800 // dlights from this effect don't cast shadows. +#define PT_WORLDSPACERAND 0x1000 // effect has orgwrand or velwrand properties + unsigned int fluidmask; + + unsigned int state; +#define PS_INRUNLIST 0x1 // particle type is currently in execution list +} part_type_t; + +typedef struct pcfg_s +{ + struct pcfg_s *next; + char name[1]; +} pcfg_t; +static pcfg_t *loadedconfigs; + +#ifndef TYPESONLY + +//triangle fan sparks use these. // defined but not used +//static double sint[7] = {0.000000, 0.781832, 0.974928, 0.433884, -0.433884, -0.974928, -0.781832}; +//static double cost[7] = {1.000000, 0.623490, -0.222521, -0.900969, -0.900969, -0.222521, 0.623490}; + +#define crand() (rand()%32767/16383.5f-1) + +//static void P_ReadPointFile_f (void); + +#define MAX_BEAMSEGS (1<<11) // default max # of beam segments +#define MAX_PARTICLES (1<<18) // max # of particles at one time +#define MAX_DECALS (1<<18) // max # of decal fragments at one time +#define MAX_TRAILSTATES (1<<10) // default max # of trailstates + +//int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61}; +//int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66}; +//int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3, 2, 1}; + +static particle_t *free_particles; +static particle_t *particles; //contains the initial list of alloced particles. +static int r_numparticles; +static int r_particlerecycle; + +static beamseg_t *free_beams; +static beamseg_t *beams; +static int r_numbeams; + +static clippeddecal_t *free_decals; +static clippeddecal_t *decals; +static int r_numdecals; +static int r_decalrecycle; + +static trailstate_t *trailstates; +static int ts_cycle; // current cyclic index of trailstates +static int r_numtrailstates; + +static qboolean r_plooksdirty; //a particle effect was changed, reevaluate shared looks. + +static void FinishParticleType(part_type_t *ptype); + +static void R_ParticleDesc_Callback(struct cvar_s *var); +static cvar_t r_bouncysparks = {"r_bouncysparks", "1"}; +static cvar_t r_part_rain = {"r_part_rain", "1"}; +#if UNSUPPORTED +static cvar_t r_bloodstains = {"r_bloodstains", "1"}; +#endif +static cvar_t r_decal_noperpendicular = {"r_decal_noperpendicular", "1"}; +cvar_t r_particledesc = {"r_particledesc", "classic"}; +static cvar_t r_part_rain_quantity = {"r_part_rain_quantity", "1"}; +static cvar_t r_particle_tracelimit = {"r_particle_tracelimit", "0x7fffffff"}; +static cvar_t r_part_sparks = {"r_part_sparks", "1"}; +static cvar_t r_part_sparks_trifan = {"r_part_sparks_trifan", "1"}; +static cvar_t r_part_sparks_textured = {"r_part_sparks_textured", "1"}; +static cvar_t r_part_beams = {"r_part_beams", "1"}; +static cvar_t r_part_contentswitch = {"r_part_contentswitch", "1"}; +static cvar_t r_part_density = {"r_part_density", "1"}; +static cvar_t r_part_maxparticles = {"r_part_maxparticles", "65536"}; +static cvar_t r_part_maxdecals = {"r_part_maxdecals", "8192"}; +static cvar_t r_lightflicker = {"r_lightflicker", "1"}; + +static float particletime; + +typedef struct +{ + int firstidx; + int firstvert; + int numidx; + int numvert; + + gltexture_t *texture; + blendmode_t blendmode; + int beflags; +} scenetris_t; +#define MAX_INDICIES 0xffff +static scenetris_t *cl_stris; +static unsigned int cl_numstris; +static unsigned int cl_maxstris; +static vec3_t *cl_strisvertv; +static vec2_t *cl_strisvertt; +static vec4_t *cl_strisvertc; +static unsigned int cl_numstrisvert; +static unsigned int cl_maxstrisvert; +static unsigned short *cl_strisidx; +static unsigned int cl_numstrisidx; +static unsigned int cl_maxstrisidx; + + +/* +Q1BSP_RecursiveHullTrace +Optimised version of vanilla's SV_RecursiveHullCheck that avoids the excessive pointcontents calls by using the traceline itself to check for contents. +call Q1BSP_RecursiveHullCheck for a drop-in replacement of SV_RecursiveHullCheck, if desired. +*/ +enum +{ + rht_solid, + rht_empty, + rht_impact +}; +struct rhtctx_s +{ + vec3_t start, end; + mclipnode_t *clipnodes; + mplane_t *planes; +}; +static int Q1BSP_RecursiveHullTrace (struct rhtctx_s *ctx, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace) +{ + mclipnode_t *node; + mplane_t *plane; + float t1, t2; + vec3_t mid; + int side; + float midf; + int rht; + +reenter: + + if (num < 0) + { + /*hit a leaf*/ + if (num == CONTENTS_SOLID) + { + if (trace->allsolid) + trace->startsolid = true; + return rht_solid; + } + else + { + trace->allsolid = false; + if (num == CONTENTS_EMPTY) + trace->inopen = true; + else + trace->inwater = true; + return rht_empty; + } + } + + /*its a node*/ + + /*get the node info*/ + node = ctx->clipnodes + num; + plane = ctx->planes + node->planenum; + + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + } + + /*if its completely on one side, resume on that side*/ + if (t1 >= 0 && t2 >= 0) + { + //return Q1BSP_RecursiveHullTrace (hull, node->children[0], p1f, p2f, p1, p2, trace); + num = node->children[0]; + goto reenter; + } + if (t1 < 0 && t2 < 0) + { + //return Q1BSP_RecursiveHullTrace (hull, node->children[1], p1f, p2f, p1, p2, trace); + num = node->children[1]; + goto reenter; + } + + if (plane->type < 3) + { + t1 = ctx->start[plane->type] - plane->dist; + t2 = ctx->end[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, ctx->start) - plane->dist; + t2 = DotProduct (plane->normal, ctx->end) - plane->dist; + } + + side = t1 < 0; + + midf = t1 / (t1 - t2); + if (midf < p1f) midf = p1f; + if (midf > p2f) midf = p2f; + VectorInterpolate(ctx->start, midf, ctx->end, mid); + + rht = Q1BSP_RecursiveHullTrace(ctx, node->children[side], p1f, midf, p1, mid, trace); + if (rht != rht_empty && !trace->allsolid) + return rht; + rht = Q1BSP_RecursiveHullTrace(ctx, node->children[side^1], midf, p2f, mid, p2, trace); + if (rht != rht_solid) + return rht; + + if (side) + { + /*we impacted the back of the node, so flip the plane*/ + trace->plane.dist = -plane->dist; + VectorScale(plane->normal, -1, trace->plane.normal); + midf = (t1 + DIST_EPSILON) / (t1 - t2); + } + else + { + /*we impacted the front of the node*/ + trace->plane.dist = plane->dist; + VectorCopy(plane->normal, trace->plane.normal); + midf = (t1 - DIST_EPSILON) / (t1 - t2); + } + + t1 = DotProduct (trace->plane.normal, ctx->start) - trace->plane.dist; + t2 = DotProduct (trace->plane.normal, ctx->end) - trace->plane.dist; + midf = (t1 - DIST_EPSILON) / (t1 - t2); + if (midf < 0) + midf = 0; + if (midf > 1) + midf = 1; + trace->fraction = midf; + VectorCopy (mid, trace->endpos); + VectorInterpolate(ctx->start, midf, ctx->end, trace->endpos); + + return rht_impact; +} +static qboolean Q1BSP_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace) +{ //this function is basicall meant as a drop-in replacement for fte's SV_RecursiveHullCheck. p1f and p2f must be 0+1 respectively, num must be hull->firstclipnode + /*if (VectorEquals(p1, p2)) + { + //points cannot cross planes, so do it faster + switch(Q1_HullPointContents(hull, num, p1)) + { + case CONTENTS_SOLID: + trace->startsolid = true; + break; + case CONTENTS_EMPTY: + trace->allsolid = false; + trace->inopen = true; + break; + default: + trace->allsolid = false; + trace->inwater = true; + break; + } + return true; + } + else*/ + { + struct rhtctx_s ctx; + VectorCopy(p1, ctx.start); + VectorCopy(p2, ctx.end); + ctx.clipnodes = hull->clipnodes; + ctx.planes = hull->planes; + return Q1BSP_RecursiveHullTrace(&ctx, num, p1f, p2f, p1, p2, trace) != rht_impact; + } +} + +float CL_TraceLine (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal, int *entnum) +{ //FIXME: not sure what to do about startsolid. + int i; + trace_t trace; + float frac = 1; + entity_t *ent; + vec3_t relstart, relend; + VectorCopy (end, impact); + VectorSet(normal, 0, 0, 1); + if (entnum) + *entnum = 0; + for (i = 0; i < cl.num_entities; i++) + { + ent = &cl.entities[i]; + if (!ent->model || ent->model->needload || ent->model->type != mod_brush) + continue; + + //FIXME: deal with rotations + VectorSubtract(start, ent->origin, relstart); + VectorSubtract(end, ent->origin, relend); + + memset (&trace, 0, sizeof(trace)); + trace.fraction = 1; + Q1BSP_RecursiveHullCheck(&ent->model->hulls[0], ent->model->hulls[0].firstclipnode, 0, 1, relstart, relend, &trace); +// SV_RecursiveHullCheck (ent->model->hulls, ent->model->hulls[0].firstclipnode, 0, 1, relstart, relend, &trace); + + if (frac > trace.fraction) + { + frac = trace.fraction; + + //FIXME: deal with rotations. + VectorAdd(trace.endpos, ent->origin, impact); + VectorCopy (trace.plane.normal, normal); + + if (entnum) + *entnum = i; + if (frac <= 0) + break; + } + } + return frac; +} + +//these are not the actual values, but they'll do +#define FTECONTENTS_EMPTY 0 +#define FTECONTENTS_SOLID 1 +#define FTECONTENTS_WATER 2 +#define FTECONTENTS_SLIME 4 +#define FTECONTENTS_LAVA 8 +#define FTECONTENTS_SKY 16 +#define FTECONTENTS_FLUID (FTECONTENTS_WATER|FTECONTENTS_SLIME|FTECONTENTS_LAVA|FTECONTENTS_SKY) +#define FTECONTENTS_PLAYERCLIP 0 + +int SV_HullPointContents (hull_t *hull, int num, vec3_t p); +static unsigned int CL_PointContentsMask (vec3_t p) +{ + static const unsigned int cont_qtof[] = + { + 0, //invalid + FTECONTENTS_EMPTY, + FTECONTENTS_SOLID, + FTECONTENTS_WATER, + FTECONTENTS_SLIME, + FTECONTENTS_LAVA, + FTECONTENTS_SKY + }; + + unsigned int cont; + + cont = -SV_HullPointContents (&cl.worldmodel->hulls[0], 0, p); + if (cont < sizeof(cont_qtof)/sizeof(cont_qtof[0])) + return cont_qtof[cont]; + else + return cont_qtof[-(CONTENTS_WATER)]; //assume water +} + +static int numparticletypes; +static part_type_t *part_type; +static part_type_t *part_run_list; + +static struct { + char *oldn; + char *newn; +} legacynames[] = +{ + {"t_rocket", "TR_ROCKET"}, + {"t_grenade", "TR_GRENADE"}, + {"t_gib", "TR_BLOOD"}, + + {"te_plasma", "TE_TEI_PLASMAHIT"}, + {"te_smoke", "TE_TEI_SMOKE"}, + + {NULL} +}; + +static struct partalias_s +{ + struct partalias_s *next; + const char *from; + const char *to; +} *partaliaslist; +typedef struct associatedeffect_s +{ + struct associatedeffect_s *next; + char mname[MAX_QPATH]; + char pname[MAX_QPATH]; + unsigned int flags; + enum + { + AE_TRAIL, + AE_EMIT, + } type; +} associatedeffect_t; +static associatedeffect_t *associatedeffect; +static void PScript_AssociateEffect_f(void) +{ + const char *modelname = Cmd_Argv(1); + const char *effectname = Cmd_Argv(2); + unsigned int flags = 0; + int type; + associatedeffect_t *ae; + int i; + + if (!strcmp(Cmd_Argv(0), "r_trail")) + type = AE_TRAIL; + else + { + type = AE_EMIT; + for (i = 3; i < Cmd_Argc(); i++) + { + const char *fn = Cmd_Argv(i); + if (!strcmp(fn, "replace") || !strcmp(fn, "1")) + flags |= MOD_EMITREPLACE; + else if (!strcmp(fn, "forwards") || !strcmp(fn, "forward")) + flags |= MOD_EMITFORWARDS; + else if (!strcmp(fn, "0")) + ; //1 or 0 are legacy, meaning replace or not + else + Con_DPrintf("%s %s: unknown flag %s\n", Cmd_Argv(0), modelname, fn); + } + } + + if ( + strstr(modelname, "player") || + strstr(modelname, "eyes") || + strstr(modelname, "flag") || + strstr(modelname, "tf_stan") || + strstr(modelname, ".bsp") || + strstr(modelname, "turr")) + { + //there is a very real possibility of attaching 'large' effects to models so that they become more visible (eg: a stream of particles passing through walls showing you the entity that they're eminating from) + Con_Printf("Sorry: Not allowed to attach effects to model \"%s\"\n", modelname); + return; + } + + if (strlen (modelname) >= MAX_QPATH || strlen(effectname) >= MAX_QPATH) + return; + + /*replace the old one if it exists*/ + for(ae = associatedeffect; ae; ae = ae->next) + { + if (!strcmp(ae->mname, modelname)) + if ((ae->type==AE_TRAIL) == (type==AE_TRAIL)) + break; + } + if (!ae) + { + ae = Z_Malloc(sizeof(*ae)); + strcpy(ae->mname, modelname); + ae->next = associatedeffect; + associatedeffect = ae; + } + strcpy(ae->pname, effectname); + ae->type = type; + ae->flags = flags; + + r_plooksdirty = true; +} +static void P_PartRedirect_f(void) +{ + struct partalias_s **link, *l; + const char *from = Cmd_Argv(1); + const char *to = Cmd_Argv(2); + + //user wants to list all + if (!*from) + { + for (l = partaliaslist; l; l = l->next) + { + Con_Printf("%s -> %s\n", l->from, l->to); + } + return; + } + + //unlink the current value + for (link = &partaliaslist; (l=*link); link = &(*link)->next) + { + if (!q_strcasecmp(l->from, from)) + { + //they didn't specify a to, so just print out this one effect without removing it. + if (Cmd_Argc() == 2) + { + Con_Printf("particle %s is currently remapped to %s\n", l->from, l->to); + return; + } + *link = l->next; + Z_Free(l); + break; + } + } + + //create a new entry. + if (*to && q_strcasecmp(from, to)) + { + l = Z_Malloc(sizeof(*l) + strlen(from) + strlen(to) + 2); + l->from = (char*)(l + 1); + strcpy((char*)l->from, from); + l->to = l->from + strlen(l->from)+1; + strcpy((char*)l->to, to); + l->next = partaliaslist; + partaliaslist = l; + } + + r_plooksdirty = true; +} +void PScript_UpdateModelEffects(qmodel_t *mod) +{ + associatedeffect_t *ae; + mod->emiteffect = P_INVALID; + mod->traileffect = P_INVALID; + for(ae = associatedeffect; ae; ae = ae->next) + { + if (!strcmp(ae->mname, mod->name)) + { + switch(ae->type) + { + case AE_TRAIL: + mod->traileffect = PScript_FindParticleType(ae->pname); + break; + case AE_EMIT: + mod->emiteffect = PScript_FindParticleType(ae->pname); + mod->flags &= ~(MOD_EMITREPLACE|MOD_EMITFORWARDS); + mod->flags |= ae->flags; + break; + } + } + } +} + +static part_type_t *P_GetParticleType(const char *config, const char *name) +{ + int i; + part_type_t *ptype; + part_type_t *oldlist = part_type; + char cfgbuf[MAX_QPATH]; + char *dot = strchr(name, '.'); + if (dot && (dot - name) < MAX_QPATH-1) + { + config = cfgbuf; + memcpy(cfgbuf, name, dot - name); + cfgbuf[dot - name] = 0; + name = dot+1; + } + + for (i = 0; legacynames[i].oldn; i++) + { + if (!strcmp(name, legacynames[i].oldn)) + { + name = legacynames[i].newn; + break; + } + } + for (i = 0; i < numparticletypes; i++) + { + ptype = &part_type[i]; + if (!q_strcasecmp(ptype->name, name)) + if (!q_strcasecmp(ptype->config, config)) //must be an exact match. + return ptype; + } + part_type = Z_Realloc(part_type, sizeof(part_type_t)*(numparticletypes+1)); + ptype = &part_type[numparticletypes++]; + memset(ptype, 0, sizeof(*ptype)); + q_strlcpy(ptype->name, name, sizeof(ptype->name)); + q_strlcpy(ptype->config, config, sizeof(ptype->config)); + ptype->assoc = P_INVALID; + ptype->inwater = P_INVALID; + ptype->cliptype = P_INVALID; + ptype->emit = P_INVALID; + + if (oldlist) + { + if (part_run_list) + part_run_list = (part_type_t*)((char*)part_run_list - (char*)oldlist + (char*)part_type); + + for (i = 0; i < numparticletypes; i++) + if (part_type[i].nexttorun) + part_type[i].nexttorun = (part_type_t*)((char*)part_type[i].nexttorun - (char*)oldlist + (char*)part_type); + } + + ptype->loaded = 0; + ptype->ramp = NULL; + ptype->particles = NULL; + ptype->beams = NULL; + + r_plooksdirty = true; + return ptype; +} + +//unconditionally allocates a particle object. this allows out-of-order allocations. +static int P_AllocateParticleType(const char *config, const char *name) //guarentees that the particle type exists, returning it's index. +{ + part_type_t *pt = P_GetParticleType(config, name); + return pt - part_type; +} + +static void PScript_RetintEffect(part_type_t *to, part_type_t *from, const char *colourcodes) +{ + char name[sizeof(to->name)]; + char config[sizeof(to->config)]; + + q_strlcpy(name, to->name, sizeof(to->name)); + q_strlcpy(config, to->config, sizeof(to->config)); + + //'to' was already purged, so we don't need to care about that. + memcpy(to, from, sizeof(*to)); + + q_strlcpy(to->name, name, sizeof(to->name)); + q_strlcpy(to->config, config, sizeof(to->config)); + + //make sure 'to' has its own copy of any lists, so that we don't have issues when freeing this memory again. +#if UNSUPPORTED + if (to->models) + { + to->models = Z_Malloc(to->nummodels * sizeof(*to->models)); + memcpy(to->models, from->models, to->nummodels * sizeof(*to->models)); + } +#endif + if (to->sounds) + { + to->sounds = Z_Malloc(to->numsounds * sizeof(*to->sounds)); + memcpy(to->sounds, from->sounds, to->numsounds * sizeof(*to->sounds)); + } + if (to->ramp) + { + to->ramp = Z_Malloc(to->rampindexes * sizeof(*to->ramp)); + memcpy(to->ramp, from->ramp, to->rampindexes * sizeof(*to->ramp)); + } + + //'from' might still have some links so we need to clear those out. + to->nexttorun = NULL; + to->particles = NULL; + to->clippeddecals = NULL; + to->beams = NULL; + to->slooks = &to->looks; + r_plooksdirty = true; + + to->colorindex = strtoul(colourcodes, (char**)&colourcodes, 10); + if (*colourcodes == '_') + colourcodes++; + to->colorrand = strtoul(colourcodes, (char**)&colourcodes, 10); +} + +//public interface. get without creating. +int PScript_FindParticleType(const char *fullname) +{ + int i; + part_type_t *ptype = NULL; + char cfg[MAX_QPATH]; + char *dot; + const char *name = fullname; + + //check particle aliases, mostly for tex_sky1 -> weather.te_rain for example, or whatever + struct partalias_s *l; + int recurselimit = 5; + for (l = partaliaslist; l; ) + { + if (!q_strcasecmp(l->from, name)) + { + name = l->to; + + if (recurselimit --> 0) + l = partaliaslist; + else + return P_INVALID; + } + else + l = l->next; + } + + dot = strchr(name, '.'); + if (dot && (dot - name) < MAX_QPATH-1) + { + memcpy(cfg, name, dot - name); + cfg[dot-name] = 0; + name = dot+1; + } + else + *cfg = 0; + + for (i = 0; legacynames[i].oldn; i++) + { + if (!strcmp(name, legacynames[i].oldn)) + { + name = legacynames[i].newn; + break; + } + } + + if (*cfg) + { //favour the namespace if one is specified + for (i = 0; i < numparticletypes; i++) + { + if (!q_strcasecmp(part_type[i].name, name)) + { + if (!q_strcasecmp(part_type[i].config, cfg)) + { + ptype = &part_type[i]; + break; + } + } + } + } + else + { + //but be prepared to load it from any namespace if its not got a namespace specified. + for (i = 0; i < numparticletypes; i++) + { + if (!q_strcasecmp(part_type[i].name, name)) + { + ptype = &part_type[i]; + if (ptype->loaded) //(mostly) ignore ones that are not currently loaded + break; + } + } + } + if (!ptype || !ptype->loaded) + { + if (!q_strncasecmp(name, "te_explosion2_", 14)) + { + int from = PScript_FindParticleType(va("%s.te_explosion2", cfg)); + if (from != P_INVALID) + { + int to = P_AllocateParticleType(cfg, name); + PScript_RetintEffect(&part_type[to], &part_type[from], name+14); + return to; + } + } + if (*cfg) + if (P_LoadParticleSet(cfg, true, true)) + return PScript_FindParticleType(fullname); + +/* if (fallback) + { + if (!strncmp(name, "classic_", 8)) + i = fallback->FindParticleType(name+8); + else + i = fallback->FindParticleType(name); + if (i != P_INVALID) + return i+FALLBACKBIAS; + } +*/ + return P_INVALID; + } + return i; +} + +static int CheckAssosiation(const char *config, const char *name, int from) +{ + int to, orig; + + orig = to = P_AllocateParticleType(config, name); + + while(to != P_INVALID) + { + if (to == from) + { + Con_Printf("Assosiation of %s would cause infinate loop\n", name); + return P_INVALID; + } + to = part_type[to].assoc; + } + return orig; +} + +static void P_LoadTexture(part_type_t *ptype, qboolean warn) +{ +#if UNSUPPORTED + for (i = 0; i < ptype->nummodels; i++) + ptype->models[i].model = NULL; +#endif + + if (*ptype->texname) + { + byte *data = NULL; + char filename[MAX_QPATH]; + int fwidth=0, fheight=0; + int hunkmark; + char *texname = va("%s%s%s", ptype->texname, ptype->looks.premul?"_premul":"", ptype->looks.nearest?"_nearest":""); + qboolean malloced = false; + + ptype->looks.texture = TexMgr_FindTexture(NULL, texname); + if (!ptype->looks.texture) + { + hunkmark = Hunk_LowMark(); + if (!data) + { + q_snprintf (filename, sizeof(filename), "textures/%s", ptype->texname); + data = Image_LoadImage (filename, &fwidth, &fheight, &malloced); + } + if (!data) + { + q_snprintf (filename, sizeof(filename), "%s", ptype->texname); + data = Image_LoadImage (filename, &fwidth, &fheight, &malloced); + } + + if (data) + { + ptype->looks.texture = TexMgr_LoadImage(NULL, texname, fwidth, fheight, SRC_RGBA, data, filename, 0, (ptype->looks.premul?TEXPREF_PREMULTIPLY:0)|(ptype->looks.nearest?TEXPREF_NEAREST:TEXPREF_LINEAR)|TEXPREF_NOPICMIP|TEXPREF_ALPHA); + } + if (malloced) + free(data); + Hunk_FreeToLowMark(hunkmark); + } + } + else + ptype->looks.texture = 0; + + if (!ptype->looks.texture) + { + //the specified texture isn't valid. make something up based upon the particle's type + ptype->s1 = 0; + ptype->t1 = 0; + ptype->s2 = 1; + ptype->t2 = 1; + ptype->randsmax = 1; + +#define PARTICLETEXTURESIZE 64 + if (ptype->looks.type == PT_SPARK) + { + static gltexture_t *thetex; + if (!thetex) + { + static byte data[4*4*4]; + memset(data, 0xff, sizeof(data)); + thetex = TexMgr_LoadImage(NULL, "particles/white", 4, 4, SRC_RGBA, data, "", (src_offset_t)data, TEXPREF_PERSIST|TEXPREF_NOPICMIP|TEXPREF_LINEAR|TEXPREF_ALPHA); + } + ptype->looks.texture = thetex; + } + else if (ptype->looks.type == PT_BEAM) //untextured beams get a single continuous blob + { + static gltexture_t *thetex; + if (!thetex) + { + int y, x; + float dy, d; + static byte data[PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4]; + memset(data, 0xff, sizeof(data)); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + d = 256 * (1 - (dy*dy)); + if (d < 0) d = 0; + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (byte) d; + } + } + thetex = TexMgr_LoadImage(NULL, "particles/beamtexture", PARTICLETEXTURESIZE, PARTICLETEXTURESIZE, SRC_RGBA, data, "", (src_offset_t)data, TEXPREF_PERSIST|TEXPREF_NOPICMIP|TEXPREF_LINEAR|TEXPREF_ALPHA); + } + ptype->looks.texture = thetex; + } + else if (ptype->looks.type == PT_SPARKFAN) //untextured beams get a single continuous blob + { + static gltexture_t *thetex; + if (!thetex) + { + int y, x; + float dy, dx, d; + static byte data[PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4]; + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = y / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = x / (PARTICLETEXTURESIZE*0.5f-1); + d = 256 * (1 - (dx+dy)); + if (d < 0) d = 0; + data[(y*PARTICLETEXTURESIZE+x)*4+0] = (byte) d; + data[(y*PARTICLETEXTURESIZE+x)*4+1] = (byte) d; + data[(y*PARTICLETEXTURESIZE+x)*4+2] = (byte) d; + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (byte) d/2; + } + } + thetex = TexMgr_LoadImage(NULL, "particles/ptritexture", PARTICLETEXTURESIZE, PARTICLETEXTURESIZE, SRC_RGBA, data, "", (src_offset_t)data, TEXPREF_PERSIST|TEXPREF_NOPICMIP|TEXPREF_LINEAR|TEXPREF_ALPHA); + } + ptype->looks.texture = thetex; + } + else if (strstr(ptype->texname, "classicparticle")) + { + extern gltexture_t *particletexture1; + ptype->looks.texture = particletexture1; + ptype->s2 = 0.5; + ptype->t2 = 0.5; + } + else if (strstr(ptype->texname, "glow") || strstr(ptype->texname, "ball") || ptype->looks.type == PT_TEXTUREDSPARK) //sparks and special names get a nice circular texture. + { + static gltexture_t *thetex; + if (!thetex) + { + int y, x; + float dy, dx, d; + static byte data[PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4]; + memset(data, 0xff, sizeof(data)); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + d = 256 * (1 - (dx*dx+dy*dy)); + if (d < 0) d = 0; + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (byte) d; + } + } + thetex = TexMgr_LoadImage(NULL, "particles/balltexture", PARTICLETEXTURESIZE, PARTICLETEXTURESIZE, SRC_RGBA, data, "", (src_offset_t)data, TEXPREF_PERSIST|TEXPREF_NOPICMIP|TEXPREF_LINEAR|TEXPREF_ALPHA); + } + ptype->looks.texture = thetex; + } + else //anything else gets a fuzzy texture + { + static gltexture_t *thetex; + if (!thetex) + { + int y, x; + static byte exptexture[16][16] = + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0}, + {0,0,0,1,1,1,1,1,3,1,1,2,1,0,0,0}, + {0,0,0,1,1,1,1,4,4,4,5,4,2,1,1,0}, + {0,0,1,1,6,5,5,8,6,8,3,6,3,2,1,0}, + {0,0,1,5,6,7,5,6,8,8,8,3,3,1,0,0}, + {0,0,0,1,6,8,9,9,9,9,4,6,3,1,0,0}, + {0,0,2,1,7,7,9,9,9,9,5,3,1,0,0,0}, + {0,0,2,4,6,8,9,9,9,9,8,6,1,0,0,0}, + {0,0,2,2,3,5,6,8,9,8,8,4,4,1,0,0}, + {0,0,1,2,4,1,8,7,8,8,6,5,4,1,0,0}, + {0,1,1,1,7,8,1,6,7,5,4,7,1,0,0,0}, + {0,1,2,1,1,5,1,3,4,3,1,1,0,0,0,0}, + {0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + }; + static byte data[16*16*4]; + for (x=0 ; x<16 ; x++) + { + for (y=0 ; y<16 ; y++) + { + data[(y*16+x)*4+0] = 255; + data[(y*16+x)*4+1] = 255; + data[(y*16+x)*4+2] = 255; + data[(y*16+x)*4+3] = exptexture[x][y]*255/9.0; + } + } + thetex = TexMgr_LoadImage(NULL, "particles/fuzzyparticle", 16, 16, SRC_RGBA, data, "", (src_offset_t)data, TEXPREF_PERSIST|TEXPREF_NOPICMIP|TEXPREF_LINEAR|TEXPREF_ALPHA); + } + ptype->looks.texture = thetex; + } + } +} + +static void P_ResetToDefaults(part_type_t *ptype) +{ + particle_t *parts; + part_type_t *torun; + char tnamebuf[sizeof(ptype->name)]; + char tconfbuf[sizeof(ptype->config)]; + + // go with a lazy clear of list.. mark everything as DEAD and let + // the beam rendering handle removing nodes + beamseg_t *beamsegs = ptype->beams; + while (beamsegs) + { + beamsegs->flags |= BS_DEAD; + beamsegs = beamsegs->next; + } + + // forget any particles before its wiped + while (ptype->particles) + { + parts = ptype->particles->next; + ptype->particles->next = free_particles; + free_particles = ptype->particles; + ptype->particles = parts; + } + + // if we're in the runstate loop through and remove from linked list + if (ptype->state & PS_INRUNLIST) + { + if (part_run_list == ptype) + part_run_list = part_run_list->nexttorun; + else + { + for (torun = part_run_list; torun != NULL; torun = torun->nexttorun) + { + if (torun->nexttorun == ptype) + torun->nexttorun = torun->nexttorun->nexttorun; + } + } + } + + //some things need to be preserved before we clear everything. + beamsegs = ptype->beams; + strcpy(tnamebuf, ptype->name); + strcpy(tconfbuf, ptype->config); + + //free uneeded info + if (ptype->ramp) + Z_Free(ptype->ramp); +#if UNSUPPORTED + if (ptype->models) + Z_Free(ptype->models); +#endif + if (ptype->sounds) + Z_Free(ptype->sounds); + + //reset everything we're too lazy to specifically set + memset(ptype, 0, sizeof(*ptype)); + + //now set any non-0 defaults. + + ptype->beams = beamsegs; + ptype->rainfrequency = 1; + strcpy(ptype->name, tnamebuf); + strcpy(ptype->config, tconfbuf); + ptype->assoc=P_INVALID; + ptype->inwater = P_INVALID; + ptype->cliptype = P_INVALID; + ptype->emit = P_INVALID; + ptype->fluidmask = FTECONTENTS_FLUID; + ptype->alpha = 1; + ptype->alphachange = 1; + ptype->clipbounce = 0.8; + ptype->clipcount = 1; + ptype->colorindex = -1; + ptype->rotationstartmin = -M_PI; //start with a random angle + ptype->rotationstartrand = M_PI-ptype->rotationstartmin; + ptype->spawnchance = 1; + ptype->dl_time = 0; + VectorSet(ptype->dl_rgb, 1, 1, 1); + ptype->dl_corona_intensity = 0.25; + ptype->dl_corona_scale = 0.5; + VectorSet(ptype->dl_scales, 0, 1, 1); + ptype->looks.stretch = 0.05; + + ptype->randsmax = 1; + ptype->s2 = 1; + ptype->t2 = 1; +} + +char *PScript_ReadLine(char *buffer, size_t buffersize, const char *filedata, size_t filesize, size_t *offset) +{ + const char *start = filedata + *offset; + const char *f = start; + const char *e = filedata+filesize; + if (f >= e) + return NULL; //eof + while (f < e) + { + if (*f++ == '\n') + break; + } + + *offset = f-filedata; + + buffersize--; + if (buffersize >= (size_t)(f-start)) + buffersize = f-start; + memcpy(buffer, start, buffersize); + buffer[buffersize] = 0; //null terminate it + + return buffer; +} + +//This is the function that loads the effect descriptions. +void PScript_ParseParticleEffectFile(const char *config, qboolean part_parseweak, char *context, size_t filesize) +{ + const char *var, *value; + char *buf; + qboolean settype; + qboolean setalphadelta; + qboolean setbeamlen; + + part_type_t *ptype; + int pnum, assoc; + char line[512]; + char part_parsenamespace[MAX_QPATH]; + + byte *palrgba = (byte *)d_8to24table; + size_t offset = 0; + + q_strlcpy(part_parsenamespace, config, sizeof(part_parsenamespace)); + config = part_parsenamespace; + +nexteffect: + + if (!PScript_ReadLine(line, sizeof(line), context, filesize, &offset)) + return; //eof +reparse: + + Cmd_TokenizeString(line); + + var = Cmd_Argv(0); + + + if (!strcmp(var, "r_effect") || !strcmp(var, "r_trail")) + { //add an emit/trail effect to all ents using said model + PScript_AssociateEffect_f(); + goto nexteffect; + } + else if (!strcmp(var, "r_partredirect")) + { //add an emit/trail effect to all ents using said model + P_PartRedirect_f(); + goto nexteffect; + } + else if (strcmp(var, "r_part")) + { + if (*var) + Con_SafePrintf("Unknown particle command \"%s\"\n", var); + goto nexteffect; + } + + settype = false; + setalphadelta = false; + setbeamlen = false; + + if (Cmd_Argc()!=2) + { + if (!strcmp(Cmd_Argv(1), "namespace")) + { + q_strlcpy(part_parsenamespace, Cmd_Argv(2), sizeof(part_parsenamespace)); + if (Cmd_Argc() >= 4) + part_parseweak = atoi(Cmd_Argv(3)); + goto nexteffect; + } + Con_Printf("No name for particle effect\n"); + goto nexteffect; + } + + buf = PScript_ReadLine(line, sizeof(line), context, filesize, &offset); + if (!buf) + return; //eof + while (*buf && *buf <= ' ') + buf++; //no whitespace please. + if (*buf != '{') + { + Con_Printf("This is a multiline command and should be used within config files\n"); + goto reparse; + } + + var = Cmd_Argv(1); + if (*var == '+') + ptype = P_GetParticleType(config, var+1); + else + ptype = P_GetParticleType(config, var); + + //'weak' configs do not replace 'strong' configs + //we allow weak to replace weak as a solution to the +assoc chain thing (to add, we effectively need to 'replace'). + if ((part_parseweak && ptype->loaded==2)) + { + int depth = 1; + while(1) + { + buf = PScript_ReadLine(line, sizeof(line), context, filesize, &offset); + if (!buf) + return; + + while (*buf && *buf <= ' ') + buf++; //no whitespace please. + if (*buf == '{') + depth++; + else if (*buf == '}') + { + if (--depth == 0) + break; + } + } + goto nexteffect; + } + + if (*var == '+') + { + if (ptype->loaded) + { + int i, parenttype; + char newname[256]; + for (i = 0; i < 64; i++) + { + parenttype = ptype - part_type; + q_snprintf(newname, sizeof(newname), "+%i%s", i, ptype->name); + ptype = P_GetParticleType(config, newname); + if (!ptype->loaded) + { + if (part_type[parenttype].assoc != P_INVALID) + Con_Printf("warning: assoc on particle chain %s overridden\n", var+1); + part_type[parenttype].assoc = ptype - part_type; + break; + } + } + if (i == 64) + { + Con_Printf("Too many duplicate names, gave up\n"); + return; + } + } + } + else + { + if (ptype->loaded) + { + assoc = ptype->assoc; + while (assoc != P_INVALID && assoc < numparticletypes) + { + if (*part_type[assoc].name == '+') + { + part_type[assoc].loaded = false; + assoc = part_type[assoc].assoc; + } + else + break; + } + } + } + if (!ptype) + { + Con_Printf("Bad name\n"); + return; + } + + pnum = ptype-part_type; + + P_ResetToDefaults(ptype); + + while(1) + { + buf = PScript_ReadLine(line, sizeof(line), context, filesize, &offset); + if (!buf) + { + Con_Printf("Unexpected end of buffer with effect %s\n", ptype->name); + return; + } +skipread: + while (*buf && *buf <= ' ') + buf++; //no whitespace please. + if (*buf == '}') + break; + + Cmd_TokenizeString(buf); + var = Cmd_Argv(0); + value = Cmd_Argv(1); + + // TODO: switch this mess to some sort of binary tree to increase parse speed +#if UNSUPPORTED + if (!strcmp(var, "if")) + { + //cheesy way to handle if statements inside particle configs. + Cmd_if_f(); + } + else +#endif + if (!strcmp(var, "shader")) + { + q_strlcpy(ptype->texname, ptype->name, sizeof(ptype->texname)); + +#if UNSUPPORTED + Con_DPrintf("%s.%s: shaders are not supported in this build\n", ptype->config, ptype->name); +#endif + buf = PScript_ReadLine(line, sizeof(line), context, filesize, &offset); + if (!buf) + continue; + while (*buf && *buf <= ' ') + buf++; //no leading whitespace please. + if (*buf == '{') + { + int nest = 1; + char *str = Z_Malloc(3); + int slen = 2; + str[0] = '{'; + str[1] = '\n'; + str[2] = 0; + while(nest) + { + buf = PScript_ReadLine(line, sizeof(line), context, filesize, &offset); + if (!buf) + { + Con_Printf("Unexpected end of buffer with effect %s\n", ptype->name); + break; + } + while (*buf && *buf <= ' ') + buf++; //no leading whitespace please. + if (*buf == '}') + --nest; + if (*buf == '{') + nest++; + str = Z_Realloc(str, slen + strlen(buf) + 2); + strcpy(str + slen, buf); + slen += strlen(str + slen); + str[slen++] = '\n'; + } + str[slen] = 0; +#if UNSUPPORTED + R_RegisterShader(ptype->texname, SUF_NONE, str); +#endif + Z_Free(str); + } + else + goto skipread; + } + else if (!strcmp(var, "texture") || !strcmp(var, "linear_texture") || !strcmp(var, "nearest_texture") || !strcmp(var, "nearesttexture")) + { + q_strlcpy(ptype->texname, value, sizeof(ptype->texname)); + ptype->looks.nearest = !strncmp(var, "nearest", 7); + } + else if (!strcmp(var, "tcoords")) + { + float tscale; + + tscale = atof(Cmd_Argv(5)); + if (tscale <= 0) + tscale = 1; + + ptype->s1 = atof(value)/tscale; + ptype->t1 = atof(Cmd_Argv(2))/tscale; + ptype->s2 = atof(Cmd_Argv(3))/tscale; + ptype->t2 = atof(Cmd_Argv(4))/tscale; + + ptype->randsmax = atoi(Cmd_Argv(6)); + ptype->texsstride = atof(Cmd_Argv(7)); + + 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; + if (Cmd_Argc()>2) + ptype->rotationstartrand = atof(Cmd_Argv(2))*M_PI/180-ptype->rotationstartmin; + else + ptype->rotationstartrand = 0; + } + else if (!strcmp(var, "rotationspeed")) + { + ptype->rotationmin = atof(value)*M_PI/180; + if (Cmd_Argc()>2) + ptype->rotationrand = atof(Cmd_Argv(2))*M_PI/180-ptype->rotationmin; + else + ptype->rotationrand = 0; + } + else if (!strcmp(var, "beamtexstep")) + { + ptype->rotationstartmin = 1/atof(value); + ptype->rotationstartrand = 0; + setbeamlen = true; + } + else if (!strcmp(var, "beamtexspeed")) + { + ptype->rotationmin = atof(value); + } + else if (!strcmp(var, "scale")) + { + ptype->scale = atof(value); + if (Cmd_Argc()>2) + ptype->scalerand = atof(Cmd_Argv(2)) - ptype->scale; + } + else if (!strcmp(var, "scalerand")) + ptype->scalerand = atof(value); + + else if (!strcmp(var, "scalefactor")) + ptype->looks.scalefactor = atof(value); + else if (!strcmp(var, "scaledelta")) + ptype->scaledelta = atof(value); + else if (!strcmp(var, "stretchfactor")) //affects sparks + { + ptype->looks.stretch = atof(value); + ptype->looks.minstretch = (Cmd_Argc()>2)?atof(Cmd_Argv(2)):0; + } + + else if (!strcmp(var, "step")) + { + ptype->count = 1/atof(value); + if (Cmd_Argc()>2) + ptype->countrand = 1/atof(Cmd_Argv(2)); + } + else if (!strcmp(var, "count")) + { + ptype->count = atof(value); + if (Cmd_Argc()>2) + ptype->countrand = atof(Cmd_Argv(2)); + if (Cmd_Argc()>3) + ptype->countextra = atof(Cmd_Argv(3)); + } + else if (!strcmp(var, "rainfrequency")) + { //multiplier to ramp up the effect or whatever (without affecting spawn patterns). + ptype->rainfrequency = atof(value); + } + + else if (!strcmp(var, "alpha")) + ptype->alpha = atof(value); + else if (!strcmp(var, "alpharand")) + ptype->alpharand = atof(value); +#ifndef NOLEGACY + else if (!strcmp(var, "alphachange")) + { + Con_DPrintf("%s.%s: alphachange is deprecated, use alphadelta\n", ptype->config, ptype->name); + ptype->alphachange = atof(value); + } +#endif + else if (!strcmp(var, "alphadelta")) + { + ptype->alphachange = atof(value); + setalphadelta = true; + } + else if (!strcmp(var, "die")) + { + ptype->die = atof(value); + if (Cmd_Argc()>2) + { + float mn=ptype->die,mx=atof(Cmd_Argv(2)); + if (mn > mx) + { + mn = mx; + mx = ptype->die; + } + ptype->die = mx; + ptype->randdie = mx-mn; + } + } +#ifndef NOLEGACY + else if (!strcmp(var, "diesubrand")) + { + Con_DPrintf("%s.%s: diesubrand is deprecated, use die with two arguments\n", ptype->config, ptype->name); + ptype->randdie = atof(value); + } +#endif + + else if (!strcmp(var, "randomvel")) + { //shortcut for velwrand (and velbias for z bias) + ptype->velbias[0] = ptype->velbias[1] = 0; + ptype->velwrand[0] = ptype->velwrand[1] = atof(value); + if (Cmd_Argc()>3) + { + ptype->velbias[2] = atof(Cmd_Argv(2)); + ptype->velwrand[2] = atof(Cmd_Argv(3)); + ptype->velwrand[2] -= ptype->velbias[2]; /*make vert be the total range*/ + ptype->velwrand[2] /= 2; /*vert is actually +/- 1, not 0 to 1, so rescale it*/ + ptype->velbias[2] += ptype->velwrand[2]; /*and bias must be centered to the range*/ + } + else if (Cmd_Argc()>2) + { + ptype->velwrand[2] = atof(Cmd_Argv(2)); + ptype->velbias[2] = 0; + } + else + { + ptype->velwrand[2] = ptype->velwrand[0]; + ptype->velbias[2] = 0; + } + } + else if (!strcmp(var, "veladd")) + { + ptype->veladd = atof(value); + ptype->randomveladd = 0; + if (Cmd_Argc()>2) + ptype->randomveladd = atof(Cmd_Argv(2)) - ptype->veladd; + } + else if (!strcmp(var, "orgadd")) + { + ptype->orgadd = atof(value); + ptype->randomorgadd = 0; + if (Cmd_Argc()>2) + ptype->randomorgadd = atof(Cmd_Argv(2)) - ptype->orgadd; + } + + else if (!strcmp(var, "orgbias")) + { + ptype->orgbias[0] = atof(value); + ptype->orgbias[1] = atof(Cmd_Argv(2)); + ptype->orgbias[2] = atof(Cmd_Argv(3)); + } + else if (!strcmp(var, "orgwrand")) + { + ptype->orgwrand[0] = atof(value); + ptype->orgwrand[1] = atof(Cmd_Argv(2)); + ptype->orgwrand[2] = atof(Cmd_Argv(3)); + } + + else if (!strcmp(var, "velbias")) + { + ptype->velbias[0] = atof(value); + ptype->velbias[1] = atof(Cmd_Argv(2)); + ptype->velbias[2] = atof(Cmd_Argv(3)); + } + else if (!strcmp(var, "velwrand")) + { + ptype->velwrand[0] = atof(value); + ptype->velwrand[1] = atof(Cmd_Argv(2)); + ptype->velwrand[2] = atof(Cmd_Argv(3)); + } + + else if (!strcmp(var, "friction")) + { + ptype->friction[2] = ptype->friction[1] = ptype->friction[0] = atof(value); + + if (Cmd_Argc()>3) + { + ptype->friction[2] = atof(Cmd_Argv(3)); + ptype->friction[1] = atof(Cmd_Argv(2)); + } + else if (Cmd_Argc()>2) + { + ptype->friction[2] = atof(Cmd_Argv(2)); + } + } + else if (!strcmp(var, "gravity")) + ptype->gravity = atof(value); + else if (!strcmp(var, "flurry")) + ptype->flurry = atof(value); + + else if (!strcmp(var, "assoc")) + { + assoc = CheckAssosiation(config, value, pnum); //careful - this can realloc all the particle types + ptype = &part_type[pnum]; + ptype->assoc = assoc; + } + else if (!strcmp(var, "inwater")) + { + // the underwater effect switch should only occur for + // 1 level so the standard assoc check works + assoc = CheckAssosiation(config, value, pnum); + ptype = &part_type[pnum]; + ptype->inwater = assoc; + } + else if (!strcmp(var, "underwater")) + { + ptype->flags |= PT_TRUNDERWATER; + +parsefluid: + if ((ptype->flags & (PT_TRUNDERWATER|PT_TROVERWATER)) == (PT_TRUNDERWATER|PT_TROVERWATER)) + { + ptype->flags &= ~PT_TRUNDERWATER; + Con_Printf("%s.%s: both over and under water\n", ptype->config, ptype->name); + } + if (Cmd_Argc() == 1) + ptype->fluidmask = FTECONTENTS_FLUID; + else + { + int i = Cmd_Argc(); + ptype->fluidmask = 0; + while (i --> 1) + { + const char *value = Cmd_Argv(i); + if (!strcmp(value, "water")) + ptype->fluidmask |= FTECONTENTS_WATER; + else if (!strcmp(value, "slime")) + ptype->fluidmask |= FTECONTENTS_SLIME; + else if (!strcmp(value, "lava")) + ptype->fluidmask |= FTECONTENTS_LAVA; + else if (!strcmp(value, "sky")) + ptype->fluidmask |= FTECONTENTS_SKY; + else if (!strcmp(value, "fluid")) + ptype->fluidmask |= FTECONTENTS_FLUID; + else if (!strcmp(value, "solid")) + ptype->fluidmask |= FTECONTENTS_SOLID; + else if (!strcmp(value, "playerclip")) + ptype->fluidmask |= FTECONTENTS_PLAYERCLIP; + else if (!strcmp(value, "none")) + ptype->fluidmask |= 0; + else + Con_Printf("%s.%s: unknown contents: %s\n", ptype->config, ptype->name, value); + } + } + } + else if (!strcmp(var, "notunderwater")) + { + ptype->flags |= PT_TROVERWATER; + goto parsefluid; + } + else if (!strcmp(var, "model")) + { +#if UNSUPPORTED + partmodels_t *mod; + char *e; + + ptype->models = Z_Realloc(ptype->models, sizeof(partmodels_t)*(ptype->nummodels+1)); + q_strlcpy(ptype->models[ptype->nummodels].name, Cmd_Argv(1), sizeof(ptype->models[ptype->nummodels].name)); + mod = &ptype->models[ptype->nummodels++]; + + mod->framestart = 0; + mod->framecount = 1; + mod->framerate = 10; + mod->alpha = 1; + mod->skin = 0; + mod->traileffect = P_INVALID; + mod->rflags = RF_NOSHADOW; + mod->scalemin = mod->scalemax = 1; + + 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, "frame=", 6)) + { + mod->framestart = atof(e+6); + mod->framecount = 1; + } + else if (!q_strncasecmp(e, "framestart=", 11)) + mod->framestart = atof(e+11); + else if (!q_strncasecmp(e, "framecount=", 11)) + mod->framecount = atof(e+11); + else if (!q_strncasecmp(e, "frameend=", 9)) //misnomer. + mod->framecount = atof(e+9); + else if (!q_strncasecmp(e, "frames=", 7)) + mod->framecount = atof(e+7); + else if (!q_strncasecmp(e, "framerate=", 10)) + mod->framerate = atof(e+10); + else if (!q_strncasecmp(e, "skin=", 5)) + mod->skin = atoi(e+5); + else if (!q_strncasecmp(e, "alpha=", 6)) + mod->alpha = atof(e+6); + else if (!q_strncasecmp(e, "scalemin=", 9)) + mod->scalemin = atof(e+9); + else if (!q_strncasecmp(e, "scalemax=", 9)) + mod->scalemax = atof(e+9); + else if (!q_strncasecmp(e, "trail=", 6)) + { + mod->traileffect = P_AllocateParticleType(config, e+6);//careful - this can realloc all the particle types + ptype = &part_type[pnum]; + } + else if (!q_strncasecmp(e, "orient", 6)) + mod->rflags |= RF_USEORIENTATION; //use the dir to orient the model, instead of always facing up. + else if (!q_strncasecmp(e, "additive", 8)) + mod->rflags |= RF_ADDITIVE; //additive blend + else if (!q_strncasecmp(e, "transparent", 11)) + mod->rflags |= RF_TRANSLUCENT; //force blend + else if (!q_strncasecmp(e, "fullbright", 10)) + mod->rflags |= Q2RF_FULLBRIGHT; //fullbright, woo + else if (!q_strncasecmp(e, "shadow", 6)) + mod->rflags &= ~RF_NOSHADOW; //clear noshadow + else if (!q_strncasecmp(e, "noshadow", 8)) + mod->rflags |= RF_NOSHADOW; //set noshadow (cos... you know...) + else + Con_Printf("Bad named argument: %s\n", e); + } + } + else + { + mod->framestart = atof(Cmd_Argv(2)); + mod->framecount = atof(Cmd_Argv(3)); + mod->framerate = atof(Cmd_Argv(4)); + mod->alpha = atof(Cmd_Argv(5)); + if (*Cmd_Argv(6)) + { + mod->traileffect = P_AllocateParticleType(config, Cmd_Argv(6));//careful - this can realloc all the particle types + ptype = &part_type[pnum]; + } + else + mod->traileffect = P_INVALID; + } +#else + Con_DPrintf("%s.%s: model particles are not supported in this build\n", ptype->config, ptype->name); +#endif + } + else if (!strcmp(var, "sound")) + { + const char *e; + ptype->sounds = Z_Realloc(ptype->sounds, sizeof(partsounds_t)*(ptype->numsounds+1)); + q_strlcpy(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 = 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), (char**)&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++; + } + else if (!strcmp(var, "colorindex")) + { + if (Cmd_Argc()>2) + ptype->colorrand = strtoul(Cmd_Argv(2), NULL, 0); + ptype->colorindex = strtoul(value, NULL, 0); + } + else if (!strcmp(var, "colorrand")) + ptype->colorrand = atoi(value); // now obsolete + else if (!strcmp(var, "citracer")) + ptype->flags |= PT_CITRACER; + + else if (!strcmp(var, "red")) + ptype->rgb[0] = atof(value)/255; + else if (!strcmp(var, "green")) + ptype->rgb[1] = atof(value)/255; + else if (!strcmp(var, "blue")) + ptype->rgb[2] = atof(value)/255; + else if (!strcmp(var, "rgb")) + { //byte version + ptype->rgb[0] = ptype->rgb[1] = ptype->rgb[2] = atof(value)/255; + if (Cmd_Argc()>3) + { + ptype->rgb[1] = atof(Cmd_Argv(2))/255; + ptype->rgb[2] = atof(Cmd_Argv(3))/255; + } + } + else if (!strcmp(var, "rgbf")) + { //float version + ptype->rgb[0] = ptype->rgb[1] = ptype->rgb[2] = atof(value); + if (Cmd_Argc()>3) + { + ptype->rgb[1] = atof(Cmd_Argv(2)); + ptype->rgb[2] = atof(Cmd_Argv(3)); + } + } + + else if (!strcmp(var, "reddelta")) + { + ptype->rgbchange[0] = atof(value)/255; + if (!ptype->rgbchangetime) + ptype->rgbchangetime = ptype->die; + } + else if (!strcmp(var, "greendelta")) + { + ptype->rgbchange[1] = atof(value)/255; + if (!ptype->rgbchangetime) + ptype->rgbchangetime = ptype->die; + } + else if (!strcmp(var, "bluedelta")) + { + ptype->rgbchange[2] = atof(value)/255; + if (!ptype->rgbchangetime) + ptype->rgbchangetime = ptype->die; + } + else if (!strcmp(var, "rgbdelta")) + { //byte version + ptype->rgbchange[0] = ptype->rgbchange[1] = ptype->rgbchange[2] = atof(value)/255; + if (Cmd_Argc()>3) + { + ptype->rgbchange[1] = atof(Cmd_Argv(2))/255; + ptype->rgbchange[2] = atof(Cmd_Argv(3))/255; + } + if (!ptype->rgbchangetime) + ptype->rgbchangetime = ptype->die; + } + else if (!strcmp(var, "rgbdeltaf")) + { //float version + ptype->rgbchange[0] = ptype->rgbchange[1] = ptype->rgbchange[2] = atof(value); + if (Cmd_Argc()>3) + { + ptype->rgbchange[1] = atof(Cmd_Argv(2)); + ptype->rgbchange[2] = atof(Cmd_Argv(3)); + } + if (!ptype->rgbchangetime) + ptype->rgbchangetime = ptype->die; + } + else if (!strcmp(var, "rgbdeltatime")) + ptype->rgbchangetime = atof(value); + + else if (!strcmp(var, "redrand")) + ptype->rgbrand[0] = atof(value)/255; + else if (!strcmp(var, "greenrand")) + ptype->rgbrand[1] = atof(value)/255; + else if (!strcmp(var, "bluerand")) + ptype->rgbrand[2] = atof(value)/255; + else if (!strcmp(var, "rgbrand")) + { //byte version + ptype->rgbrand[0] = ptype->rgbrand[1] = ptype->rgbrand[2] = atof(value)/255; + if (Cmd_Argc()>3) + { + ptype->rgbrand[1] = atof(Cmd_Argv(2))/255; + ptype->rgbrand[2] = atof(Cmd_Argv(3))/255; + } + } + else if (!strcmp(var, "rgbrandf")) + { //float version + ptype->rgbrand[0] = ptype->rgbrand[1] = ptype->rgbrand[2] = atof(value); + if (Cmd_Argc()>3) + { + ptype->rgbrand[1] = atof(Cmd_Argv(2)); + ptype->rgbrand[2] = atof(Cmd_Argv(3)); + } + } + + else if (!strcmp(var, "rgbrandsync")) + { + ptype->rgbrandsync[0] = ptype->rgbrandsync[1] = ptype->rgbrandsync[2] = atof(value); + if (Cmd_Argc()>3) + { + ptype->rgbrandsync[1] = atof(Cmd_Argv(2)); + ptype->rgbrandsync[2] = atof(Cmd_Argv(3)); + } + } + else if (!strcmp(var, "redrandsync")) + ptype->rgbrandsync[0] = atof(value); + else if (!strcmp(var, "greenrandsync")) + ptype->rgbrandsync[1] = atof(value); + else if (!strcmp(var, "bluerandsync")) + ptype->rgbrandsync[2] = atof(value); + + else if (!strcmp(var, "stains")) + ptype->stainonimpact = atof(value); + else if (!strcmp(var, "blend")) + { + //small note: use premultiplied alpha where possible. this reduces the required state switches. + ptype->looks.premul = false; + if (!strcmp(value, "adda") || !strcmp(value, "add")) + ptype->looks.blendmode = BM_ADDA; + else if (!strcmp(value, "addc")) + ptype->looks.blendmode = BM_ADDC; + else if (!strcmp(value, "subtract")) + ptype->looks.blendmode = BM_SUBTRACT; + else if (!strcmp(value, "invmoda") || !strcmp(value, "invmod")) + ptype->looks.blendmode = BM_INVMODA; + else if (!strcmp(value, "invmodc")) + ptype->looks.blendmode = BM_INVMODC; + else if (!strcmp(value, "blendcolour") || !strcmp(value, "blendcolor")) + ptype->looks.blendmode = BM_BLENDCOLOUR; + else if (!strcmp(value, "blendalpha") || !strcmp(value, "blend")) + ptype->looks.blendmode = BM_BLEND; + else if (!strcmp(value, "premul_subtract")) + { + ptype->looks.premul = 1; + ptype->looks.blendmode = BM_INVMODC; + } + else if (!strcmp(value, "premul_add")) + { + ptype->looks.premul = 2; + ptype->looks.blendmode = BM_PREMUL; + } + else if (!strcmp(value, "premul_blend")) + { + ptype->looks.premul = 1; + ptype->looks.blendmode = BM_PREMUL; + } + else + { + Con_DPrintf("%s.%s: uses unknown blend type '%s', assuming legacy 'blendalpha'\n", ptype->config, ptype->name, value); + ptype->looks.blendmode = BM_BLEND; //fallback + } + } + else if (!strcmp(var, "spawnmode")) + { + if (!strcmp(value, "circle")) + ptype->spawnmode = SM_CIRCLE; + else if (!strcmp(value, "ball")) + ptype->spawnmode = SM_BALL; + else if (!strcmp(value, "spiral")) + ptype->spawnmode = SM_SPIRAL; + else if (!strcmp(value, "tracer")) + ptype->spawnmode = SM_TRACER; + else if (!strcmp(value, "telebox")) + ptype->spawnmode = SM_TELEBOX; + else if (!strcmp(value, "lavasplash")) + ptype->spawnmode = SM_LAVASPLASH; + else if (!strcmp(value, "uniformcircle")) + ptype->spawnmode = SM_UNICIRCLE; + else if (!strcmp(value, "syncfield")) + { + ptype->spawnmode = SM_FIELD; +#ifndef NOLEGACY + ptype->spawnparam1 = 16; + ptype->spawnparam2 = 0; +#endif + } + else if (!strcmp(value, "distball")) + ptype->spawnmode = SM_DISTBALL; + else if (!strcmp(value, "box")) + ptype->spawnmode = SM_BOX; + else + { + Con_DPrintf("%s.%s: uses unknown spawn type '%s', assuming 'box'\n", ptype->config, ptype->name, value); + ptype->spawnmode = SM_BOX; + } + + if (Cmd_Argc()>2) + { + if (Cmd_Argc()>3) + ptype->spawnparam2 = atof(Cmd_Argv(3)); + ptype->spawnparam1 = atof(Cmd_Argv(2)); + } + } + else if (!strcmp(var, "type")) + { + if (!strcmp(value, "beam")) + ptype->looks.type = PT_BEAM; + else if (!strcmp(value, "spark") || !strcmp(value, "linespark")) + ptype->looks.type = PT_SPARK; + else if (!strcmp(value, "sparkfan") || !strcmp(value, "trianglefan")) + ptype->looks.type = PT_SPARKFAN; + else if (!strcmp(value, "texturedspark")) + ptype->looks.type = PT_TEXTUREDSPARK; + else if (!strcmp(value, "decal") || !strcmp(value, "cdecal")) + ptype->looks.type = PT_CDECAL; + else if (!strcmp(value, "udecal")) + ptype->looks.type = PT_UDECAL; + else if (!strcmp(value, "normal")) + ptype->looks.type = PT_NORMAL; + else + { + Con_DPrintf("%s.%s: uses unknown render type '%s', assuming 'normal'\n", ptype->config, ptype->name, value); + ptype->looks.type = PT_NORMAL; //fallback + } + settype = true; + } + else if (!strcmp(var, "clippeddecal")) //mask, match + { + if (Cmd_Argc()>=2) + {//decal only appears where: (surfflags&mask)==match + ptype->surfflagmatch = ptype->surfflagmask = strtoul(Cmd_Argv(1), NULL, 0); + if (Cmd_Argc()>=3) + ptype->surfflagmatch = strtoul(Cmd_Argv(2), NULL, 0); + } + ptype->looks.type = PT_CDECAL; + settype = true; + } +#ifndef NOLEGACY + else if (!strcmp(var, "isbeam")) + { + Con_DPrintf("%s.%s: isbeam is deprecated, use type beam\n", ptype->config, ptype->name); + ptype->looks.type = PT_BEAM; + settype = true; + } +#endif + else if (!strcmp(var, "spawntime")) + ptype->spawntime = atof(value); + else if (!strcmp(var, "spawnchance")) + ptype->spawnchance = atof(value); + else if (!strcmp(var, "cliptype")) + { + assoc = P_AllocateParticleType(config, value);//careful - this can realloc all the particle types + ptype = &part_type[pnum]; + ptype->cliptype = assoc; + } + else if (!strcmp(var, "clipcount")) + ptype->clipcount = atof(value); + else if (!strcmp(var, "clipbounce")) + { + ptype->clipbounce = atof(value); + if (ptype->clipbounce < 0 && ptype->cliptype == P_INVALID) + ptype->cliptype = pnum; + } + else if (!strcmp(var, "bounce")) + { + ptype->cliptype = pnum; + ptype->clipbounce = atof(value); + } + + else if (!strcmp(var, "emit")) + { + assoc = P_AllocateParticleType(config, value);//careful - this can realloc all the particle types + ptype = &part_type[pnum]; + ptype->emit = assoc; + } + else if (!strcmp(var, "emitinterval")) + ptype->emittime = atof(value); + else if (!strcmp(var, "emitintervalrand")) + ptype->emitrand = atof(value); + else if (!strcmp(var, "emitstart")) + ptype->emitstart = atof(value); + +#ifndef NOLEGACY + // old names + else if (!strcmp(var, "areaspread")) + { + Con_DPrintf("%s.%s: areaspread is deprecated, use spawnorg\n", ptype->config, ptype->name); + ptype->areaspread = atof(value); + } + else if (!strcmp(var, "areaspreadvert")) + { + Con_DPrintf("%s.%s: areaspreadvert is deprecated, use spawnorg\n", ptype->config, ptype->name); + ptype->areaspreadvert = atof(value); + } + else if (!strcmp(var, "offsetspread")) + { + Con_DPrintf("%s.%s: offsetspread is deprecated, use spawnvel\n", ptype->config, ptype->name); + ptype->spawnvel = atof(value); + } + else if (!strcmp(var, "offsetspreadvert")) + { + Con_DPrintf("%s.%s: offsetspreadvert is deprecated, use spawnvel\n", ptype->config, ptype->name); + ptype->spawnvelvert = atof(value); + } +#endif + + // current names + else if (!strcmp(var, "spawnorg")) + { + ptype->areaspreadvert = ptype->areaspread = atof(value); + + if (Cmd_Argc()>2) + ptype->areaspreadvert = atof(Cmd_Argv(2)); + } + else if (!strcmp(var, "spawnvel")) + { + ptype->spawnvelvert = ptype->spawnvel = atof(value); + + if (Cmd_Argc()>2) + ptype->spawnvelvert = atof(Cmd_Argv(2)); + } + +#ifndef NOLEGACY + // spawn mode param fields + else if (!strcmp(var, "spawnparam1")) + { + ptype->spawnparam1 = atof(value); + Con_DPrintf("%s.%s: 'spawnparam1' is deprecated, use 'spawnmode foo X'\n", ptype->config, ptype->name); + } + else if (!strcmp(var, "spawnparam2")) + { + ptype->spawnparam2 = atof(value); + Con_DPrintf("%s.%s: 'spawnparam2' is deprecated, use 'spawnmode foo X Y'\n", ptype->config, ptype->name); + } +/* else if (!strcmp(var, "spawnparam3")) + ptype->spawnparam3 = atof(value); */ + else if (!strcmp(var, "up")) + { + ptype->orgbias[2] = atof(value); + Con_DPrintf("%s.%s: up is deprecated, use orgbias 0 0 Z\n", ptype->config, ptype->name); + } +#endif + + else if (!strcmp(var, "rampmode")) + { + if (!strcmp(value, "none")) + ptype->rampmode = RAMP_NONE; +#ifndef NOLEGACY + else if (!strcmp(value, "absolute")) + { + Con_DPrintf("%s.%s: 'rampmode absolute' is deprecated, use 'rampmode nearest'\n", ptype->config, ptype->name); + ptype->rampmode = RAMP_NEAREST; + } +#endif + else if (!strcmp(value, "nearest")) + ptype->rampmode = RAMP_NEAREST; + else if (!strcmp(value, "lerp")) //don't use the name 'linear'. ramps are there to avoid linear... + ptype->rampmode = RAMP_LERP; + else if (!strcmp(value, "delta")) + ptype->rampmode = RAMP_DELTA; + else + { + Con_DPrintf("%s.%s: uses unknown ramp mode '%s', assuming 'delta'\n", ptype->config, ptype->name, value); + ptype->rampmode = RAMP_DELTA; + } + } + else if (!strcmp(var, "rampindexlist")) + { // better not use this with delta ramps... + int cidx, i; + + i = 1; + while (i < Cmd_Argc()) + { + ptype->ramp = Z_Realloc(ptype->ramp, sizeof(ramp_t)*(ptype->rampindexes+1)); + + cidx = atoi(Cmd_Argv(i)); + ptype->ramp[ptype->rampindexes].alpha = cidx > 255 ? 0.5 : 1; + + cidx = (cidx & 0xff) * 4; + ptype->ramp[ptype->rampindexes].rgb[0] = palrgba[cidx] * (1/255.0); + ptype->ramp[ptype->rampindexes].rgb[1] = palrgba[cidx+1] * (1/255.0); + ptype->ramp[ptype->rampindexes].rgb[2] = palrgba[cidx+2] * (1/255.0); + + ptype->ramp[ptype->rampindexes].scale = ptype->scale; + + ptype->rampindexes++; + i++; + } + } + else if (!strcmp(var, "rampindex")) + { + int cidx; + ptype->ramp = Z_Realloc(ptype->ramp, sizeof(ramp_t)*(ptype->rampindexes+1)); + + cidx = atoi(value); + ptype->ramp[ptype->rampindexes].alpha = cidx > 255 ? 0.5 : 1; + + if (Cmd_Argc() > 2) // they gave alpha + ptype->ramp[ptype->rampindexes].alpha *= atof(Cmd_Argv(2)); + + cidx = (cidx & 0xff) * 4; + ptype->ramp[ptype->rampindexes].rgb[0] = palrgba[cidx] * (1/255.0); + ptype->ramp[ptype->rampindexes].rgb[1] = palrgba[cidx+1] * (1/255.0); + ptype->ramp[ptype->rampindexes].rgb[2] = palrgba[cidx+2] * (1/255.0); + + if (Cmd_Argc() > 3) // they gave scale + ptype->ramp[ptype->rampindexes].scale = atof(Cmd_Argv(3)); + else + ptype->ramp[ptype->rampindexes].scale = ptype->scale; + + + ptype->rampindexes++; + } + else if (!strcmp(var, "ramp")) + { + ptype->ramp = Z_Realloc(ptype->ramp, sizeof(ramp_t)*(ptype->rampindexes+1)); + + ptype->ramp[ptype->rampindexes].rgb[0] = atof(value)/255; + if (Cmd_Argc()>3) //seperate rgb + { + ptype->ramp[ptype->rampindexes].rgb[1] = atof(Cmd_Argv(2))/255; + ptype->ramp[ptype->rampindexes].rgb[2] = atof(Cmd_Argv(3))/255; + + if (Cmd_Argc()>4) //have we alpha and scale changes? + { + ptype->ramp[ptype->rampindexes].alpha = atof(Cmd_Argv(4)); + if (Cmd_Argc()>5) //have we scale changes? + ptype->ramp[ptype->rampindexes].scale = atof(Cmd_Argv(5)); + else + ptype->ramp[ptype->rampindexes].scale = ptype->scaledelta; + } + else + { + ptype->ramp[ptype->rampindexes].alpha = ptype->alpha; + ptype->ramp[ptype->rampindexes].scale = ptype->scaledelta; + } + } + else //they only gave one value + { + ptype->ramp[ptype->rampindexes].rgb[1] = ptype->ramp[ptype->rampindexes].rgb[0]; + ptype->ramp[ptype->rampindexes].rgb[2] = ptype->ramp[ptype->rampindexes].rgb[0]; + + ptype->ramp[ptype->rampindexes].alpha = ptype->alpha; + ptype->ramp[ptype->rampindexes].scale = ptype->scaledelta; + } + + ptype->rampindexes++; + } + else if (!strcmp(var, "viewspace")) + { +#if UNSUPPORTED + ptype->viewspacefrac = (Cmd_Argc()>1)?atof(value):1; +#else + Con_DPrintf("%s.%s: viewspace particles are not supported in this build\n", ptype->config, ptype->name); +#endif + } + else if (!strcmp(var, "perframe")) + ptype->flags |= PT_INVFRAMETIME; + else if (!strcmp(var, "averageout")) + ptype->flags |= PT_AVERAGETRAIL; + else if (!strcmp(var, "nostate")) + ptype->flags |= PT_NOSTATE; + else if (!strcmp(var, "nospreadfirst")) + ptype->flags |= PT_NOSPREADFIRST; + else if (!strcmp(var, "nospreadlast")) + ptype->flags |= PT_NOSPREADLAST; + + else if (!strcmp(var, "lightradius")) + { //float version + ptype->dl_radius[0] = ptype->dl_radius[1] = atof(value); + if (Cmd_Argc()>2) + ptype->dl_radius[1] = atof(Cmd_Argv(2)); + ptype->dl_radius[1] -= ptype->dl_radius[0]; + } + else if (!strcmp(var, "lightradiusfade")) + ptype->dl_decay[3] = atof(value); + else if (!strcmp(var, "lightrgb")) + { + ptype->dl_rgb[0] = atof(value); + ptype->dl_rgb[1] = atof(Cmd_Argv(2)); + ptype->dl_rgb[2] = atof(Cmd_Argv(3)); + } + else if (!strcmp(var, "lightrgbfade")) + { + ptype->dl_decay[0] = atof(value); + ptype->dl_decay[1] = atof(Cmd_Argv(2)); + ptype->dl_decay[2] = atof(Cmd_Argv(3)); + } + else if (!strcmp(var, "lightcorona")) + { + ptype->dl_corona_intensity = atof(value); + ptype->dl_corona_scale = atof(Cmd_Argv(2)); + } + else if (!strcmp(var, "lighttime")) + ptype->dl_time = atof(value); + else if (!strcmp(var, "lightshadows")) + ptype->flags = (ptype->flags & ~PT_NODLSHADOW) | (atof(value)?0:PT_NODLSHADOW); + else if (!strcmp(var, "lightcubemap")) + ptype->dl_cubemapnum = atoi(value); + else if (!strcmp(var, "lightscales")) + { //ambient diffuse specular + ptype->dl_scales[0] = atof(value); + ptype->dl_scales[1] = atof(Cmd_Argv(2)); + ptype->dl_scales[2] = atof(Cmd_Argv(3)); + } + else if (!strcmp(var, "spawnstain")) + { +#if UNSUPPORTED + ptype->stain_radius = atof(value); + ptype->stain_rgb[0] = atof(Cmd_Argv(2)); + ptype->stain_rgb[1] = atof(Cmd_Argv(3)); + ptype->stain_rgb[2] = atof(Cmd_Argv(4)); +#else + Con_DPrintf("%s.%s: spawnstain is not supported in this build\n", ptype->config, ptype->name); +#endif + } + else if (Cmd_Argc()) + Con_DPrintf("%s.%s: %s is not a recognised particle type field\n", ptype->config, ptype->name, var); + } + ptype->loaded = part_parseweak?1:2; + if (ptype->clipcount < 1) + ptype->clipcount = 1; + + if (!settype) + { + if (ptype->looks.type == PT_NORMAL && !*ptype->texname) + { + if (ptype->scale) + { + ptype->looks.type = PT_SPARKFAN; + Con_DPrintf("%s.%s: effect lacks a texture. assuming type sparkfan.\n", ptype->config, ptype->name); + } + else + { + ptype->looks.type = PT_SPARK; + Con_DPrintf("%s.%s: effect lacks a texture. assuming type spark.\n", ptype->config, ptype->name); + } + } + else if (ptype->looks.type == PT_SPARK) + { + if (*ptype->texname) + ptype->looks.type = PT_TEXTUREDSPARK; + else if (ptype->scale) + ptype->looks.type = PT_SPARKFAN; + } + } + + // use old behavior if not using alphadelta + if (!setalphadelta) + ptype->alphachange = (-ptype->alphachange / ptype->die) * ptype->alpha; + + FinishParticleType(ptype); + + if (ptype->looks.type == PT_BEAM && !setbeamlen) + ptype->rotationstartmin = 1/128.0; + + goto nexteffect; +} + +#if 1//_DEBUG +// R_BeamInfo_f - debug junk +static void P_BeamInfo_f (void) +{ + beamseg_t *bs; + int i, j, k, l, m; + + i = 0; + + for (bs = free_beams; bs; bs = bs->next) + i++; + + Con_Printf("%i free beams\n", i); + + for (i = 0; i < numparticletypes; i++) + { + m = l = k = j = 0; + for (bs = part_type[i].beams; bs; bs = bs->next) + { + if (!bs->p) + k++; + + if (bs->flags & BS_DEAD) + l++; + + if (bs->flags & BS_LASTSEG) + m++; + + j++; + } + + if (j) + Con_Printf("Type %i = %i NULL p, %i DEAD, %i LASTSEG, %i total\n", i, k, l, m, j); + } +} + +static void P_PartInfo_f (void) +{ + particle_t *p; + clippeddecal_t *d; + part_type_t *ptype; + int totalp = 0, totald = 0, freep, freed, runningp=0, runningd=0, runninge=0, runningt=0; + + int i, j, k; + + Con_DPrintf("Full list of effects:\n"); + for (i = 0; i < numparticletypes; i++) + { + j = 0; + for (p = part_type[i].particles; p; p = p->next) + j++; + totalp += j; + + k = 0; + for (d = part_type[i].clippeddecals; d; d = d->next) + k++; + totald += k; + + if (j||k) + { + Con_DPrintf("Type %s.%s = %i+%i total\n", part_type[i].config, part_type[i].name, j,k); + if (!(part_type[i].state & PS_INRUNLIST)) + Con_Printf(CON_WARNING "%s.%s NOT RUNNING\n", part_type[i].config, part_type[i].name); + } + } + + Con_Printf("Running effects:\n"); + // maintain run list + for (ptype = part_run_list; ptype; ptype = ptype->nexttorun) + { + Con_Printf("Type %s.%s", ptype->config, ptype->name); + + j = 0; + for (p = ptype->particles; p; p = p->next) + j++; + if (j) + { + Con_Printf("\t%i particles", j); + if (ptype->cliptype >= 0 || ptype->stainonimpact) + { + Con_Printf("(+traceline)"); + runningt += j; + } + } + runningp += j; + + k = 0; + for (d = ptype->clippeddecals; d; d = d->next) + k++; + if (k) + Con_Printf("%s%i decals", ptype->particles?", ":"\t", k); + runningd += k; + + Con_Printf("\n"); + runninge++; + } + Con_Printf("End of list\n"); + + for (p = free_particles, freep = 0; p; p = p->next) + freep++; + for (d = free_decals, freed = 0; d; d = d->next) + freed++; + + Con_DPrintf("%i running effects.\n", runninge); + Con_Printf("%i particles, %i free, %i traces.\n", runningp, freep, runningt); + Con_Printf("%i decals, %i free.\n", runningd, freed); + + if (totalp != runningp) + Con_Printf("%i particles unaccounted for\n", totalp - runningp); + if (totald != runningd) + Con_Printf("%i decals unaccounted for\n", totald - runningd); +} +#endif + +static void FinishParticleType(part_type_t *ptype) +{ + //if there is a chance that it moves + if (ptype->gravity || ptype->veladd || ptype->spawnvel || ptype->spawnvelvert || DotProduct(ptype->velwrand,ptype->velwrand) || DotProduct(ptype->velbias,ptype->velbias) || ptype->flurry) + ptype->flags |= PT_VELOCITY; + if (DotProduct(ptype->velbias,ptype->velbias) || DotProduct(ptype->velwrand,ptype->velwrand) || DotProduct(ptype->orgwrand,ptype->orgwrand)) + ptype->flags |= PT_WORLDSPACERAND; + //if it has friction + if (ptype->friction[0] || ptype->friction[1] || ptype->friction[2]) + ptype->flags |= PT_FRICTION; + + P_LoadTexture(ptype, true); + if (ptype->dl_decay[3] && !ptype->dl_time) + ptype->dl_time = ptype->dl_radius[0] / ptype->dl_decay[3]; + if (ptype->looks.scalefactor > 1 && !ptype->looks.invscalefactor) + { + ptype->scale *= ptype->looks.scalefactor; + ptype->scalerand *= ptype->looks.scalefactor; + /*too lazy to go through ramps*/ + ptype->looks.scalefactor = 1; + } + ptype->looks.invscalefactor = 1-ptype->looks.scalefactor; + + if (ptype->looks.type == PT_TEXTUREDSPARK && !ptype->looks.stretch) + ptype->looks.stretch = 0.05; //the old default. + + if (ptype->looks.type == PT_SPARK && r_part_sparks.value<0) + ptype->looks.type = PT_INVISIBLE; + if (ptype->looks.type == PT_TEXTUREDSPARK && !r_part_sparks_textured.value) + ptype->looks.type = PT_SPARK; + if (ptype->looks.type == PT_SPARKFAN && !r_part_sparks_trifan.value) + ptype->looks.type = PT_SPARK; + if (ptype->looks.type == PT_SPARK && !r_part_sparks.value) + ptype->looks.type = PT_INVISIBLE; + if (ptype->looks.type == PT_BEAM && r_part_beams.value <= 0) + ptype->looks.type = PT_INVISIBLE; + + if (ptype->rampmode && !ptype->ramp) + { + ptype->rampmode = RAMP_NONE; + Con_Printf("%s.%s: Particle has a ramp mode but no ramp\n", ptype->config, ptype->name); + } + else if (ptype->ramp && !ptype->rampmode) + { + Con_Printf("%s.%s: Particle has a ramp but no ramp mode\n", ptype->config, ptype->name); + } + r_plooksdirty = true; +} + +#ifdef PSET_SCRIPT_EFFECTINFO +static void FinishEffectinfoParticleType(part_type_t *ptype, qboolean blooddecalonimpact) +{ + if (ptype->looks.type == PT_CDECAL) + { + if (ptype->die == 9999) + ptype->die = 20; + ptype->alphachange = -(ptype->alpha / ptype->die); + } + else if (ptype->looks.type == PT_UDECAL) + { + //dp's decals have a size as a radius. fte's udecals are 'just' quads. + //also, dp uses 'stretch'. + ptype->looks.stretch *= 1/1.414213562373095; + ptype->scale *= ptype->looks.stretch; + ptype->scalerand *= ptype->looks.stretch; + ptype->scaledelta *= ptype->looks.stretch; + ptype->looks.stretch = 1; + } + else if (ptype->looks.type == PT_NORMAL) + { + //fte's textured particles are *0.25 for some reason. + //but fte also uses radiuses, while dp uses total size so we only need to double it here.. + ptype->scale *= 2*ptype->looks.stretch; + ptype->scalerand *= 2*ptype->looks.stretch; + ptype->scaledelta *= 2*2*ptype->looks.stretch; + ptype->looks.stretch = 1; + } + if (blooddecalonimpact) //DP blood particles generate decals unconditionally (and prevent blood from bouncing) + ptype->clipbounce = -2; + if (ptype->looks.type == PT_TEXTUREDSPARK) + { + ptype->looks.stretch *= 0.04; + if (ptype->looks.stretch < 0) + ptype->looks.stretch = 0.000001; + } + + if (ptype->die == 9999) //internal: means unspecified. + { + if (ptype->alphachange) + ptype->die = (ptype->alpha+ptype->alpharand)/-ptype->alphachange; + else + ptype->die = 15; + } + ptype->looks.minstretch = 0.5; + FinishParticleType(ptype); +} +static void P_ImportEffectInfo(const char *config, char *line, qboolean part_parseweak) +{ + part_type_t *ptype = NULL; + int parenttype; + char arg[8][1024]; + unsigned int args = 0; + qboolean blooddecalonimpact = false; //tracked separately because it needs to override another field + + float teximages[256][4]; + + { + int i; + char *file; + const char *line; + char linebuf[1024]; + //default assumes 8*8 grid, but we allow more + for (i = 0; i < 256; i++) + { + teximages[i][0] = 1/8.0 * (i & 7); + teximages[i][1] = 1/8.0 * (1+(i & 7)); + teximages[i][2] = 1/8.0 * (1+(i>>3)); + teximages[i][3] = 1/8.0 * (i>>3); + } + + file = (char*)COM_LoadMallocFile("particles/particlefont.txt", NULL); + if (file) + { + size_t offset = 0; + while (PScript_ReadLine(linebuf, sizeof(linebuf), file, com_filesize, &offset)) + { + float s1,s2,t1,t2; + line = COM_Parse(linebuf); + i = atoi(com_token); + line = COM_Parse(line); + s1 = atof(com_token); + line = COM_Parse(line); + t1 = atof(com_token); + line = COM_Parse(line); + s2 = atof(com_token); + line = COM_Parse(line); + t2 = atof(com_token); + if (line) + { + teximages[i][0] = s1; + teximages[i][1] = s2; + teximages[i][2] = t2; + teximages[i][3] = t1; + } + } + free(file); + } + } + + for (;line && *line;) + { + char *eol; + + //multi-line comments need special handling. + while (*line == ' ' || *line == '\t') + line++; + if (line[0] == '/' && line[1] == '*') + { + line += 2; + while (*line) + { + if (line[0] == '*' && line[1] == '/') + { + line+=2; + break; + } + line++; + } + continue; + } + + eol = strchr(line, '\n'); + args = 0; + if (eol) + *eol++=0; + for (args = 0; line; ) + { + line = (char*)COM_Parse(line); + if (line && args < sizeof(arg)/sizeof(arg[args])) + { + q_strlcpy(arg[args], com_token, sizeof(arg[args])); + args++; + } + } + line = eol; + + if (args <= 0) + continue; + + if (!strcmp(arg[0], "effect")) + { + char newname[64]; + int i; + + if (ptype) + FinishEffectinfoParticleType(ptype, blooddecalonimpact); + blooddecalonimpact = false; + + ptype = P_GetParticleType(config, arg[1]); + if (ptype->loaded) + { + for (i = 0; i < 64; i++) + { + parenttype = ptype - part_type; + q_snprintf(newname, sizeof(newname), "%i+%s", i, arg[1]); + ptype = P_GetParticleType(config, newname); + if (!ptype->loaded) + { + part_type[parenttype].assoc = ptype - part_type; + break; + } + } + if (i == 64) + { + Con_Printf("Too many duplicate names, gave up\n"); + break; + } + } + P_ResetToDefaults(ptype); + ptype->loaded = part_parseweak?1:2; + ptype->scale = 1; + ptype->alpha = 0; + ptype->alpharand = 1; + ptype->alphachange = -1; + ptype->die = 9999; + strcpy(ptype->texname, "particles/particlefont"); + ptype->rgb[0] = 1; + ptype->rgb[1] = 1; + ptype->rgb[2] = 1; + +// ptype->spawnmode = SM_BALL; + + ptype->colorindex = -1; + ptype->spawnchance = 1; + ptype->looks.scalefactor = 2; + ptype->looks.invscalefactor = 0; + ptype->looks.type = PT_NORMAL; + ptype->looks.blendmode = BM_PREMUL; + ptype->looks.premul = 1; + 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) + { + Con_Printf("Bad effectinfo file\n"); + break; + } + else if (!strcmp(arg[0], "countabsolute") && args == 2) + ptype->countextra = atof(arg[1]); + else if (!strcmp(arg[0], "count") && args == 2) + ptype->count = atof(arg[1]); + else if (!strcmp(arg[0], "type") && args == 2) + { + if (!strcmp(arg[1], "decal") || !strcmp(arg[1], "cdecal")) + { + ptype->looks.type = PT_CDECAL; + ptype->looks.blendmode = BM_INVMODC; + ptype->looks.premul = 2; + } + else if (!strcmp(arg[1], "udecal")) + { + ptype->looks.type = PT_UDECAL; + ptype->looks.blendmode = BM_INVMODC; + ptype->looks.premul = 2; + } + else if (!strcmp(arg[1], "alphastatic")) + { + ptype->looks.type = PT_NORMAL; + ptype->looks.blendmode = BM_PREMUL;//BM_BLEND; + ptype->looks.premul = 1; + } + else if (!strcmp(arg[1], "static")) + { + ptype->looks.type = PT_NORMAL; + ptype->looks.blendmode = BM_PREMUL;//BM_ADDA; + ptype->looks.premul = 2; + } + else if (!strcmp(arg[1], "smoke")) + { + ptype->looks.type = PT_NORMAL; + ptype->looks.blendmode = BM_PREMUL;//BM_ADDA; + ptype->looks.premul = 2; + } + else if (!strcmp(arg[1], "spark")) + { + ptype->looks.type = PT_TEXTUREDSPARK; + ptype->looks.blendmode = BM_PREMUL;//BM_ADDA; + ptype->looks.premul = 2; + } + else if (!strcmp(arg[1], "bubble")) + { + ptype->looks.type = PT_NORMAL; + ptype->looks.blendmode = BM_PREMUL;//BM_ADDA; + ptype->looks.premul = 2; + } + else if (!strcmp(arg[1], "blood")) + { + ptype->looks.type = PT_NORMAL; + ptype->looks.blendmode = BM_INVMODC; + ptype->looks.premul = 2; + ptype->gravity = 800*1; + blooddecalonimpact = true; + } + else if (!strcmp(arg[1], "beam")) + { + ptype->looks.type = PT_BEAM; + ptype->looks.blendmode = BM_PREMUL;//BM_ADDA; + ptype->looks.premul = 2; + } + else if (!strcmp(arg[1], "snow")) + { + ptype->looks.type = PT_NORMAL; + ptype->looks.blendmode = BM_PREMUL;//BM_ADDA; + ptype->looks.premul = 2; + ptype->flurry = 32; //may not still be valid later, but at least it would be an obvious issue with the original. + } + else + { + Con_Printf("effectinfo type %s not supported\n", arg[1]); + } + } + else if (!strcmp(arg[0], "tex") && args == 3) + { + int mini = atoi(arg[1]); + int maxi = atoi(arg[2]); + ptype->s1 = teximages[mini][0]; + ptype->s2 = teximages[mini][1]; + ptype->t1 = teximages[mini][2]; + ptype->t2 = teximages[mini][3]; + ptype->texsstride = teximages[(mini+1)&(sizeof(teximages)/sizeof(teximages[0])-1)][0] - teximages[mini][0]; + ptype->randsmax = (maxi - mini); + if (ptype->randsmax < 1) + ptype->randsmax = 1; + } + else if (!strcmp(arg[0], "size") && args == 3) + { + float s1 = atof(arg[1]), s2 = atof(arg[2]); + ptype->scale = s1; + ptype->scalerand = (s2-s1); + } + else if (!strcmp(arg[0], "sizeincrease") && args == 2) + ptype->scaledelta = atof(arg[1]); + else if (!strcmp(arg[0], "color") && args == 3) + { + unsigned int rgb1 = strtoul(arg[1], NULL, 0), rgb2 = strtoul(arg[2], NULL, 0); + int i; + for (i = 0; i < 3; i++) + { + ptype->rgb[i] = ((rgb1>>(16-i*8)) & 0xff)/255.0; + ptype->rgbrand[i] = (int)(((rgb2>>(16-i*8)) & 0xff) - ((rgb1>>(16-i*8)) & 0xff))/255.0; + ptype->rgbrandsync[i] = 1; + } + } + else if (!strcmp(arg[0], "alpha") && args == 4) + { + float a1 = atof(arg[1]), a2 = atof(arg[2]), f = atof(arg[3]); + if (a1 > a2) + { //backwards + ptype->alpha = a2/256; + ptype->alpharand = (a1-a2)/256; + } + else + { + ptype->alpha = a1/256; + ptype->alpharand = (a2-a1)/256; + } + ptype->alphachange = -f/256; + } + else if (!strcmp(arg[0], "velocityoffset") && args == 4) + { /*a 3d world-coord addition*/ + ptype->velbias[0] = atof(arg[1]); + ptype->velbias[1] = atof(arg[2]); + ptype->velbias[2] = atof(arg[3]); + } + else if (!strcmp(arg[0], "velocityjitter") && args == 4) + { + ptype->velwrand[0] = atof(arg[1]); + ptype->velwrand[1] = atof(arg[2]); + ptype->velwrand[2] = atof(arg[3]); + } + else if (!strcmp(arg[0], "originoffset") && args == 4) + { /*a 3d world-coord addition*/ + ptype->orgbias[0] = atof(arg[1]); + ptype->orgbias[1] = atof(arg[2]); + ptype->orgbias[2] = atof(arg[3]); + } + else if (!strcmp(arg[0], "originjitter") && args == 4) + { + ptype->orgwrand[0] = atof(arg[1]); + ptype->orgwrand[1] = atof(arg[2]); + ptype->orgwrand[2] = atof(arg[3]); + } + else if (!strcmp(arg[0], "gravity") && args == 2) + { + ptype->gravity = 800*atof(arg[1]); + } + else if (!strcmp(arg[0], "bounce") && args == 2) + { + ptype->clipbounce = atof(arg[1]); + 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]); + else if (!strcmp(arg[0], "liquidfriction") && args == 2) + ; + else if (!strcmp(arg[0], "underwater") && args == 1) + ptype->flags |= PT_TRUNDERWATER; + else if (!strcmp(arg[0], "notunderwater") && args == 1) + ptype->flags |= PT_TROVERWATER; + else if (!strcmp(arg[0], "velocitymultiplier") && args == 2) + ptype->veladd = atof(arg[1]); + else if (!strcmp(arg[0], "trailspacing") && args == 2) + ptype->count = 1 / atof(arg[1]); + else if (!strcmp(arg[0], "time") && args == 3) + { + ptype->die = atof(arg[1]); + ptype->randdie = atof(arg[2]) - ptype->die; + if (ptype->randdie < 0) + { + ptype->die = atof(arg[2]); + ptype->randdie = atof(arg[1]) - ptype->die; + } + } + else if (!strcmp(arg[0], "stretchfactor") && args == 2) + ptype->looks.stretch = atof(arg[1]); + else if (!strcmp(arg[0], "blend") && args == 2) + { + if (!strcmp(arg[1], "invmod")) + { + ptype->looks.blendmode = BM_INVMODC; + ptype->looks.premul = 2; + } + else if (!strcmp(arg[1], "alpha")) + { + ptype->looks.blendmode = BM_PREMUL; + ptype->looks.premul = 1; + } + else if (!strcmp(arg[1], "add")) + { + ptype->looks.blendmode = BM_PREMUL; + ptype->looks.premul = 2; + } + else + Con_Printf("effectinfo 'blend %s' not supported\n", arg[1]); + } + else if (!strcmp(arg[0], "orientation") && args == 2) + { + if (!strcmp(arg[1], "billboard")) + ptype->looks.type = PT_NORMAL; + else if (!strcmp(arg[1], "spark")) + ptype->looks.type = PT_TEXTUREDSPARK; + else if (!strcmp(arg[1], "oriented")) //FIXME: not sure this points the right way. also, its double-sided in dp. + { + if (ptype->looks.type != PT_CDECAL) + ptype->looks.type = PT_UDECAL; + } + else if (!strcmp(arg[1], "beam")) + ptype->looks.type = PT_BEAM; + else + Con_Printf("effectinfo 'orientation %s' not supported\n", arg[1]); + } + else if (!strcmp(arg[0], "lightradius") && args == 2) + { + ptype->dl_radius[0] = atof(arg[1]); + ptype->dl_radius[1] = 0; + } + else if (!strcmp(arg[0], "lightradiusfade") && args == 2) + ptype->dl_decay[3] = atof(arg[1]); + else if (!strcmp(arg[0], "lightcolor") && args == 4) + { + ptype->dl_rgb[0] = atof(arg[1]); + ptype->dl_rgb[1] = atof(arg[2]); + ptype->dl_rgb[2] = atof(arg[3]); + } + else if (!strcmp(arg[0], "lighttime") && args == 2) + ptype->dl_time = atof(arg[1]); + else if (!strcmp(arg[0], "lightshadow") && args == 2) + ptype->flags = (ptype->flags & ~PT_NODLSHADOW) | (!atoi(arg[1])?PT_NODLSHADOW:0); + else if (!strcmp(arg[0], "lightcubemapnum") && args == 2) + ptype->dl_cubemapnum = atoi(arg[1]); + else if (!strcmp(arg[0], "lightcorona") && args == 3) + { + ptype->dl_corona_intensity = atof(arg[1])*0.25; //dp scales them by 0.25 + ptype->dl_corona_scale = atof(arg[2]); + } +#if 1 + else if (!strcmp(arg[0], "staincolor") && args == 3) //stainmaps multiplier + Con_DPrintf2("Particle effect token %s not supported\n", arg[0]); + else if (!strcmp(arg[0], "stainalpha") && args == 3) //affects stainmaps AND stain-decals. + Con_DPrintf2("Particle effect token %s not supported\n", arg[0]); + else if (!strcmp(arg[0], "stainsize") && args == 3) //affects stainmaps AND stain-decals. + Con_DPrintf2("Particle effect token %s not supported\n", arg[0]); + else if (!strcmp(arg[0], "staintex") && args == 3) //actually spawns a decal + Con_DPrintf2("Particle effect token %s not supported\n", arg[0]); + else if (!strcmp(arg[0], "stainless") && args == 2) + Con_DPrintf2("Particle effect token %s not supported\n", arg[0]); +#endif + else if (!strcmp(arg[0], "rotate") && args == 5) + { + ptype->rotationstartmin = atof(arg[1]); + ptype->rotationstartrand = atof(arg[2]) - ptype->rotationstartmin; + ptype->rotationmin = atof(arg[3]); + ptype->rotationrand = atof(arg[4]) - ptype->rotationmin; + ptype->rotationstartmin *= M_PI/180; + ptype->rotationstartrand *= M_PI/180; + ptype->rotationmin *= M_PI/180; + ptype->rotationrand *= M_PI/180; + ptype->rotationstartmin += M_PI/4; + } + else + Con_Printf("Particle effect token not recognised, or invalid args: %s %s %s %s %s %s\n", arg[0], args<2?"":arg[1], args<3?"":arg[2], args<4?"":arg[3], args<5?"":arg[4], args<6?"":arg[5]); + args = 0; + } + + if (ptype) + FinishEffectinfoParticleType(ptype, blooddecalonimpact); + + r_plooksdirty = true; +} + +static qboolean P_ImportEffectInfo_Name(char *config) +{ + char *file; + + file = (char*)COM_LoadMallocFile(va("%s.txt", config), NULL); + if (!file) + { + Con_Printf("%s.txt not found\n", config); + return false; + } + P_ImportEffectInfo(config, file, false); + free(file); + return true; +} +#endif + +/* +=============== +R_InitParticles +=============== +*/ +void PScript_InitParticles (void) +{ + Cvar_RegisterVariable(&r_bouncysparks); + Cvar_RegisterVariable(&r_part_rain); +#if UNSUPPORTED + Cvar_RegisterVariable(&r_bloodstains); +#endif + Cvar_RegisterVariable(&r_decal_noperpendicular); + Cvar_RegisterVariable(&r_particledesc); + Cvar_RegisterVariable(&r_part_rain_quantity); + Cvar_RegisterVariable(&r_particle_tracelimit); + Cvar_RegisterVariable(&r_part_sparks); + Cvar_RegisterVariable(&r_part_sparks_trifan); + Cvar_RegisterVariable(&r_part_sparks_textured); + Cvar_RegisterVariable(&r_part_beams); + Cvar_RegisterVariable(&r_part_contentswitch); + Cvar_RegisterVariable(&r_part_density); + Cvar_RegisterVariable(&r_part_maxparticles); + Cvar_RegisterVariable(&r_part_maxdecals); + Cvar_RegisterVariable(&r_lightflicker); + + Cmd_AddCommand("r_partredirect", P_PartRedirect_f); + +//#if _DEBUG + Cmd_AddCommand("r_partinfo", P_PartInfo_f); + Cmd_AddCommand("r_beaminfo", P_BeamInfo_f); +//#endif +} + +void PScript_ClearSurfaceParticles(qmodel_t *mod) +{ + mod->skytime = 0; + mod->skytris = NULL; + while(mod->skytrimem) + { + void *f = mod->skytrimem; + mod->skytrimem = mod->skytrimem->next; + Z_Free(f); + } +} +static void PScript_ClearAllSurfaceParticles(void) +{ //make sure we hit all models, even ones from the previous map. maybe this is overkill + extern qmodel_t mod_known[]; + extern int mod_numknown; + int i; + for (i = 0; i < mod_numknown; i++) + PScript_ClearSurfaceParticles(&mod_known[i]); +} + +void PScript_Shutdown (void) +{ + Cvar_SetCallback(&r_particledesc, NULL); + + CL_ClearTrailStates(); + +// if (fallback) +// fallback->ShutdownParticles(); + + pe_default = P_INVALID; + pe_size2 = P_INVALID; + pe_size3 = P_INVALID; + pe_defaulttrail = P_INVALID; + + while(loadedconfigs) + { + pcfg_t *cfg; + cfg = loadedconfigs; + loadedconfigs = cfg->next; + Z_Free(cfg); + } + + while (numparticletypes > 0) + { + numparticletypes--; +#if UNSUPPORTED + if (part_type[numparticletypes].models) + Z_Free(part_type[numparticletypes].models); +#endif + if (part_type[numparticletypes].sounds) + Z_Free(part_type[numparticletypes].sounds); + if (part_type[numparticletypes].ramp) + Z_Free(part_type[numparticletypes].ramp); + } + Z_Free (part_type); + part_type = NULL; + part_run_list = NULL; +// fallback = NULL; + + Z_Free (particles); + particles = NULL; + Z_Free (beams); + beams = NULL; + Z_Free (decals); + decals = NULL; + Z_Free (trailstates); + trailstates = NULL; + + free_particles = NULL; + free_decals = NULL; + free_beams = NULL; + + PScript_ClearAllSurfaceParticles(); + + r_numparticles = 0; + r_numdecals = 0; + + //FIXME: clear static ent trailstates, delta ent trailstates, beam trailstates +} + +qboolean PScript_Startup (void) +{ + int newmaxp, newmaxd; + + newmaxp = r_part_maxparticles.value; + if (newmaxp < 1) + newmaxp = 1; + if (newmaxp > MAX_PARTICLES) + newmaxp = MAX_PARTICLES; + newmaxd = r_part_maxdecals.value; + if (newmaxd < 1) + newmaxd = 1; + if (newmaxd > MAX_DECALS) + newmaxd = MAX_DECALS; + +// if (newmaxp != r_numparticles || newmaxd != r_numdecals) +// PScript_Shutdown(); + + if (!r_numparticles) //already inited + { + r_numparticles = newmaxp; + r_numdecals = newmaxd; + + buildsintable(); + + r_numbeams = MAX_BEAMSEGS; + r_numtrailstates = MAX_TRAILSTATES; + + particles = (particle_t *)Z_Malloc (r_numparticles * sizeof(particle_t)); + + beams = (beamseg_t *)Z_Malloc (r_numbeams * sizeof(beamseg_t)); + + decals = (clippeddecal_t *)Z_Malloc (r_numdecals * sizeof(clippeddecal_t)); + + trailstates = (trailstate_t *)Z_Malloc (r_numtrailstates * sizeof(trailstate_t)); + memset(trailstates, 0, r_numtrailstates * sizeof(trailstate_t)); + ts_cycle = 0; + + Cvar_SetCallback(&r_particledesc, R_ParticleDesc_Callback); + } + r_particledesc.callback(&r_particledesc); + + return true; +} + +void PScript_RecalculateSkyTris (void) +{ + qmodel_t *m = cl.worldmodel; + size_t modidx; + + PScript_ClearAllSurfaceParticles(); + + for (modidx = 0; modidx < MAX_MODELS; modidx++) + { + m = cl.model_precache[modidx]; + + if (m && !m->needload && m->type == mod_brush) + { + int t; + int i; + int ptype; + msurface_t *surf; + char key[128]; + const char *data = COM_Parse(m->entities); + int *remaps; + remaps = malloc(sizeof(*remaps)*m->numtextures); + if (!remaps) + break; + for (t = 0; t < m->numtextures; t++) + remaps[t] = P_INVALID; + + //parse the worldspawn entity fields for "_texpart_FOO" keys to give texture "FOO" particles from the effect specified by the value + if (data && com_token[0] == '{') + { + while (1) + { + data = COM_Parse(data); + if (!data) + break; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strcpy(key, com_token + 1); + else + strcpy(key, com_token); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + data = COM_Parse(data); + if (!data) + break; // error + if (!q_strncasecmp("texpart_", key, 8)) + { + /*in quakespasm there are always two textures added on the end (rather than pointing to textures outside the model)*/ + for (t = 0; t < m->numtextures-2; t++) + { + if (!q_strcasecmp(key+8, m->textures[t]->name)) + remaps[t] = PScript_FindParticleType(com_token); + } + } + } + } + + for (t = 0; t < m->numtextures; t++) + { + ptype = remaps[t]; + if (ptype == P_INVALID) + ptype = PScript_FindParticleType(va("tex_%s", m->textures[t]->name)); + + if (ptype >= 0) + { + for (i=0; inummodelsurfaces; i++) + { + surf = m->surfaces + i + m->firstmodelsurface; + if (surf->texinfo->texture == m->textures[t]) + { + /*FIXME: it would be a good idea to determine the surface's (midpoint) pvs cluster so that we're not spamming for the entire map*/ + PScript_EmitSkyEffectTris(m, surf, ptype); + } + } + } + } + free(remaps); + } + } +} +/* +=============== +P_ClearParticles +=============== +*/ +void PScript_ClearParticles (void) +{ + int i; + + PScript_Startup(); + +// if (fallback) +// fallback->ClearParticles(); + + free_particles = &particles[0]; + for (i=0 ;inext) + { + //already loaded? + if (!strcmp(cfg->name, name)) + return false; + } + cfg = Z_Malloc(sizeof(*cfg) + strlen(name)); + if (!cfg) + return false; + strcpy(cfg->name, name); + cfg->next = loadedconfigs; + loadedconfigs = cfg; + + if (!strcmp(name, "classic")) + { +#ifdef PSET_CLASSIC + if (fallback) + fallback->ShutdownParticles(); + fallback = &pe_classic; + if (fallback) + { + fallback->InitParticles(); + fallback->ClearParticles(); + } +#endif + return true; + } + + file = (char*)COM_LoadMallocFile(va("particles/%s.cfg", name), NULL); + if (!file) + file = (char*)COM_LoadMallocFile(va("%s.cfg", name), NULL); + if (file) + { + PScript_ParseParticleEffectFile(name, implicit, file, com_filesize); + free(file); + } + else + { +#ifdef PSET_SCRIPT_EFFECTINFO + if (!strcmp(name, "effectinfo") || !strncmp(name, "effectinfo_", 11)) + { + //FIXME: we're loading this too early to deal with per-map stuff. + //FIXME: wait until after particle precache info has been received, and only reload if the loaded configs actually changed. + P_ImportEffectInfo_Name(name); + return true; + } +#endif + if (showwarning) + Con_Printf(CON_WARNING "Couldn't find particle description %s\n", name); + return false; + } + return true; +} + +static void R_Particles_KillAllEffects(void) +{ + int i; + pcfg_t *cfg; + + for (i = 0; i < numparticletypes; i++) + { + *part_type[i].texname = '\0'; + part_type[i].scale = 0; + part_type[i].loaded = 0; + if (part_type->ramp) + Z_Free(part_type->ramp); + part_type->ramp = NULL; + part_type->rampmode = RAMP_NONE; + } + +// f_modified_particles = false; + +/* if (fallback) + { + fallback->ShutdownParticles(); + fallback = NULL; + } +*/ + while(loadedconfigs) + { + cfg = loadedconfigs; + loadedconfigs = cfg->next; + Z_Free(cfg); + } +} + +static void R_ParticleDesc_Callback(struct cvar_s *var) +{ + const char *c; + + R_Particles_KillAllEffects(); + r_plooksdirty = true; + + for (c = var->string; (c=COM_Parse(c)); ) + { + if (*com_token) + P_LoadParticleSet(com_token, false, true); + } + + if (cls.state == ca_connected && cl.model_precache[1]) + { + //per-map configs. because we can. + memcpy(com_token, "map_", 4); + COM_FileBase(cl.model_precache[1]->name, com_token+4, sizeof(com_token)-4); + P_LoadParticleSet(com_token, false, false); + } + + //make sure nothing is stale. + CL_RegisterParticles(); +} + +static void P_AddRainParticles(qmodel_t *mod, vec3_t axis[3], vec3_t eorg, int visframe, float contribution) +{ + float x; + float y; + part_type_t *type; + + vec3_t org, vdist, worg, wnorm; + + skytris_t *st; + if (!r_part_rain_quantity.value) + return; + + mod->skytime += contribution; + + for (st = mod->skytris; st; st = st->next) + { + if (st->face->visframe != visframe) + { + st->nexttime = mod->skytime; + continue; + } + + if ((unsigned int)st->ptype >= (unsigned int)numparticletypes) + continue; + type = &part_type[st->ptype]; + if (!type->loaded) //woo, batch skipping. + continue; + + while (st->nexttime < mod->skytime) + { + if (!free_particles) + return; + + st->nexttime += 10000.0/(st->area*r_part_rain_quantity.value*type->rainfrequency); + + x = frandom()*frandom(); + y = frandom() * (1-x); + VectorMA(st->org, x, st->x, org); + VectorMA(org, y, st->y, org); + + worg[0] = DotProduct(org, axis[0]) + eorg[0]; + worg[1] = -DotProduct(org, axis[1]) + eorg[1]; + worg[2] = DotProduct(org, axis[2]) + eorg[2]; + + //ignore it if its too far away + VectorSubtract(worg, r_refdef.vieworg, vdist); + if (VectorLength(vdist) > (1024+512)*frandom()) + continue; + + if (st->face->flags & SURF_PLANEBACK) + VectorScale(st->face->plane->normal, -1, vdist); + else + VectorCopy(st->face->plane->normal, vdist); + + wnorm[0] = DotProduct(vdist, axis[0]); + wnorm[1] = -DotProduct(vdist, axis[1]); + wnorm[2] = DotProduct(vdist, axis[2]); + + VectorMA(worg, 0.5, wnorm, worg); + if (!(CL_PointContentsMask(worg) & FTECONTENTS_SOLID)) //should be paranoia, at least for the world. + { + PScript_RunParticleEffectState(worg, wnorm, 1, st->ptype, NULL); + } + } + } +} + +static void R_Part_SkyTri(qmodel_t *mod, float *v1, float *v2, float *v3, msurface_t *surf, int ptype) +{ + float dot; + float xm; + float ym; + float theta; + vec3_t xd; + vec3_t yd; + + skytris_t *st; + + skytriblock_t *mem = mod->skytrimem; + if (!mem || mem->count == sizeof(mem->tris)/sizeof(mem->tris[0])) + { + mod->skytrimem = Z_Malloc(sizeof(*mod->skytrimem)); + mod->skytrimem->next = mem; + mod->skytrimem->count = 0; + mem = mod->skytrimem; + } + + st = &mem->tris[mem->count]; + VectorCopy(v1, st->org); + VectorSubtract(v2, st->org, st->x); + VectorSubtract(v3, st->org, st->y); + + VectorCopy(st->x, xd); + VectorCopy(st->y, yd); +/* + xd[2] = 0; //prevent area from being valid on vertical surfaces + yd[2] = 0; +*/ + xm = VectorLength(xd); + ym = VectorLength(yd); + + dot = DotProduct(xd, yd); + theta = acos(dot/(xm*ym)); + st->area = sin(theta)*xm*ym; + st->nexttime = mod->skytime; + st->face = surf; + st->ptype = ptype; + + if (st->area<=0) + return;//bummer. + mem->count++; + + st->next = mod->skytris; + mod->skytris = st; +} + + + +void PScript_EmitSkyEffectTris(qmodel_t *mod, msurface_t *fa, int ptype) +{ + vec3_t verts[64]; + int v1; + int v2; + int v3; + int numverts; + int i, lindex; + float *vec; + + if (ptype < 0 || ptype >= numparticletypes) + return; + + // + // convert edges back to a normal polygon + // + numverts = 0; + for (i=0 ; inumedges ; i++) + { + lindex = mod->surfedges[fa->firstedge + i]; + + if (lindex > 0) + vec = mod->vertexes[mod->edges[lindex].v[0]].position; + else + vec = mod->vertexes[mod->edges[-lindex].v[1]].position; + VectorCopy (vec, verts[numverts]); + numverts++; + + if (numverts>=64) + { + Con_Printf("Too many verts on sky surface\n"); + return; + } + } + + v1 = 0; + v2 = 1; + for (v3 = 2; v3 < numverts; v3++) + { + R_Part_SkyTri(mod, verts[v1], verts[v2], verts[v3], fa, ptype); + + v2 = v3; + } +} + +// Trailstate functions +static void P_CleanTrailstate(trailstate_t *ts) +{ + // clear LASTSEG flag from lastbeam so it can be reused + if (ts->lastbeam) + { + ts->lastbeam->flags &= ~BS_LASTSEG; + ts->lastbeam->flags |= BS_NODRAW; + } + + // clean structure + memset(ts, 0, sizeof(trailstate_t)); +} + +void PScript_DelinkTrailstate(trailstate_t **tsk) +{ + trailstate_t *ts; + trailstate_t *assoc; + + if (*tsk == NULL) + return; // not linked to a trailstate + + ts = *tsk; // store old pointer + *tsk = NULL; // clear pointer + + if (ts->key != tsk) + return; // prevent overwrite + + assoc = ts->assoc; // store assoc + P_CleanTrailstate(ts); // clean directly linked trailstate + + // clean trailstates assoc linked + while (assoc) + { + ts = assoc->assoc; + P_CleanTrailstate(assoc); + assoc = ts; + } +} + +static trailstate_t *P_NewTrailstate(trailstate_t **key) +{ + trailstate_t *ts; + + // bounds check here in case r_numtrailstates changed + if (ts_cycle >= r_numtrailstates) + ts_cycle = 0; + + // get trailstate + ts = trailstates + ts_cycle; + + // clear trailstate + P_CleanTrailstate(ts); + + // set key + ts->key = key; + + // advance index cycle + ts_cycle++; + + // return clean trailstate + return ts; +} + +#define NUMVERTEXNORMALS 162 +static float r_avertexnormals[NUMVERTEXNORMALS][3] = { +#include "anorms.h" +}; +static vec2_t avelocities[NUMVERTEXNORMALS]; +#define BEAMLENGTH 16 +// vec3_t avelocity = {23, 7, 3}; +// float partstep = 0.01; +// float timescale = 0.01; + + +#if UNSUPPORTED +static void PScript_ApplyOrgVel(vec3_t oorg, vec3_t ovel, vec3_t eforg, vec3_t axis[3], int pno, int pmax, part_type_t *ptype) +{ + vec3_t ofsvec, arsvec; + + float k,l,m; + int spawnspc, i=pno, j; + + + l=0; + j=0; + k=0; + m=0; + + spawnspc = 8; + switch (ptype->spawnmode) + { + case SM_UNICIRCLE: + m = pmax; + if (ptype->looks.type == PT_BEAM) + m--; + + if (m < 1) + m = 0; + else + m = (M_PI*2)/m; + + if (ptype->spawnparam1) /* use for weird shape hacks */ + m *= ptype->spawnparam1; + break; + case SM_TELEBOX: + spawnspc = 4; + l = -ptype->areaspreadvert; + case SM_LAVASPLASH: + j = k = -ptype->areaspread; + if (ptype->spawnparam1) + m = ptype->spawnparam1; + else + m = 0.55752; /* default weird number for tele/lavasplash used in vanilla Q1 */ + + if (ptype->spawnparam2) + spawnspc = (int)ptype->spawnparam2; + break; + case SM_FIELD: + if (!avelocities[0][0]) + { + for (j=0 ; jspawnmode) + { + case SM_BOX: + ofsvec[0] = crandom(); + ofsvec[1] = crandom(); + ofsvec[2] = crandom(); + + arsvec[0] = ofsvec[0]*ptype->areaspread; + arsvec[1] = ofsvec[1]*ptype->areaspread; + arsvec[2] = ofsvec[2]*ptype->areaspreadvert; + break; + case SM_TELEBOX: + ofsvec[0] = k; + ofsvec[1] = j; + ofsvec[2] = l+4; + VectorNormalize(ofsvec); + VectorScale(ofsvec, 1.0-(frandom())*m, ofsvec); + + // org is just like the original + arsvec[0] = j + (rand()%spawnspc); + arsvec[1] = k + (rand()%spawnspc); + arsvec[2] = l + (rand()%spawnspc); + + // advance telebox loop + j += spawnspc; + if (j >= ptype->areaspread) + { + j = -ptype->areaspread; + k += spawnspc; + if (k >= ptype->areaspread) + { + k = -ptype->areaspread; + l += spawnspc; + if (l >= ptype->areaspreadvert) + l = -ptype->areaspreadvert; + } + } + break; + case SM_LAVASPLASH: + // calc directions, org with temp vector + ofsvec[0] = k + (rand()%spawnspc); + ofsvec[1] = j + (rand()%spawnspc); + ofsvec[2] = 256; + + arsvec[0] = ofsvec[0]; + arsvec[1] = ofsvec[1]; + arsvec[2] = frandom()*ptype->areaspreadvert; + + VectorNormalize(ofsvec); + VectorScale(ofsvec, 1.0-(frandom())*m, ofsvec); + + // advance splash loop + j += spawnspc; + if (j >= ptype->areaspread) + { + j = -ptype->areaspread; + k += spawnspc; + if (k >= ptype->areaspread) + k = -ptype->areaspread; + } + break; + case SM_UNICIRCLE: + ofsvec[0] = cos(m*i); + ofsvec[1] = sin(m*i); + ofsvec[2] = 0; + VectorScale(ofsvec, ptype->areaspread, arsvec); + break; + case SM_FIELD: + arsvec[0] = (cl.time * avelocities[i][0]) + m; + arsvec[1] = (cl.time * avelocities[i][1]) + m; + arsvec[2] = cos(arsvec[1]); + + ofsvec[0] = arsvec[2]*cos(arsvec[0]); + ofsvec[1] = arsvec[2]*sin(arsvec[0]); + ofsvec[2] = -sin(arsvec[1]); + +// arsvec[0] = r_avertexnormals[j][0]*ptype->areaspread + ofsvec[0]*BEAMLENGTH; +// arsvec[1] = r_avertexnormals[j][1]*ptype->areaspread + ofsvec[1]*BEAMLENGTH; +// arsvec[2] = r_avertexnormals[j][2]*ptype->areaspreadvert + ofsvec[2]*BEAMLENGTH; + + l = ptype->spawnparam2 * sin(cl.time+j+m); + arsvec[0] = r_avertexnormals[j][0]*(ptype->areaspread+l) + ofsvec[0]*ptype->spawnparam1; + arsvec[1] = r_avertexnormals[j][1]*(ptype->areaspread+l) + ofsvec[1]*ptype->spawnparam1; + arsvec[2] = r_avertexnormals[j][2]*(ptype->areaspreadvert+l) + ofsvec[2]*ptype->spawnparam1; + + VectorNormalize(ofsvec); + + j++; + if (j >= NUMVERTEXNORMALS) + { + j = 0; + m += 0.1762891; // some BS number to try to "randomize" things + } + break; + case SM_DISTBALL: + { + float rdist; + + rdist = ptype->spawnparam2 - crandom()*(1-(crandom() * ptype->spawnparam1)); + + // this is a strange spawntype, which is based on the fact that + // crandom()*crandom() provides something similar to an exponential + // probability curve + ofsvec[0] = hrandom(); + ofsvec[1] = hrandom(); + if (ptype->areaspreadvert) + ofsvec[2] = hrandom(); + else + ofsvec[2] = 0; + + VectorNormalize(ofsvec); + VectorScale(ofsvec, rdist, ofsvec); + + arsvec[0] = ofsvec[0]*ptype->areaspread; + arsvec[1] = ofsvec[1]*ptype->areaspread; + arsvec[2] = ofsvec[2]*ptype->areaspreadvert; + } + break; + default: // SM_BALL, SM_CIRCLE + ofsvec[0] = hrandom(); + ofsvec[1] = hrandom(); + if (ptype->areaspreadvert) + ofsvec[2] = hrandom(); + else + ofsvec[2] = 0; + + VectorNormalize(ofsvec); + if (ptype->spawnmode != SM_CIRCLE) + VectorScale(ofsvec, frandom(), ofsvec); + + arsvec[0] = ofsvec[0]*ptype->areaspread; + arsvec[1] = ofsvec[1]*ptype->areaspread; + arsvec[2] = ofsvec[2]*ptype->areaspreadvert; + break; + } + + k = ptype->orgadd + frandom()*ptype->randomorgadd; + l = ptype->veladd + frandom()*ptype->randomveladd; + +#if 1 + VectorMA(ovel, ofsvec[0]*ptype->spawnvel, axis[0], ovel); + VectorMA(ovel, ofsvec[1]*ptype->spawnvel, axis[1], ovel); + VectorMA(ovel, l+ofsvec[2]*ptype->spawnvelvert, axis[2], ovel); + + VectorMA(eforg, arsvec[0], axis[0], oorg); + VectorMA(oorg, arsvec[1], axis[1], oorg); + VectorMA(oorg, k+arsvec[2], axis[2], oorg); +#else + oorg[0] = eforg[0] + arsvec[0]; + oorg[1] = eforg[1] + arsvec[1]; + oorg[2] = eforg[2] + arsvec[2]; + + // apply arsvec+ofsvec + if (efdir) + { + ovel[0] += efdir[0]*l+ofsvec[0]*ptype->spawnvel; + ovel[1] += efdir[1]*l+ofsvec[1]*ptype->spawnvel; + ovel[2] += efdir[2]*l+ofsvec[2]*ptype->spawnvelvert; + + oorg[0] += efdir[0]*k; + oorg[1] += efdir[1]*k; + oorg[2] += efdir[2]*k; + } + else + {//efdir is effectively up - '0 0 -1' + ovel[0] += ofsvec[0]*ptype->spawnvel; + ovel[1] += ofsvec[1]*ptype->spawnvel; + ovel[2] += ofsvec[2]*ptype->spawnvelvert - l; + + oorg[2] -= k; + } +#endif + + if (ptype->flags & PT_WORLDSPACERAND) + { + do + { + ofsvec[0] = crand(); + ofsvec[1] = crand(); + ofsvec[2] = crand(); + } while(DotProduct(ofsvec,ofsvec)>1); //crap, but I'm trying to mimic dp + oorg[0] += ofsvec[0] * ptype->orgwrand[0]; + oorg[1] += ofsvec[1] * ptype->orgwrand[1]; + oorg[2] += ofsvec[2] * ptype->orgwrand[2]; + ovel[0] += ofsvec[0] * ptype->velwrand[0]; + ovel[1] += ofsvec[1] * ptype->velwrand[1]; + ovel[2] += ofsvec[2] * ptype->velwrand[2]; + VectorAdd(ovel, ptype->velbias, ovel); + } + + VectorAdd(oorg, ptype->orgbias, oorg); +} +#endif + +static void PScript_EffectSpawned(part_type_t *ptype, vec3_t org, vec3_t axis[3], int dlkey, float countscale) +{ +#if UNSUPPORTED + if (ptype->nummodels) + { + int count = ptype->countextra + countscale*(ptype->count+ptype->countrand*frandom()); + int i; + partmodels_t *mod; + if (!ptype->countextra && !ptype->count) + count = countscale; + for (i = 0; i < count; i++) + { + mod = &ptype->models[rand() % ptype->nummodels]; + if (!mod->model) + mod->model = Mod_ForName(mod->name, false); + if (mod->model) + { + vec3_t morg, mdir; + float scale = frandom() * (mod->scalemax-mod->scalemin) + mod->scalemin; + PScript_ApplyOrgVel(morg, mdir, org, axis, i, count, ptype); + CL_SpawnSpriteEffect(morg, mdir, (mod->rflags&RF_USEORIENTATION)?axis[2]:NULL, mod->model, mod->framestart, mod->framecount, mod->framerate?mod->framerate:10, mod->alpha?mod->alpha:1, scale, ptype->rotationmin*180/M_PI, ptype->gravity, mod->traileffect, mod->rflags & ~RF_USEORIENTATION, mod->skin); + } + } + } +#endif + if (ptype->dl_radius[0] || ptype->dl_radius[1])// && r_rocketlight.value) + { + float radius; + dlight_t *dl; + + static int flickertime; + static int flicker; + int i = realtime*20; + if (flickertime != i) + { + flickertime = i; + flicker = rand(); + } + radius = ptype->dl_radius[0] + (r_lightflicker.value?((flicker + dlkey*2000)&0xffff)*(1.0f/0xffff):0.5)*ptype->dl_radius[1]; + +#if 1 //qs + dl = CL_AllocDlight (dlkey); + VectorCopy (org, dl->origin); + dl->radius = radius; + dl->minlight = 0; + dl->die = cl.time + ptype->dl_time; + dl->decay = ptype->dl_decay[3]; + VectorCopy(ptype->dl_rgb, dl->color); + //FIXME: no rgb decay + //FIXME: rtlights... *cough* +#else //orig fte code + dl = CL_NewDlight(dlkey, org, radius, ptype->dl_time, ptype->dl_rgb[0], ptype->dl_rgb[1], ptype->dl_rgb[2]); + dl->channelfade[0] = ptype->dl_decay[0]; + dl->channelfade[1] = ptype->dl_decay[1]; + dl->channelfade[2] = ptype->dl_decay[2]; + dl->decay = ptype->dl_decay[3]; + dl->corona = ptype->dl_corona_intensity; + dl->coronascale = ptype->dl_corona_scale; +#ifdef RTLIGHTS + dl->lightcolourscales[0] = ptype->dl_scales[0]; + dl->lightcolourscales[1] = ptype->dl_scales[1]; + dl->lightcolourscales[2] = ptype->dl_scales[2]; +#endif + if (ptype->flags & PT_NODLSHADOW) + dl->flags |= LFLAG_NOSHADOWS; + if (ptype->dl_cubemapnum) + Q_snprintfz(dl->cubemapname, sizeof(dl->cubemapname), "cubemaps/%i", ptype->dl_cubemapnum); +#endif + } + if (ptype->numsounds) + { + int i; + float w,tw; + for (i = 0, tw = 0; i < ptype->numsounds; i++) + tw += ptype->sounds[i].weight; + w = frandom() * tw; //select the sound by weight + //and figure out which one that weight corresponds to + for (i = 0, tw = 0; i < ptype->numsounds; i++) + { + tw += ptype->sounds[i].weight; + if (w <= tw) + { + if (*ptype->sounds[i].name && ptype->sounds[i].vol > 0) + { //FIXME: no delay, no pitch + S_StartSound(0, 0, S_PrecacheSound(ptype->sounds[i].name), org, ptype->sounds[i].vol, ptype->sounds[i].atten); + } + break; + } + } + } +#if UNSUPPORTED + if (ptype->stain_radius) + Surf_AddStain(org, ptype->stain_rgb[0], ptype->stain_rgb[1], ptype->stain_rgb[2], ptype->stain_radius); +#endif +} + +#ifdef USE_DECALS +void PerpendicularVector( vec3_t dst, const vec3_t src ); +#define DEG2RAD( a ) ( (a) * M_PI_DIV_180 ) +static void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + zrot[0][0] = cos( DEG2RAD( degrees ) ); + zrot[0][1] = sin( DEG2RAD( degrees ) ); + zrot[1][0] = -sin( DEG2RAD( degrees ) ); + zrot[1][1] = cos( DEG2RAD( degrees ) ); + + R_ConcatRotations( m, zrot, tmpmat ); + R_ConcatRotations( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) + { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} + +typedef struct +{ + part_type_t *ptype; + int entity; + qmodel_t *model; + vec3_t center; + vec3_t normal; + vec3_t tangent1; + vec3_t tangent2; + + float scale0; + float scale1; + float scale2; + + float bias1; + float bias2; +} decalctx_t; +static void PScript_AddDecals(void *vctx, vec3_t *points, size_t numtris) +{ + decalctx_t *ctx = vctx; + part_type_t *ptype = ctx->ptype; + clippeddecal_t *d; + unsigned int i; + vec3_t vec; + byte *palrgba = (byte *)d_8to24table; + while(numtris-->0) + { + if (!free_decals) + break; + + d = free_decals; + free_decals = d->next; + d->next = ptype->clippeddecals; + ptype->clippeddecals = d; + + for (i = 0; i < 3; i++) + { + VectorCopy(points[i], d->vertex[i]); + VectorSubtract(d->vertex[i], ctx->center, vec); + d->texcoords[i][0] = (DotProduct(vec, ctx->tangent1)*ctx->scale1)+ctx->bias1; + d->texcoords[i][1] = (DotProduct(vec, ctx->tangent2)*ctx->scale2)+ctx->bias2; + if (r_decal_noperpendicular.value) + { + //the decal code is already making sure the surfaces are mostly aligned, which should solve some issues. + //this means we can make sure that there's NO fading at all, so no issues if the center of the effect is not actually aligned with any surface (yay inprecision). + d->valpha[i] = 1; + } + else + { + //fade the alpha depending on the distance from the center) + //FIXME: should be fabsed by glsl so that linear interpolation works correctly + d->valpha[i] = 1 - fabs((DotProduct(vec, ctx->normal)*ctx->scale0)); + } + } + points += 3; + + d->entity = ctx->entity; + d->model = ctx->model; + d->die = ptype->randdie*frandom(); + + if (ptype->die) + d->rgba[3] = ptype->alpha + d->die*ptype->alphachange; + else + d->rgba[3] = ptype->alpha; + d->rgba[3] += ptype->alpharand*frandom(); + + if (ptype->colorindex >= 0) + { + int cidx; + cidx = ptype->colorrand > 0 ? rand() % ptype->colorrand : 0; + cidx = ptype->colorindex + cidx; + if (cidx > 255) + d->rgba[3] = d->rgba[3] / 2; // Hexen 2 style transparency + cidx = (cidx & 0xff) * 4; + d->rgba[0] = palrgba[cidx] * (1/255.0); + d->rgba[1] = palrgba[cidx+1] * (1/255.0); + d->rgba[2] = palrgba[cidx+2] * (1/255.0); + } + else + VectorCopy(ptype->rgb, d->rgba); + + vec[2] = frandom(); + vec[0] = vec[2]*ptype->rgbrandsync[0] + frandom()*(1-ptype->rgbrandsync[0]); + vec[1] = vec[2]*ptype->rgbrandsync[1] + frandom()*(1-ptype->rgbrandsync[1]); + vec[2] = vec[2]*ptype->rgbrandsync[2] + frandom()*(1-ptype->rgbrandsync[2]); + d->rgba[0] += vec[0]*ptype->rgbrand[0] + ptype->rgbchange[0]*d->die; + d->rgba[1] += vec[1]*ptype->rgbrand[1] + ptype->rgbchange[1]*d->die; + d->rgba[2] += vec[2]*ptype->rgbrand[2] + ptype->rgbchange[2]*d->die; + + d->die = particletime + ptype->die - d->die; + + if (ptype->looks.type != PT_CDECAL) + d->die += 20; + + // maintain run list + if (!(ptype->state & PS_INRUNLIST)) + { + ptype->nexttorun = part_run_list; + part_run_list = ptype; + ptype->state |= PS_INRUNLIST; + } + } +} + + +typedef struct fragmentdecal_s fragmentdecal_t; +static void Mod_ClipDecal(qmodel_t *mod, vec3_t center, vec3_t normal, vec3_t tangent1, vec3_t tangent2, float size, unsigned int surfflagmask, unsigned int surfflagmatch, void (*callback)(void *ctx, vec3_t *points, size_t numpoints), void *ctx); + +//clipped decals actually work by defining the area of the decal with some planes, and then chopping away the entirety of the world based upon those planes (hurrah for bsp to trivially reject most of it) +//the decal is then textured according to some texture projection. +#define MAXFRAGMENTVERTS (128*3) +struct fragmentdecal_s +{ + vec3_t center; + + vec3_t normal; + vec3_t planenorm[6]; + float planedist[6]; + int numplanes; + + vec_t radius; + + //will only appear on surfaces with the matching surfaceflag + unsigned int surfflagmask; + unsigned int surfflagmatch; + + void (*callback)(void *ctx, vec3_t *points, size_t numpoints); + void *ctx; +}; +static int Fragment_ClipPolyToPlane(vec3_t *inverts, vec3_t *outverts, int incount, float *plane, float planedist) +{ + float dotv[MAXFRAGMENTVERTS+1]; + char keep[MAXFRAGMENTVERTS+1]; +#define KEEP_KILL 0 +#define KEEP_KEEP 1 +#define KEEP_BORDER 2 + int i; + int outcount = 0; + int clippedcount = 0; + float d; + float *p1, *p2; + float *out; +#define FRAG_EPSILON (1.0/32) //0.5 + + for (i = 0; i < incount; i++) + { + dotv[i] = DotProduct(inverts[i], plane) - planedist; + if (dotv[i]<-FRAG_EPSILON) + { + keep[i] = KEEP_KILL; + clippedcount++; + } + else if (dotv[i] > FRAG_EPSILON) + keep[i] = KEEP_KEEP; + else + keep[i] = KEEP_BORDER; + } + dotv[i] = dotv[0]; + keep[i] = keep[0]; + + if (clippedcount == incount) + return 0; //all were clipped + if (clippedcount == 0) + { //none were clipped + for (i = 0; i < incount; i++) + VectorCopy(inverts[i], outverts[i]); + return incount; + } + + for (i = 0; i < incount; i++) + { + p1 = inverts[i]; + if (keep[i] == KEEP_BORDER) + { + out = outverts[outcount++]; + VectorCopy(p1, out); + continue; + } + if (keep[i] == KEEP_KEEP) + { + out = outverts[outcount++]; + VectorCopy(p1, out); + } + if (keep[i+1] == KEEP_BORDER || keep[i] == keep[i+1]) + continue; + p2 = inverts[(i+1)%incount]; + d = dotv[i] - dotv[i+1]; + if (d) + d = dotv[i] / d; + + out = outverts[outcount++]; + VectorInterpolate(p1, d, p2, out); + } + return outcount; +} +static void Fragment_ClipPoly(fragmentdecal_t *dec, int numverts, vec3_t *inverts) +{ + //emit the triangle, and clip it's fragments. + int p; + vec3_t verts[2][MAXFRAGMENTVERTS]; + vec3_t decalfragmentverts[MAXFRAGMENTVERTS]; + int flip; + vec3_t d1, d2, n; + size_t numtris; + + if (numverts > MAXFRAGMENTVERTS) + return; + + if (r_decal_noperpendicular.value) + { + VectorSubtract(inverts[1], inverts[0], d1); + for (p = 2; ; p++) + { + if (p >= numverts) + return; + VectorSubtract(inverts[p], inverts[0], d2); + CrossProduct(d1, d2, n); + if (DotProduct(n,n)>.1) + break; + } + VectorNormalizeFast(n); + if (DotProduct(n, dec->normal) < 0.1) + return; //faces too far way from the normal + } + + flip = 0; + //clip to the first plane specially, so we don't have extra copys + numverts = Fragment_ClipPolyToPlane(inverts, verts[flip], numverts, dec->planenorm[0], dec->planedist[0]); + + if (numverts < 3) //totally clipped. + return; + + //clip the polygon to the 6 planes. + for (p = 1; p < dec->numplanes; p++) + { + numverts = Fragment_ClipPolyToPlane(verts[flip], verts[flip^1], numverts, dec->planenorm[p], dec->planedist[p]); + flip^=1; + + if (numverts < 3) //totally clipped. + return; + } + + //decompose the resulting polygon into triangles. + + numtris = 0; + while(numverts-->2) + { + if (numtris+3 > MAXFRAGMENTVERTS) + { + dec->callback(dec->ctx, decalfragmentverts, numtris); + numtris = 0; + break; + } + + VectorCopy(verts[flip][0], decalfragmentverts[numtris*3+0]); + VectorCopy(verts[flip][numverts-1], decalfragmentverts[numtris*3+1]); + VectorCopy(verts[flip][numverts], decalfragmentverts[numtris*3+2]); + numtris++; + } + if (numtris) + dec->callback(dec->ctx, decalfragmentverts, numtris); +} +//this could be inlined, but I'm lazy. +static void Q1BSP_Fragment_Surface (fragmentdecal_t *dec, msurface_t *surf) +{ + int i; + vec3_t verts[MAXFRAGMENTVERTS]; + glpoly_t *poly; + +#if 0 + //fixme: proper shader-based rejection + //dec->surfflagmask; + //dec->surfflagmatch; +#else + //water and sky should not get decals. + if (surf->flags & (SURF_DRAWSKY|SURF_DRAWTURB)) + return; +#endif + + for (poly = surf->polys; poly; poly = poly->next) + { + if (poly->numverts > MAXFRAGMENTVERTS) + continue; + + for (i = 0; i < poly->numverts; i++) + VectorCopy(poly->verts[i], verts[i]); + Fragment_ClipPoly(dec, i, verts); + } +} +static void Q1BSP_ClipDecalToNodes (qmodel_t *mod, fragmentdecal_t *dec, mnode_t *node) +{ + mplane_t *splitplane; + float dist; + msurface_t *surf; + unsigned int i; + + if (node->contents < 0) + return; + + splitplane = node->plane; + dist = DotProduct (dec->center, splitplane->normal) - splitplane->dist; + + if (dist > dec->radius) + { + Q1BSP_ClipDecalToNodes (mod, dec, node->children[0]); + return; + } + if (dist < -dec->radius) + { + Q1BSP_ClipDecalToNodes (mod, dec, node->children[1]); + return; + } + +// mark the polygons + surf = mod->surfaces + node->firstsurface; + if (r_decal_noperpendicular.value) + { + for (i=0 ; inumsurfaces ; i++, surf++) + { + if (surf->flags & SURF_PLANEBACK) + { + if (-DotProduct(surf->plane->normal, dec->normal) > -0.5) + continue; + } + else + { + if (DotProduct(surf->plane->normal, dec->normal) > -0.5) + continue; + } + Q1BSP_Fragment_Surface(dec, surf); + } + } + else + { + for (i=0 ; inumsurfaces ; i++, surf++) + Q1BSP_Fragment_Surface(dec, surf); + } + + Q1BSP_ClipDecalToNodes (mod, dec, node->children[0]); + Q1BSP_ClipDecalToNodes (mod, dec, node->children[1]); +} + + +static void Mod_ClipDecal(qmodel_t *mod, vec3_t center, vec3_t normal, vec3_t tangent1, vec3_t tangent2, float size, unsigned int surfflagmask, unsigned int surfflagmatch, void (*callback)(void *ctx, vec3_t *points, size_t numpoints), void *ctx) +{ //quad marks a full, independant quad + int p; + float r; + fragmentdecal_t dec; + + VectorCopy(center, dec.center); + VectorCopy(normal, dec.normal); + dec.radius = 0; + dec.callback = callback; + dec.ctx = ctx; + dec.surfflagmask = surfflagmask; + dec.surfflagmatch = surfflagmatch; + + VectorCopy(tangent1, dec.planenorm[0]); + VectorScale(tangent1, -1, dec.planenorm[1]); + VectorCopy(tangent2, dec.planenorm[2]); + VectorScale(tangent2, -1, dec.planenorm[3]); + VectorCopy(dec.normal, dec.planenorm[4]); + VectorScale(dec.normal, -1, dec.planenorm[5]); + for (p = 0; p < 6; p++) + { + r = sqrt(DotProduct(dec.planenorm[p], dec.planenorm[p])); + VectorScale(dec.planenorm[p], 1/r, dec.planenorm[p]); + r*= size/2; + if (r > dec.radius) + dec.radius = r; + dec.planedist[p] = -(r - DotProduct(dec.center, dec.planenorm[p])); + } + dec.numplanes = 6; + + if (mod && !mod->needload && mod->type == mod_brush) + Q1BSP_ClipDecalToNodes(mod, &dec, mod->nodes + mod->hulls[0].firstclipnode); +} +#endif + +int PScript_RunParticleEffectState (vec3_t org, vec3_t dir, float count, int typenum, trailstate_t **tsk) +{ + part_type_t *ptype = &part_type[typenum]; + int i, j, k, l, spawnspc; + float m, pcount;//, orgadd, veladd; + vec3_t axis[3]={{1,0,0},{0,1,0},{0,0,-1}}; + particle_t *p; + beamseg_t *b, *bfirst; + vec3_t ofsvec, arsvec; // offsetspread vec, areaspread vec + + float orgadd, veladd; + trailstate_t *ts; + byte *palrgba = (byte *)d_8to24table; + +// if (typenum >= FALLBACKBIAS && fallback) +// return fallback->RunParticleEffectState(org, dir, count, typenum-FALLBACKBIAS, NULL); + + if (typenum < 0 || typenum >= numparticletypes) + return 1; + + if (!ptype->loaded) + return 1; + + // inwater check, switch only once + if (r_part_contentswitch.value && ptype->inwater >= 0 && cl.worldmodel) + { + unsigned int cont; + cont = CL_PointContentsMask(org); + + if (cont & FTECONTENTS_FLUID) + ptype = &part_type[ptype->inwater]; + } + + // eliminate trailstate if flag set + if (ptype->flags & PT_NOSTATE) + tsk = NULL; + + // trailstate allocation/deallocation + if (tsk) + { + // if *tsk = NULL get a new one + if (*tsk == NULL) + { + ts = P_NewTrailstate(tsk); + *tsk = ts; + } + else + { + ts = *tsk; + + if (ts->key != tsk) // trailstate was overwritten + { + ts = P_NewTrailstate(tsk); // so get a new one + *tsk = ts; + } + } + } + else + ts = NULL; + + // get msvc to shut up + j = k = l = 0; + m = 0; + + while(ptype) + { + if (r_part_contentswitch.value && (ptype->flags & (PT_TRUNDERWATER | PT_TROVERWATER)) && cl.worldmodel) + { + unsigned int cont; + cont = CL_PointContentsMask(org); + + if ((ptype->flags & PT_TROVERWATER) && (cont & ptype->fluidmask)) + goto skip; + if ((ptype->flags & PT_TRUNDERWATER) && !(cont & ptype->fluidmask)) + goto skip; + } + + if (dir && (dir[0] || dir[1] || dir[2])) + { + void PerpendicularVector( vec3_t dst, const vec3_t src ); + VectorCopy(dir, axis[2]); + VectorNormalize(axis[2]); + PerpendicularVector(axis[0], axis[2]); + VectorNormalize(axis[0]); + CrossProduct(axis[2], axis[0], axis[1]); + VectorNormalize(axis[1]); + } + PScript_EffectSpawned(ptype, org, axis, 0, count); + + if (ptype->looks.type == PT_CDECAL) + { +#ifdef USE_DECALS + vec3_t vec={0.5, 0.5, 0.5}; + int i; + decalctx_t ctx; + vec3_t bestdir; + vec3_t start, end; + + if (!free_decals) + return 0; + + ctx.entity = 0; + + VectorCopy(org, ctx.center); + if (!dir || (dir[0] == 0 && dir[1] == 0 && dir[2] == 0)) + { + float bestfrac = 1; + float frac; + vec3_t impact, normal; + int what; + bestdir[0] = 0; + bestdir[1] = 0.73; + bestdir[2] = 0.73; + VectorNormalize(bestdir); + for (i = 0; i < 6; i++) + { + if (i >= 3) + { + end[0] = (i==3)*16; + end[1] = (i==4)*16; + end[2] = (i==5)*16; + } + else + { + end[0] = -(i==0)*16; + end[1] = -(i==1)*16; + end[2] = -(i==2)*16; + } + VectorSubtract(org, end, start); + VectorAdd(org, end, end); + + + frac = CL_TraceLine(start, end, impact, normal, &what); + if (bestfrac > frac) + { + bestfrac = frac; + VectorCopy(normal, bestdir); + VectorCopy(impact, ctx.center); + ctx.entity = what; + } + } + dir = bestdir; + } + else + { //try to get it exactly on the plane, otherwise network or collision inprecisions can leave us further away from the surface than the radius of the decal + VectorSubtract(org, dir, start); + VectorAdd(org, dir, end); + CL_TraceLine(start, end, ctx.center, bestdir, &ctx.entity); + } + if (ctx.entity) + { + entity_t *ent = CL_EntityNum(ctx.entity); + if (ent->model) //looks like its active. + { + ctx.model = ent->model; + //FIXME: rotate normal + VectorSubtract(ctx.center, ent->origin, ctx.center); + } + else + { + ctx.entity = 0; + ctx.model = cl.worldmodel; + } + } + else + { + ctx.entity = 0; + ctx.model = cl.worldmodel; + } + if (!ctx.model) + return 0; + + VectorScale(dir, -1, ctx.normal); + VectorNormalize(ctx.normal); + + //we know the normal now. pick two random tangents. + VectorNormalize(vec); + CrossProduct(ctx.normal, vec, ctx.tangent1); + RotatePointAroundVector(ctx.tangent2, ctx.normal, ctx.tangent1, frandom()*360); + CrossProduct(ctx.normal, ctx.tangent2, ctx.tangent1); + + VectorNormalize(ctx.tangent1); + VectorNormalize(ctx.tangent2); + + ctx.ptype = ptype; + ctx.scale1 = ptype->s2 - ptype->s1; + ctx.bias1 = ptype->s1 + ctx.scale1/2; + ctx.scale2 = ptype->t2 - ptype->t1; + ctx.bias2 = ptype->t1 + ctx.scale2/2; + m = ptype->scale + frandom() * ptype->scalerand; + ctx.scale0 = 2.0 / m; + ctx.scale1 /= m; + ctx.scale2 /= m; + + //inserts decals through a callback. + Mod_ClipDecal(ctx.model, ctx.center, ctx.normal, ctx.tangent2, ctx.tangent1, m, ptype->surfflagmask, ptype->surfflagmatch, PScript_AddDecals, &ctx); +#endif + if (ptype->assoc < 0) + break; + ptype = &part_type[ptype->assoc]; + continue; + } + // init spawn specific variables + b = bfirst = NULL; + spawnspc = 8; + pcount = ptype->countextra + r_part_density.value*count*(ptype->count+ptype->countrand*frandom()); + if (ptype->flags & PT_INVFRAMETIME) + pcount /= host_frametime; + if (ts) + pcount += ts->state2.emittime; + + switch (ptype->spawnmode) + { + case SM_UNICIRCLE: + m = pcount; + if (ptype->looks.type == PT_BEAM) + m--; + + if (m < 1) + m = 0; + else + m = (M_PI*2)/m; + + if (ptype->spawnparam1) /* use for weird shape hacks */ + m *= ptype->spawnparam1; + break; + case SM_TELEBOX: + spawnspc = 4; + l = -ptype->areaspreadvert; + case SM_LAVASPLASH: + j = k = -ptype->areaspread; + if (ptype->spawnparam1) + m = ptype->spawnparam1; + else + m = 0.55752; /* default weird number for tele/lavasplash used in vanilla Q1 */ + + if (ptype->spawnparam2) + spawnspc = (int)ptype->spawnparam2; + break; + case SM_FIELD: + if (!avelocities[0][0]) + { + for (j=0 ; jspawntime && ts) + { + if (ts->state1.statetime > particletime) + return 0; // timelimit still in effect + + ts->state1.statetime = particletime + ptype->spawntime; // record old time + } + + // random chance for point effects + if (ptype->spawnchance < frandom()) + { + i = ceil(pcount); + break; + } + + /*this is a hack, use countextra=1, count=0*/ + if (!ptype->die && ptype->count == 1 && ptype->countrand == 0 && pcount < 1) + pcount = 1; + + // particle spawning loop + for (i = 0; i < pcount; i++) + { + if (!free_particles) + break; + p = free_particles; + if (ptype->looks.type == PT_BEAM) + { + if (!free_beams) + break; + if (b) + { + b = b->next = free_beams; + free_beams = free_beams->next; + } + else + { + b = bfirst = free_beams; + free_beams = free_beams->next; + } + b->texture_s = i; // TODO: FIX THIS NUMBER + b->flags = 0; + b->p = p; + VectorClear(b->dir); + } + free_particles = p->next; + p->next = ptype->particles; + ptype->particles = p; + + p->die = ptype->randdie*frandom(); + p->scale = ptype->scale+ptype->scalerand*frandom(); + if (ptype->die) + p->rgba[3] = ptype->alpha+p->die*ptype->alphachange; + else + p->rgba[3] = ptype->alpha; + p->rgba[3] += ptype->alpharand*frandom(); + // p->color = 0; + if (ptype->emittime < 0) + p->state.trailstate = NULL; + else + p->state.nextemit = particletime + ptype->emitstart - p->die; + + p->rotationspeed = ptype->rotationmin + frandom()*ptype->rotationrand; + p->angle = ptype->rotationstartmin + frandom()*ptype->rotationstartrand; + p->s1 = ptype->s1; + p->t1 = ptype->t1; + p->s2 = ptype->s2; + p->t2 = ptype->t2; + if (ptype->randsmax!=1) + { + m = ptype->texsstride * (rand()%ptype->randsmax); + p->s1 += m; + p->s2 += m; + } + + if (ptype->colorindex >= 0) + { + int cidx; + cidx = ptype->colorrand > 0 ? rand() % ptype->colorrand : 0; + cidx = ptype->colorindex + cidx; + if (cidx > 255) + p->rgba[3] = p->rgba[3] / 2; // Hexen 2 style transparency + cidx = (cidx & 0xff) * 4; + p->rgba[0] = palrgba[cidx] * (1/255.0); + p->rgba[1] = palrgba[cidx+1] * (1/255.0); + p->rgba[2] = palrgba[cidx+2] * (1/255.0); + } + else + VectorCopy(ptype->rgb, p->rgba); + + // use org temporarily for rgbsync + p->org[2] = frandom(); + p->org[0] = p->org[2]*ptype->rgbrandsync[0] + frandom()*(1-ptype->rgbrandsync[0]); + p->org[1] = p->org[2]*ptype->rgbrandsync[1] + frandom()*(1-ptype->rgbrandsync[1]); + p->org[2] = p->org[2]*ptype->rgbrandsync[2] + frandom()*(1-ptype->rgbrandsync[2]); + + p->rgba[0] += p->org[0]*ptype->rgbrand[0] + ptype->rgbchange[0]*p->die; + 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 0 + PScript_ApplyOrgVel(p->org, p->vel, org, axis, i, pcount, ptype); +#else + 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(); + ofsvec[2] = crandom(); + + arsvec[0] = ofsvec[0]*ptype->areaspread; + arsvec[1] = ofsvec[1]*ptype->areaspread; + arsvec[2] = ofsvec[2]*ptype->areaspreadvert; + break; + case SM_TELEBOX: + ofsvec[0] = k; + ofsvec[1] = j; + ofsvec[2] = l+4; + VectorNormalize(ofsvec); + VectorScale(ofsvec, 1.0-(frandom())*m, ofsvec); + + // org is just like the original + arsvec[0] = j + (rand()%spawnspc); + arsvec[1] = k + (rand()%spawnspc); + arsvec[2] = l + (rand()%spawnspc); + + // advance telebox loop + j += spawnspc; + if (j >= ptype->areaspread) + { + j = -ptype->areaspread; + k += spawnspc; + if (k >= ptype->areaspread) + { + k = -ptype->areaspread; + l += spawnspc; + if (l >= ptype->areaspreadvert) + l = -ptype->areaspreadvert; + } + } + break; + case SM_LAVASPLASH: + // calc directions, org with temp vector + ofsvec[0] = k + (rand()%spawnspc); + ofsvec[1] = j + (rand()%spawnspc); + ofsvec[2] = 256; + + arsvec[0] = ofsvec[0]; + arsvec[1] = ofsvec[1]; + arsvec[2] = frandom()*ptype->areaspreadvert; + + VectorNormalize(ofsvec); + VectorScale(ofsvec, 1.0-(frandom())*m, ofsvec); + + // advance splash loop + j += spawnspc; + if (j >= ptype->areaspread) + { + j = -ptype->areaspread; + k += spawnspc; + if (k >= ptype->areaspread) + k = -ptype->areaspread; + } + break; + case SM_UNICIRCLE: + ofsvec[0] = cos(m*i); + ofsvec[1] = sin(m*i); + ofsvec[2] = 0; + VectorScale(ofsvec, ptype->areaspread, arsvec); + break; + case SM_FIELD: + arsvec[0] = (cl.time * avelocities[i][0]) + m; + arsvec[1] = (cl.time * avelocities[i][1]) + m; + arsvec[2] = cos(arsvec[1]); + + ofsvec[0] = arsvec[2]*cos(arsvec[0]); + ofsvec[1] = arsvec[2]*sin(arsvec[0]); + ofsvec[2] = -sin(arsvec[1]); + +// arsvec[0] = r_avertexnormals[j][0]*ptype->areaspread + ofsvec[0]*BEAMLENGTH; +// arsvec[1] = r_avertexnormals[j][1]*ptype->areaspread + ofsvec[1]*BEAMLENGTH; +// arsvec[2] = r_avertexnormals[j][2]*ptype->areaspreadvert + ofsvec[2]*BEAMLENGTH; + + orgadd = ptype->spawnparam2 * sin(cl.time+j+m); + arsvec[0] = r_avertexnormals[j][0]*(ptype->areaspread+orgadd) + ofsvec[0]*ptype->spawnparam1; + arsvec[1] = r_avertexnormals[j][1]*(ptype->areaspread+orgadd) + ofsvec[1]*ptype->spawnparam1; + arsvec[2] = r_avertexnormals[j][2]*(ptype->areaspreadvert+orgadd) + ofsvec[2]*ptype->spawnparam1; + + VectorNormalize(ofsvec); + + j++; + if (j >= NUMVERTEXNORMALS) + { + j = 0; + m += 0.1762891; // some BS number to try to "randomize" things + } + break; + case SM_DISTBALL: + { + float rdist; + + rdist = ptype->spawnparam2 - crandom()*(1-(crandom() * ptype->spawnparam1)); + + // this is a strange spawntype, which is based on the fact that + // crandom()*crandom() provides something similar to an exponential + // probability curve + ofsvec[0] = hrandom(); + ofsvec[1] = hrandom(); + if (ptype->areaspreadvert) + ofsvec[2] = hrandom(); + else + ofsvec[2] = 0; + + VectorNormalize(ofsvec); + VectorScale(ofsvec, rdist, ofsvec); + + arsvec[0] = ofsvec[0]*ptype->areaspread; + arsvec[1] = ofsvec[1]*ptype->areaspread; + arsvec[2] = ofsvec[2]*ptype->areaspreadvert; + } + break; + default: // SM_BALL, SM_CIRCLE + { + ofsvec[0] = hrandom(); + ofsvec[1] = hrandom(); + if (ptype->areaspreadvert) + ofsvec[2] = hrandom(); + else + ofsvec[2] = 0; + + VectorNormalize(ofsvec); + if (ptype->spawnmode != SM_CIRCLE) + VectorScale(ofsvec, frandom(), ofsvec); + + arsvec[0] = ofsvec[0]*ptype->areaspread; + arsvec[1] = ofsvec[1]*ptype->areaspread; + arsvec[2] = ofsvec[2]*ptype->areaspreadvert; + } + break; + } + + // apply arsvec+ofsvec + orgadd = ptype->orgadd + frandom()*ptype->randomorgadd; + veladd = ptype->veladd + frandom()*ptype->randomveladd; +#if 1 + if (dir) + veladd *= VectorLength(dir); + VectorMA(p->vel, ofsvec[0]*ptype->spawnvel, axis[0], p->vel); + VectorMA(p->vel, ofsvec[1]*ptype->spawnvel, axis[1], p->vel); + VectorMA(p->vel, veladd+ofsvec[2]*ptype->spawnvelvert, axis[2], p->vel); + + VectorMA(org, arsvec[0], axis[0], p->org); + VectorMA(p->org, arsvec[1], axis[1], p->org); + VectorMA(p->org, orgadd+arsvec[2], axis[2], p->org); +#else + p->org[0] = org[0] + arsvec[0]; + p->org[1] = org[1] + arsvec[1]; + p->org[2] = org[2] + arsvec[2]; + if (dir) + { + p->vel[0] += dir[0]*veladd+ofsvec[0]*ptype->spawnvel; + p->vel[1] += dir[1]*veladd+ofsvec[1]*ptype->spawnvel; + p->vel[2] += dir[2]*veladd+ofsvec[2]*ptype->spawnvelvert; + + p->org[0] += dir[0]*orgadd; + p->org[1] += dir[1]*orgadd; + p->org[2] += dir[2]*orgadd; + } + else + { + p->vel[0] += ofsvec[0]*ptype->spawnvel; + p->vel[1] += ofsvec[1]*ptype->spawnvel; + p->vel[2] += ofsvec[2]*ptype->spawnvelvert - veladd; + + p->org[2] -= orgadd; + } +#endif + if (ptype->flags & PT_WORLDSPACERAND) + { + do + { + ofsvec[0] = crand(); + ofsvec[1] = crand(); + ofsvec[2] = crand(); + } while(DotProduct(ofsvec,ofsvec)>1); //crap, but I'm trying to mimic dp + p->org[0] += ofsvec[0] * ptype->orgwrand[0]; + p->org[1] += ofsvec[1] * ptype->orgwrand[1]; + p->org[2] += ofsvec[2] * ptype->orgwrand[2]; + p->vel[0] += ofsvec[0] * ptype->velwrand[0]; + p->vel[1] += ofsvec[1] * ptype->velwrand[1]; + p->vel[2] += ofsvec[2] * 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; + + VectorCopy(p->org, p->oldorg); + } + + // update beam list + if (ptype->looks.type == PT_BEAM) + { + if (b) + { + // update dir for bfirst for certain modes since it will never get updated + switch (ptype->spawnmode) + { + case SM_UNICIRCLE: + // kinda hackish here, assuming ofsvec contains the point at i-1 + arsvec[0] = cos(m*(i-2)); + arsvec[1] = sin(m*(i-2)); + arsvec[2] = 0; + VectorSubtract(b->p->org, arsvec, bfirst->dir); + VectorNormalize(bfirst->dir); + break; + default: + break; + } + + b->flags |= BS_NODRAW; + b->next = ptype->beams; + ptype->beams = bfirst; + } + } + + // save off emit times in trailstate + if (ts) + ts->state2.emittime = pcount - i; + + // maintain run list + if (!(ptype->state & PS_INRUNLIST) && (ptype->particles || ptype->clippeddecals)) + { + if (part_run_list) + { + //insert after, to try to avoid edge-case weirdness + ptype->nexttorun = part_run_list->nexttorun; + part_run_list->nexttorun = ptype; + } + else + { + ptype->nexttorun = part_run_list; + part_run_list = ptype; + } + ptype->state |= PS_INRUNLIST; + } + +skip: + + // go to next associated effect + if (ptype->assoc < 0) + break; + + // new trailstate + if (ts) + { + tsk = &(ts->assoc); + // if *tsk = NULL get a new one + if (*tsk == NULL) + { + ts = P_NewTrailstate(tsk); + *tsk = ts; + } + else + { + ts = *tsk; + + if (ts->key != tsk) // trailstate was overwritten + { + ts = P_NewTrailstate(tsk); // so get a new one + *tsk = ts; + } + } + } + + ptype = &part_type[ptype->assoc]; + } + + return 0; +} + +int PScript_RunParticleEffectTypeString (vec3_t org, vec3_t dir, float count, const char *name) +{ + int type = PScript_FindParticleType(name); + if (type < 0) + return 1; + + return PScript_RunParticleEffectState(org, dir, count, type, NULL); +} + +int PScript_EntParticleTrail(vec3_t oldorg, entity_t *ent, const char *name) +{ + vec3_t axis[3]; + int type = PScript_FindParticleType(name); + if (type < 0) + return 1; + + AngleVectors(ent->angles, axis[0], axis[1], axis[2]); + return PScript_ParticleTrail(oldorg, ent->origin, type, ent-cl.entities, axis, &ent->trailstate); +} + +/* +=============== +P_RunParticleEffect + +=============== +*/ +int PScript_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count) +{ + int ptype; + + ptype = PScript_FindParticleType(va("pe_%i", color)); + if (PScript_RunParticleEffectState(org, dir, count, ptype, NULL)) + { + if (count > 130 && PART_VALID(pe_size3)) + { + part_type[pe_size3].colorindex = color & ~0x7; + part_type[pe_size3].colorrand = 8; + return PScript_RunParticleEffectState(org, dir, count, pe_size3, NULL); + } + else if (count > 20 && PART_VALID(pe_size2)) + { + part_type[pe_size2].colorindex = color & ~0x7; + part_type[pe_size2].colorrand = 8; + return PScript_RunParticleEffectState(org, dir, count, pe_size2, NULL); + } + else if (PART_VALID(pe_default)) + { + part_type[pe_default].colorindex = color & ~0x7; + part_type[pe_default].colorrand = 8; + return PScript_RunParticleEffectState(org, dir, count, pe_default, NULL); + } +// else if (fallback) +// return fallback->RunParticleEffect(org, dir, color, count); + return true; + } + return false; +} + +#if UNSUPPORTED +//h2 stylie +static void PScript_RunParticleEffect2 (vec3_t org, vec3_t dmin, vec3_t dmax, int color, int effect, int count) +{ + int i, j; + float num; + float invcount; + vec3_t nvel; + + int ptype = PScript_FindParticleType(va("pe2_%i_%i", effect, color)); + if (!PART_VALID(ptype)) + { + ptype = PScript_FindParticleType(va("pe2_%i", effect)); + if (!PART_VALID(ptype)) + { + /*if (fallback) + { + fallback->RunParticleEffect2(org, dmin, dmax, color, effect, count); + return; + }*/ + ptype = pe_default; + } + if (!PART_VALID(ptype)) + return; + part_type[ptype].colorindex = color; + } + + invcount = 1/part_type[ptype].count; // using this to get R_RPET to always spawn 1 + count = count * part_type[ptype].count; + + for (i=0 ; iRunParticleEffect3(org, box, color, effect, count); +// return; +// } + ptype = pe_default; + } + if (!PART_VALID(ptype)) + return; + part_type[ptype].colorindex = color; + } + + invcount = 1/part_type[ptype].count; // using this to get R_RPET to always spawn 1 + count = count * part_type[ptype].count; + + for (i=0 ; iRunParticleEffect4(org, radius, color, effect, count); +// return; +// } + ptype = pe_default; + } + if (!PART_VALID(ptype)) + return; + part_type[ptype].colorindex = color; + } + + invcount = 1/part_type[ptype].count; // using this to get R_RPET to always spawn 1 + count = count * part_type[ptype].count; + + for (i=0 ; iveladd; + float step; + float stop; + float tdegree = 2.0*M_PI/256; /* MSVC whine */ + float sdegree = 0; + float nrfirst, nrlast; + byte *palrgba = (byte *)d_8to24table; + + VectorCopy(startpos, start); + + // eliminate trailstate if flag set + if (ptype->flags & PT_NOSTATE) + tsk = NULL; + + // trailstate allocation/deallocation + if (tsk) + { + // if *tsk = NULL get a new one + if (*tsk == NULL) + { + ts = P_NewTrailstate(tsk); + *tsk = ts; + } + else + { + ts = *tsk; + + if (ts->key != tsk) // trailstate was overwritten + { + ts = P_NewTrailstate(tsk); // so get a new one + *tsk = ts; + } + } + } + else + ts = NULL; + + PScript_EffectSpawned(ptype, start, dlaxis, dlkey, 1); + + if (ptype->assoc>=0) + { + if (ts) + PScript_ParticleTrail(start, end, ptype->assoc, dlkey, NULL, &(ts->assoc)); + else + PScript_ParticleTrail(start, end, ptype->assoc, dlkey, NULL, NULL); + } + + if (r_part_contentswitch.value && (ptype->flags & (PT_TRUNDERWATER | PT_TROVERWATER)) && cl.worldmodel) + { + unsigned int cont; + cont = CL_PointContentsMask(startpos); + + if ((ptype->flags & PT_TROVERWATER) && (cont & ptype->fluidmask)) + return; + if ((ptype->flags & PT_TRUNDERWATER) && !(cont & ptype->fluidmask)) + return; + } + + // time limit for trails + if (ptype->spawntime && ts) + { + if (ts->state1.statetime > particletime) + return; // timelimit still in effect + + ts->state1.statetime = particletime + ptype->spawntime; // record old time + ts = NULL; // clear trailstate so we don't save length/lastseg + } + + // random chance for trails + if (ptype->spawnchance < frandom()) + return; // don't spawn but return success + + if (!ptype->die) + ts = NULL; + + // use ptype step to calc step vector and step size + step = (ptype->count*r_part_density.value) + ptype->countextra; + if (step>0) + { + step = 1/step; + if (step < 0.01) + step = 0.01; + } + else + step = 0; + + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + if (ptype->flags & PT_AVERAGETRAIL) + { + float tavg; + // mangle len/step to get last point to be at end + tavg = len / step; + tavg = tavg / ceil(tavg); + step *= tavg; + len += step; + } + + VectorScale(vec, step, vstep); + + // add offset +// VectorAdd(start, ptype->orgbias, start); + + // spawn mode precalculations + if (ptype->spawnmode == SM_SPIRAL) + { + VectorVectors(vec, right, up); + + // precalculate degree of rotation + if (ptype->spawnparam1) + tdegree = 2.0*M_PI/ptype->spawnparam1; /* distance per rotation inversed */ + sdegree = ptype->spawnparam2*(M_PI/180); + } + else if (ptype->spawnmode == SM_CIRCLE) + { + VectorVectors(vec, right, up); + } + + // store last stop here for lack of a better solution besides vectors + if (ts) + { + ts->state2.laststop = stop = ts->state2.laststop + len; //when to stop + len = ts->state1.lastdist; + } + else + { + stop = len; + len = 0; + } + +// len = ts->lastdist/step; +// len = (len - (int)len)*step; +// VectorMA (start, -len, vec, start); + + if (ptype->flags & PT_NOSPREADFIRST) + nrfirst = len + step*1.5; + else + nrfirst = len; + + if (ptype->flags & PT_NOSPREADLAST) + nrlast = stop; + else + nrlast = stop + step; + + b = bfirst = NULL; + + if (len < stop) + count = (stop-len) / step; + else + { + count = 0; + step = 0; + VectorClear(vstep); + } + count += ptype->countextra; + + while (count-->0)//len < stop) + { + len += step; + + if (!free_particles) + { + len = stop; + break; + } + + p = free_particles; + if (ptype->looks.type == PT_BEAM) + { + if (!free_beams) + { + len = stop; + break; + } + if (b) + { + b = b->next = free_beams; + free_beams = free_beams->next; + } + else + { + b = bfirst = free_beams; + free_beams = free_beams->next; + } + b->texture_s = len; // not sure how to calc this + b->flags = 0; + b->p = p; + VectorCopy(vec, b->dir); + } + + free_particles = p->next; + p->next = ptype->particles; + ptype->particles = p; + + p->die = ptype->randdie*frandom(); + p->scale = ptype->scale+ptype->scalerand*frandom(); + if (ptype->die) + p->rgba[3] = ptype->alpha+p->die*ptype->alphachange; + else + p->rgba[3] = ptype->alpha; + p->rgba[3] += ptype->alpharand*frandom(); +// p->color = 0; + +// if (ptype->spawnmode == SM_TRACER) + if (ptype->spawnparam1) + tcount = (int)(len * ptype->count / ptype->spawnparam1); + else + tcount = (int)(len * ptype->count); + + if (ptype->colorindex >= 0) + { + int cidx; + cidx = ptype->colorrand > 0 ? rand() % ptype->colorrand : 0; + if (ptype->flags & PT_CITRACER) // colorindex behavior as per tracers in std Q1 + cidx += ((tcount & 4) << 1); + + cidx = ptype->colorindex + cidx; + if (cidx > 255) + p->rgba[3] = p->rgba[3] / 2; + cidx = (cidx & 0xff) * 4; + p->rgba[0] = palrgba[cidx] * (1/255.0); + p->rgba[1] = palrgba[cidx+1] * (1/255.0); + p->rgba[2] = palrgba[cidx+2] * (1/255.0); + } + else + VectorCopy(ptype->rgb, p->rgba); + + // use org temporarily for rgbsync + p->org[2] = frandom(); + p->org[0] = p->org[2]*ptype->rgbrandsync[0] + frandom()*(1-ptype->rgbrandsync[0]); + p->org[1] = p->org[2]*ptype->rgbrandsync[1] + frandom()*(1-ptype->rgbrandsync[1]); + p->org[2] = p->org[2]*ptype->rgbrandsync[2] + frandom()*(1-ptype->rgbrandsync[2]); + + p->rgba[0] += p->org[0]*ptype->rgbrand[0] + ptype->rgbchange[0]*p->die; + 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; + + VectorClear (p->vel); + if (ptype->emittime < 0) + p->state.trailstate = NULL; // init trailstate + else + p->state.nextemit = particletime + ptype->emitstart - p->die; + + p->rotationspeed = ptype->rotationmin + frandom()*ptype->rotationrand; + p->angle = ptype->rotationstartmin + frandom()*ptype->rotationstartrand; + p->s1 = ptype->s1; + p->t1 = ptype->t1; + p->s2 = ptype->s2; + p->t2 = ptype->t2; + if (ptype->randsmax!=1) + { + float offs; + offs = ptype->texsstride * (rand()%ptype->randsmax); + p->s1 += offs; + p->s2 += offs; + while (p->s1 >= 1) + { + p->s1 -= 1; + p->s2 -= 1; + p->t1 += ptype->texsstride; + p->t2 += ptype->texsstride; + } + } + + if (len < nrfirst || len >= nrlast) + { + // no offset or areaspread for these particles... + p->vel[0] = vec[0]*veladd; + p->vel[1] = vec[1]*veladd; + p->vel[2] = vec[2]*veladd; + + VectorCopy(start, p->org); + } + else + { + switch(ptype->spawnmode) + { + case SM_TRACER: + if (tcount & 1) + { + p->vel[0] = vec[1]*ptype->spawnvel; + p->vel[1] = -vec[0]*ptype->spawnvel; + p->org[0] = vec[1]*ptype->areaspread; + p->org[1] = -vec[0]*ptype->areaspread; + } + else + { + p->vel[0] = -vec[1]*ptype->spawnvel; + p->vel[1] = vec[0]*ptype->spawnvel; + p->org[0] = -vec[1]*ptype->areaspread; + p->org[1] = vec[0]*ptype->areaspread; + } + + p->vel[0] += vec[0]*veladd; + p->vel[1] += vec[1]*veladd; + p->vel[2] = vec[2]*veladd; + + p->org[0] += start[0]; + p->org[1] += start[1]; + p->org[2] = start[2]; + break; + case SM_SPIRAL: + { + float tsin, tcos; + float tright, tup; + + tcos = cos(len*tdegree+sdegree); + tsin = sin(len*tdegree+sdegree); + + tright = tcos*ptype->areaspread; + tup = tsin*ptype->areaspread; + + p->org[0] = start[0] + right[0]*tright + up[0]*tup; + p->org[1] = start[1] + right[1]*tright + up[1]*tup; + p->org[2] = start[2] + right[2]*tright + up[2]*tup; + + tright = tcos*ptype->spawnvel; + tup = tsin*ptype->spawnvel; + + p->vel[0] = vec[0]*veladd + right[0]*tright + up[0]*tup; + p->vel[1] = vec[1]*veladd + right[1]*tright + up[1]*tup; + p->vel[2] = vec[2]*veladd + right[2]*tright + up[2]*tup; + } + break; + // TODO: directionalize SM_BALL/SM_CIRCLE/SM_DISTBALL + case SM_BALL: + p->org[0] = crandom(); + p->org[1] = crandom(); + p->org[2] = crandom(); + VectorNormalize(p->org); + VectorScale(p->org, frandom(), p->org); + + p->vel[0] = vec[0]*veladd + p->org[0]*ptype->spawnvel; + p->vel[1] = vec[1]*veladd + p->org[1]*ptype->spawnvel; + p->vel[2] = vec[2]*veladd + p->org[2]*ptype->spawnvelvert; + + p->org[0] = p->org[0]*ptype->areaspread + start[0]; + p->org[1] = p->org[1]*ptype->areaspread + start[1]; + p->org[2] = p->org[2]*ptype->areaspreadvert + start[2]; + break; + + case SM_CIRCLE: + { + float tsin, tcos; + + tcos = cos(len*tdegree)*ptype->areaspread; + tsin = sin(len*tdegree)*ptype->areaspread; + + p->org[0] = start[0] + right[0]*tcos + up[0]*tsin + vstep[0] * (len*tdegree); + p->org[1] = start[1] + right[1]*tcos + up[1]*tsin + vstep[1] * (len*tdegree); + p->org[2] = start[2] + right[2]*tcos + up[2]*tsin + vstep[2] * (len*tdegree)*50; + + tcos = cos(len*tdegree)*ptype->spawnvel; + tsin = sin(len*tdegree)*ptype->spawnvel; + + p->vel[0] = vec[0]*veladd + right[0]*tcos + up[0]*tsin; + p->vel[1] = vec[1]*veladd + right[1]*tcos + up[1]*tsin; + p->vel[2] = vec[2]*veladd + right[2]*tcos + up[2]*tsin; + } + break; + + case SM_DISTBALL: + { + float rdist; + + rdist = ptype->spawnparam2 - crandom()*(1-(crandom() * ptype->spawnparam1)); + + // this is a strange spawntype, which is based on the fact that + // crandom()*crandom() provides something similar to an exponential + // probability curve + p->org[0] = crandom(); + p->org[1] = crandom(); + p->org[2] = crandom(); + + VectorNormalize(p->org); + VectorScale(p->org, rdist, p->org); + + p->vel[0] = vec[0]*veladd + p->org[0]*ptype->spawnvel; + p->vel[1] = vec[1]*veladd + p->org[1]*ptype->spawnvel; + p->vel[2] = vec[2]*veladd + p->org[2]*ptype->spawnvelvert; + + p->org[0] = p->org[0]*ptype->areaspread + start[0]; + p->org[1] = p->org[1]*ptype->areaspread + start[1]; + p->org[2] = p->org[2]*ptype->areaspreadvert + start[2]; + } + break; + default: + p->org[0] = crandom(); + p->org[1] = crandom(); + p->org[2] = crandom(); + + p->vel[0] = vec[0]*veladd + p->org[0]*ptype->spawnvel; + p->vel[1] = vec[1]*veladd + p->org[1]*ptype->spawnvel; + p->vel[2] = vec[2]*veladd + p->org[2]*ptype->spawnvelvert; + + p->org[0] = p->org[0]*ptype->areaspread + start[0]; + p->org[1] = p->org[1]*ptype->areaspread + start[1]; + p->org[2] = p->org[2]*ptype->areaspreadvert + start[2]; + break; + } + + if (ptype->orgadd) + { + p->org[0] += vec[0]*ptype->orgadd; + p->org[1] += vec[1]*ptype->orgadd; + p->org[2] += vec[2]*ptype->orgadd; + } + } + if (ptype->flags & PT_WORLDSPACERAND) + { + vec3_t vtmp; + do + { + vtmp[0] = crand(); + vtmp[1] = crand(); + vtmp[2] = crand(); + } while(DotProduct(vtmp,vtmp)>1); //crap, but I'm trying to mimic dp + p->org[0] += vtmp[0] * ptype->orgwrand[0]; + p->org[1] += vtmp[1] * ptype->orgwrand[1]; + p->org[2] += vtmp[2] * ptype->orgwrand[2]; + p->vel[0] += vtmp[0] * ptype->velwrand[0]; + p->vel[1] += vtmp[1] * ptype->velwrand[1]; + p->vel[2] += vtmp[2] * ptype->velwrand[2]; + VectorAdd(p->vel, ptype->velbias, p->vel); + } + VectorAdd(p->org, ptype->orgbias, p->org); + + VectorAdd (start, vstep, start); + + if (ptype->countrand) + { + float rstep = frandom() / ptype->countrand; + VectorMA(start, rstep, vec, start); + step += rstep; + } + + p->die = particletime + ptype->die - p->die; + VectorCopy(p->org, p->oldorg); + } + + if (ts) + { + ts->state1.lastdist = len; + + // update beamseg list + if (ptype->looks.type == PT_BEAM) + { + if (b) + { + if (ptype->beams) + { + if (ts->lastbeam) + { + b->next = ts->lastbeam->next; + ts->lastbeam->next = bfirst; + ts->lastbeam->flags &= ~BS_LASTSEG; + } + else + { + b->next = ptype->beams; + ptype->beams = bfirst; + } + } + else + { + ptype->beams = bfirst; + b->next = NULL; + } + + b->flags |= BS_LASTSEG; + ts->lastbeam = b; + } + + if ((!free_particles || !free_beams) && ts->lastbeam) + { + ts->lastbeam->flags &= ~BS_LASTSEG; + ts->lastbeam->flags |= BS_NODRAW; + ts->lastbeam = NULL; + } + } + } + else if (ptype->looks.type == PT_BEAM) + { + if (b) + { + b->flags |= BS_NODRAW; + b->next = ptype->beams; + ptype->beams = bfirst; + } + } + + // maintain run list + if (!(ptype->state & PS_INRUNLIST)) + { + ptype->nexttorun = part_run_list; + part_run_list = ptype; + ptype->state |= PS_INRUNLIST; + } + + return; +} + +int PScript_ParticleTrail (vec3_t startpos, vec3_t end, int type, int dlkey, vec3_t axis[3], trailstate_t **tsk) +{ + part_type_t *ptype = &part_type[type]; + + // TODO: fallback particle system won't have a decent trailstate which will mess up + // high fps trails +// if (type >= FALLBACKBIAS && fallback) +// return fallback->ParticleTrail(startpos, end, type-FALLBACKBIAS, dlkey, axis, NULL); + + if (type < 0 || type >= numparticletypes) + return 1; //bad value + + if (!ptype->loaded) + return 1; + + // inwater check, switch only once + if (r_part_contentswitch.value && ptype->inwater >= 0 && cl.worldmodel) + { + unsigned int cont; + cont = CL_PointContentsMask(startpos); + + if (cont & FTECONTENTS_FLUID) + ptype = &part_type[ptype->inwater]; + } + + PScript_ParticleTrailDraw (startpos, end, ptype, tsk, dlkey, axis); + return 0; +} + +static vec3_t pright, pup; + +static void R_AddFanSparkParticle(scenetris_t *t, particle_t *p, plooks_t *type) +{ + vec3_t v, cr, o2; + float scale; + + if (cl_numstrisvert+3 > cl_maxstrisvert) + { + cl_maxstrisvert+=64*3; + cl_strisvertv = Z_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = Z_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = Z_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + + scale = (p->org[0] - r_origin[0])*vpn[0] + (p->org[1] - r_origin[1])*vpn[1] + + (p->org[2] - r_origin[2])*vpn[2]; + scale = (scale*p->scale)*(type->invscalefactor) + p->scale * (type->scalefactor*250); + if (scale < 20) + scale = 0.05; + else + scale = 0.05 + scale * 0.0001; + + if (type->premul) + { + vec4_t rgba; + float a = p->rgba[3]; + if (a > 1) + a = 1; + rgba[0] = p->rgba[0] * a; + rgba[1] = p->rgba[1] * a; + rgba[2] = p->rgba[2] * a; + rgba[3] = (type->premul==2)?0:a; + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+2]); + } + else + { + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+2]); + } + + Vector2Set(cl_strisvertt[cl_numstrisvert+0], p->s1, p->t1); + Vector2Set(cl_strisvertt[cl_numstrisvert+1], p->s1, p->t2); + Vector2Set(cl_strisvertt[cl_numstrisvert+2], p->s2, p->t1); + + + VectorMA(p->org, -scale, p->vel, o2); + VectorSubtract(r_refdef.vieworg, o2, v); + CrossProduct(v, p->vel, cr); + VectorNormalize(cr); + + VectorCopy(p->org, cl_strisvertv[cl_numstrisvert+0]); + VectorMA(o2, -p->scale, cr, cl_strisvertv[cl_numstrisvert+1]); + VectorMA(o2, p->scale, cr, cl_strisvertv[cl_numstrisvert+2]); + + if (cl_numstrisidx+3 > cl_maxstrisidx) + { + cl_maxstrisidx += 64*3; + cl_strisidx = Z_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 1; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + + cl_numstrisvert += 3; + + t->numvert += 3; + t->numidx += 3; + +} + +//static void R_AddLineSparkParticle(int count, particle_t **plist, plooks_t *type) +static void R_AddLineSparkParticle(scenetris_t *t, particle_t *p, plooks_t *type) +{ + if (cl_numstrisvert+2 > cl_maxstrisvert) + { + cl_maxstrisvert+=64*2; + cl_strisvertv = Z_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = Z_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = Z_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + + if (type->premul) + { + float a = p->rgba[3]; + if (a > 1) + a = 1; + VectorScale(p->rgba, a, cl_strisvertc[cl_numstrisvert+0]); + cl_strisvertc[cl_numstrisvert+0][3] = (type->premul==2)?0:a; + Vector4Clear(cl_strisvertc[cl_numstrisvert+1]); + } + else + { + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+0]); + VectorCopy(p->rgba, cl_strisvertc[cl_numstrisvert+1]); + cl_strisvertc[cl_numstrisvert+1][3] = 0; + } + Vector2Set(cl_strisvertt[cl_numstrisvert+0], p->s1, p->t1); + Vector2Set(cl_strisvertt[cl_numstrisvert+1], p->s2, p->t2); + + VectorCopy(p->org, cl_strisvertv[cl_numstrisvert+0]); + VectorMA(p->org, -1.0/10, p->vel, cl_strisvertv[cl_numstrisvert+1]); + + if (cl_numstrisidx+2 > cl_maxstrisidx) + { + cl_maxstrisidx += 64*2; + cl_strisidx = Z_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 1; + + cl_numstrisvert += 2; + + t->numvert += 2; + t->numidx += 2; +} + +static void R_AddTSparkParticle(scenetris_t *t, particle_t *p, plooks_t *type) +{ + vec3_t v, cr, o2; +// float scale; + + if (cl_numstrisvert+4 > cl_maxstrisvert) + { + cl_maxstrisvert+=64*4; + cl_strisvertv = Z_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = Z_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = Z_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + + if (type->premul) + { + vec4_t rgba; + float a = p->rgba[3]; + if (a > 1) + a = 1; + rgba[0] = p->rgba[0] * a; + rgba[1] = p->rgba[1] * a; + rgba[2] = p->rgba[2] * a; + rgba[3] = (type->premul==2)?0:a; + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+2]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+3]); + } + else + { + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+2]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+3]); + } + + Vector2Set(cl_strisvertt[cl_numstrisvert+0], p->s1, p->t1); + Vector2Set(cl_strisvertt[cl_numstrisvert+1], p->s1, p->t2); + Vector2Set(cl_strisvertt[cl_numstrisvert+2], p->s2, p->t2); + Vector2Set(cl_strisvertt[cl_numstrisvert+3], p->s2, p->t1); + + { + vec3_t movedir; + float halfscale = p->scale*0.5; + float length = VectorNormalize2(p->vel, movedir); + if (type->stretch < 0) + length = -type->stretch; //fixed lengths + else if (type->stretch) + length *= type->stretch; //velocity multiplier + else + Sys_Error("type->stretch should be 0.05\n"); +// length *= 0.05; //fallback + + if (length < halfscale * type->minstretch) + length = halfscale * type->minstretch; + + VectorMA(p->org, -length, movedir, o2); + VectorSubtract(r_refdef.vieworg, o2, v); + CrossProduct(v, p->vel, cr); + VectorNormalize(cr); + VectorMA(o2, -p->scale/2, cr, cl_strisvertv[cl_numstrisvert+0]); + VectorMA(o2, p->scale/2, cr, cl_strisvertv[cl_numstrisvert+1]); + + VectorMA(p->org, length, movedir, o2); + } + + VectorSubtract(r_refdef.vieworg, o2, v); + CrossProduct(v, p->vel, cr); + VectorNormalize(cr); + + VectorMA(o2, p->scale*0.5, cr, cl_strisvertv[cl_numstrisvert+2]); + VectorMA(o2, -p->scale*0.5, cr, cl_strisvertv[cl_numstrisvert+3]); + + + + if (cl_numstrisidx+6 > cl_maxstrisidx) + { + cl_maxstrisidx += 64*6; + cl_strisidx = Z_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 1; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 3; + + cl_numstrisvert += 4; + + t->numvert += 4; + t->numidx += 6; +} + +static void R_DrawParticleBeam(scenetris_t *t, beamseg_t *b, plooks_t *type) +{ + vec3_t v; + vec3_t cr; + beamseg_t *c; + particle_t *p; + particle_t *q; + float ts; + + c = b->next; + + q = c->p; + if (!q) + return; + p = b->p; + + if (cl_numstrisvert+4 > cl_maxstrisvert) + { + cl_maxstrisvert+=64*4; + cl_strisvertv = Z_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = Z_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = Z_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + +// q->rgba[3] = 1; +// p->rgba[3] = 1; + + VectorSubtract(r_refdef.vieworg, q->org, v); + VectorNormalize(v); + CrossProduct(c->dir, v, cr); + VectorNormalize(cr); + ts = c->texture_s*q->angle + particletime*q->rotationspeed; + Vector4Copy(q->rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(q->rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector2Set(cl_strisvertt[cl_numstrisvert+0], ts, p->t1); + Vector2Set(cl_strisvertt[cl_numstrisvert+1], ts, p->t2); + VectorMA(q->org, -q->scale, cr, cl_strisvertv[cl_numstrisvert+0]); + VectorMA(q->org, q->scale, cr, cl_strisvertv[cl_numstrisvert+1]); + + VectorSubtract(r_refdef.vieworg, p->org, v); + VectorNormalize(v); + CrossProduct(b->dir, v, cr); // replace with old p->dir? + VectorNormalize(cr); + ts = b->texture_s*p->angle + particletime*p->rotationspeed; + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+2]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+3]); + Vector2Set(cl_strisvertt[cl_numstrisvert+2], ts, p->t2); + Vector2Set(cl_strisvertt[cl_numstrisvert+3], ts, p->t1); + VectorMA(p->org, p->scale, cr, cl_strisvertv[cl_numstrisvert+2]); + VectorMA(p->org, -p->scale, cr, cl_strisvertv[cl_numstrisvert+3]); + + t->numvert += 4; + + if (cl_numstrisidx+6 > cl_maxstrisidx) + { + cl_maxstrisidx += 64*6; + cl_strisidx = Z_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 1; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 3; + cl_numstrisvert += 4; + t->numidx += 4; +} + +static void R_AddClippedDecal(scenetris_t *t, clippeddecal_t *d, plooks_t *type) +{ + if (cl_numstrisvert+4 > cl_maxstrisvert) + { + cl_maxstrisvert+=64*4; + cl_strisvertv = Z_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = Z_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = Z_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + + if (d->entity > 0) + { + entity_t *le = CL_EntityNum(d->entity); + if (le->angles[0] || le->angles[1] || le->angles[2]) + { //FIXME: deal with rotated entities. + d->die = -1; + return; + } + VectorAdd(d->vertex[0], le->origin, cl_strisvertv[cl_numstrisvert+0]); + VectorAdd(d->vertex[1], le->origin, cl_strisvertv[cl_numstrisvert+1]); + VectorAdd(d->vertex[2], le->origin, cl_strisvertv[cl_numstrisvert+2]); + } + else + { + VectorCopy(d->vertex[0], cl_strisvertv[cl_numstrisvert+0]); + VectorCopy(d->vertex[1], cl_strisvertv[cl_numstrisvert+1]); + VectorCopy(d->vertex[2], cl_strisvertv[cl_numstrisvert+2]); + } + + if (type->premul) + { + vec4_t rgba; + float a = d->rgba[3]; + if (a > 1) + a = 1; + rgba[0] = d->rgba[0] * a; + rgba[1] = d->rgba[1] * a; + rgba[2] = d->rgba[2] * a; + rgba[3] = (type->premul==2)?0:a; + Vector4Scale(rgba, d->valpha[0], cl_strisvertc[cl_numstrisvert+0]); + Vector4Scale(rgba, d->valpha[1], cl_strisvertc[cl_numstrisvert+1]); + Vector4Scale(rgba, d->valpha[2], cl_strisvertc[cl_numstrisvert+2]); + } + else + { + Vector4Copy(d->rgba, cl_strisvertc[cl_numstrisvert+0]); + cl_strisvertc[cl_numstrisvert+0][3] *= d->valpha[0]; + Vector4Copy(d->rgba, cl_strisvertc[cl_numstrisvert+1]); + cl_strisvertc[cl_numstrisvert+1][3] *= d->valpha[1]; + Vector4Copy(d->rgba, cl_strisvertc[cl_numstrisvert+2]); + cl_strisvertc[cl_numstrisvert+2][3] *= d->valpha[2]; + } + + Vector2Copy(d->texcoords[0], cl_strisvertt[cl_numstrisvert+0]); + Vector2Copy(d->texcoords[1], cl_strisvertt[cl_numstrisvert+1]); + Vector2Copy(d->texcoords[2], cl_strisvertt[cl_numstrisvert+2]); + + if (cl_numstrisidx+3 > cl_maxstrisidx) + { + cl_maxstrisidx += 64*3; + cl_strisidx = Z_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 1; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + + cl_numstrisvert += 3; + + t->numvert += 3; + t->numidx += 3; +} + +static void R_AddUnclippedDecal(scenetris_t *t, particle_t *p, plooks_t *type) +{ + float x, y; + vec3_t sdir, tdir; + + if (cl_numstrisvert+4 > cl_maxstrisvert) + { + cl_maxstrisvert+=64*4; + cl_strisvertv = Z_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = Z_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = Z_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + + if (type->premul) + { + vec4_t rgba; + float a = p->rgba[3]; + if (a > 1) + a = 1; + rgba[0] = p->rgba[0] * a; + rgba[1] = p->rgba[1] * a; + rgba[2] = p->rgba[2] * a; + rgba[3] = (type->premul==2)?0:a; + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+2]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+3]); + } + else + { + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+2]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+3]); + } + + Vector2Set(cl_strisvertt[cl_numstrisvert+0], p->s1, p->t1); + Vector2Set(cl_strisvertt[cl_numstrisvert+1], p->s1, p->t2); + Vector2Set(cl_strisvertt[cl_numstrisvert+2], p->s2, p->t2); + Vector2Set(cl_strisvertt[cl_numstrisvert+3], p->s2, p->t1); + +// if (p->vel[1] == 1) + { + VectorSet(sdir, 1, 0, 0); + VectorSet(tdir, 0, 1, 0); + } + + if (p->angle) + { + x = sin(p->angle)*p->scale; + y = cos(p->angle)*p->scale; + + cl_strisvertv[cl_numstrisvert+0][0] = p->org[0] - x*sdir[0] - y*tdir[0]; + cl_strisvertv[cl_numstrisvert+0][1] = p->org[1] - x*sdir[1] - y*tdir[1]; + cl_strisvertv[cl_numstrisvert+0][2] = p->org[2] - x*sdir[2] - y*tdir[2]; + cl_strisvertv[cl_numstrisvert+1][0] = p->org[0] - y*sdir[0] + x*tdir[0]; + cl_strisvertv[cl_numstrisvert+1][1] = p->org[1] - y*sdir[1] + x*tdir[1]; + cl_strisvertv[cl_numstrisvert+1][2] = p->org[2] - y*sdir[2] + x*tdir[2]; + cl_strisvertv[cl_numstrisvert+2][0] = p->org[0] + x*sdir[0] + y*tdir[0]; + cl_strisvertv[cl_numstrisvert+2][1] = p->org[1] + x*sdir[1] + y*tdir[1]; + cl_strisvertv[cl_numstrisvert+2][2] = p->org[2] + x*sdir[2] + y*tdir[2]; + cl_strisvertv[cl_numstrisvert+3][0] = p->org[0] + y*sdir[0] - x*tdir[0]; + cl_strisvertv[cl_numstrisvert+3][1] = p->org[1] + y*sdir[1] - x*tdir[1]; + cl_strisvertv[cl_numstrisvert+3][2] = p->org[2] + y*sdir[2] - x*tdir[2]; + } + else + { + VectorMA(p->org, -p->scale, tdir, cl_strisvertv[cl_numstrisvert+0]); + VectorMA(p->org, -p->scale, sdir, cl_strisvertv[cl_numstrisvert+1]); + VectorMA(p->org, p->scale, tdir, cl_strisvertv[cl_numstrisvert+2]); + VectorMA(p->org, p->scale, sdir, cl_strisvertv[cl_numstrisvert+3]); + } + + if (cl_numstrisidx+6 > cl_maxstrisidx) + { + cl_maxstrisidx += 64*6; + cl_strisidx = Z_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 1; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 3; + + cl_numstrisvert += 4; + + t->numvert += 4; + t->numidx += 6; +} + +static void R_AddTexturedParticle(scenetris_t *t, particle_t *p, plooks_t *type) +{ + float scale, x, y; + + if (cl_numstrisvert+4 > cl_maxstrisvert) + { + cl_maxstrisvert+=64*4; + cl_strisvertv = Z_Realloc(cl_strisvertv, sizeof(*cl_strisvertv)*cl_maxstrisvert); + cl_strisvertt = Z_Realloc(cl_strisvertt, sizeof(*cl_strisvertt)*cl_maxstrisvert); + cl_strisvertc = Z_Realloc(cl_strisvertc, sizeof(*cl_strisvertc)*cl_maxstrisvert); + } + + if (type->scalefactor == 1) + scale = p->scale*0.25; + else + { + scale = (p->org[0] - r_origin[0])*vpn[0] + (p->org[1] - r_origin[1])*vpn[1] + + (p->org[2] - r_origin[2])*vpn[2]; + scale = (scale*p->scale)*(type->invscalefactor) + p->scale * (type->scalefactor*250); + if (scale < 20) + scale = 0.25; + else + scale = 0.25 + scale * 0.001; + } + + if (type->premul) + { + vec4_t rgba; + float a = p->rgba[3]; + if (a > 1) + a = 1; + rgba[0] = p->rgba[0] * a; + rgba[1] = p->rgba[1] * a; + rgba[2] = p->rgba[2] * a; + rgba[3] = (type->premul==2)?0:a; + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+2]); + Vector4Copy(rgba, cl_strisvertc[cl_numstrisvert+3]); + } + else + { + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+0]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+1]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+2]); + Vector4Copy(p->rgba, cl_strisvertc[cl_numstrisvert+3]); + } + + Vector2Set(cl_strisvertt[cl_numstrisvert+0], p->s1, p->t1); + Vector2Set(cl_strisvertt[cl_numstrisvert+1], p->s1, p->t2); + Vector2Set(cl_strisvertt[cl_numstrisvert+2], p->s2, p->t2); + Vector2Set(cl_strisvertt[cl_numstrisvert+3], p->s2, p->t1); + + if (p->angle) + { + x = sin(p->angle)*scale; + y = cos(p->angle)*scale; + + cl_strisvertv[cl_numstrisvert+0][0] = p->org[0] - x*pright[0] - y*pup[0]; + cl_strisvertv[cl_numstrisvert+0][1] = p->org[1] - x*pright[1] - y*pup[1]; + cl_strisvertv[cl_numstrisvert+0][2] = p->org[2] - x*pright[2] - y*pup[2]; + cl_strisvertv[cl_numstrisvert+1][0] = p->org[0] - y*pright[0] + x*pup[0]; + cl_strisvertv[cl_numstrisvert+1][1] = p->org[1] - y*pright[1] + x*pup[1]; + cl_strisvertv[cl_numstrisvert+1][2] = p->org[2] - y*pright[2] + x*pup[2]; + cl_strisvertv[cl_numstrisvert+2][0] = p->org[0] + x*pright[0] + y*pup[0]; + cl_strisvertv[cl_numstrisvert+2][1] = p->org[1] + x*pright[1] + y*pup[1]; + cl_strisvertv[cl_numstrisvert+2][2] = p->org[2] + x*pright[2] + y*pup[2]; + cl_strisvertv[cl_numstrisvert+3][0] = p->org[0] + y*pright[0] - x*pup[0]; + cl_strisvertv[cl_numstrisvert+3][1] = p->org[1] + y*pright[1] - x*pup[1]; + cl_strisvertv[cl_numstrisvert+3][2] = p->org[2] + y*pright[2] - x*pup[2]; + } + else + { + VectorMA(p->org, -scale, pup, cl_strisvertv[cl_numstrisvert+0]); + VectorMA(p->org, -scale, pright, cl_strisvertv[cl_numstrisvert+1]); + VectorMA(p->org, scale, pup, cl_strisvertv[cl_numstrisvert+2]); + VectorMA(p->org, scale, pright, cl_strisvertv[cl_numstrisvert+3]); + } + + if (cl_numstrisidx+6 > cl_maxstrisidx) + { + cl_maxstrisidx += 64*6; + cl_strisidx = Z_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx); + } + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 1; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 0; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 2; + cl_strisidx[cl_numstrisidx++] = (cl_numstrisvert - t->firstvert) + 3; + + cl_numstrisvert += 4; + + t->numvert += 4; + t->numidx += 6; +} + +static void PScript_DrawParticleTypes (float pframetime) +{ +#if UNSUPPORTED + float viewtranslation[16]; + static float lastviewmatrix[16]; +#endif + + void (*bdraw)(scenetris_t *t, beamseg_t *p, plooks_t *type); + void (*tdraw)(scenetris_t *t, particle_t *p, plooks_t *type); + + vec3_t oldorg; + vec3_t stop, normal; + part_type_t *type, *lastvalidtype; + particle_t *p, *kill; + clippeddecal_t *d, *dkill; + ramp_t *ramp; + float grav; + vec3_t friction; + scenetris_t *scenetri; + float dist; + particle_t *kill_list, *kill_first; //the kill list is to stop particles from being freed and reused whilst still in this loop + //which is bad because beams need to find out when particles died. Reuse can do wierd things. + //remember that they're not drawn instantly either. + beamseg_t *b, *bkill; + + int traces=r_particle_tracelimit.value; + int rampind; + static float flurrytime; + qboolean doflurry; + int batchflags; + unsigned int i, o; + + if (r_plooksdirty) + { + int i, j; + + pe_default = PScript_FindParticleType("PE_DEFAULT"); + pe_size2 = PScript_FindParticleType("PE_SIZE2"); + pe_size3 = PScript_FindParticleType("PE_SIZE3"); + pe_defaulttrail = PScript_FindParticleType("PE_DEFAULTTRAIL"); + + for (i = 0; i < numparticletypes; i++) + { + //set the fallback + part_type[i].slooks = &part_type[i].looks; + for (j = i-1; j-- > 0;) + { + if (!memcmp(&part_type[i].looks, &part_type[j].looks, sizeof(plooks_t))) + { + part_type[i].slooks = part_type[j].slooks; + break; + } + } + } + r_plooksdirty = false; + CL_RegisterParticles(); + PScript_RecalculateSkyTris(); + } + + VectorScale (vup, 1.5, pup); + VectorScale (vright, 1.5, pright); + + kill_list = kill_first = NULL; + + flurrytime -= pframetime; + if (flurrytime < 0) + { + doflurry = true; + flurrytime = 0.1+frandom()*0.3; + } + else + doflurry = false; + + + if (!free_decals) + { + //mark some as dead, so we can keep spawning new ones next frame. + for (i = 0; i < 256; i++) + { + decals[r_decalrecycle].die = -1; + if (++r_decalrecycle >= r_numdecals) + r_decalrecycle = 0; + } + } + if (!free_particles) + { + //mark some as dead. + for (i = 0; i < 256; i++) + { + particles[r_particlerecycle].die = -1; + if (++r_particlerecycle >= r_numparticles) + r_particlerecycle = 0; + } + } + +#if UNSUPPORTED + { + float tmp[16]; + Matrix4_Invert(r_refdef.m_view, tmp); + Matrix4_Multiply(tmp, lastviewmatrix, viewtranslation); + memcpy(lastviewmatrix, r_refdef.m_view, sizeof(tmp)); + } +#endif + + for (type = part_run_list, lastvalidtype = NULL; type != NULL; type = type->nexttorun) + { + if (type->clippeddecals) + { + if (cl_numstris && cl_stris[cl_numstris-1].texture == type->looks.texture && cl_stris[cl_numstris-1].blendmode == type->looks.blendmode && cl_stris[cl_numstris-1].beflags == 0) + scenetri = &cl_stris[cl_numstris-1]; + else + { + if (cl_numstris == cl_maxstris) + { + cl_maxstris+=8; + cl_stris = Z_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris); + } + scenetri = &cl_stris[cl_numstris++]; + scenetri->texture = type->looks.texture; + scenetri->blendmode = type->looks.blendmode; + scenetri->beflags = 0; + scenetri->firstidx = cl_numstrisidx; + scenetri->firstvert = cl_numstrisvert; + scenetri->numvert = 0; + scenetri->numidx = 0; + } + + for ( ;; ) + { + dkill = type->clippeddecals; + if (dkill && dkill->die < particletime) + { + type->clippeddecals = dkill->next; + dkill->next = free_decals; + free_decals = dkill; + continue; + } + break; + } + for (d=type->clippeddecals ; d ; d=d->next) + { + for ( ;; ) + { + dkill = d->next; + if (dkill && dkill->die < particletime) + { + d->next = dkill->next; + dkill->next = free_decals; + free_decals = dkill; + continue; + } + break; + } + + + if (d->die - particletime <= type->die) + { + switch (type->rampmode) + { + case RAMP_NEAREST: + rampind = (int)(type->rampindexes * (type->die - (d->die - particletime)) / type->die); + if (rampind >= type->rampindexes) + rampind = type->rampindexes - 1; + ramp = type->ramp + rampind; + VectorCopy(ramp->rgb, d->rgba); + d->rgba[3] = ramp->alpha; + break; + case RAMP_LERP: + { + float frac = (type->rampindexes * (type->die - (d->die - particletime)) / type->die); + int s1, s2; + s1 = frac; + s2 = s1+1; + if (s1 > type->rampindexes-1) + s1 = type->rampindexes-1; + if (s2 > type->rampindexes-1) + s2 = type->rampindexes-1; + frac -= s1; + VectorInterpolate(type->ramp[s1].rgb, frac, type->ramp[s2].rgb, d->rgba); + FloatInterpolate(type->ramp[s1].alpha, frac, type->ramp[s2].alpha, d->rgba[3]); + } + break; + case RAMP_DELTA: //particle ramps + ramp = type->ramp + (int)(type->rampindexes * (type->die - (d->die - particletime)) / type->die); + VectorMA(d->rgba, pframetime, ramp->rgb, d->rgba); + d->rgba[3] -= pframetime*ramp->alpha; + break; + case RAMP_NONE: //particle changes acording to it's preset properties. + if (particletime < (d->die-type->die+type->rgbchangetime)) + { + d->rgba[0] += pframetime*type->rgbchange[0]; + d->rgba[1] += pframetime*type->rgbchange[1]; + d->rgba[2] += pframetime*type->rgbchange[2]; + } + d->rgba[3] += pframetime*type->alphachange; + } + } + + if (cl_numstrisvert - scenetri->firstvert >= MAX_INDICIES-6) + { + //generate a new mesh if the old one overflowed. yay smc... + if (cl_numstris == cl_maxstris) + { + cl_maxstris+=8; + cl_stris = Z_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris); + } + scenetri = &cl_stris[cl_numstris++]; + scenetri->texture = scenetri[-1].texture; + scenetri->blendmode = scenetri[-1].blendmode; + scenetri->beflags = scenetri[-1].beflags; + scenetri->firstidx = cl_numstrisidx; + scenetri->firstvert = cl_numstrisvert; + scenetri->numvert = 0; + scenetri->numidx = 0; + } + R_AddClippedDecal(scenetri, d, type->slooks); + } + } + + bdraw = NULL; + tdraw = NULL; + batchflags = 0; + + // set drawing methods by type and cvars and hope branch + // prediction takes care of the rest + switch(type->looks.type) + { + default: + case PT_INVISIBLE: + break; + case PT_BEAM: + bdraw = R_DrawParticleBeam; + break; + case PT_CDECAL: + break; + case PT_UDECAL: + tdraw = R_AddUnclippedDecal; + break; + case PT_NORMAL: + tdraw = R_AddTexturedParticle; + break; + case PT_SPARK: + tdraw = R_AddLineSparkParticle; + batchflags = BEF_LINES; + break; + case PT_SPARKFAN: + tdraw = R_AddFanSparkParticle; + break; + case PT_TEXTUREDSPARK: + tdraw = R_AddTSparkParticle; + break; + } + + if (cl_numstris && cl_stris[cl_numstris-1].texture == type->looks.texture && cl_stris[cl_numstris-1].blendmode == type->looks.blendmode && cl_stris[cl_numstris-1].beflags == batchflags) + scenetri = &cl_stris[cl_numstris-1]; + else + { + if (cl_numstris == cl_maxstris) + { + cl_maxstris+=8; + cl_stris = Z_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris); + } + scenetri = &cl_stris[cl_numstris++]; + scenetri->texture = type->looks.texture; + scenetri->blendmode = type->looks.blendmode; + scenetri->beflags = batchflags; + scenetri->firstidx = cl_numstrisidx; + scenetri->firstvert = cl_numstrisvert; + scenetri->numvert = 0; + scenetri->numidx = 0; + } + + if (!type->die) + { + while ((p=type->particles)) + { + if (scenetri && tdraw) + { + if (cl_numstrisvert - scenetri->firstvert >= MAX_INDICIES-6) + { + //generate a new mesh if the old one overflowed. yay smc... + if (cl_numstris == cl_maxstris) + { + cl_maxstris+=8; + cl_stris = Z_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris); + } + scenetri = &cl_stris[cl_numstris++]; + scenetri->texture = scenetri[-1].texture; + scenetri->blendmode = scenetri[-1].blendmode; + scenetri->beflags = scenetri[-1].beflags; + scenetri->firstidx = cl_numstrisidx; + scenetri->firstvert = cl_numstrisvert; + scenetri->numvert = 0; + scenetri->numidx = 0; + } + tdraw(scenetri, p, type->slooks); + } + + // make sure emitter runs at least once + if (type->emit >= 0 && type->emitstart <= 0) + PScript_RunParticleEffectState(p->org, p->vel, 1, type->emit, NULL); + +#if UNSUPPORTED + // make sure stain effect runs + if (type->stainonimpact && r_bloodstains.value) + { + if (traces-->0&&CL_TraceLine(oldorg, p->org, stop, normal, NULL)<1) + { + Surf_AddStain(stop, (p->rgba[1]*-10+p->rgba[2]*-10), + (p->rgba[0]*-10+p->rgba[2]*-10), + (p->rgba[0]*-10+p->rgba[1]*-10), + 30*p->rgba[3]*type->stainonimpact*r_bloodstains.value); + } + } +#endif + + type->particles = p->next; +// p->next = free_particles; +// free_particles = p; + p->next = kill_list; + kill_list = p; + if (!kill_first) // branch here is probably faster than list traversal later + kill_first = p; + } + + if (type->beams) + { + b = type->beams; + } + + while ((b=type->beams) && (b->flags & BS_DEAD)) + { + type->beams = b->next; + b->next = free_beams; + free_beams = b; + } + + while (b) + { + if (!(b->flags & BS_NODRAW)) + { + // no BS_NODRAW implies b->next != NULL + // BS_NODRAW should imply b->next == NULL or b->next->flags & BS_DEAD + VectorCopy(b->next->p->org, stop); + VectorCopy(b->p->org, oldorg); + VectorSubtract(stop, oldorg, b->next->dir); + VectorNormalize(b->next->dir); + if (bdraw) + bdraw(scenetri, b, type->slooks); + } + + // clean up dead entries ahead of current + for ( ;; ) + { + bkill = b->next; + if (bkill && (bkill->flags & BS_DEAD)) + { + b->next = bkill->next; + bkill->next = free_beams; + free_beams = bkill; + continue; + } + break; + } + + b->flags |= BS_DEAD; + b = b->next; + } + + goto endtype; + } + + //kill off early ones. + if (type->emittime < 0) + { + for ( ;; ) + { + kill = type->particles; + if (kill && kill->die < particletime) + { + PScript_DelinkTrailstate(&kill->state.trailstate); + type->particles = kill->next; + kill->next = kill_list; + kill_list = kill; + if (!kill_first) + kill_first = kill; + continue; + } + break; + } + } + else + { + for ( ;; ) + { + kill = type->particles; + if (kill && kill->die < particletime) + { + type->particles = kill->next; + kill->next = kill_list; + kill_list = kill; + if (!kill_first) + kill_first = kill; + continue; + } + break; + } + } + + grav = type->gravity*pframetime; + friction[0] = 1 - type->friction[0]*pframetime; + friction[1] = 1 - type->friction[1]*pframetime; + friction[2] = 1 - type->friction[2]*pframetime; + + for (p=type->particles ; p ; p=p->next) + { + if (type->emittime < 0) + { + for ( ;; ) + { + kill = p->next; + if (kill && kill->die < particletime) + { + PScript_DelinkTrailstate(&kill->state.trailstate); + p->next = kill->next; + kill->next = kill_list; + kill_list = kill; + if (!kill_first) + kill_first = kill; + continue; + } + break; + } + } + else + { + for ( ;; ) + { + kill = p->next; + if (kill && kill->die < particletime) + { + p->next = kill->next; + kill->next = kill_list; + kill_list = kill; + if (!kill_first) + kill_first = kill; + continue; + } + break; + } + } + + VectorCopy(p->org, oldorg); + if (type->flags & PT_VELOCITY) + { + p->org[0] += p->vel[0]*pframetime; + p->org[1] += p->vel[1]*pframetime; + p->org[2] += p->vel[2]*pframetime; + p->vel[2] -= grav; + if (type->flags & PT_FRICTION) + { + p->vel[0] *= friction[0]; + p->vel[1] *= friction[1]; + p->vel[2] *= friction[2]; + } + if (type->flurry && doflurry) + { //these should probably be partially synced, + p->vel[0] += crandom() * type->flurry; + p->vel[1] += crandom() * type->flurry; + } + } + +#if UNSUPPORTED + if (type->viewspacefrac) + { + vec3_t tmp; + Matrix4x4_CM_Transform3(viewtranslation, p->org, tmp); + VectorInterpolate(p->org, type->viewspacefrac, tmp, p->org); + Matrix4x4_CM_Transform3x3(viewtranslation, p->vel, tmp); + VectorInterpolate(p->vel, type->viewspacefrac, tmp, p->vel); + } +#endif + + p->angle += p->rotationspeed*pframetime; + + switch (type->rampmode) + { + case RAMP_NEAREST: + rampind = (int)(type->rampindexes * (type->die - (p->die - particletime)) / type->die); + if (rampind >= type->rampindexes) + rampind = type->rampindexes - 1; + ramp = type->ramp + rampind; + VectorCopy(ramp->rgb, p->rgba); + p->rgba[3] = ramp->alpha; + p->scale = ramp->scale; + break; + case RAMP_LERP: + { + float frac = (type->rampindexes * (type->die - (p->die - particletime)) / type->die); + int s1, s2; + s1 = frac; + s2 = s1+1; + if (s1 > type->rampindexes-1) + s1 = type->rampindexes-1; + if (s2 > type->rampindexes-1) + s2 = type->rampindexes-1; + frac -= s1; + VectorInterpolate(type->ramp[s1].rgb, frac, type->ramp[s2].rgb, p->rgba); + FloatInterpolate(type->ramp[s1].alpha, frac, type->ramp[s2].alpha, p->rgba[3]); + FloatInterpolate(type->ramp[s1].scale, frac, type->ramp[s2].scale, p->scale); + } + break; + case RAMP_DELTA: //particle ramps + rampind = (int)(type->rampindexes * (type->die - (p->die - particletime)) / type->die); + if (rampind >= type->rampindexes) + rampind = type->rampindexes - 1; + ramp = type->ramp + rampind; + VectorMA(p->rgba, pframetime, ramp->rgb, p->rgba); + p->rgba[3] -= pframetime*ramp->alpha; + p->scale += pframetime*ramp->scale; + break; + case RAMP_NONE: //particle changes acording to it's preset properties. + if (particletime < (p->die-type->die+type->rgbchangetime)) + { + p->rgba[0] += pframetime*type->rgbchange[0]; + p->rgba[1] += pframetime*type->rgbchange[1]; + p->rgba[2] += pframetime*type->rgbchange[2]; + } + p->rgba[3] += pframetime*type->alphachange; + p->scale += pframetime*type->scaledelta; + } + + if (type->emit >= 0) + { + if (type->emittime < 0) + PScript_ParticleTrail(oldorg, p->org, type->emit, 0, NULL, &p->state.trailstate); + else if (p->state.nextemit < particletime) + { + p->state.nextemit = particletime + type->emittime + frandom()*type->emitrand; + PScript_RunParticleEffectState(p->org, p->vel, 1, type->emit, NULL); + } + } + + if (type->cliptype>=0 && r_bouncysparks.value) + { + VectorSubtract(p->org, p->oldorg, stop); + if (!type->clipbounce || DotProduct(stop,stop) > 10*10) + { + int e; + if (traces-->0&&CL_TraceLine(p->oldorg, p->org, stop, normal, &e)<1) + { +#if UNSUPPORTED + if (type->stainonimpact && r_bloodstains.value) + Surf_AddStain(stop, p->rgba[1]*-10+p->rgba[2]*-10, + p->rgba[0]*-10+p->rgba[2]*-10, + p->rgba[0]*-10+p->rgba[1]*-10, + 30*p->rgba[3]*r_bloodstains.value); +#endif + + if (type->clipbounce < 0) + { + p->die = -1; +#ifdef USE_DECALS + if (type->clipbounce == -2) + { //this type of particle splatters itself as a decal when it hits a wall. + decalctx_t ctx; + float m; + vec3_t vec={0.5, 0.5, 0.431}; + qmodel_t *model; + + ctx.entity = e; + if (!ctx.entity) + { + model = cl.worldmodel; + VectorCopy(p->org, ctx.center); + } + else if (e) + { //this trace hit a door or something. + entity_t *ent = CL_EntityNum(e); + model = ent->model; + VectorSubtract(p->org, ent->origin, ctx.center); + //FIXME: rotate center+normal around entity. + } + else + continue; //err, no idea. + + VectorScale(normal, -1, ctx.normal); + VectorNormalize(ctx.normal); + + VectorNormalize(vec); + CrossProduct(ctx.normal, vec, ctx.tangent1); + RotatePointAroundVector(ctx.tangent2, ctx.normal, ctx.tangent1, frandom()*360); + CrossProduct(ctx.normal, ctx.tangent2, ctx.tangent1); + + VectorNormalize(ctx.tangent1); + VectorNormalize(ctx.tangent2); + + ctx.ptype = type; + ctx.scale1 = type->s2 - type->s1; + ctx.bias1 = type->s1 + (ctx.scale1*0.5); + ctx.scale2 = type->t2 - type->t1; + ctx.bias2 = type->t1 + (ctx.scale2*0.5); + m = p->scale*(1.5+frandom()*0.5)*0.5; //decals should be a little bigger, for some reason. + ctx.scale0 = 2.0 / m; + ctx.scale1 /= m; + ctx.scale2 /= m; + + //inserts decals through a callback. + Mod_ClipDecal(model, ctx.center, ctx.normal, ctx.tangent2, ctx.tangent1, m, type->surfflagmask, type->surfflagmatch, PScript_AddDecals, &ctx); + } +#endif + continue; + } + else if (part_type + type->cliptype == type) + { //bounce + dist = DotProduct(p->vel, normal);// * (-1-(rand()/(float)0x7fff)/2); + dist *= -type->clipbounce; + VectorMA(p->vel, dist, normal, p->vel); + VectorCopy(stop, p->org); + + if (!*type->texname && VectorLength(p->vel)<1000*pframetime && type->looks.type == PT_NORMAL) + { + p->die = -1; + continue; + } + } + else + { + p->die = -1; + VectorNormalize(p->vel); + + if (type->clipbounce) + { + VectorScale(normal, type->clipbounce, normal); + PScript_RunParticleEffectState(stop, normal, type->clipcount/part_type[type->cliptype].count, type->cliptype, NULL); + } + else + PScript_RunParticleEffectState(stop, p->vel, type->clipcount/part_type[type->cliptype].count, type->cliptype, NULL); + continue; + } + } + VectorCopy(p->org, p->oldorg); + } + } +#if UNSUPPORTED + else if (type->stainonimpact && r_bloodstains.value) + { + VectorSubtract(p->org, p->oldorg, stop); + if (DotProduct(stop,stop) > 10*10) + { + if (traces-->0&&CL_TraceLine(p->oldorg, p->org, stop, normal, NULL)<1) + { + if (type->stainonimpact < 0) + Surf_AddStain(stop, (p->rgba[0]*-1), + (p->rgba[1]*-1), + (p->rgba[2]*-1), + p->scale*-type->stainonimpact*r_bloodstains.value); + else + Surf_AddStain(stop, (p->rgba[1]*-10+p->rgba[2]*-10), + (p->rgba[0]*-10+p->rgba[2]*-10), + (p->rgba[0]*-10+p->rgba[1]*-10), + 30*p->rgba[3]*type->stainonimpact*r_bloodstains.value); + p->die = -1; + continue; + } + VectorCopy(p->org, p->oldorg); + } + } +#endif + + if (scenetri && tdraw) + { + if (cl_numstrisvert - scenetri->firstvert >= MAX_INDICIES-6) + { + //generate a new mesh if the old one overflowed. yay smc... + if (cl_numstris == cl_maxstris) + { + cl_maxstris+=8; + cl_stris = Z_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris); + } + scenetri = &cl_stris[cl_numstris++]; + scenetri->texture = scenetri[-1].texture; + scenetri->blendmode = scenetri[-1].blendmode; + scenetri->beflags = scenetri[-1].beflags; + scenetri->firstidx = cl_numstrisidx; + scenetri->firstvert = cl_numstrisvert; + scenetri->numvert = 0; + scenetri->numidx = 0; + } + tdraw(scenetri, p, type->slooks); + } + } + + // beams are dealt with here + + // kill early entries + for ( ;; ) + { + bkill = type->beams; + if (bkill && (bkill->flags & BS_DEAD || bkill->p->die < particletime) && !(bkill->flags & BS_LASTSEG)) + { + type->beams = bkill->next; + bkill->next = free_beams; + free_beams = bkill; + continue; + } + break; + } + + + b = type->beams; + if (b) + { + for ( ;; ) + { + if (b->next) + { + // mark dead entries + if (b->flags & (BS_LASTSEG|BS_DEAD|BS_NODRAW)) + { + // kill some more dead entries + for ( ;; ) + { + bkill = b->next; + if (bkill && (bkill->flags & BS_DEAD) && !(bkill->flags & BS_LASTSEG)) + { + b->next = bkill->next; + bkill->next = free_beams; + free_beams = bkill; + continue; + } + break; + } + + if (!bkill) // have to check so we don't hit NULL->next + continue; + } + else + { + if (!(b->next->flags & BS_DEAD)) + { + VectorCopy(b->next->p->org, stop); + VectorCopy(b->p->org, oldorg); + VectorSubtract(stop, oldorg, b->next->dir); + VectorNormalize(b->next->dir); + if (bdraw) + { + VectorAdd(stop, oldorg, stop); + VectorScale(stop, 0.5, stop); +#if UNSUPPORTED + RQ_AddDistReorder(bdraw, b, type->slooks, stop); +#endif + } + } + + if (b->p->die < particletime) + b->flags |= BS_DEAD; + } + } + else + { + if (b->p->die < particletime) // end of the list check + b->flags |= BS_DEAD; + + break; + } + + if (b->p->die < particletime) + b->flags |= BS_DEAD; + + b = b->next; + } + } + +endtype: + + // delete from run list if necessary + if (!type->particles && !type->beams && !type->clippeddecals) + { + if (!lastvalidtype) + part_run_list = type->nexttorun; + else if (lastvalidtype->nexttorun == type) + lastvalidtype->nexttorun = type->nexttorun; + else + lastvalidtype->nexttorun->nexttorun = type->nexttorun; + type->state &= ~PS_INRUNLIST; + } + else + lastvalidtype = type; + } + + // lazy delete for particles is done here + if (kill_list) + { + kill_first->next = free_particles; + free_particles = kill_list; + } + + particletime += pframetime; + + if (!cl_numstris) + return; + + Fog_DisableGFog (); //additive stuff looks like arse. this stuff should really be done in a fragment shader, although we could also fake things here + + //mess around with tmu states + GL_DisableMultitexture(); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); //make sure the colour values are used. + + //other states + glEnable(GL_BLEND); //yes, we need blending + glDisable(GL_ALPHA_TEST); + glDepthMask(GL_FALSE); //don't write depth. this prevents the particles from fighting each other, although alpha-blended particles will still be weird. + GL_PolygonOffset (OFFSET_DECAL); + glDisable(GL_CULL_FACE); + + //mess around with where glDrawElements gets its data from + GL_BindBuffer (GL_ARRAY_BUFFER, 0); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); + GL_ClientActiveTextureFunc (GL_TEXTURE0_ARB); + glEnableClientState (GL_VERTEX_ARRAY); + glEnableClientState (GL_TEXTURE_COORD_ARRAY); + glEnableClientState (GL_COLOR_ARRAY); + + for (o = 0; o < 3; o++) + { + static const struct + { + unsigned int order; + GLenum srcf; + GLenum dstf; + } factors[] = { + {1, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, //BM_BLEND + {1, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR}, //BM_BLENDCOLOUR + {2, GL_SRC_ALPHA, GL_ONE}, //BM_ADDA + {2, GL_SRC_COLOR, GL_ONE}, //BM_ADDC sort-additive + {0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR}, //BM_SUBTRACT + {0, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA}, //BM_INVMODA sort-decal + {0, GL_ZERO, GL_ONE_MINUS_SRC_COLOR}, //BM_INVMODC sort-decal + {2, GL_ONE, GL_ONE_MINUS_SRC_ALPHA} //BM_PREMUL sort-additive + }; + + for (i = 0; i < cl_numstris; i++) + { + if (factors[cl_stris[i].blendmode].order != o) + continue; + + glBlendFunc(factors[cl_stris[i].blendmode].srcf, factors[cl_stris[i].blendmode].dstf); + + glVertexPointer(3, GL_FLOAT, sizeof(*cl_strisvertv), cl_strisvertv + cl_stris[i].firstvert); + glTexCoordPointer(2, GL_FLOAT, sizeof(*cl_strisvertt), cl_strisvertt + cl_stris[i].firstvert); + glColorPointer(4, GL_FLOAT, sizeof(*cl_strisvertc), cl_strisvertc + cl_stris[i].firstvert); + if (cl_stris[i].beflags & BEF_LINES) + { + glDisable(GL_TEXTURE_2D); + glShadeModel(GL_SMOOTH); +// glDrawRangeElements(GL_LINES, 0, cl_stris[i].numvert, cl_stris[i].numidx, GL_UNSIGNED_SHORT, cl_strisidx + cl_stris[i].firstidx); + glDrawElements(GL_LINES, cl_stris[i].numidx, GL_UNSIGNED_SHORT, cl_strisidx + cl_stris[i].firstidx); + glEnable(GL_TEXTURE_2D); + } + else + { + GL_Bind(cl_stris[i].texture); +// glDrawRangeElements(GL_TRIANGLES, 0, cl_stris[i].numvert, cl_stris[i].numidx, GL_UNSIGNED_SHORT, cl_strisidx + cl_stris[i].firstidx); + glDrawElements(GL_TRIANGLES, cl_stris[i].numidx, GL_UNSIGNED_SHORT, cl_strisidx + cl_stris[i].firstidx); + } + } + } + glDisableClientState (GL_VERTEX_ARRAY); + glDisableClientState (GL_TEXTURE_COORD_ARRAY); + glDisableClientState (GL_COLOR_ARRAY); + glEnable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glShadeModel(GL_FLAT); + glDepthMask(GL_TRUE); + glEnable(GL_CULL_FACE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_PolygonOffset (OFFSET_NONE); + cl_numstris = 0; + cl_numstrisvert = 0; + cl_numstrisidx = 0; +} + +/* +=============== +R_DrawParticles +=============== +*/ +void PScript_DrawParticles (void) +{ + int i; + entity_t *ent; + vec3_t axis[3]; + float pframetime; + static float oldtime; + + pframetime = cl.time - oldtime; + if (pframetime < 0) + pframetime = 0; + if (pframetime > 1) + pframetime = 1; + oldtime = cl.time; + + if (r_part_rain.value) + { + for (i = 0; i < cl.num_entities; i++) + { + ent = &cl.entities[i]; + if (!ent->model || ent->model->needload) + continue; + if (!ent->model->skytris) + continue; + AngleVectors(ent->angles, axis[0], axis[1], axis[2]); + //this timer, as well as the per-tri timer, are unable to deal with certain rates+sizes. it would be good to fix that... + //it would also be nice to do mdls too... + P_AddRainParticles(ent->model, axis, ent->origin, ((i==0)?r_visframecount:0), pframetime); + } + + //FIXME: static entities too! + } + + PScript_DrawParticleTypes(pframetime); + +// if (fallback) +// fallback->DrawParticles(); +} + +#endif +#endif + + diff --git a/Quake/r_sprite.c b/Quake/r_sprite.c index 6765e978..9e626d2c 100644 --- a/Quake/r_sprite.c +++ b/Quake/r_sprite.c @@ -86,6 +86,7 @@ void R_DrawSpriteModel (entity_t *e) mspriteframe_t *frame; float *s_up, *s_right; float angle, sr, cr; + float scale; //TODO: frustum cull it? @@ -144,7 +145,12 @@ void R_DrawSpriteModel (entity_t *e) if (psprite->type == SPR_ORIENTED) GL_PolygonOffset (OFFSET_DECAL); - glColor3f (1,1,1); + if (e->netstate.scale != 16) + scale = e->netstate.scale/16.0; + else + scale = 1; + + glColor3f (e->netstate.colormod[0]/32.0,e->netstate.colormod[0]/32.0,e->netstate.colormod[0]/32.0); GL_DisableMultitexture(); @@ -154,28 +160,30 @@ void R_DrawSpriteModel (entity_t *e) glBegin (GL_TRIANGLE_FAN); //was GL_QUADS, but changed to support r_showtris glTexCoord2f (0, frame->tmax); - VectorMA (e->origin, frame->down, s_up, point); - VectorMA (point, frame->left, s_right, point); + VectorMA (e->origin, frame->down*scale, s_up, point); + VectorMA (point, frame->left*scale, s_right, point); glVertex3fv (point); glTexCoord2f (0, 0); - VectorMA (e->origin, frame->up, s_up, point); - VectorMA (point, frame->left, s_right, point); + VectorMA (e->origin, frame->up*scale, s_up, point); + VectorMA (point, frame->left*scale, s_right, point); glVertex3fv (point); glTexCoord2f (frame->smax, 0); - VectorMA (e->origin, frame->up, s_up, point); - VectorMA (point, frame->right, s_right, point); + VectorMA (e->origin, frame->up*scale, s_up, point); + VectorMA (point, frame->right*scale, s_right, point); glVertex3fv (point); glTexCoord2f (frame->smax, frame->tmax); - VectorMA (e->origin, frame->down, s_up, point); - VectorMA (point, frame->right, s_right, point); + VectorMA (e->origin, frame->down*scale, s_up, point); + VectorMA (point, frame->right*scale, s_right, point); glVertex3fv (point); glEnd (); glDisable (GL_ALPHA_TEST); + glColor3f (1, 1, 1); + //johnfitz: offset decals if (psprite->type == SPR_ORIENTED) GL_PolygonOffset (OFFSET_NONE); diff --git a/Quake/r_world.c b/Quake/r_world.c index cdc8fe81..4f10525b 100644 --- a/Quake/r_world.c +++ b/Quake/r_world.c @@ -81,6 +81,7 @@ void R_MarkSurfaces (void) mnode_t *node; msurface_t *surf, **mark; int i, j; + unsigned int k; qboolean nearwaterportal; // clear lightmap chains @@ -146,7 +147,7 @@ void R_MarkSurfaces (void) //becuase his tool doesn't actually remove the surfaces from the bsp surfaces lump //nor does it remove references to them in each leaf's marksurfaces list for (i=0, node = cl.worldmodel->nodes ; inumnodes ; i++, node++) - for (j=0, surf=&cl.worldmodel->surfaces[node->firstsurface] ; jnumsurfaces ; j++, surf++) + for (k=0, surf=&cl.worldmodel->surfaces[node->firstsurface] ; knumsurfaces ; k++, surf++) if (surf->visframe == r_visframecount) { R_ChainSurface(surf, chain_world); @@ -261,7 +262,7 @@ void R_BuildLightmapChains (qmodel_t *model, texchain_t chain) for (s = t->texturechains[chain]; s; s = s->texturechain) if (!s->culled) - R_RenderDynamicLightmaps (s); + R_RenderDynamicLightmaps (model, s); } } diff --git a/Quake/render.h b/Quake/render.h index 4ecb9733..6e0262e0 100644 --- a/Quake/render.h +++ b/Quake/render.h @@ -53,6 +53,7 @@ typedef struct entity_s int update_type; entity_state_t baseline; // to fill in defaults in updates + entity_state_t netstate; // the latest network state double msgtime; // time of last update vec3_t msg_origins[2]; // last two updates (0 is newest) @@ -91,6 +92,9 @@ typedef struct entity_s vec3_t currentorigin; //johnfitz -- transform lerping vec3_t previousangles; //johnfitz -- transform lerping vec3_t currentangles; //johnfitz -- transform lerping + + struct trailstate_s *trailstate; //spike -- managed by the particle system, so we don't loose our position and spawn the wrong number of particles, and we can track beams etc + struct trailstate_s *emitstate; //spike -- for effects which are not so static. } entity_t; // !!! if this is changed, it must be changed in asm_draw.h too !!! diff --git a/Quake/sbar.c b/Quake/sbar.c index 9c68780f..c7d101f9 100644 --- a/Quake/sbar.c +++ b/Quake/sbar.c @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" +extern qboolean premul_hud; int sb_updates; // if >= vid.numpages, no update needed #define STAT_MINUS 10 // num frame for '-' stats digit @@ -63,6 +64,11 @@ int hipweapons[4] = {HIT_LASER_CANNON_BIT,HIT_MJOLNIR_BIT,4,HIT_PROXIMITY_GUN_B //MED 01/04/97 added hipnotic items array qpic_t *hsb_items[2]; +//spike -- fix -game hipnotic by autodetecting hud types. the fte protocols will deal with the networking issue, other than demos, anyway +static int hudtype; +#define hipnotic (hudtype==1) +#define rogue (hudtype==2) + void Sbar_MiniDeathmatchOverlay (void); void Sbar_DeathmatchOverlay (void); void M_DrawPic (int x, int y, qpic_t *pic); @@ -105,6 +111,21 @@ void Sbar_Changed (void) sb_updates = 0; // update next frame } + +qpic_t *Sbar_CheckPicFromWad (const char *name) +{ + extern qpic_t *pic_nul; + qpic_t *r; + if (!hudtype) + return pic_nul; //one already failed, don't waste cpu + if (!W_GetLumpinfo(name)) + r = pic_nul; + else + r = Draw_PicFromWad(name); + if (r == pic_nul) + hudtype = 0; + return r; +} /* =============== Sbar_LoadPics -- johnfitz -- load all the sbar pics @@ -194,55 +215,59 @@ void Sbar_LoadPics (void) sb_ibar = Draw_PicFromWad ("ibar"); sb_scorebar = Draw_PicFromWad ("scorebar"); -//MED 01/04/97 added new hipnotic weapons - if (hipnotic) - { - hsb_weapons[0][0] = Draw_PicFromWad ("inv_laser"); - hsb_weapons[0][1] = Draw_PicFromWad ("inv_mjolnir"); - hsb_weapons[0][2] = Draw_PicFromWad ("inv_gren_prox"); - hsb_weapons[0][3] = Draw_PicFromWad ("inv_prox_gren"); - hsb_weapons[0][4] = Draw_PicFromWad ("inv_prox"); + hudtype = 0; - hsb_weapons[1][0] = Draw_PicFromWad ("inv2_laser"); - hsb_weapons[1][1] = Draw_PicFromWad ("inv2_mjolnir"); - hsb_weapons[1][2] = Draw_PicFromWad ("inv2_gren_prox"); - hsb_weapons[1][3] = Draw_PicFromWad ("inv2_prox_gren"); - hsb_weapons[1][4] = Draw_PicFromWad ("inv2_prox"); +//MED 01/04/97 added new hipnotic weapons + if (!hudtype) + { + hudtype = 1; + hsb_weapons[0][0] = Sbar_CheckPicFromWad ("inv_laser"); + hsb_weapons[0][1] = Sbar_CheckPicFromWad ("inv_mjolnir"); + hsb_weapons[0][2] = Sbar_CheckPicFromWad ("inv_gren_prox"); + hsb_weapons[0][3] = Sbar_CheckPicFromWad ("inv_prox_gren"); + hsb_weapons[0][4] = Sbar_CheckPicFromWad ("inv_prox"); + + hsb_weapons[1][0] = Sbar_CheckPicFromWad ("inv2_laser"); + hsb_weapons[1][1] = Sbar_CheckPicFromWad ("inv2_mjolnir"); + hsb_weapons[1][2] = Sbar_CheckPicFromWad ("inv2_gren_prox"); + hsb_weapons[1][3] = Sbar_CheckPicFromWad ("inv2_prox_gren"); + hsb_weapons[1][4] = Sbar_CheckPicFromWad ("inv2_prox"); for (i = 0; i < 5; i++) { - hsb_weapons[2+i][0] = Draw_PicFromWad (va("inva%i_laser",i+1)); - hsb_weapons[2+i][1] = Draw_PicFromWad (va("inva%i_mjolnir",i+1)); - hsb_weapons[2+i][2] = Draw_PicFromWad (va("inva%i_gren_prox",i+1)); - hsb_weapons[2+i][3] = Draw_PicFromWad (va("inva%i_prox_gren",i+1)); - hsb_weapons[2+i][4] = Draw_PicFromWad (va("inva%i_prox",i+1)); + hsb_weapons[2+i][0] = Sbar_CheckPicFromWad (va("inva%i_laser",i+1)); + hsb_weapons[2+i][1] = Sbar_CheckPicFromWad (va("inva%i_mjolnir",i+1)); + hsb_weapons[2+i][2] = Sbar_CheckPicFromWad (va("inva%i_gren_prox",i+1)); + hsb_weapons[2+i][3] = Sbar_CheckPicFromWad (va("inva%i_prox_gren",i+1)); + hsb_weapons[2+i][4] = Sbar_CheckPicFromWad (va("inva%i_prox",i+1)); } - hsb_items[0] = Draw_PicFromWad ("sb_wsuit"); - hsb_items[1] = Draw_PicFromWad ("sb_eshld"); + hsb_items[0] = Sbar_CheckPicFromWad ("sb_wsuit"); + hsb_items[1] = Sbar_CheckPicFromWad ("sb_eshld"); } - if (rogue) + if (!hudtype) { - rsb_invbar[0] = Draw_PicFromWad ("r_invbar1"); - rsb_invbar[1] = Draw_PicFromWad ("r_invbar2"); + hudtype = 2; + rsb_invbar[0] = Sbar_CheckPicFromWad ("r_invbar1"); + rsb_invbar[1] = Sbar_CheckPicFromWad ("r_invbar2"); - rsb_weapons[0] = Draw_PicFromWad ("r_lava"); - rsb_weapons[1] = Draw_PicFromWad ("r_superlava"); - rsb_weapons[2] = Draw_PicFromWad ("r_gren"); - rsb_weapons[3] = Draw_PicFromWad ("r_multirock"); - rsb_weapons[4] = Draw_PicFromWad ("r_plasma"); + rsb_weapons[0] = Sbar_CheckPicFromWad ("r_lava"); + rsb_weapons[1] = Sbar_CheckPicFromWad ("r_superlava"); + rsb_weapons[2] = Sbar_CheckPicFromWad ("r_gren"); + rsb_weapons[3] = Sbar_CheckPicFromWad ("r_multirock"); + rsb_weapons[4] = Sbar_CheckPicFromWad ("r_plasma"); - rsb_items[0] = Draw_PicFromWad ("r_shield1"); - rsb_items[1] = Draw_PicFromWad ("r_agrav1"); + rsb_items[0] = Sbar_CheckPicFromWad ("r_shield1"); + rsb_items[1] = Sbar_CheckPicFromWad ("r_agrav1"); // PGM 01/19/97 - team color border - rsb_teambord = Draw_PicFromWad ("r_teambord"); + rsb_teambord = Sbar_CheckPicFromWad ("r_teambord"); // PGM 01/19/97 - team color border - rsb_ammo[0] = Draw_PicFromWad ("r_ammolava"); - rsb_ammo[1] = Draw_PicFromWad ("r_ammomulti"); - rsb_ammo[2] = Draw_PicFromWad ("r_ammoplasma"); + rsb_ammo[0] = Sbar_CheckPicFromWad ("r_ammolava"); + rsb_ammo[1] = Sbar_CheckPicFromWad ("r_ammomulti"); + rsb_ammo[2] = Sbar_CheckPicFromWad ("r_ammoplasma"); } } @@ -281,13 +306,22 @@ Sbar_DrawPicAlpha -- johnfitz */ void Sbar_DrawPicAlpha (int x, int y, qpic_t *pic, float alpha) { - glDisable (GL_ALPHA_TEST); - glEnable (GL_BLEND); - glColor4f(1,1,1,alpha); - Draw_Pic (x, y + 24, pic); - glColor4f(1,1,1,1); // ericw -- changed from glColor3f to work around intel 855 bug with "r_oldwater 0" and "scr_sbaralpha 0" - glDisable (GL_BLEND); - glEnable (GL_ALPHA_TEST); + if (premul_hud) + { + glColor4f(alpha,alpha,alpha,alpha); + Draw_Pic (x, y + 24, pic); + glColor4f(1,1,1,1); + } + else + { + glDisable (GL_ALPHA_TEST); + glEnable (GL_BLEND); + glColor4f(1,1,1,alpha); + Draw_Pic (x, y + 24, pic); + glColor4f(1,1,1,1); // ericw -- changed from glColor3f to work around intel 855 bug with "r_oldwater 0" and "scr_sbaralpha 0" + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); + } } /* @@ -918,6 +952,35 @@ void Sbar_DrawFace (void) Sbar_DrawPic (112, 0, sb_faces[f][anim]); } +static void Sbar_Voice(int y) +{ + cvar_t snd_voip_showmeter; + int loudness; + snd_voip_showmeter.value = 1; + if (!snd_voip_showmeter.value) + return; + loudness = S_Voip_Loudness(snd_voip_showmeter.value>=2); + if (loudness >= 0) + { + int cw = 8; + int w; + int x=160; + int s, i; + float range = loudness/100.0f; + w = (5+16+1)*cw; + x -= w/2; + Draw_Character (x, y, 'M'); x+=cw; + Draw_Character (x, y, 'i'); x+=cw; + Draw_Character (x, y, 'c'); x+=cw; + x+=cw; + Draw_Character (x, y, 0xe080); x+=cw; + for (s=x,i=0 ; i<16 ; i++, x+=cw) + Draw_Character(x, y, 0xe081); + Draw_Character (x, y, 0xe082); + Draw_Character (s + (x-s) * range - cw/2, y, 0xe083); + } +} + /* =============== Sbar_Draw @@ -941,6 +1004,13 @@ void Sbar_Draw (void) GL_SetCanvas (CANVAS_DEFAULT); //johnfitz + if (sb_lines > 24) + Sbar_Voice(-32); + else if (sb_lines > 0) + Sbar_Voice(-8); + else + Sbar_Voice(16); + //johnfitz -- don't waste fillrate by clearing the area behind the sbar w = CLAMP (320.0f, scr_sbarscale.value * 320.0f, (float)glwidth); if (sb_lines && glwidth > w) @@ -1138,6 +1208,9 @@ void Sbar_DeathmatchOverlay (void) top = Sbar_ColorForMap (top); bottom = Sbar_ColorForMap (bottom); + if (S_Voip_Speaking(k)) //spike -- display an underlay for people who are speaking + Draw_Fill ( x, y, 320-x*2, 8, ((k+1)==cl.viewentity)?75:73, 1); + Draw_Fill ( x, y, 40, 4, top, 1); //johnfitz -- stretched overlays Draw_Fill ( x, y+4, 40, 4, bottom, 1); //johnfitz -- stretched overlays @@ -1170,6 +1243,9 @@ void Sbar_DeathmatchOverlay (void) } #endif + sprintf (num, "%4i", s->ping); + M_PrintWhite (x-8*5, y, num); //johnfitz -- was Draw_String, changed for stretched overlays + // draw name M_Print (x+64, y, s->name); //johnfitz -- was Draw_String, changed for stretched overlays @@ -1177,6 +1253,13 @@ void Sbar_DeathmatchOverlay (void) } GL_SetCanvas (CANVAS_SBAR); //johnfitz + + if (!cls.message.cursize && cl.expectingpingtimes < realtime) + { + cl.expectingpingtimes = realtime + 5; + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString(&cls.message, "ping"); + } } /* diff --git a/Quake/server.h b/Quake/server.h index 7eb3f51a..a89b6576 100644 --- a/Quake/server.h +++ b/Quake/server.h @@ -75,6 +75,25 @@ typedef struct unsigned protocol; //johnfitz unsigned protocolflags; + + sizebuf_t multicast; // selectively copied to clients by the multicast builtin + byte multicast_buf[MAX_DATAGRAM]; + + const char *particle_precache[MAX_PARTICLETYPES]; // NULL terminated + + entity_state_t *static_entities; + int num_statics; + int max_statics; + + struct ambientsound_s + { + vec3_t origin; + unsigned int soundindex; + float volume; + float attenuation; + } *ambientsounds; + int num_ambients; + int max_ambients; } server_t; @@ -84,9 +103,10 @@ typedef struct typedef struct client_s { qboolean active; // false = client is free - qboolean spawned; // false = don't send datagrams + qboolean spawned; // false = don't send datagrams (set when client acked the first entities) qboolean dropasap; // has been told to go to another level - qboolean sendsignon; // only valid before spawned + int sendsignon; // only valid before spawned + int signonidx; double last_message; // reliable messages must be sent // periodically @@ -111,6 +131,64 @@ typedef struct client_s // client known data for deltas int old_frags; + + sizebuf_t datagram; + byte datagram_buf[MAX_DATAGRAM]; + + unsigned int limit_entities; //vanilla is 600 + unsigned int limit_unreliable; //max allowed size for unreliables + unsigned int limit_reliable; //max (total) size of a reliable message. + unsigned int limit_models; // + unsigned int limit_sounds; // + + qboolean pextknown; + unsigned int protocol_pext2; + unsigned int resendstats[MAX_CL_STATS/32]; //the stats which need to be resent. + int oldstats_i[MAX_CL_STATS]; //previous values of stats. if these differ from the current values, reflag resendstats. + float oldstats_f[MAX_CL_STATS]; //previous values of stats. if these differ from the current values, reflag resendstats. + struct entity_num_state_s{ + unsigned int num; //ascending order, there can be gaps. + entity_state_t state; + } *previousentities; + size_t numpreviousentities; + size_t maxpreviousentities; + unsigned int snapshotresume; + unsigned int *pendingentities_bits; //UF_ flags for each entity + size_t numpendingentities; //realloc if too small + struct deltaframe_s + { //quick overview of how this stuff actually works: + //when the server notices a gap in the ack sequence, we walk through the dropped frames and reflag everything that was dropped. + //if the server isn't tracking enough frames, then we just treat those as dropped; + //small note: when an entity is new, it re-flags itself as new for the next packet too, this reduces the immediate impact of packetloss on new entities. + //reflagged state includes stats updates, entity updates, and entity removes. + int sequence; //to see if its stale + float timestamp; + unsigned int resendstats[MAX_CL_STATS/32]; + struct + { + unsigned int num; + unsigned int bits; + } *ents; + int numents; //doesn't contain an entry for every entity, just ones that were sent this frame. no 0 bits + int maxents; + } *frames; + size_t numframes; //preallocated power-of-two + int lastacksequence; + int lastmovemessage; + + client_voip_t voip; //spike -- for voip + struct + { + char name[MAX_QPATH]; + FILE *file; + qboolean started; //actually sending + unsigned int startpos; //within the pak, so we don't break stuff when seeking + unsigned int size; + unsigned int sendpos; //file offset we last tried sending + unsigned int ackpos; //if they don't ack this properly, we restart sending from here instead. + //for more speed, the server should build a collection of blocks to track which parts were actually acked, thereby avoiding redundant resends, but in the intererest of simplicity... + } download; + qboolean knowntoqc; // putclientinserver was called } client_t; @@ -128,6 +206,8 @@ typedef struct client_s #define MOVETYPE_NOCLIP 8 #define MOVETYPE_FLYMISSILE 9 // extra size to monsters #define MOVETYPE_BOUNCE 10 +//#define MOVETYPE_EXT_BOUNCEMISSILE 11 +#define MOVETYPE_EXT_FOLLOW 12 // edict->solid values #define SOLID_NOT 0 // no interaction with other objects @@ -135,6 +215,7 @@ typedef struct client_s #define SOLID_BBOX 2 // touch on edge, block #define SOLID_SLIDEBOX 3 // touch on edge, but not an onground #define SOLID_BSP 4 // bsp clip, touch on edge, block +#define SOLID_EXT_CORPSE 5 // passes through slidebox+other corpses, but not bsp/bbox/triggers // edict->deadflag values #define DEAD_NO 0 @@ -161,13 +242,6 @@ typedef struct client_s #define FL_WATERJUMP 2048 // player jumping out of water #define FL_JUMPRELEASED 4096 // for jump debouncing -// entity effects - -#define EF_BRIGHTFIELD 1 -#define EF_MUZZLEFLASH 2 -#define EF_BRIGHTLIGHT 4 -#define EF_DIMLIGHT 8 - #define SPAWNFLAG_NOT_EASY 256 #define SPAWNFLAG_NOT_MEDIUM 512 #define SPAWNFLAG_NOT_HARD 1024 @@ -194,11 +268,14 @@ extern edict_t *sv_player; void SV_Init (void); void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count); -void SV_StartSound (edict_t *entity, int channel, const char *sample, int volume, - float attenuation); +void SV_StartSound (edict_t *entity, float *origin, int channel, + const char *sample, int volume, float attenuation); void SV_DropClient (qboolean crash); +void SVFTE_Ack(client_t *client, int sequence); +void SVFTE_DestroyFrames(client_t *client); +void SV_BuildEntityState(edict_t *ent, entity_state_t *state); void SV_SendClientMessages (void); void SV_ClearDatagram (void); @@ -219,10 +296,11 @@ void SV_Physics (void); qboolean SV_CheckBottom (edict_t *ent); qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink); -void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg); +void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg); void SV_MoveToGoal (void); +void SV_ConnectClient (int clientnum); //called from the netcode to add new clients. also called from pr_ext to spawn new botclients. void SV_CheckForNewClients (void); void SV_RunClients (void); void SV_SaveSpawnparms (); diff --git a/Quake/snd_codec.c b/Quake/snd_codec.c index dc37fb66..e57772fa 100644 --- a/Quake/snd_codec.c +++ b/Quake/snd_codec.c @@ -45,7 +45,7 @@ static snd_codec_t *codecs; S_CodecRegister ================= */ -static void S_CodecRegister(snd_codec_t *codec) +void S_CodecRegister(snd_codec_t *codec) { codec->next = codecs; codecs = codec; diff --git a/Quake/snd_dma.c b/Quake/snd_dma.c index cc5df69d..50fb87b5 100644 --- a/Quake/snd_dma.c +++ b/Quake/snd_dma.c @@ -24,6 +24,18 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // snd_dma.c -- main control for any streaming sound output device +/* FIXME -- spike +** with SDL, the SDL api provides a callback that is called whenever SDL thinks more audio is needed +** if we were to move our mixing into the callback instead, we would obsolete _snd_mixahead and reduce audio latency as a result (instead of having to pre-mix audio just in case). +** this callback can typically also be assumed to be on another thread, so mixing audio there would result in a drop in cpu usage on the main thread, increasing framerates. +** typically quake's audio mixer isn't that expensive, but when you have maps with 1000 static sounds with 8-channel surround sound, things can start to get pricy. +** +** S_Update_ would become a stub, and we'd need to call SDL_LockAudio to block the callback from happening any time we change an audio channel. +** snd_mix.c would also need to be threadsafe with regard to the rest of the code +** +** alternatively, sdl2 provides a different audio api that more closely matches what we currently do, where we would directly submit audio (snd_mixahead would again be obsolete). +*/ + #include "quakedef.h" #include "snd_codec.h" #include "bgmusic.h" @@ -39,8 +51,9 @@ static void S_StopAllSoundsC (void); // Internal sound data & structures // ======================================================================= -channel_t snd_channels[MAX_CHANNELS]; +channel_t *snd_channels; int total_channels; +int max_channels; static int snd_blocked = 0; static qboolean snd_initialized = false; @@ -52,6 +65,7 @@ vec3_t listener_origin; vec3_t listener_forward; vec3_t listener_right; vec3_t listener_up; +float voicevolumescale = 1; //for audio ducking while speaking #define sound_nominal_clip_dist 1000.0 @@ -182,7 +196,9 @@ void S_Init (void) Cvar_RegisterVariable(&sndspeed); Cvar_RegisterVariable(&snd_mixspeed); Cvar_RegisterVariable(&snd_filterquality); - + + S_Voip_Init(); + if (safemode || COM_CheckParm("-nosound")) return; @@ -401,11 +417,17 @@ void SND_Spatialize (channel_t *ch) vec_t lscale, rscale, scale; vec3_t source_vec; + if (ch->entchannel == -2) + { + ch->leftvol = ch->master_vol; //voip comes out full volume + ch->rightvol = ch->master_vol; + return; + } // anything coming from the view entity will always be full volume if (ch->entnum == cl.viewentity) { - ch->leftvol = ch->master_vol; - ch->rightvol = ch->master_vol; + ch->leftvol = ch->master_vol * voicevolumescale; + ch->rightvol = ch->master_vol * voicevolumescale; return; } @@ -427,12 +449,12 @@ void SND_Spatialize (channel_t *ch) // add in distance effect scale = (1.0 - dist) * rscale; - ch->rightvol = (int) (ch->master_vol * scale); + ch->rightvol = (int) (ch->master_vol * scale * voicevolumescale); if (ch->rightvol < 0) ch->rightvol = 0; scale = (1.0 - dist) * lscale; - ch->leftvol = (int) (ch->master_vol * scale); + ch->leftvol = (int) (ch->master_vol * scale * voicevolumescale); if (ch->leftvol < 0) ch->leftvol = 0; } @@ -532,20 +554,17 @@ void S_StopSound (int entnum, int entchannel) void S_StopAllSounds (qboolean clear) { - int i; - if (!sound_started) return; total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics - - for (i = 0; i < MAX_CHANNELS; i++) - { - if (snd_channels[i].sfx) - snd_channels[i].sfx = NULL; + if (max_channels != total_channels + 64) + { //shrink it if needed + max_channels = total_channels + 64; + free(snd_channels); + snd_channels = malloc(sizeof(channel_t) * max_channels); } - - memset(snd_channels, 0, MAX_CHANNELS * sizeof(channel_t)); + memset(snd_channels, 0, max_channels * sizeof(channel_t)); if (clear) S_ClearBuffer (); @@ -593,10 +612,18 @@ void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation) if (!sfx) return; - if (total_channels == MAX_CHANNELS) + if (total_channels == max_channels) { - Con_Printf ("total_channels == MAX_CHANNELS\n"); - return; + int nm = max_channels+64; + ss = realloc(snd_channels, sizeof(*ss)*nm); + if (!ss) + { + Con_Printf ("unable to increase max_channels\n"); + return; + } + snd_channels = ss; + memset(snd_channels+max_channels, 0, sizeof(*ss)*(nm-max_channels)); + max_channels = nm; } ss = &snd_channels[total_channels]; @@ -632,14 +659,15 @@ S_UpdateAmbientSounds static void S_UpdateAmbientSounds (void) { mleaf_t *l; - int vol, ambient_channel; + int ambient_channel; channel_t *chan; + static float vol, levels[NUM_AMBIENTS]; //Spike: fixing ambient levels not changing at high enough framerates due to integer precison. // no ambients when disconnected - if (cls.state != ca_connected) + if (cls.state != ca_connected || cls.signon != SIGNONS) return; // calc ambient sound levels - if (!cl.worldmodel) + if (!cl.worldmodel || cl.worldmodel->needload) return; l = Mod_PointInLeaf (listener_origin, cl.worldmodel); @@ -660,20 +688,20 @@ static void S_UpdateAmbientSounds (void) vol = 0; // don't adjust volume too fast - if (chan->master_vol < vol) + if (levels[ambient_channel] < vol) { - chan->master_vol += (int) (host_frametime * ambient_fade.value); - if (chan->master_vol > vol) - chan->master_vol = vol; + levels[ambient_channel] += (host_frametime * ambient_fade.value); + if (levels[ambient_channel] > vol) + levels[ambient_channel] = vol; } else if (chan->master_vol > vol) { - chan->master_vol -= (int) (host_frametime * ambient_fade.value); - if (chan->master_vol < vol) - chan->master_vol = vol; + levels[ambient_channel] -= (host_frametime * ambient_fade.value); + if (levels[ambient_channel] < vol) + levels[ambient_channel] = vol; } - chan->leftvol = chan->rightvol = chan->master_vol; + chan->leftvol = chan->rightvol = chan->master_vol = levels[ambient_channel]; } } diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index 239e020b..ce6a0cd2 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -52,35 +52,58 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) sc->width = 1; else sc->width = inwidth; - sc->stereo = 0; + if (sc->stereo == 1) + { //crappy approach to stereo - strip it out by merging left+right channels + sc->stereo = 0; -// resample / decimate to the current source rate - - if (stepscale == 1 && inwidth == 1 && sc->width == 1) - { -// fast special case - for (i = 0; i < outcount; i++) - ((signed char *)sc->data)[i] = (int)( (unsigned char)(data[i]) - 128); - } - else - { -// general case samplefrac = 0; fracstep = stepscale*256; for (i = 0; i < outcount; i++) { srcsample = samplefrac >> 8; + srcsample<<=1; samplefrac += fracstep; if (inwidth == 2) - sample = LittleShort ( ((short *)data)[srcsample] ); + sample = LittleShort ( ((short *)data)[srcsample] ) + LittleShort ( ((short *)data)[srcsample+1] ); else - sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + sample = ((int)( (unsigned char)(data[srcsample]) - 128) << 8) + ((int)( (unsigned char)(data[srcsample+1]) - 128) << 8); + sample /= 2; if (sc->width == 2) ((short *)sc->data)[i] = sample; else ((signed char *)sc->data)[i] = sample >> 8; } } + else + { + // resample / decimate to the current source rate + + if (stepscale == 1 && inwidth == 1 && sc->width == 1) + { + // fast special case + for (i = 0; i < outcount; i++) + ((signed char *)sc->data)[i] = (int)( (unsigned char)(data[i]) - 128); + } + else + { + // general case + samplefrac = 0; + fracstep = stepscale*256; + for (i = 0; i < outcount; i++) + { + srcsample = samplefrac >> 8; + samplefrac += fracstep; + if (inwidth == 2) + sample = LittleShort ( ((short *)data)[srcsample] ); + else + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + if (sc->width == 2) + ((short *)sc->data)[i] = sample; + else + ((signed char *)sc->data)[i] = sample >> 8; + } + } + } } //============================================================================= @@ -122,7 +145,7 @@ sfxcache_t *S_LoadSound (sfx_t *s) } info = GetWavinfo (s->name, data, com_filesize); - if (info.channels != 1) + if (info.channels != 1 && info.channels != 2) { Con_Printf ("%s is a stereo sample\n",s->name); return NULL; @@ -137,7 +160,7 @@ sfxcache_t *S_LoadSound (sfx_t *s) stepscale = (float)info.rate / shm->speed; len = info.samples / stepscale; - len = len * info.width * info.channels; + len = len * info.width;// * info.channels; if (info.samples == 0 || len == 0) { @@ -149,11 +172,11 @@ sfxcache_t *S_LoadSound (sfx_t *s) if (!sc) return NULL; - sc->length = info.samples; + sc->length = info.samples / info.channels; sc->loopstart = info.loopstart; sc->speed = info.rate; sc->width = info.width; - sc->stereo = info.channels; + sc->stereo = info.channels-1; ResampleSfx (s, sc->speed, sc->width, data + info.dataofs); diff --git a/Quake/snd_mix.c b/Quake/snd_mix.c index 8dc3e02f..1fc345e7 100644 --- a/Quake/snd_mix.c +++ b/Quake/snd_mix.c @@ -244,6 +244,8 @@ static void S_ApplyFilter(filter_t *filter, int *data, int stride, int count) int parity; input = (float *) malloc(sizeof(float) * (filter->kernelsize + count)); + if (!input) + return; // set up the input buffer // memory holds the previous filter->kernelsize samples of input. diff --git a/Quake/snd_voip.c b/Quake/snd_voip.c new file mode 100755 index 00000000..85b5cd7b --- /dev/null +++ b/Quake/snd_voip.c @@ -0,0 +1,2866 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" + +/* +authorship and stuff: +Audio resampling functions: Originally from DarkPlaces, presumably LordHavoc is the original author. +Raw Audio Capture (DSound): Spike. +Networking: Spike. +Raw Audio Playback/Streaming: qqshka: This code is taken straight from ezQuake. +Codec: The Speex team. +Codec: Opus peeps... + +Compatibility: +This code requires the engine to know if the peer also supports the extension. +Servers send a 'cmd pext\n' stufftext. The client intercepts this and replaces it with a 'cmd pext KEY VALUE ...\n' command. +When the server receives the extended pext command, it reads the key+value pairs, and tells the client the extensions that be used for the connection. +No voice commands nor packets will be sent which the other is unable to understand. The exception is in the form of demos. Such demos will require the replaying client to understand the data. + + +*/ + +/*****************************************************************************************************************************/ +/*System componant (should be inside sys_win.c, only win32 supported)*/ +/*sidenote: should probably just statically link*/ + +#ifdef USE_CODEC_OPUS +#ifndef _WIN32 //for some reason, the win32 libs lack opus_encode* symbols. the dlls contain them though. I've no idea what's going on there. +#define OPUS_STATIC +#endif +#endif + +typedef struct { + void **funcptr; + char *name; +} dllfunction_t; +typedef void *dllhandle_t; +dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs); +void Sys_CloseLibrary(dllhandle_t *lib); + +#include "arch_def.h" + +#ifdef _WIN32 +#include +void Sys_CloseLibrary(dllhandle_t *lib) +{ + FreeLibrary((HMODULE)lib); +} +dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) +{ + int i; + HMODULE lib; + + lib = LoadLibrary(name); + if (!lib) + { +#ifdef _WIN64 + lib = LoadLibrary(va("%s_64", name)); +#elif defined(_WIN32) + lib = LoadLibrary(va("%s_32", name)); +#endif + if (!lib) + return NULL; + } + + if (funcs) + { + for (i = 0; funcs[i].name; i++) + { + *funcs[i].funcptr = GetProcAddress(lib, funcs[i].name); + if (!*funcs[i].funcptr) + break; + } + if (funcs[i].name) + { + Con_SafePrintf("Symbol %s missing in module %s\n", funcs[i].name, name); + Sys_CloseLibrary((dllhandle_t*)lib); + lib = NULL; + } + } + + return (dllhandle_t*)lib; +} +#elif defined(PLATFORM_UNIX) +//unixes should have a dlopen (this test includes osx) +#include +void Sys_CloseLibrary(dllhandle_t *lib) +{ + dlclose((void*)lib); +} +dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) +{ + int i; + dllhandle_t *lib; + + lib = dlopen (name, RTLD_LAZY|RTLD_LOCAL); + if (!lib) + return NULL; + + if (funcs) + { + for (i = 0; funcs[i].name; i++) + { + *funcs[i].funcptr = dlsym(lib, funcs[i].name); + if (!*funcs[i].funcptr) + break; + } + if (funcs[i].name) + { + Con_SafePrintf("Symbol %s missing in module %s\n", funcs[i].name, name); + Sys_CloseLibrary((dllhandle_t*)lib); + lib = NULL; + } + } + + return (dllhandle_t*)lib; +} +#else +void Sys_CloseLibrary(dllhandle_t *lib) +{ +} +dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) +{ + Con_SafePrintf("Sys_LoadLibrary(%s) is not implemented on this platform\n", name); + return NULL; +} +#endif + + +/*****************************************************************************************************************************/ +/*audio capture componant. only dsound supported in this implementation.*/ + +/*sound capture driver stuff*/ +typedef struct +{ + void *(*Init) (int samplerate); /*create a new context*/ + void (*Start) (void *ctx); /*begin grabbing new data, old data is potentially flushed*/ + unsigned int (*Update) (void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes); /*grab the data into a different buffer*/ + void (*Stop) (void *ctx); /*stop grabbing new data, old data may remain*/ + void (*Shutdown) (void *ctx); /*destroy everything*/ +} snd_capture_driver_t; + +#if defined(USE_SDL2) && SDL_VERSION_ATLEAST(2,0,5) + #define USE_SDL_CAPTURE +#elif defined(_WIN32) + #define USE_DSOUND_CAPTURE +#else + //user is screwed. linux has too many competing apis that probably won't work. + #pragma message("No VOIP audio capture supported") +#endif + +#ifdef USE_DSOUND_CAPTURE +#include +#include +static HINSTANCE hInstDSC; +static HRESULT (WINAPI *pDirectSoundCaptureCreate)(GUID FAR *lpGUID, LPDIRECTSOUNDCAPTURE FAR *lplpDS, IUnknown FAR *pUnkOuter); + +typedef struct +{ + LPDIRECTSOUNDCAPTURE DSCapture; + LPDIRECTSOUNDCAPTUREBUFFER DSCaptureBuffer; + long lastreadpos; +} dsndcapture_t; +static const long bufferbytes = 1024*1024; + +static const long inputwidth = 2; + +static void *DSOUND_Capture_Init (int rate) +{ + dsndcapture_t *result; + DSCBUFFERDESC bufdesc; + + WAVEFORMATEX wfxFormat; + + wfxFormat.wFormatTag = WAVE_FORMAT_PCM; + wfxFormat.nChannels = 1; + wfxFormat.nSamplesPerSec = rate; + wfxFormat.wBitsPerSample = 8*inputwidth; + wfxFormat.nBlockAlign = wfxFormat.nChannels * (wfxFormat.wBitsPerSample / 8); + wfxFormat.nAvgBytesPerSec = wfxFormat.nSamplesPerSec * wfxFormat.nBlockAlign; + wfxFormat.cbSize = 0; + + bufdesc.dwSize = sizeof(bufdesc); + bufdesc.dwBufferBytes = bufferbytes; + bufdesc.dwFlags = 0; + bufdesc.dwReserved = 0; + bufdesc.lpwfxFormat = &wfxFormat; + + /*probably already inited*/ + if (!hInstDSC) + { + hInstDSC = LoadLibrary("dsound.dll"); + + if (hInstDSC == NULL) + { + Con_SafePrintf ("Couldn't load dsound.dll\n"); + return NULL; + } + } + /*global pointer, used only in this function*/ + if (!pDirectSoundCaptureCreate) + { + pDirectSoundCaptureCreate = (void *)GetProcAddress(hInstDSC, "DirectSoundCaptureCreate"); + + if (!pDirectSoundCaptureCreate) + { + Con_SafePrintf ("Couldn't get DS proc addr\n"); + return NULL; + } + +// pDirectSoundCaptureEnumerate = (void *)GetProcAddress(hInstDS,"DirectSoundCaptureEnumerateA"); + } + + result = Z_Malloc(sizeof(*result)); + if (!FAILED(pDirectSoundCaptureCreate(NULL, &result->DSCapture, NULL))) + { + if (!FAILED(IDirectSoundCapture_CreateCaptureBuffer(result->DSCapture, &bufdesc, &result->DSCaptureBuffer, NULL))) + { + return result; + } + IDirectSoundCapture_Release(result->DSCapture); + Con_SafePrintf ("Couldn't create a capture buffer\n"); + } + Z_Free(result); + return NULL; +} + +static void DSOUND_Capture_Start(void *ctx) +{ + DWORD capturePos; + dsndcapture_t *c = ctx; + IDirectSoundCaptureBuffer_Start(c->DSCaptureBuffer, DSBPLAY_LOOPING); + + c->lastreadpos = 0; + IDirectSoundCaptureBuffer_GetCurrentPosition(c->DSCaptureBuffer, &capturePos, &c->lastreadpos); +} + +static void DSOUND_Capture_Stop(void *ctx) +{ + dsndcapture_t *c = ctx; + IDirectSoundCaptureBuffer_Stop(c->DSCaptureBuffer); +} + +static void DSOUND_Capture_Shutdown(void *ctx) +{ + dsndcapture_t *c = ctx; + if (c->DSCaptureBuffer) + { + IDirectSoundCaptureBuffer_Stop(c->DSCaptureBuffer); + IDirectSoundCaptureBuffer_Release(c->DSCaptureBuffer); + } + if (c->DSCapture) + { + IDirectSoundCapture_Release(c->DSCapture); + } + Z_Free(ctx); +} + +/*minsamples is a hint*/ +static unsigned int DSOUND_Capture_Update(void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes) +{ + dsndcapture_t *c = ctx; + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + + DWORD capturePos; + DWORD readPos; + long filled; + +// Query to see how much data is in buffer. + hr = IDirectSoundCaptureBuffer_GetCurrentPosition(c->DSCaptureBuffer, &capturePos, &readPos); + if (hr != DS_OK) + { + return 0; + } + filled = readPos - c->lastreadpos; + if (filled < 0) + filled += bufferbytes; // unwrap offset + + if (filled > (long)maxbytes) //figure out how much we need to empty it by, and if that's enough to be worthwhile. + filled = maxbytes; + else if (filled < (long)minbytes) + return 0; + +// filled /= inputwidth; +// filled *= inputwidth; + + // Lock free space in the DS + hr = IDirectSoundCaptureBuffer_Lock(c->DSCaptureBuffer, c->lastreadpos, filled, (void **) &lpbuf1, &dwsize1, (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy from DS to the buffer + memcpy(buffer, lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + memcpy(buffer+dwsize1, lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + c->lastreadpos = (c->lastreadpos + dwsize1 + dwsize2) % bufferbytes; + IDirectSoundCaptureBuffer_Unlock(c->DSCaptureBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + } + else + { + return 0; + } + return filled; +} +static snd_capture_driver_t DSOUND_Capture = +{ + DSOUND_Capture_Init, + DSOUND_Capture_Start, + DSOUND_Capture_Update, + DSOUND_Capture_Stop, + DSOUND_Capture_Shutdown +}; +#endif +#ifdef USE_SDL_CAPTURE +//Requires SDL 2.0.5+ supposedly. +//Bugging out for me on windows, with really low audio levels. looks like there's been some float->int conversion without a multiplier. asking for float audio gives stupidly low values too. +typedef struct +{ + SDL_AudioDeviceID dev; +} sdlcapture_t; + +static void SDL_Capture_Start(void *ctx) +{ + sdlcapture_t *d = ctx; + SDL_PauseAudioDevice(d->dev, SDL_FALSE); +} + +static void SDL_Capture_Stop(void *ctx) +{ + sdlcapture_t *d = ctx; + SDL_PauseAudioDevice(d->dev, SDL_TRUE); +} + +static void SDL_Capture_Shutdown(void *ctx) +{ + sdlcapture_t *d = ctx; + SDL_CloseAudioDevice(d->dev); + Z_Free(d); +} + +static void *SDL_Capture_Init (int rate) +{ + SDL_AudioSpec want, have; + sdlcapture_t c, *r; + + SDL_memset(&want, 0, sizeof(want)); /* or SDL_zero(want) */ + want.freq = rate; + want.format = AUDIO_S16; + want.channels = 1; + want.samples = 256; //this seems to be chunk sizes rather than total buffer size, so lets keep it reasonably small for lower latencies + want.callback = NULL; + + c.dev = SDL_OpenAudioDevice(NULL, true, &want, &have, 0); + if (!c.dev) //failed? + return NULL; + + r = Z_Malloc(sizeof(*r)); + *r = c; + return r; +} + +/*minbytes is a hint to not bother wasting time*/ +static unsigned int SDL_Capture_Update(void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes) +{ + sdlcapture_t *c = ctx; + unsigned int queuedsize = SDL_GetQueuedAudioSize(c->dev); + if (queuedsize < minbytes) + return 0; + if (queuedsize > maxbytes) + queuedsize = maxbytes; + + queuedsize = SDL_DequeueAudio(c->dev, buffer, queuedsize); + return queuedsize; +} +static snd_capture_driver_t SDL_Capture = +{ + SDL_Capture_Init, + SDL_Capture_Start, + SDL_Capture_Update, + SDL_Capture_Stop, + SDL_Capture_Shutdown +}; +#endif + +/*****************************************************************************************************************************/ + +/*Mixer stuff*/ + +#define LINEARUPSCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = inrate / (double)outrate; \ + infrac = floor(scale * 65536); \ + outsamps = insamps / scale; \ + inaccum = 0; \ + outnlsamps = floor(1.0 / scale); \ + outsamps -= outnlsamps; \ + \ + while (outsamps) \ + { \ + *out = ((0xFFFF - inaccum)*in[0] + inaccum*in[1]) >> (16 - outlshift + outrshift); \ + inaccum += infrac; \ + in += (inaccum >> 16); \ + inaccum &= 0xFFFF; \ + out++; \ + outsamps--; \ + } \ + while (outnlsamps) \ + { \ + *out = (*in >> outrshift) << outlshift; \ + out++; \ + outnlsamps--; \ + } \ + } + +#define LINEARUPSCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = inrate / (double)outrate; \ + infrac = floor(scale * 65536); \ + outsamps = insamps / scale; \ + inaccum = 0; \ + outnlsamps = floor(1.0 / scale); \ + outsamps -= outnlsamps; \ + \ + while (outsamps) \ + { \ + out[0] = ((0xFFFF - inaccum)*in[0] + inaccum*in[2]) >> (16 - outlshift + outrshift); \ + out[1] = ((0xFFFF - inaccum)*in[1] + inaccum*in[3]) >> (16 - outlshift + outrshift); \ + inaccum += infrac; \ + in += (inaccum >> 16) * 2; \ + inaccum &= 0xFFFF; \ + out += 2; \ + outsamps--; \ + } \ + while (outnlsamps) \ + { \ + out[0] = (in[0] >> outrshift) << outlshift; \ + out[1] = (in[1] >> outrshift) << outlshift; \ + out += 2; \ + outnlsamps--; \ + } \ + } + +#define LINEARUPSCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = inrate / (double)outrate; \ + infrac = floor(scale * 65536); \ + outsamps = insamps / scale; \ + inaccum = 0; \ + outnlsamps = floor(1.0 / scale); \ + outsamps -= outnlsamps; \ + \ + while (outsamps) \ + { \ + *out = ((((0xFFFF - inaccum)*in[0] + inaccum*in[2]) >> (16 - outlshift + outrshift)) + \ + (((0xFFFF - inaccum)*in[1] + inaccum*in[3]) >> (16 - outlshift + outrshift))) >> 1; \ + inaccum += infrac; \ + in += (inaccum >> 16) * 2; \ + inaccum &= 0xFFFF; \ + out++; \ + outsamps--; \ + } \ + while (outnlsamps) \ + { \ + out[0] = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \ + out++; \ + outnlsamps--; \ + } \ + } + +#define LINEARDOWNSCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = outrate / (double)inrate; \ + infrac = floor(scale * 65536); \ + inaccum = 0; \ + insamps--; \ + outsampleft = 0; \ + \ + while (insamps) \ + { \ + inaccum += infrac; \ + if (inaccum >> 16) \ + { \ + inaccum &= 0xFFFF; \ + outsampleft += (infrac - inaccum) * (*in); \ + *out = outsampleft >> (16 - outlshift + outrshift); \ + out++; \ + outsampleft = inaccum * (*in); \ + } \ + else \ + outsampleft += infrac * (*in); \ + in++; \ + insamps--; \ + } \ + outsampleft += (0xFFFF - inaccum) * (*in);\ + *out = outsampleft >> (16 - outlshift + outrshift); \ + } + +#define LINEARDOWNSCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = outrate / (double)inrate; \ + infrac = floor(scale * 65536); \ + inaccum = 0; \ + insamps--; \ + outsampleft = 0; \ + outsampright = 0; \ + \ + while (insamps) \ + { \ + inaccum += infrac; \ + if (inaccum >> 16) \ + { \ + inaccum &= 0xFFFF; \ + outsampleft += (infrac - inaccum) * in[0]; \ + outsampright += (infrac - inaccum) * in[1]; \ + out[0] = outsampleft >> (16 - outlshift + outrshift); \ + out[1] = outsampright >> (16 - outlshift + outrshift); \ + out += 2; \ + outsampleft = inaccum * in[0]; \ + outsampright = inaccum * in[1]; \ + } \ + else \ + { \ + outsampleft += infrac * in[0]; \ + outsampright += infrac * in[1]; \ + } \ + in += 2; \ + insamps--; \ + } \ + outsampleft += (0xFFFF - inaccum) * in[0];\ + outsampright += (0xFFFF - inaccum) * in[1];\ + out[0] = outsampleft >> (16 - outlshift + outrshift); \ + out[1] = outsampright >> (16 - outlshift + outrshift); \ + } + +#define LINEARDOWNSCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = outrate / (double)inrate; \ + infrac = floor(scale * 65536); \ + inaccum = 0; \ + insamps--; \ + outsampleft = 0; \ + \ + while (insamps) \ + { \ + inaccum += infrac; \ + if (inaccum >> 16) \ + { \ + inaccum &= 0xFFFF; \ + outsampleft += (infrac - inaccum) * ((in[0] + in[1]) >> 1); \ + *out = outsampleft >> (16 - outlshift + outrshift); \ + out++; \ + outsampleft = inaccum * ((in[0] + in[1]) >> 1); \ + } \ + else \ + outsampleft += infrac * ((in[0] + in[1]) >> 1); \ + in += 2; \ + insamps--; \ + } \ + outsampleft += (0xFFFF - inaccum) * ((in[0] + in[1]) >> 1);\ + *out = outsampleft >> (16 - outlshift + outrshift); \ + } + +#define STANDARDRESCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = inrate / (double)outrate; \ + infrac = floor(scale * 65536); \ + outsamps = insamps / scale; \ + inaccum = 0; \ + \ + while (outsamps) \ + { \ + *out = (*in >> outrshift) << outlshift; \ + inaccum += infrac; \ + in += (inaccum >> 16); \ + inaccum &= 0xFFFF; \ + out++; \ + outsamps--; \ + } \ + } + +#define STANDARDRESCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = inrate / (double)outrate; \ + infrac = floor(scale * 65536); \ + outsamps = insamps / scale; \ + inaccum = 0; \ + \ + while (outsamps) \ + { \ + out[0] = (in[0] >> outrshift) << outlshift; \ + out[1] = (in[1] >> outrshift) << outlshift; \ + inaccum += infrac; \ + in += (inaccum >> 16) * 2; \ + inaccum &= 0xFFFF; \ + out += 2; \ + outsamps--; \ + } \ + } + +#define STANDARDRESCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \ + { \ + scale = inrate / (double)outrate; \ + infrac = floor(scale * 65536); \ + outsamps = insamps / scale; \ + inaccum = 0; \ + \ + while (outsamps) \ + { \ + out[0] = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \ + inaccum += infrac; \ + in += (inaccum >> 16) * 2; \ + inaccum &= 0xFFFF; \ + out++; \ + outsamps--; \ + } \ + } + +#define QUICKCONVERT(in, insamps, out, outlshift, outrshift) \ + { \ + while (insamps) \ + { \ + *out = (*in >> outrshift) << outlshift; \ + out++; \ + in++; \ + insamps--; \ + } \ + } + +#define QUICKCONVERTSTEREOTOMONO(in, insamps, out, outlshift, outrshift) \ + { \ + while (insamps) \ + { \ + *out = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \ + out++; \ + in += 2; \ + insamps--; \ + } \ + } + +// SND_ResampleStream: takes a sound stream and converts with given parameters. Limited to +// 8-16-bit signed conversions and mono-to-mono/stereo-to-stereo conversions. +// Not an in-place algorithm. +void SND_ResampleStream (void *in, int inrate, int inwidth, int inchannels, int insamps, void *out, int outrate, int outwidth, int outchannels, int resampstyle) +{ + double scale; + signed char *in8 = (signed char *)in; + short *in16 = (short *)in; + signed char *out8 = (signed char *)out; + short *out16 = (short *)out; + int outsamps, outnlsamps, outsampleft, outsampright; + int infrac, inaccum; + + if (insamps <= 0) + return; + + if (inchannels == outchannels && inwidth == outwidth && inrate == outrate) + { + memcpy(out, in, inwidth*insamps*inchannels); + return; + } + + if (inchannels == 1 && outchannels == 1) + { + if (inwidth == 1) + { + if (outwidth == 1) + { + if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALE(in8, inrate, insamps, out8, outrate, 0, 0) + else + STANDARDRESCALE(in8, inrate, insamps, out8, outrate, 0, 0) + } + else // downsample + { + if (resampstyle > 1) + LINEARDOWNSCALE(in8, inrate, insamps, out8, outrate, 0, 0) + else + STANDARDRESCALE(in8, inrate, insamps, out8, outrate, 0, 0) + } + return; + } + else + { + if (inrate == outrate) // quick convert + QUICKCONVERT(in8, insamps, out16, 8, 0) + else if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALE(in8, inrate, insamps, out16, outrate, 8, 0) + else + STANDARDRESCALE(in8, inrate, insamps, out16, outrate, 8, 0) + } + else // downsample + { + if (resampstyle > 1) + LINEARDOWNSCALE(in8, inrate, insamps, out16, outrate, 8, 0) + else + STANDARDRESCALE(in8, inrate, insamps, out16, outrate, 8, 0) + } + return; + } + } + else // 16-bit + { + if (outwidth == 2) + { + if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALE(in16, inrate, insamps, out16, outrate, 0, 0) + else + STANDARDRESCALE(in16, inrate, insamps, out16, outrate, 0, 0) + } + else // downsample + { + if (resampstyle > 1) + LINEARDOWNSCALE(in16, inrate, insamps, out16, outrate, 0, 0) + else + STANDARDRESCALE(in16, inrate, insamps, out16, outrate, 0, 0) + } + return; + } + else + { + if (inrate == outrate) // quick convert + QUICKCONVERT(in16, insamps, out8, 0, 8) + else if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALE(in16, inrate, insamps, out8, outrate, 0, 8) + else + STANDARDRESCALE(in16, inrate, insamps, out8, outrate, 0, 8) + } + else // downsample + { + if (resampstyle > 1) + LINEARDOWNSCALE(in16, inrate, insamps, out8, outrate, 0, 8) + else + STANDARDRESCALE(in16, inrate, insamps, out8, outrate, 0, 8) + } + return; + } + } + } + else if (outchannels == 2 && inchannels == 2) + { + if (inwidth == 1) + { + if (outwidth == 1) + { + if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0) + else + STANDARDRESCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0) + } + else // downsample + { + if (resampstyle > 1) + LINEARDOWNSCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0) + else + STANDARDRESCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0) + } + } + else + { + if (inrate == outrate) // quick convert + { + insamps *= 2; + QUICKCONVERT(in8, insamps, out16, 8, 0) + } + else if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0) + else + STANDARDRESCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0) + } + else // downsample + { + if (resampstyle > 1) + LINEARDOWNSCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0) + else + STANDARDRESCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0) + } + } + } + else // 16-bit + { + if (outwidth == 2) + { + if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0) + else + STANDARDRESCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0) + } + else // downsample + { + if (resampstyle > 1) + LINEARDOWNSCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0) + else + STANDARDRESCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0) + } + } + else + { + if (inrate == outrate) // quick convert + { + insamps *= 2; + QUICKCONVERT(in16, insamps, out8, 0, 8) + } + else if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8) + else + STANDARDRESCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8) + } + else // downsample + { + if (resampstyle > 1) + LINEARDOWNSCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8) + else + STANDARDRESCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8) + } + } + } + } +#if 0 + else if (outchannels == 1 && inchannels == 2) + { + if (inwidth == 1) + { + if (outwidth == 1) + { + if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0) + else + STANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0) + } + else // downsample + STANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0) + } + else + { + if (inrate == outrate) // quick convert + QUICKCONVERTSTEREOTOMONO(in8, insamps, out16, 8, 0) + else if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0) + else + STANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0) + } + else // downsample + STANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0) + } + } + else // 16-bit + { + if (outwidth == 2) + { + if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0) + else + STANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0) + } + else // downsample + STANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0) + } + else + { + if (inrate == outrate) // quick convert + QUICKCONVERTSTEREOTOMONO(in16, insamps, out8, 0, 8) + else if (inrate < outrate) // upsample + { + if (resampstyle) + LINEARUPSCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8) + else + STANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8) + } + else // downsample + STANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8) + } + } + } +#endif +} + + + + + + +#define MAX_RAW_CACHE (1024 * 32) // have no idea which size it actually should be. this specifies the maximum buffer size. if it gets full, the entire thing is dumped in order to try to reduce latency (we have no time drifting or anything) + +typedef struct +{ + qboolean inuse; + int id; + sfx_t sfx; +} streaming_t; + +static void S_RawClearStream(streaming_t *s); + +#define MAX_RAW_SOURCES (MAX_SCOREBOARD+1) + +static streaming_t s_streamers[MAX_RAW_SOURCES] = {{0}}; + +/*static void S_RawClear(void) +{ + int i; + streaming_t *s; + + for (s = s_streamers, i = 0; i < MAX_RAW_SOURCES; i++, s++) + { + S_RawClearStream(s); + } + + memset(s_streamers, 0, sizeof(s_streamers)); +}*/ + +// Stop playing particular stream and make it free. +static void S_RawClearStream(streaming_t *s) +{ + int i; + sfxcache_t * currentcache; + + if (!s) + return; + + // get current cache if any. + currentcache = (sfxcache_t *)Cache_Check(&s->sfx.cache); + if (currentcache) + { + currentcache->loopstart = -1; //stop mixing it + } + + // remove link on sfx from the channels array. + for (i = 0; i < total_channels; i++) + { + if (snd_channels[i].sfx == &s->sfx) + { + snd_channels[i].sfx = NULL; + break; + } + } + + // free cache. + if (s->sfx.cache.data) + Cache_Free(&s->sfx.cache, false); + + // clear whole struct. + memset(s, 0, sizeof(*s)); +} + +// Searching for free slot or re-use previous one with the same sourceid. +static streaming_t * S_RawGetFreeStream(int sourceid) +{ + int i; + streaming_t *s, *free = NULL; + + for (s = s_streamers, i = 0; i < MAX_RAW_SOURCES; i++, s++) + { + if (!s->inuse) + { + if (!free) + { + free = s; // found free stream slot. + } + + continue; + } + + if (s->id == sourceid) + { + return s; // re-using slot. + } + } + + return free; +} + +// Streaming audio. +// This is useful when there is one source, and the sound is to be played with no attenuation. +void S_RawAudio(int sourceid, byte *data, unsigned int speed, unsigned int samples, unsigned int channelsnum, unsigned int width, float volume) +{ + int i; + int newsize; + int prepadl; + int spare; + int outsamples; + double speedfactor; + sfxcache_t * currentcache; + streaming_t * s; + + // search for free slot or re-use previous one with the same sourceid. + s = S_RawGetFreeStream(sourceid); + + if (!s) + { + Con_DPrintf("No free audio streams or stream not found\n"); + return; + } + + // empty data mean someone tell us to shut up particular slot. + if (!data) + { + S_RawClearStream(s); + return; + } + + // attempting to add new stream + if (!s->inuse) + { + sfxcache_t * newcache; + + // clear whole struct. + memset(s, 0, sizeof(*s)); + // allocate cache. + newsize = MAX_RAW_CACHE+sizeof(sfxcache_t); + + newcache = Cache_Alloc(&s->sfx.cache, newsize, "rawaudio"); + if (!newcache) + { + Con_DPrintf("Cache_Alloc failed\n"); + return; + } + + s->inuse = true; + s->id = sourceid; + // strcpy(s->sfx.name, ""); // FIXME: probably we should put some specific tag name here? + s->sfx.cache.data = newcache; + newcache->speed = shm->speed; + newcache->stereo = channelsnum-1; + newcache->width = width; + newcache->loopstart = -1; + newcache->length = 0; + newcache = NULL; + + // Con_Printf("Added new raw stream\n"); + } + + // get current cache if any. + currentcache = (sfxcache_t *)Cache_Check(&s->sfx.cache); + if (!currentcache) + { + Con_DPrintf("Cache_Check failed\n"); + S_RawClearStream(s); + return; + } + + if ( currentcache->speed != shm->speed + || currentcache->stereo != (int)channelsnum-1 + || currentcache->width != (int)width) + { + currentcache->speed = shm->speed; + currentcache->stereo = channelsnum-1; + currentcache->width = width; + // newcache->loopstart = -1; + currentcache->length = 0; + // Con_Printf("Restarting raw stream\n"); + } + + speedfactor = (double)speed/shm->speed; + outsamples = samples/speedfactor; + + //prepadl is the length of data at the start of the sample which appears to be unused. its the amount of data that we can discard. + prepadl = 0x7fffffff; + for (i = 0; i < total_channels; i++) + { + if (snd_channels[i].sfx == &s->sfx) + { + if (prepadl > (snd_channels[i].pos/*>>PITCHSHIFT*/)) + prepadl = (snd_channels[i].pos/*>>PITCHSHIFT*/); + break; + } + } + + if (prepadl == 0x7fffffff) + { + prepadl = 0; + spare = 0; + if (spare > shm->speed) + { + Con_DPrintf("Sacrificed raw sound stream\n"); + spare = 0; //too far out. sacrifice it all + } + } + else + { + if (prepadl < 0) + prepadl = 0; + spare = currentcache->length - prepadl; + if (spare < 0) //remaining samples since last time + spare = 0; + + if (spare > shm->speed * 2) // more than 2 seconds of sound + { + Con_DPrintf("Sacrificed raw sound stream\n"); + spare = 0; //too far out. sacrifice it all + } + } + + newsize = (spare + outsamples) * (currentcache->stereo+1) * currentcache->width; + if (newsize > MAX_RAW_CACHE) + { //this can happen quite often when our playback driver isn't playing sound due to the window not having focus. + Con_DPrintf("VOIP stream overflowed\n"); + S_RawClearStream(s); + return; + } + + // move along spare/remaning samples in the begging of the buffer. + memmove(currentcache->data, + currentcache->data + prepadl * (currentcache->stereo+1) * currentcache->width, + spare * (currentcache->stereo+1) * currentcache->width); + + currentcache->length = spare + outsamples; + + // resample. + { + short *outpos = (short *)(currentcache->data + spare * (currentcache->stereo+1) * currentcache->width); + SND_ResampleStream(data, + speed, + width, + channelsnum, + samples, + outpos, + shm->speed, + currentcache->width, + currentcache->stereo+1, + true + ); + } + + currentcache->loopstart = -1;//currentcache->total_length; + + for (i = 0; i < total_channels; i++) + { + if (snd_channels[i].sfx == &s->sfx) + { +#if 0 + // FIXME: qqshka: hrm, should it be just like this all the time??? I think it should. + snd_channels[i].pos = 0; + snd_channels[i].end = paintedtime + currentcache->total_length; +#else + snd_channels[i].pos -= prepadl; // * channels[i].rate; + snd_channels[i].end += outsamples; + snd_channels[i].master_vol = (int) (volume * 255); // this should changed volume on alredy playing sound. + + if (snd_channels[i].end < paintedtime) + { + snd_channels[i].pos = 0; + snd_channels[i].end = paintedtime + currentcache->length; + } +#endif + break; + } + } + + //this one wasn't playing, lets start it then. + if (i == total_channels) + { + // Con_DPrintf("start sound\n"); + S_StartSound(sourceid+1, -2, &s->sfx, r_origin, 1, 0); + } +} + +/*****************************************************************************************************************************/ +/*client/coding/decoding componant*/ + +//#define USE_SPEEX_CODEC +//#define USE_SPEEX_DSP + +/*the client cvars*/ +cvar_t cl_voip_send = {"cl_voip_send", "0"}; //0=off,1=voice activation,2=continuous +cvar_t cl_voip_test = {"cl_voip_test", "0"}; +cvar_t cl_voip_vad_threshhold = {"cl_voip_vad_threshhold", "15"}; +cvar_t cl_voip_vad_delay = {"cl_voip_vad_delay", "0.3"}; +cvar_t cl_voip_capturingvol = {"cl_voip_capturingvol", "0.5", true}; +cvar_t cl_voip_showmeter = {"cl_voip_showmeter", "1", true}; + +cvar_t cl_voip_play = {"cl_voip_play", "1", true}; +cvar_t cl_voip_micamp = {"cl_voip_micamp", "2", true}; +cvar_t cl_voip_ducking = {"cl_voip_ducking", "0.5", true}; +cvar_t cl_voip_codec = {"cl_voip_codec", "", true}; //opus by default (quakespasm actually comes with a dll for that one) +cvar_t cl_voip_noisefilter = {"cl_voip_noisefilter", "1", true}; +cvar_t cl_voip_autogain = {"cl_voip_autogain", "0", true}; +cvar_t cl_voip_opus_bitrate = {"cl_voip_opus_bitrate", "3000", true}; + +#ifdef USE_SPEEX_CODEC +#include +#endif +#ifdef USE_SPEEX_DSP +#include +#endif + +#ifndef QDECL +#define QDECL //__cdecl +#endif + +enum +{ + VOIP_SPEEX_OLD = 0, //original supported codec (with needless padding [because I didn't understand speex's format] narrowband speex encoded+played at the wrong rate [because it was easier at the time]) + VOIP_RAW16 = 1, //support is not recommended, but useful for testing. 16bit, 11khz, mono + VOIP_OPUS = 2, //supposed to be better than speex. + VOIP_SPEEX_NARROW = 3, //narrowband speex. packed data. + VOIP_SPEEX_WIDE = 4, //wideband speex. packed data. + VOIP_SPEEX_ULTRAWIDE = 5,//for wasteful people + VOIP_PCMA = 6, //G711 is kinda shit, encoding audio at 8khz with funny truncation for 13bit to 8bit, but its quite simple, and smaller than raw pcm, as well as a well-known voip standard + VOIP_PCMU = 7, //ulaw version of g711 (instead of alaw) + + + VOIP_INVALID = 16 //not currently generating audio. +}; +static struct +{ +#ifdef USE_SPEEX_CODEC + struct + { + qboolean inited; + qboolean loaded; + dllhandle_t *speexlib; + + SpeexBits encbits; + SpeexBits decbits[MAX_SCOREBOARD]; + + const SpeexMode *modenb; + const SpeexMode *modewb; + } speex; +#endif +#ifdef USE_SPEEX_DSP + struct + { + qboolean inited; + qboolean loaded; + dllhandle_t *speexdsplib; + + SpeexPreprocessState *preproc; //filter out noise + int curframesize; + int cursamplerate; + } speexdsp; +#endif + + struct + { + qboolean inited[2]; + qboolean loaded[2]; + dllhandle_t *opuslib[2]; + } opus; + + unsigned char enccodec; + void *encoder; + unsigned int encframesize; + unsigned int encsamplerate; + int curbitrate; + + void *decoder[MAX_SCOREBOARD]; + unsigned char deccodec[MAX_SCOREBOARD]; + unsigned char decseq[MAX_SCOREBOARD]; /*sender's sequence, to detect+cover minor packetloss*/ + unsigned char decgen[MAX_SCOREBOARD]; /*last generation. if it changes, we flush speex to reset packet loss*/ + unsigned int decsamplerate[MAX_SCOREBOARD]; + unsigned int decframesize[MAX_SCOREBOARD]; + float lastspoke[MAX_SCOREBOARD]; /*time when they're no longer considered talking. if future, they're talking*/ + float lastspoke_any; + + unsigned char capturebuf[32768]; /*pending data*/ + unsigned int capturepos;/*amount of pending data*/ + unsigned int encsequence;/*the outgoing sequence count*/ + unsigned int enctimestamp;/*for rtp streaming*/ + unsigned int generation;/*incremented whenever capture is restarted*/ + qboolean wantsend; /*set if we're capturing data to send*/ + float voiplevel; /*your own voice level*/ + unsigned int dumps; /*trigger a new generation thing after a bit*/ + unsigned int keeps; /*for vad_delay*/ + + snd_capture_driver_t *cdriver;/*capture driver's functions*/ + void *cdriverctx; /*capture driver context*/ + + qboolean voipsendbutton; +} s_voip; + +//snd_capture_driver_t DSOUND_Capture; +//snd_capture_driver_t OSS_Capture; + +#define OPUS_APPLICATION_VOIP 2048 +#define OPUS_SET_BITRATE_REQUEST 4002 +#define OPUS_RESET_STATE 4028 +#ifdef OPUS_STATIC +#include +#define qopus_encoder_create opus_encoder_create +#define qopus_encoder_destroy opus_encoder_destroy +#define qopus_encoder_ctl opus_encoder_ctl +#define qopus_encode opus_encode +#define qopus_decoder_create opus_decoder_create +#define qopus_decoder_destroy opus_decoder_destroy +#define qopus_decoder_ctl opus_decoder_ctl +#define qopus_decode opus_decode +#else +#define opus_int32 int +#define opus_int16 short +#define OpusEncoder void +#define OpusDecoder void +static OpusEncoder *(QDECL *qopus_encoder_create)(opus_int32 Fs, int channels, int application, int *error); +static void (QDECL *qopus_encoder_destroy)(OpusEncoder *st); +static int (QDECL *qopus_encoder_ctl)(OpusEncoder *st, int request, ...); +static opus_int32 (QDECL *qopus_encode)(OpusEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes); +static OpusDecoder *(QDECL *qopus_decoder_create)(opus_int32 Fs, int channels, int *error); +static void (QDECL *qopus_decoder_destroy)(OpusDecoder *st); +static int (QDECL *qopus_decoder_ctl)(OpusDecoder *st, int request, ...); +static int (QDECL *qopus_decode)(OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec); +static dllfunction_t qopusencodefuncs[] = +{ + {(void*)&qopus_encoder_create, "opus_encoder_create"}, + {(void*)&qopus_encoder_destroy, "opus_encoder_destroy"}, + {(void*)&qopus_encoder_ctl, "opus_encoder_ctl"}, + {(void*)&qopus_encode, "opus_encode"}, + + {NULL} +}; +static dllfunction_t qopusdecodefuncs[] = +{ + {(void*)&qopus_decoder_create, "opus_decoder_create"}, + {(void*)&qopus_decoder_destroy, "opus_decoder_destroy"}, + {(void*)&qopus_decoder_ctl, "opus_decoder_ctl"}, + {(void*)&qopus_decode, "opus_decode"}, + + {NULL} +}; +#endif + +#ifdef SPEEX_STATIC +#define qspeex_lib_get_mode speex_lib_get_mode +#define qspeex_bits_init speex_bits_init +#define qspeex_bits_reset speex_bits_reset +#define qspeex_bits_write speex_bits_write + +#define qspeex_preprocess_state_init speex_preprocess_state_init +#define qspeex_preprocess_state_destroy speex_preprocess_state_destroy +#define qspeex_preprocess_ctl speex_preprocess_ctl +#define qspeex_preprocess_run speex_preprocess_run + +#define qspeex_encoder_init speex_encoder_init +#define qspeex_encoder_destroy speex_encoder_destroy +#define qspeex_encoder_ctl speex_encoder_ctl +#define qspeex_encode_int speex_encode_int + +#define qspeex_decoder_init speex_decoder_init +#define qspeex_decoder_destroy speex_decoder_destroy +#define qspeex_decode_int speex_decode_int +#define qspeex_bits_read_from speex_bits_read_from +#else +#ifdef USE_SPEEX_CODEC +static const SpeexMode *(QDECL *qspeex_lib_get_mode)(int mode); +static void (QDECL *qspeex_bits_init)(SpeexBits *bits); +static void (QDECL *qspeex_bits_reset)(SpeexBits *bits); +static int (QDECL *qspeex_bits_write)(SpeexBits *bits, char *bytes, int max_len); + +static void * (QDECL *qspeex_encoder_init)(const SpeexMode *mode); +static int (QDECL *qspeex_encoder_ctl)(void *state, int request, void *ptr); +static int (QDECL *qspeex_encode_int)(void *state, spx_int16_t *in, SpeexBits *bits); + +static void *(QDECL *qspeex_decoder_init)(const SpeexMode *mode); +static void (QDECL *qspeex_decoder_destroy)(void *state); +static int (QDECL *qspeex_decode_int)(void *state, SpeexBits *bits, spx_int16_t *out); +static void (QDECL *qspeex_bits_read_from)(SpeexBits *bits, char *bytes, int len); + +static dllfunction_t qspeexfuncs[] = +{ + {(void*)&qspeex_lib_get_mode, "speex_lib_get_mode"}, + {(void*)&qspeex_bits_init, "speex_bits_init"}, + {(void*)&qspeex_bits_reset, "speex_bits_reset"}, + {(void*)&qspeex_bits_write, "speex_bits_write"}, + + {(void*)&qspeex_encoder_init, "speex_encoder_init"}, + {(void*)&qspeex_encoder_ctl, "speex_encoder_ctl"}, + {(void*)&qspeex_encode_int, "speex_encode_int"}, + + {(void*)&qspeex_decoder_init, "speex_decoder_init"}, + {(void*)&qspeex_decoder_destroy, "speex_decoder_destroy"}, + {(void*)&qspeex_decode_int, "speex_decode_int"}, + {(void*)&qspeex_bits_read_from, "speex_bits_read_from"}, + + {NULL} +}; +#endif + +#ifdef USE_SPEEX_DSP +static SpeexPreprocessState *(QDECL *qspeex_preprocess_state_init)(int frame_size, int sampling_rate); +static void (QDECL *qspeex_preprocess_state_destroy)(SpeexPreprocessState *st); +static int (QDECL *qspeex_preprocess_ctl)(SpeexPreprocessState *st, int request, void *ptr); +static int (QDECL *qspeex_preprocess_run)(SpeexPreprocessState *st, spx_int16_t *x); + +static dllfunction_t qspeexdspfuncs[] = +{ + {(void*)&qspeex_preprocess_state_init, "speex_preprocess_state_init"}, + {(void*)&qspeex_preprocess_state_destroy, "speex_preprocess_state_destroy"}, + {(void*)&qspeex_preprocess_ctl, "speex_preprocess_ctl"}, + {(void*)&qspeex_preprocess_run, "speex_preprocess_run"}, + + {NULL} +}; +#endif +#endif + +#ifdef USE_SPEEX_DSP +static qboolean S_SpeexDSP_Init(void) +{ +#ifndef SPEEX_STATIC + if (s_voip.speexdsp.inited) + return s_voip.speexdsp.loaded; + s_voip.speexdsp.inited = true; + + + s_voip.speexdsp.speexdsplib = Sys_LoadLibrary("libspeexdsp", qspeexdspfuncs); + if (!s_voip.speexdsp.speexdsplib) + { + Con_Printf("libspeexdsp not found. Your mic may be noisy.\n"); + return false; + } +#endif + + s_voip.speexdsp.loaded = true; + return s_voip.speexdsp.loaded; +} +#endif + +#ifdef USE_SPEEX_CODEC +static qboolean S_Speex_Init(void) +{ +#ifndef SPEEX_STATIC + if (s_voip.speex.inited) + return s_voip.speex.loaded; + s_voip.speex.inited = true; + + s_voip.speex.speexlib = Sys_LoadLibrary("libspeex", qspeexfuncs); + if (!s_voip.speex.speexlib) + { + Con_Printf("libspeex not found. Voice chat is not available.\n"); + return false; + } +#endif + + s_voip.speex.modenb = qspeex_lib_get_mode(SPEEX_MODEID_NB); + s_voip.speex.modewb = qspeex_lib_get_mode(SPEEX_MODEID_WB); + + s_voip.speex.loaded = true; + return s_voip.speex.loaded; +} +#endif + +static qboolean S_Opus_Init(int encdec) +{ +#ifndef OPUS_STATIC +#ifdef _WIN32 + char *modulename = "libopus-0.dll"; + char *altmodulename = "libopusenc-0.dll"; +#else + char *modulename = "libopus.so.0"; + char *altmodulename = "libopusenc.so.0"; +#endif + + if (s_voip.opus.inited[encdec]) + return s_voip.opus.loaded[encdec]; + s_voip.opus.inited[encdec] = true; + + s_voip.opus.opuslib[encdec] = Sys_LoadLibrary(modulename, encdec?qopusencodefuncs:qopusdecodefuncs); + if (!s_voip.opus.opuslib[encdec] && encdec) + s_voip.opus.opuslib[encdec] = Sys_LoadLibrary(altmodulename, encdec?qopusencodefuncs:qopusdecodefuncs); + if (!s_voip.opus.opuslib[encdec]) + { + Con_Printf("%s or its exports not found. Opus voip %s is not available.\n", modulename, encdec?"transmission":"reception"); + return false; + } +#endif + + s_voip.opus.loaded[encdec] = true; + return s_voip.opus.loaded[encdec]; +} + +//g711 used to be patented, but those have since expired. +//there's two forms, a-law is generally considered better quality, u-law is a little simpler. +static size_t PCMA_Decode(short *out, unsigned char *in, size_t samples) +{ + size_t i = 0; + for (i = 0; i < samples; i++) + { + unsigned char inv = in[i]^0x55; //g711 alaw inverts every other bit + int m = inv&0xf; + int e = (inv&0x70)>>4; + if (e) + m = (((m)<<1)|0x21) << (e-1); + else + m = (((m)<<1)|1); + if (inv & 0x80) + out[i] = -m; + else + out[i] = m; + } + return i; +} +static size_t PCMA_Encode(unsigned char *out, size_t outsize, short *in, size_t samples) +{ + size_t i = 0; + for (i = 0; i < samples; i++) + { + int o = in[i]; + unsigned char b; + if (o < 0) + { + o = -o; + b = 0x80; + } + else + b = 0; + + if (o >= 0x0800) + b |= ((o>>7)&0xf) | 0x70; + else if (o >= 0x0400) + b |= ((o>>6)&0xf) | 0x60; + else if (o >= 0x0200) + b |= ((o>>5)&0xf) | 0x50; + else if (o >= 0x0100) + b |= ((o>>4)&0xf) | 0x40; + else if (o >= 0x0080) + b |= ((o>>3)&0xf) | 0x30; + else if (o >= 0x0040) + b |= ((o>>2)&0xf) | 0x20; + else if (o >= 0x0020) + b |= ((o>>1)&0xf) | 0x10; + else + b |= ((o>>1)&0xf) | 0x00; + out[i] = b^0x55; //invert every-other bit. + } + + return samples; +} +static size_t PCMU_Decode(short *out, unsigned char *in, size_t samples) +{ + size_t i = 0; + for (i = 0; i < samples; i++) + { + unsigned char inv = in[i]^0xff; + int m = (((inv&0xf)<<1)|0x21) << ((inv&0x70)>>4); + m -= 33; + if (inv & 0x80) + out[i] = -m; + else + out[i] = m; + } + return i; +} +static size_t PCMU_Encode(unsigned char *out, size_t outsize, short *in, size_t samples) +{ + size_t i = 0; + for (i = 0; i < samples; i++) + { + int o = in[i]; + unsigned char b; + if (o < 0) + { + o = ~o; + b = 0x80; + } + else + b = 0; + o+=33; + + if (o >= 0x1000) + b |= ((o>>8)&0xf) | 0x70; + else if (o >= 0x0800) + b |= ((o>>7)&0xf) | 0x60; + else if (o >= 0x0400) + b |= ((o>>6)&0xf) | 0x50; + else if (o >= 0x0200) + b |= ((o>>5)&0xf) | 0x40; + else if (o >= 0x0100) + b |= ((o>>4)&0xf) | 0x30; + else if (o >= 0x0080) + b |= ((o>>3)&0xf) | 0x20; + else if (o >= 0x0040) + b |= ((o>>2)&0xf) | 0x10; + else + b |= ((o>>1)&0xf) | 0x00; + out[i] = b^0xff; + } + + return samples; +} + +void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, unsigned char seq, unsigned int bytes, unsigned char *data) +{ + unsigned char *start; + short decodebuf[8192]; + unsigned int decodesamps, len, drops; + int r; + + if (sender >= MAX_SCOREBOARD) + return; + + decodesamps = 0; + drops = 0; + start = data; + + s_voip.lastspoke[sender] = realtime + 0.5; + if (s_voip.lastspoke[sender] > s_voip.lastspoke_any) + s_voip.lastspoke_any = s_voip.lastspoke[sender]; + + //if they re-started speaking, flush any old state to avoid things getting weirdly delayed and reset the codec properly. + if (s_voip.decgen[sender] != gen || s_voip.deccodec[sender] != codec) + { + S_RawAudio(sender, NULL, s_voip.decsamplerate[sender], 0, 1, 2, 0); + + if (s_voip.deccodec[sender] != codec) + { + //make sure old state is closed properly. + switch(s_voip.deccodec[sender]) + { +#ifdef USE_SPEEX_CODEC + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_ULTRAWIDE: + qspeex_decoder_destroy(s_voip.decoder[sender]); + break; +#endif + case VOIP_RAW16: + case VOIP_PCMA: + case VOIP_PCMU: + break; + case VOIP_OPUS: + qopus_decoder_destroy(s_voip.decoder[sender]); + break; + } + s_voip.decoder[sender] = NULL; + s_voip.deccodec[sender] = VOIP_INVALID; + } + + switch(codec) + { + default: //codec not supported. + return; + case VOIP_RAW16: + s_voip.decsamplerate[sender] = 11025; + break; + case VOIP_PCMA: + case VOIP_PCMU: + s_voip.decsamplerate[sender] = 8000; + s_voip.decframesize[sender] = 8000/20; + break; +#ifdef USE_SPEEX_CODEC + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_ULTRAWIDE: + { + const SpeexMode *smode; + if (!S_Speex_Init()) + return; //speex not usable. + if (codec == VOIP_SPEEX_NARROW) + { + s_voip.decsamplerate[sender] = 8000; + s_voip.decframesize[sender] = 160; + smode = s_voip.speex.modenb; + } + else if (codec == VOIP_SPEEX_WIDE) + { + s_voip.decsamplerate[sender] = 16000; + s_voip.decframesize[sender] = 320; + smode = s_voip.speex.modewb; + } + else if (codec == VOIP_SPEEX_ULTRAWIDE) + { + s_voip.decsamplerate[sender] = 32000; + s_voip.decframesize[sender] = 640; + smode = s_voip.speex.modeuwb; + } + else + { + s_voip.decsamplerate[sender] = 11025; + s_voip.decframesize[sender] = 160; + smode = s_voip.speex.modenb; + } + if (!s_voip.decoder[sender]) + { + qspeex_bits_init(&s_voip.speex.decbits[sender]); + qspeex_bits_reset(&s_voip.speex.decbits[sender]); + s_voip.decoder[sender] = qspeex_decoder_init(smode); + if (!s_voip.decoder[sender]) + return; + } + else + qspeex_bits_reset(&s_voip.speex.decbits[sender]); + } + break; +#endif + case VOIP_OPUS: + if (!S_Opus_Init(false)) + return; + + //the lazy way to reset the codec! + if (!s_voip.decoder[sender]) + { + //opus outputs to 8, 12, 16, 24, or 48khz. pick whichever has least excess samples and resample to fit it. + if (shm->speed <= 8000) + s_voip.decsamplerate[sender] = 8000; + else if (shm->speed <= 12000) + s_voip.decsamplerate[sender] = 12000; + else if (shm->speed <= 16000) + s_voip.decsamplerate[sender] = 16000; + else if (shm->speed <= 24000) + s_voip.decsamplerate[sender] = 24000; + else + s_voip.decsamplerate[sender] = 48000; + s_voip.decoder[sender] = qopus_decoder_create(s_voip.decsamplerate[sender], 1/*FIXME: support stereo where possible*/, NULL); + if (!s_voip.decoder[sender]) + return; + + s_voip.decframesize[sender] = s_voip.decsamplerate[sender]/400; //this is the maximum size in a single frame. + } + else + qopus_decoder_ctl(s_voip.decoder[sender], OPUS_RESET_STATE); + break; + } + s_voip.deccodec[sender] = codec; + s_voip.decgen[sender] = gen; + s_voip.decseq[sender] = seq; + } + + + //if there's packetloss, tell the decoder about the missing parts. + //no infinite loops please. + if ((unsigned)(seq - s_voip.decseq[sender]) > 10) + s_voip.decseq[sender] = seq - 10; + while(s_voip.decseq[sender] != seq) + { + if (decodesamps + s_voip.decframesize[sender] > sizeof(decodebuf)/sizeof(decodebuf[0])) + { + S_RawAudio(sender, (byte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + decodesamps = 0; + } + switch(codec) + { + case VOIP_RAW16: + break; //just skip it. +#ifdef USE_SPEEX_CODEC + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_ULTRAWIDE: + qspeex_decode_int(s_voip.decoder[sender], NULL, decodebuf + decodesamps); + decodesamps += s_voip.decframesize[sender]; + break; +#endif + case VOIP_OPUS: + r = qopus_decode(s_voip.decoder[sender], NULL, 0, decodebuf + decodesamps, q_min(s_voip.decframesize[sender], sizeof(decodebuf)/sizeof(decodebuf[0]) - decodesamps), false); + if (r > 0) + decodesamps += r; + break; + } + s_voip.decseq[sender]++; + } + + while (bytes > 0) + { + if (decodesamps + s_voip.decframesize[sender] >= sizeof(decodebuf)/sizeof(decodebuf[0])) + { + S_RawAudio(sender, (byte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + decodesamps = 0; + } + switch(codec) + { + default: + bytes = 0; + break; +#ifdef USE_SPEEX_CODEC + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_ULTRAWIDE: + if (codec == VOIP_SPEEX_OLD) + { //older versions support only this, and require this extra bit. + bytes--; + len = *start++; + if (bytes < len) + break; + } + else + len = bytes; + qspeex_bits_read_from(&s_voip.speex.decbits[sender], start, len); + bytes -= len; + start += len; + while (qspeex_decode_int(s_voip.decoder[sender], &s_voip.speex.decbits[sender], decodebuf + decodesamps) == 0) + { + decodesamps += s_voip.decframesize[sender]; + s_voip.decseq[sender]++; + seq++; + if (decodesamps + s_voip.decframesize[sender] >= sizeof(decodebuf)/sizeof(decodebuf[0])) + { + S_RawAudio(sender, (byte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + decodesamps = 0; + } + } + break; +#endif + case VOIP_RAW16: + len = q_min(bytes, sizeof(decodebuf)-(sizeof(decodebuf[0])*decodesamps)); + memcpy(decodebuf+decodesamps, start, len); + decodesamps += len / sizeof(decodebuf[0]); + s_voip.decseq[sender]++; + bytes -= len; + start += len; + break; + case VOIP_PCMA: + case VOIP_PCMU: + len = q_min(bytes, sizeof(decodebuf)-(sizeof(decodebuf[0])*decodesamps)); + if (len > s_voip.decframesize[sender]*2) + len = s_voip.decframesize[sender]*2; + if (codec == VOIP_PCMA) + decodesamps += PCMA_Decode(decodebuf+decodesamps, start, len); + else + decodesamps += PCMU_Decode(decodebuf+decodesamps, start, len); + s_voip.decseq[sender]++; + bytes -= len; + start += len; + break; + case VOIP_OPUS: + len = bytes; + if (decodesamps > 0) + { + S_RawAudio(sender, (byte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + decodesamps = 0; + } + r = qopus_decode(s_voip.decoder[sender], start, len, decodebuf + decodesamps, sizeof(decodebuf)/sizeof(decodebuf[0]) - decodesamps, false); + if (r > 0) + { + int frames = r / s_voip.decframesize[sender]; + decodesamps += r; + s_voip.decseq[sender] = (s_voip.decseq[sender] + frames) & 0xff; + seq = (seq+frames)&0xff; + } + else if (r < 0) + Con_Printf("Opus decoding error %i\n", r); + + bytes -= len; + start += len; + break; + } + } + + if (drops) + Con_DPrintf("%i dropped audio frames\n", drops); + + if (decodesamps > 0) + S_RawAudio(sender, (byte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); +} + +void S_Voip_Parse(void) +{ + unsigned int sender; + unsigned int bytes; + unsigned char data[1024]; + unsigned char seq, gen; + unsigned char codec; + unsigned int i; + + sender = MSG_ReadByte(); + gen = MSG_ReadByte(); + codec = gen>>4; + gen &= 0x0f; + seq = MSG_ReadByte(); + bytes = MSG_ReadShort(); + if (bytes > sizeof(data) || cl_voip_play.value <= 0) + { + while(bytes-- > 0) + MSG_ReadByte(); + return; + } + for(i = 0; i < bytes; i++) + data[i] = MSG_ReadByte(); + + sender %= MAX_SCOREBOARD; + + //if testing, don't get confused if the server is echoing voice too! + if (cl_voip_test.value) + if ((int)sender == cl.viewentity-1) //FIXME: this isn't exactly reliable + return; + + S_Voip_Decode(sender, codec, gen, seq, bytes, data); +} +static float S_Voip_Preprocess(short *start, unsigned int samples, float micamp) +{ + unsigned int i; + float level = 0, f; + unsigned int framesize = s_voip.encframesize; + while(samples >= framesize) + { +#ifdef USE_SPEEX_DSP + if (s_voip.speexdsp.preproc) + qspeex_preprocess_run(s_voip.speexdsp.preproc, start); +#endif + for (i = 0; i < framesize; i++) + { + f = start[i] * micamp; + start[i] = f; + f = fabs(start[i]); + level += f*f; + } + + start += framesize; + samples -= framesize; + } + return level; +} +static void S_Voip_Codecs_f(void) +{ + const char *codecname; + unsigned int codecsupported; + unsigned int i; + for (i = 0; i < 16; i++) + { + switch(i) + { +#ifdef USE_SPEEX_CODEC + case VOIP_SPEEX_OLD: + codecname = "Speex (Legacy)"; + codecsupported = S_Speex_Init()?3:0; + break; + case VOIP_SPEEX_NARROW: + codecname = "Speex NarrowBand"; + codecsupported = S_Speex_Init()?3:0; + break; + case VOIP_SPEEX_WIDE: + codecname = "Speex WideBand"; + codecsupported = S_Speex_Init()?7:0; + break; + case VOIP_SPEEX_ULTRAWIDE: + codecname = "Speex UltraWideBand"; + codecsupported = S_Speex_Init()?7:0; + break; +#endif + case VOIP_RAW16: + codecname = "Raw PCM"; + codecsupported = 7; + break; + case VOIP_PCMA: + codecname = "G711 A-Law"; + codecsupported = 3; + break; + case VOIP_PCMU: + codecname = "G711 U-Law"; + codecsupported = 3; + break; + case VOIP_OPUS: + codecname = "Opus"; + codecsupported = (S_Opus_Init(false)?1:0)|(S_Opus_Init(true)?2:0); + break; + default: + continue; +// codecname = "Unknown Codec"; +// codecsupported = -1; +// break; + } + Con_Printf("%i: %s", i, codecname); + if (codecsupported == 7) + Con_Printf(" (Not recommended)\n"); //both supported + else if ((codecsupported & 3) == 3) + Con_Printf(" (Available)\n"); //both supported + else if (codecsupported & 1) + Con_Printf(" (Decode Only)\n"); + else if (codecsupported & 2) + Con_Printf(" (Encode Only)\n"); + else + Con_Printf(" (Unavailable)\n"); + } +} +void S_Voip_EncodeStop(void) +{ + if (s_voip.cdriver) + { + if (s_voip.cdriverctx) + { + if (s_voip.wantsend) + { + s_voip.cdriver->Stop(s_voip.cdriverctx); + s_voip.wantsend = false; + } + s_voip.cdriver->Shutdown(s_voip.cdriverctx); + s_voip.cdriverctx = NULL; + } + s_voip.cdriver = NULL; + } + switch(s_voip.enccodec) + { + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_ULTRAWIDE: + break; + case VOIP_OPUS: + qopus_encoder_destroy(s_voip.encoder); + break; + } + s_voip.encoder = NULL; + s_voip.enccodec = VOIP_INVALID; + s_voip.voiplevel = -1; +} +void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) +{ + unsigned char outbuf[8192]; + unsigned int outpos;//in bytes + unsigned int encpos;//in bytes + short *start; + unsigned int initseq;//in frames + unsigned int samps; + float level; + size_t len; + float micamp = cl_voip_micamp.value; + int voipsendenable = true; +#ifdef USE_SPEEX_DSP + static qboolean cl_voip_noisefilter_old = 0; + static qboolean cl_voip_autogain_old = 0; +#endif + + int voipcodec; + + if (buf) + { + /*if you're sending sound, you should be prepared to accept others yelling at you to shut up*/ + if (cl_voip_play.value <= 0) + voipsendenable = false; + else if (!(cl.protocol_pext2 & PEXT2_VOICECHAT)) + { + buf = NULL; + voipsendenable = cl_voip_test.value; + } + } + else + voipsendenable = cl_voip_test.value; + + if (!voipsendenable) + { //if we're not allowing any voice, stop and give up now. + S_Voip_EncodeStop(); + return; + } + + voipsendenable = cl_voip_send.value>0 || s_voip.voipsendbutton; + + voicevolumescale = s_voip.lastspoke_any > realtime?cl_voip_ducking.value:1; + + voipcodec = cl_voip_codec.value; + if ((voipsendenable || s_voip.cdriver) && !*cl_voip_codec.string) + { //empty cvar = pick a default that should work... + if (S_Opus_Init(true)) + voipcodec = VOIP_OPUS; +#ifdef USE_SPEEX_CODEC + else if (S_Speex_Init()) + voipcodec = VOIP_SPEEX_NARROW; +#endif + else + voipcodec = VOIP_PCMA; + } + + //if the codec has changed, shut down the old encoder context + if (voipcodec != s_voip.enccodec && s_voip.cdriver) + S_Voip_EncodeStop(); + + //create a new encoder context if we're not got one. + if (!s_voip.cdriver && voipsendenable) + { + s_voip.voiplevel = -1; + + /*Add new drivers in order of priority*/ +#ifdef USE_DSOUND_CAPTURE + if (!s_voip.cdriver || !s_voip.cdriver->Init) + s_voip.cdriver = &DSOUND_Capture; +#endif +#ifdef USE_SDL_CAPTURE + if (!s_voip.cdriver || !s_voip.cdriver->Init) + s_voip.cdriver = &SDL_Capture; +#endif + + /*no way to capture audio, give up*/ + if (!s_voip.cdriver || !s_voip.cdriver->Init) + return; + + /*see if we can init our encoding codec...*/ + switch(voipcodec) + { +#ifdef USE_SPEEX_CODEC + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_ULTRAWIDE: + { + const SpeexMode *smode; + if (!S_Speex_Init()) + { + Con_Printf("Unable to use speex codec - not installed\n"); + s_voip.cdriver = NULL; + return; + } + + if (voipcodec == VOIP_SPEEX_ULTRAWIDE) + smode = s_voip.speex.modeuwb; + else if (voipcodec == VOIP_SPEEX_WIDE) + smode = s_voip.speex.modewb; + else + smode = s_voip.speex.modenb; + qspeex_bits_init(&s_voip.speex.encbits); + qspeex_bits_reset(&s_voip.speex.encbits); + s_voip.encoder = qspeex_encoder_init(smode); + if (!s_voip.encoder) + return; + qspeex_encoder_ctl(s_voip.encoder, SPEEX_GET_FRAME_SIZE, &s_voip.encframesize); + qspeex_encoder_ctl(s_voip.encoder, SPEEX_GET_SAMPLING_RATE, &s_voip.encsamplerate); + if (voipcodec == VOIP_SPEEX_NARROW) + s_voip.encsamplerate = 8000; + else if (voipcodec == VOIP_SPEEX_WIDE) + s_voip.encsamplerate = 16000; + else if (voipcodec == VOIP_SPEEX_ULTRAWIDE) + s_voip.encsamplerate = 32000; + else + s_voip.encsamplerate = 11025; + qspeex_encoder_ctl(s_voip.encoder, SPEEX_SET_SAMPLING_RATE, &s_voip.encsamplerate); + } + break; +#endif + case VOIP_RAW16: + s_voip.encsamplerate = 11025; + s_voip.encframesize = 256; + break; + case VOIP_PCMA: + case VOIP_PCMU: + s_voip.encsamplerate = 8000; + s_voip.encframesize = 8000/20; + break; + case VOIP_OPUS: + if (!S_Opus_Init(true)) + { + Con_Printf("Unable to use opus codec - not installed\n"); + s_voip.cdriver = NULL; + return; + } + + //use whatever is convienient. + s_voip.encsamplerate = 48000; + s_voip.encframesize = s_voip.encsamplerate / 400; //2.5ms frames, at a minimum. + s_voip.encoder = qopus_encoder_create(s_voip.encsamplerate, 1, OPUS_APPLICATION_VOIP, NULL); + if (!s_voip.encoder) + return; + + s_voip.curbitrate = 0; + +// 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)); + +// opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&skip)); +// opus_encoder_ctl(enc, OPUS_SET_LSB_DEPTH(16)); + + + break; + default: + Con_Printf("Unable to use that codec - not implemented\n"); + //can't start up other coedcs, cos we're too lame. + return; + } + s_voip.enccodec = voipcodec; + + s_voip.cdriverctx = s_voip.cdriver->Init(s_voip.encsamplerate); + + if (!s_voip.cdriverctx) + Con_Printf("No microphone detected\n"); + } + + /*couldn't init a driver?*/ + if (!s_voip.cdriverctx) + { + s_voip.voiplevel = -1; + return; + } + + if (!voipsendenable && s_voip.wantsend) + { + s_voip.wantsend = false; + s_voip.capturepos += s_voip.cdriver->Update(s_voip.cdriverctx, (unsigned char*)s_voip.capturebuf + s_voip.capturepos, 1, sizeof(s_voip.capturebuf) - s_voip.capturepos); + s_voip.cdriver->Stop(s_voip.cdriverctx); + /*note: we still grab audio to flush everything that was captured while it was active*/ + } + else if (voipsendenable && !s_voip.wantsend) + { + s_voip.wantsend = true; + if (!s_voip.capturepos) + { /*if we were actually still sending, it was probably only off for a single frame, in which case don't reset it*/ + s_voip.dumps = 0; + s_voip.generation++; + s_voip.encsequence = 0; + + //reset codecs so they start with a clean slate when new audio blocks are generated. + switch(s_voip.enccodec) + { +#ifdef USE_SPEEX_CODEC + case VOIP_SPEEX_OLD: + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + qspeex_bits_reset(&s_voip.speex.encbits); + break; +#endif + case VOIP_RAW16: + break; + case VOIP_PCMA: + case VOIP_PCMU: + break; + case VOIP_OPUS: + qopus_encoder_ctl(s_voip.encoder, OPUS_RESET_STATE); + break; + } + } + else + { + s_voip.capturepos += s_voip.cdriver->Update(s_voip.cdriverctx, (unsigned char*)s_voip.capturebuf + s_voip.capturepos, 1, sizeof(s_voip.capturebuf) - s_voip.capturepos); + } + s_voip.cdriver->Start(s_voip.cdriverctx); + } + + if (s_voip.wantsend) + voicevolumescale = q_min(voicevolumescale, cl_voip_capturingvol.value); + + s_voip.capturepos += s_voip.cdriver->Update(s_voip.cdriverctx, (unsigned char*)s_voip.capturebuf + s_voip.capturepos, s_voip.encframesize*2, sizeof(s_voip.capturebuf) - s_voip.capturepos); + + if (!s_voip.wantsend && s_voip.capturepos < s_voip.encframesize*2) + { + s_voip.voiplevel = -1; + s_voip.capturepos = 0; + return; + } + + initseq = s_voip.encsequence; + level = 0; + samps=0; + //*2 for 16bit audio input. + for (encpos = 0, outpos = 0; s_voip.capturepos-encpos >= s_voip.encframesize*2 && sizeof(outbuf)-outpos > 64; ) + { + start = (short*)(s_voip.capturebuf + encpos); + +#ifdef USE_SPEEX_DSP + if (cl_voip_noisefilter.value || cl_voip_autogain.value) + { + if (!s_voip.speexdsp.preproc || cl_voip_noisefilter.value != cl_voip_noisefilter_old || cl_voip_autogain.value != cl_voip_autogain_old || s_voip.speexdsp.curframesize != s_voip.encframesize || s_voip.speexdsp.cursamplerate != s_voip.encsamplerate) + { + cl_voip_noisefilter_old = cl_voip_noisefilter.value; + cl_voip_autogain_old = cl_voip_autogain.value; + if (s_voip.speexdsp.preproc) + qspeex_preprocess_state_destroy(s_voip.speexdsp.preproc); + s_voip.speexdsp.preproc = NULL; + if (S_SpeexDSP_Init()) + { + int i; + s_voip.speexdsp.preproc = qspeex_preprocess_state_init(s_voip.encframesize, s_voip.encsamplerate); + i = cl_voip_noisefilter.value; + qspeex_preprocess_ctl(s_voip.speexdsp.preproc, SPEEX_PREPROCESS_SET_DENOISE, &i); + i = cl_voip_autogain.value; + qspeex_preprocess_ctl(s_voip.speexdsp.preproc, SPEEX_PREPROCESS_SET_AGC, &i); + + s_voip.speexdsp.curframesize = s_voip.encframesize; + s_voip.speexdsp.cursamplerate = s_voip.encsamplerate; + } + } + } + else if (s_voip.speexdsp.preproc) + { + qspeex_preprocess_state_destroy(s_voip.speexdsp.preproc); + s_voip.speexdsp.preproc = NULL; + } +#endif + + switch(s_voip.enccodec) + { +#ifdef USE_SPEEX_CODEC + case VOIP_SPEEX_OLD: + level += S_Voip_Preprocess(start, s_voip.encframesize, micamp); + qspeex_bits_reset(&s_voip.speex.encbits); + qspeex_encode_int(s_voip.encoder, start, &s_voip.speex.encbits); + len = qspeex_bits_write(&s_voip.speex.encbits, outbuf+(outpos+1), sizeof(outbuf) - (outpos+1)); + if (len < 0 || len > 255) + len = 0; + outbuf[outpos] = len; + outpos += 1+len; + s_voip.encsequence++; + samps+=s_voip.encframesize; + encpos += s_voip.encframesize*2; + break; + case VOIP_SPEEX_NARROW: + case VOIP_SPEEX_WIDE: + case VOIP_SPEEX_ULTRAWIDE: + qspeex_bits_reset(&s_voip.speex.encbits); + for (; s_voip.capturepos-encpos >= s_voip.encframesize*2 && sizeof(outbuf)-outpos > 64; ) + { + start = (short*)(s_voip.capturebuf + encpos); + level += S_Voip_Preprocess(start, s_voip.encframesize, micamp); + qspeex_encode_int(s_voip.encoder, start, &s_voip.speex.encbits); + s_voip.encsequence++; + samps+=s_voip.encframesize; + encpos += s_voip.encframesize*2; + } + len = qspeex_bits_write(&s_voip.speex.encbits, outbuf+outpos, sizeof(outbuf) - outpos); + outpos += len; + break; +#endif + case VOIP_RAW16: + len = s_voip.capturepos-encpos; //amount of data to be eaten in this frame + len = q_min((unsigned int)len, sizeof(outbuf)-outpos); + len &= ~((s_voip.encframesize*2)-1); + level += S_Voip_Preprocess(start, len/2, micamp); + memcpy(outbuf+outpos, start, len); //'encode' + outpos += len; //bytes written to output + encpos += len; //number of bytes consumed + + s_voip.encsequence++; //increment number of packets, for packetloss detection. + samps+=len / 2; //number of samplepairs eaten in this packet. for stats. + break; + case VOIP_PCMA: + case VOIP_PCMU: + len = s_voip.capturepos-encpos; //amount of data to be eaten in this frame + len = q_min(len, sizeof(outbuf)-outpos); + len = q_min(len, s_voip.encframesize*2); + level += S_Voip_Preprocess(start, len/2, micamp); + if (s_voip.enccodec == VOIP_PCMA) + outpos += PCMA_Encode(outbuf+outpos, sizeof(outbuf)-outpos, start, len/2); + else + outpos += PCMU_Encode(outbuf+outpos, sizeof(outbuf)-outpos, start, len/2); + encpos += len; //number of bytes consumed + s_voip.encsequence++; //increment number of packets, for packetloss detection. + samps+=len / 2; //number of samplepairs eaten in this packet. for stats. + break; + case VOIP_OPUS: + { + //opus rtp only supports/allows a single chunk in each packet. + //luckily it allows power-of-two frames + int frames; + int nrate; + //densely pack the frames. + start = (short*)(s_voip.capturebuf + encpos); + frames = (s_voip.capturepos-encpos)/2; + if (frames >= 2880) + frames = 2880; + else if (frames >= 1920) + frames = 1920; + else if (frames >= 960) + frames = 960; + else if (cls.demorecording) + break; //don't generate small frames if we're recording a demo, due to overheads. + else if (frames >= 480) + frames = 480; + else if (frames >= 240) + frames = 240; + else if (frames >= 120) + frames = 120; + else + { + Con_Printf("invalid Opus frame size\n"); + frames = 0; + } + + nrate = cl_voip_opus_bitrate.value; + 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); + } + + level += S_Voip_Preprocess(start, frames, micamp); + len = qopus_encode(s_voip.encoder, start, frames, outbuf+outpos, sizeof(outbuf) - outpos); + if (len >= 0) + { + s_voip.encsequence += frames / s_voip.encframesize; + outpos += len; + samps+=frames; + encpos += frames*2; + } + else + { + Con_Printf("Opus encoding error: %u\n", (unsigned int)len); + encpos = s_voip.capturepos; + } + } + break; + default: + outbuf[outpos] = 0; + break; + } + + if (s_voip.enccodec == VOIP_OPUS) + break; //one per frame + } + if (samps) + { + float nl; + s_voip.enctimestamp += samps; + nl = (3000*level) / (32767.0f*32767*samps); + s_voip.voiplevel = (s_voip.voiplevel*7 + nl)/8; + if (s_voip.voiplevel < cl_voip_vad_threshhold.value && !((int)cl_voip_send.value & 6) && !s_voip.voipsendbutton) + { + /*try and dump it, it was too quiet, and they're not pressing +voip*/ + if (s_voip.keeps > samps) + { + /*but not instantly*/ + s_voip.keeps -= samps; + } + else + { + outpos = 0; + s_voip.dumps += samps; + s_voip.keeps = 0; + } + } + else + s_voip.keeps = s_voip.encsamplerate * cl_voip_vad_delay.value; + if (outpos) + { + if (s_voip.dumps > s_voip.encsamplerate/4) + s_voip.generation++; + s_voip.dumps = 0; + } + } + + if (outpos) + { + int localplayeridx = cl.viewentity-1; //FIXME: this is not reliable + if (cl_voip_send.value != 4) + { + extern cvar_t sv_voip_echo; + if (buf && (unsigned int)(buf->maxsize - buf->cursize) >= outpos+5) + { + MSG_WriteByte(buf, clc); + MSG_WriteByte(buf, (s_voip.enccodec<<4) | (s_voip.generation & 0x0f)); /*gonna leave that nibble clear here... in this version, the client will ignore packets with those bits set. can use them for codec or something*/ + MSG_WriteByte(buf, initseq); + MSG_WriteShort(buf, outpos); + SZ_Write(buf, outbuf, outpos); + } + if (cls.demorecording && (!sv.active || !sv_voip_echo.value)) //voice is not (normally) echoed by the server, which means that you won't hear yourself speak. which is unfortunate when it comes to demos. + { //the small size of various voip packets means that the 12 bytes of angles overhead is going to give a noticable size overhead... + byte b; + unsigned short s; + int i = LittleLong (5+outpos); + fwrite (&i, 4, 1, cls.demofile); + for (i = 0; i < 3; i++) + { + float f = LittleFloat (cl.viewangles[i]); + fwrite (&f, 4, 1, cls.demofile); + } + b = clc; + fwrite (&b, 1, 1, cls.demofile); + b = (s_voip.enccodec<<4) | (s_voip.generation & 0x0f); + fwrite (&b, 1, 1, cls.demofile); + b = initseq; + fwrite (&b, 1, 1, cls.demofile); + s = outpos; + fwrite (&s, 2, 1, cls.demofile); + fwrite (outbuf, outpos, 1, cls.demofile); + } + } + + if (localplayeridx < MAX_SCOREBOARD) + { + if (cl_voip_test.value) + S_Voip_Decode(localplayeridx, s_voip.enccodec, s_voip.generation & 0x0f, initseq, outpos, outbuf); + + //update our own lastspoke, so queries shows that we're speaking when we're speaking in a generic way, even if we can't hear ourselves. + //but don't update general lastspoke, so ducking applies only when others speak. use capturingvol for yourself. they're more explicit that way. + s_voip.lastspoke[localplayeridx] = realtime + 0.5; + } + } + + /*remove sent data*/ + if (encpos) + { + memmove(s_voip.capturebuf, s_voip.capturebuf + encpos, s_voip.capturepos-encpos); + s_voip.capturepos -= encpos; + } +} +void S_Voip_Ignore(unsigned int slot, qboolean ignore) +{ + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, va("vignore %i %i", slot, ignore)); +} +static void S_Voip_Enable_f(void) +{ + s_voip.voipsendbutton = true; +} +static void S_Voip_Disable_f(void) +{ + s_voip.voipsendbutton = false; +} +static void S_Voip_f(void) +{ + if (!strcmp(Cmd_Argv(1), "maxgain")) + { +#ifdef USE_SPEEX_DSP + int i = atoi(Cmd_Argv(2)); + if (s_voip.speexdsp.preproc) + qspeex_preprocess_ctl(s_voip.speexdsp.preproc, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &i); +#endif + } +} +static void S_Voip_Play_Callback(cvar_t *var, char *oldval) +{ + if (cl.protocol_pext2 & PEXT2_VOICECHAT) + { + MSG_WriteByte (&cls.message, clc_stringcmd); + if (var->value > 0) + MSG_WriteString (&cls.message, va("unmuteall")); + else + MSG_WriteString (&cls.message, va("muteall")); + } +} +void S_Voip_MapChange(void) +{ + s_voip.voipsendbutton = false; //release +voip on each map change, because people that leave it enabled the whole time are annoying cunts (buttons get stuck sometimes). + S_Voip_Play_Callback(&cl_voip_play, ""); +} +int S_Voip_Loudness(qboolean ignorevad) +{ + if (s_voip.voiplevel > 100) + return 100; + if (!s_voip.cdriverctx || (!ignorevad && s_voip.dumps)) + return -1; + return s_voip.voiplevel; +} +qboolean S_Voip_Speaking(unsigned int plno) +{ + if (plno >= MAX_SCOREBOARD) + return false; + return s_voip.lastspoke[plno] > realtime; +} + +void S_Voip_Init(void) +{ + int i; + for (i = 0; i < MAX_SCOREBOARD; i++) + s_voip.deccodec[i] = VOIP_INVALID; + s_voip.enccodec = VOIP_INVALID; + + Cvar_RegisterVariable(&cl_voip_send); + Cvar_RegisterVariable(&cl_voip_vad_threshhold); + Cvar_RegisterVariable(&cl_voip_vad_delay); + Cvar_RegisterVariable(&cl_voip_capturingvol); + Cvar_RegisterVariable(&cl_voip_showmeter); + Cvar_RegisterVariable(&cl_voip_play); + Cvar_RegisterVariable(&cl_voip_test); + Cvar_RegisterVariable(&cl_voip_ducking); + Cvar_RegisterVariable(&cl_voip_micamp); +#ifndef FORCE_ENCODER + Cvar_RegisterVariable(&cl_voip_codec); +#endif + Cvar_RegisterVariable(&cl_voip_opus_bitrate); + Cvar_RegisterVariable(&cl_voip_noisefilter); + Cvar_RegisterVariable(&cl_voip_autogain); + Cmd_AddCommand("cl_voip_codecs", S_Voip_Codecs_f); + Cmd_AddCommand("+voip", S_Voip_Enable_f); + Cmd_AddCommand("-voip", S_Voip_Disable_f); + Cmd_AddCommand("voip", S_Voip_f); +} + + +/*****************************************************************************************************************************/ +/*server componant*/ + +cvar_t sv_voip = {"sv_voip", "1"}; +cvar_t sv_voip_echo = {"sv_voip_echo", "0"}; + + +/* +Pivicy issues: +By sending voice chat to a server, you are unsure who might be listening. +Server could be changed to record voice. +Voice will be saved in any demos made of the game. +You're never quite sure if anyone might join the server and your team before you finish saying a sentance. +You run the risk of sounds around you being recorded by quake, including but not limited to: TV channels, loved ones, phones, YouTube videos featuring certain moans. +Default on non-team games is to broadcast. +*/ + +#define VOICE_RING_SIZE 512 /*POT*/ +struct +{ + struct voice_ring_s + { + unsigned int sender; + unsigned char receiver[MAX_SCOREBOARD/8]; + unsigned char gen; + unsigned char seq; + unsigned int datalen; + unsigned char data[1024]; + } ring[VOICE_RING_SIZE]; + unsigned int write; +} voice; +void SV_VoiceReadPacket(client_t *client) +{ + unsigned int vt = client->voip.target; + unsigned int j; + struct voice_ring_s *ring; + unsigned short bytes; + client_t *cl; + unsigned char gen = MSG_ReadByte(); + unsigned char seq = MSG_ReadByte(); + /*read the data from the client*/ + bytes = MSG_ReadShort(); + ring = &voice.ring[voice.write & (VOICE_RING_SIZE-1)]; + if (bytes > sizeof(ring->data) || !sv_voip.value) + { + while (bytes-- > 0) + MSG_ReadByte(); + return; + } + else + { + voice.write++; + for (j = 0; j < bytes; j++) + ring->data[j] = MSG_ReadByte(); + } + + ring->datalen = bytes; + ring->sender = client - svs.clients; + ring->gen = gen; + ring->seq = seq; + + /*broadcast it its to their team, and its not teamplay*/ + if (vt == VT_TEAM && !teamplay.value) + vt = VT_ALL; + + /*figure out which team members are meant to receive it*/ + for (j = 0; j < MAX_SCOREBOARD/8; j++) + ring->receiver[j] = 0; + for (j = 0, cl = svs.clients; j < (unsigned int)svs.maxclients; j++, cl++) + { + if (!cl->spawned || !cl->active) + continue; + + if (vt == VT_TEAM) + { + if ((cl->colors & 0xf) == (client->colors & 0xf)) + continue; // on different teams + } + else if (vt == VT_NONMUTED) + { + if (client->voip.mute[j>>3] & (1<<(j&3))) + continue; + } + else if (vt >= VT_PLAYERSLOT0) + { + if (j != vt - VT_PLAYERSLOT0) + continue; + } + + ring->receiver[j>>3] |= 1<<(j&3); + } +} +void SV_VoiceInitClient(client_t *client) +{ + client->voip.target = VT_TEAM; + client->voip.active = false; + client->voip.read = voice.write; + memset(client->voip.mute, 0, sizeof(client->voip.mute)); +} +void SV_VoiceSendPacket(client_t *client, sizebuf_t *buf) +{ + unsigned int clno; + qboolean send; + struct voice_ring_s *ring; + + clno = client - svs.clients; + + if (!(client->protocol_pext2 & PEXT2_VOICECHAT)) + return; + if (!client->voip.active) + { + client->voip.read = voice.write; + return; + } + + while(client->voip.read < voice.write) + { + /*they might be too far behind*/ + if (client->voip.read+VOICE_RING_SIZE < voice.write) + client->voip.read = voice.write - VOICE_RING_SIZE; + + ring = &voice.ring[(client->voip.read) & (VOICE_RING_SIZE-1)]; + + /*figure out if it was for us*/ + send = false; + if (ring->receiver[clno>>3] & (1<<(clno&3))) + send = true; + + if (client->voip.mute[ring->sender>>3] & (1<<(ring->sender&3))) + send = false; + + if (ring->sender == clno && !sv_voip_echo.value) + send = false; + + if (send) + { + if (buf->maxsize - buf->cursize < (int)ring->datalen+5) + break; + MSG_WriteByte(buf, svcfte_voicechat); + MSG_WriteByte(buf, ring->sender); + MSG_WriteByte(buf, ring->gen); + MSG_WriteByte(buf, ring->seq); + MSG_WriteShort(buf, ring->datalen); + SZ_Write(buf, ring->data, ring->datalen); + } + client->voip.read++; + } +} + +static void SV_Voice_Ignore_f(void) +{ + if (cmd_source == src_command) + { + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + } + else + { + unsigned int other; + int type = 0; + + if (Cmd_Argc() < 2) + { + /*only a name = toggle*/ + type = 0; + } + else + { + /*mute if 1, unmute if 0*/ + if (atoi(Cmd_Argv(2))) + type = 1; + else + type = -1; + } + other = atoi(Cmd_Argv(1)); + if (other >= MAX_SCOREBOARD) + return; + + switch(type) + { + case -1: + host_client->voip.mute[other>>3] &= ~(1<<(other&3)); + break; + case 0: + host_client->voip.mute[other>>3] ^= (1<<(other&3)); + break; + case 1: + host_client->voip.mute[other>>3] |= (1<<(other&3)); + } + } +} +static void SV_Voice_Target_f(void) +{ + if (cmd_source == src_command) + { + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + } + else + { + unsigned int other; + const char *t = Cmd_Argv(1); + if (!strcmp(t, "team")) + host_client->voip.target = VT_TEAM; + else if (!strcmp(t, "all")) + host_client->voip.target = VT_ALL; + else if (!strcmp(t, "nonmuted")) + host_client->voip.target = VT_NONMUTED; + else if (*t >= '0' && *t <= '9') + { + other = atoi(t); + if (other >= MAX_SCOREBOARD) + return; + host_client->voip.target = VT_PLAYERSLOT0 + other; + } + else + { + /*don't know who you mean, futureproofing*/ + host_client->voip.target = VT_TEAM; + } + } +} +static void SV_Voice_MuteAll_f(void) +{ + if (cmd_source == src_command) + { + Cvar_Set("cl_voip_play", "0"); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + } + else + { + host_client->voip.active = false; + } +} +static void SV_Voice_UnmuteAll_f(void) +{ + if (cmd_source == src_command) + { + Cvar_Set("cl_voip_play", "1"); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + } + else + { + host_client->voip.active = true; + } +} + +void SV_VoiceInit(void) +{ + Cvar_RegisterVariable(&sv_voip); + Cvar_RegisterVariable(&sv_voip_echo); + + Cmd_AddCommand_ClientCommand("muteall", SV_Voice_MuteAll_f); + Cmd_AddCommand_ClientCommand("unmuteall", SV_Voice_UnmuteAll_f); + Cmd_AddCommand_ClientCommand("vignore", SV_Voice_Ignore_f); + Cmd_AddCommand_ClientCommand("voicetarg", SV_Voice_Target_f); +} diff --git a/Quake/snd_voip.h b/Quake/snd_voip.h new file mode 100755 index 00000000..00fa06b7 --- /dev/null +++ b/Quake/snd_voip.h @@ -0,0 +1,41 @@ +//spike -- this file contains prototypes+etc for voice chat. +//it should be fairly straight forward to integrate this into other engines, however, to implement it properly you'll need to deal with the whole pext2_voicechat handshake thing. +//for quakespasm-spiked this is already handled for entity deltas etc. +//you'll also need to figure out something with the 4 clientcommands that servers might receive. +//to test, cl_voip_test 1;sv_voip_echo 0;+voip should start playing even without any protocol extensions. then move on to cl_voip_test 0;sv_voip_echo 1;+voip once you have protocol stuff working. +//you'll also want to add the various voip settings to the menu, especially cl_voip_play (slider 0-1), cl_voip_send (boolean), +voip binding. + +//defined elsewhere +//#define svcfte_voicechat 84 +//#define clcfte_voicechat 83 +struct client_s; + + +//client functions +void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf); //call from CL_SendMove (null buf if not connecting, grabs new data, encodes, and writes into the buffer) +void S_Voip_MapChange(void); //call from end of CL_ParseServerinfo (tells server to reenable voice chat) +void S_Voip_Parse(void); //call from CL_ParseServerMessage+svcfte_voicechat. processes voip data from the server +int S_Voip_Loudness(qboolean ignorevad); //for sbar stuff, if you want to draw some mic-level bar (returns 0-100, or -1 for not transmitting) +qboolean S_Voip_Speaking(unsigned int plno); //for sbar stuff, if you want to query which other players are speaking (add a scoreboard back-colour or something). +void S_Voip_Init(void); //call from S_Init, registers client cvars+commands + +//server functions +void SV_VoiceInit(void); //call from SV_Init, registers server cvars+commands +void SV_VoiceInitClient(struct client_s *client); //call from start of SV_SendServerinfo, disables voice chat until the client is ready to re-enable it +void SV_VoiceSendPacket(struct client_s *client, sizebuf_t *buf); //call from near end of SV_SendClientDatagram, to forward voice data to other clients +void SV_VoiceReadPacket(struct client_s *client); //call from SV_ReadClientMessage+clcfte_voicechat. processes voip data from clients and figures out which clients to forward to +typedef struct +{ + unsigned int read; /*place in ring*/ + unsigned char mute[MAX_SCOREBOARD/8]; /*which other clients should be muted for this player, reducing bandwidth from annoying cunts*/ + qboolean active; /*client wants to hear other people*/ + enum + { + /*should we add one to respond to the last speaker? or should that be an automagic +voip_reply instead?*/ + VT_TEAM, + VT_ALL, + VT_NONMUTED, /*cheap, but allows custom private channels with no external pesters*/ + VT_PLAYERSLOT0 + /*player0+...*/ + } target; +} client_voip_t; //embedded within struct client_s as a member named voip diff --git a/Quake/stb_image.h b/Quake/stb_image.h new file mode 100755 index 00000000..3940f38d --- /dev/null +++ b/Quake/stb_image.h @@ -0,0 +1,7102 @@ +/* stb_image - v2.14 - public domain image loader - http://nothings.org/stb_image.h + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8-bit-per-channel (16 bpc not supported) + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + + Revision 2.00 release notes: + + - Progressive JPEG is now supported. + + - PPM and PGM binary formats are now supported, thanks to Ken Miller. + + - x86 platforms now make use of SSE2 SIMD instructions for + JPEG decoding, and ARM platforms can use NEON SIMD if requested. + This work was done by Fabian "ryg" Giesen. SSE2 is used by + default, but NEON must be enabled explicitly; see docs. + + With other JPEG optimizations included in this version, we see + 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup + on a JPEG on an ARM machine, relative to previous versions of this + library. The same results will not obtain for all JPGs and for all + x86/ARM machines. (Note that progressive JPEGs are significantly + slower to decode than regular JPEGs.) This doesn't mean that this + is the fastest JPEG decoder in the land; rather, it brings it + closer to parity with standard libraries. If you want the fastest + decode, look elsewhere. (See "Philosophy" section of docs below.) + + See final bullet items below for more info on SIMD. + + - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing + the memory allocator. Unlike other STBI libraries, these macros don't + support a context parameter, so if you need to pass a context in to + the allocator, you'll have to store it in a global or a thread-local + variable. + + - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and + STBI_NO_LINEAR. + STBI_NO_HDR: suppress implementation of .hdr reader format + STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API + + - You can suppress implementation of any of the decoders to reduce + your code footprint by #defining one or more of the following + symbols before creating the implementation. + + STBI_NO_JPEG + STBI_NO_PNG + STBI_NO_BMP + STBI_NO_PSD + STBI_NO_TGA + STBI_NO_GIF + STBI_NO_HDR + STBI_NO_PIC + STBI_NO_PNM (.ppm and .pgm) + + - You can request *only* certain decoders and suppress all other ones + (this will be more forward-compatible, as addition of new decoders + doesn't require you to disable them explicitly): + + STBI_ONLY_JPEG + STBI_ONLY_PNG + STBI_ONLY_BMP + STBI_ONLY_PSD + STBI_ONLY_TGA + STBI_ONLY_GIF + STBI_ONLY_HDR + STBI_ONLY_PIC + STBI_ONLY_PNM (.ppm and .pgm) + + Note that you can define multiples of these, and you will get all + of them ("only x" and "only y" is interpreted to mean "only x&y"). + + - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still + want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB + + - Compilation of all SIMD code can be suppressed with + #define STBI_NO_SIMD + It should not be necessary to disable SIMD unless you have issues + compiling (e.g. using an x86 compiler which doesn't support SSE + intrinsics or that doesn't support the method used to detect + SSE2 support at run-time), and even those can be reported as + bugs so I can refine the built-in compile-time checking to be + smarter. + + - The old STBI_SIMD system which allowed installing a user-defined + IDCT etc. has been removed. If you need this, don't upgrade. My + assumption is that almost nobody was doing this, and those who + were will find the built-in SIMD more satisfactory anyway. + + - RGB values computed for JPEG images are slightly different from + previous versions of stb_image. (This is due to using less + integer precision in SIMD.) The C code has been adjusted so + that the same RGB values will be computed regardless of whether + SIMD support is available, so your app should always produce + consistent results. But these results are slightly different from + previous versions. (Specifically, about 3% of available YCbCr values + will compute different RGB results from pre-1.49 versions by +-1; + most of the deviating values are one smaller in the G channel.) + + - If you must produce consistent results with previous versions of + stb_image, #define STBI_JPEG_OLD and you will get the same results + you used to; however, you will not get the SIMD speedups for + the YCbCr-to-RGB conversion step (although you should still see + significant JPEG speedup from the other changes). + + Please note that STBI_JPEG_OLD is a temporary feature; it will be + removed in future versions of the library. It is only intended for + near-term back-compatibility use. + + + Latest revision history: + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit TGA) + Optimizations & bugfixes + Fabian "ryg" Giesen + Arseny Kapoulkine + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson + Dave Moore Roy Eltham Hayaki Saito Phil Jordan + Won Chun Luke Graham Johan Duparc Nathan Reed + the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis + Janez Zemva John Bartholomew Michal Cichon github:svdijk + Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson + Laurent Gomila Cort Stratton Sergio Gonzalez github:romigrou + Aruelien Pocheville Thibault Reuille Cass Everitt Matthew Gregan + Ryamond Barbiero Paul Du Bois Engin Manap github:snagar + Michaelangel007@github Oriol Ferrer Mesia Dale Weiler github:Zelex + Philipp Wiesemann Josh Tobin github:rlyeh github:grim210@github + Blazej Dariusz Roszkowski github:sammyhw + + +LICENSE + +This software is dual-licensed to the public domain and under the following +license: you are granted a perpetual, irrevocable license to copy, modify, +publish, and distribute this file as you see fit. + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 16-bit-per-channel PNG +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// The output of the JPEG decoder is slightly different from versions where +// SIMD support was introduced (that is, for versions before 1.49). The +// difference is only +-1 in the 8-bit RGB channels, and only on a small +// fraction of pixels. You can force the pre-1.49 behavior by defining +// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path +// and hence cost some performance. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif +// @TODO the other variants + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available() +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available() +{ +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; +#endif +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} + +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 8) { + STBI_ASSERT(ri.bits_per_channel == 16); + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int w = *x, h = *y; + int channels = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc *image = (stbi_uc *) result; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < channels; z++) { + stbi_uc temp = image[(row * w + col) * channels + z]; + image[(row * w + col) * channels + z] = image[((h - row - 1) * w + col) * channels + z]; + image[((h - row - 1) * w + col) * channels + z] = temp; + } + } + } + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 16) { + STBI_ASSERT(ri.bits_per_channel == 8); + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int w = *x, h = *y; + int channels = req_comp ? req_comp : *comp; + int row,col,z; + stbi__uint16 *image = (stbi__uint16 *) result; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < channels; z++) { + stbi__uint16 temp = image[(row * w + col) * channels + z]; + image[(row * w + col) * channels + z] = image[((h - row - 1) * w + col) * channels + z]; + image[((h - row - 1) * w + col) * channels + z] = temp; + } + } + } + } + + return (stbi__uint16 *) result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file(&s,f); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi_uc dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (-1 << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + stbi__skip(z->s, stbi__get16be(z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) { // some version of jpegtran outputs non-JFIF-compliant files! + // somethings output this (see http://fileformats.archiveteam.org/wiki/JPEG#Color_format) + if (z->img_comp[i].id != rgb[i]) + return stbi__err("bad component ID","Corrupt JPEG"); + ++z->rgb; + } + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } else if (x != 0) { + return stbi__err("junk before marker", "Corrupt JPEG"); + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#else +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (z->rgb == 3) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); + stbi__rewind(s); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = stbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior = cur - stride; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if(is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // else: fall-through + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fall-through + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y; + STBI_NOTUSED(ri); + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) +{ + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +{ + int i; + stbi_uc *prev_out = 0; + + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + if (!stbi__mad3sizes_valid(g->w, g->h, 4, 0)) + return stbi__errpuc("too large", "GIF too large"); + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc_mad3(4, g->w, g->h, 0); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; + } + + for (;;) { + switch (stbi__get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int prev_trans = -1; + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = stbi__get16le(s); + g->transparent = stbi__get8(s); + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) + stbi__skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } + + STBI_NOTUSED(req_comp); +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + memset(g, 0, sizeof(*g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g->w; + *y = g->h; + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g->w, g->h); + } + else if (g->out) + STBI_FREE(g->out); + STBI_FREE(g); + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + if (stbi__get16be(s) != 8) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + stbi__pic_packet packets[10]; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + + if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv; + char c, p, t; + + stbi__rewind( s ); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ diff --git a/Quake/sv_main.c b/Quake/sv_main.c index 91deeac9..d27e4a8e 100644 --- a/Quake/sv_main.c +++ b/Quake/sv_main.c @@ -2,6 +2,7 @@ Copyright (C) 1996-2001 Id Software, Inc. Copyright (C) 2002-2009 John Fitzgibbons and others Copyright (C) 2010-2014 QuakeSpasm developers +Copyright (C) 2016 Spike This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -28,35 +29,1153 @@ server_static_t svs; static char localmodels[MAX_MODELS][8]; // inline model names for precache -int sv_protocol = PROTOCOL_FITZQUAKE; //johnfitz - -extern qboolean pr_alpha_supported; //johnfitz +int sv_protocol = PROTOCOL_FITZQUAKE; //johnfitz +unsigned int sv_protocol_pext2; //spike //============================================================================ +void SV_CalcStats(client_t *client, int *statsi, float *statsf) +{ + edict_t *ent = client->edict; + //FIXME: string stats! + int items; + eval_t *val = GetEdictFieldValue(ent, pr_extfields.items2); + if (val) + items = (int)ent->v.items | ((int)val->_float << 23); + else + items = (int)ent->v.items | ((int)pr_global_struct->serverflags << 28); + + memset(statsi, 0, sizeof(*statsi)*MAX_CL_STATS); + memset(statsf, 0, sizeof(*statsf)*MAX_CL_STATS); + statsf[STAT_HEALTH] = ent->v.health; +// statsf[STAT_FRAGS] = ent->v.frags; //obsolete + statsi[STAT_WEAPON] = SV_ModelIndex(PR_GetString(ent->v.weaponmodel)); + if ((unsigned int)statsi[STAT_WEAPON] >= client->limit_models) + statsi[STAT_WEAPON] = 0; + statsf[STAT_AMMO] = ent->v.currentammo; + statsf[STAT_ARMOR] = ent->v.armorvalue; + statsf[STAT_WEAPONFRAME] = ent->v.weaponframe; + statsf[STAT_SHELLS] = ent->v.ammo_shells; + statsf[STAT_NAILS] = ent->v.ammo_nails; + statsf[STAT_ROCKETS] = ent->v.ammo_rockets; + statsf[STAT_CELLS] = ent->v.ammo_cells; + statsf[STAT_ACTIVEWEAPON] = ent->v.weapon; //sent in a way that does NOT depend upon the current mod... +// statsf[STAT_TOTALSECRETS] = pr_global_struct->total_secrets; //don't bother with these, the qc sends extra updates and we don't want to end up with a race condition. +// statsf[STAT_TOTALMONSTERS] = pr_global_struct->total_monsters; +// statsf[STAT_SECRETS] = pr_global_struct->found_secrets; +// statsf[STAT_MONSTERS] = pr_global_struct->killed_monsters; +// statsi[STAT_TIME] = sv.time*1000; //in ms, this was a hack to work around vanilla QW clients not having any concept of serverside time. +// statsi[STAT_MATCHSTARTTIME] = 0*1000; //in ms, set by the mod to when the current match actually starts (so pregame stuff doesn't interfere with game clocks). +// stats[STAT_VIEW2] = NUM_FOR_EDICT(PROG_TO_EDICT(ent->v.view2)); + if ((val = GetEdictFieldValue(ent, pr_extfields.viewzoom)) && val->_float) + { + statsf[STAT_VIEWZOOM] = val->_float*255; + if (statsf[STAT_VIEWZOOM] < 1) + statsf[STAT_VIEWZOOM] = 1; + } + + //FIXME: add support for clientstat/globalstat qc builtins. + + if (client->protocol_pext2 & PEXT2_PREDINFO) + { //predinfo also kills clc_clientdata + statsi[STAT_ITEMS] = items; + statsf[STAT_VIEWHEIGHT] = ent->v.view_ofs[2]; + statsf[STAT_IDEALPITCH] = ent->v.idealpitch; + statsf[STAT_PUNCHANGLE_X] = ent->v.punchangle[0]; + statsf[STAT_PUNCHANGLE_Y] = ent->v.punchangle[1]; + statsf[STAT_PUNCHANGLE_Z] = ent->v.punchangle[2]; + } +/* + if (client->protocol_pext2 & PEXT2_PREDINFO) + { //prediction needs some info on the server's rules + statsf[STAT_MOVEVARS_FRICTION] = sv_friction.value; + statsf[STAT_MOVEVARS_WATERFRICTION] = sv_waterfriction.value; + statsf[STAT_MOVEVARS_TICRATE] = host_maxfps.value; + statsf[STAT_MOVEVARS_TIMESCALE] = sv_gamespeed.value; + statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value; + statsf[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value; + statsf[STAT_MOVEVARS_MAXSPEED] = client->maxspeed; + statsf[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_spectatormaxspeed.value; + statsf[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value; + statsf[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value; + statsf[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value; + statsf[STAT_MOVEVARS_ENTGRAVITY] = client->entgravity/sv_gravity.value; + statsf[STAT_MOVEVARS_JUMPVELOCITY] = sv_jumpvelocity.value; //bah + statsf[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value; + statsf[STAT_MOVEVARS_MAXAIRSPEED] = client->maxspeed; + statsf[STAT_MOVEVARS_STEPHEIGHT] = 18; +// statsf[STAT_MOVEVARS_AIRACCEL_QW] = 0; +// statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_gravity.value; + } +*/ +} + + +/*server-side-only flags that re-use encoding bits*/ +#define UF_REMOVE UF_16BIT /*says we removed the entity in this frame*/ +#define UF_MOVETYPE UF_EFFECTS2 /*this flag isn't present in the header itself*/ +#define UF_RESET2 UF_EXTEND1 /*so new ents are reset multiple times to avoid weird baselines*/ +//#define UF_UNUSED UF_EXTEND2 /**/ +#define UF_WEAPONFRAME_OLD UF_EXTEND2 +#define UF_VIEWANGLES UF_EXTEND3 /**/ + + + +static unsigned int SVFTE_DeltaPredCalcBits(entity_state_t *from, entity_state_t *to) +{ + unsigned int bits = 0; +// if (from && from->pmovetype != to->pmovetype) +// bits |= UFP_MOVETYPE; + +// if (to->movement[0]) +// bits |= UFP_FORWARD; +// if (to->movement[1]) +// bits |= UFP_SIDE; +// if (to->movement[2]) +// bits |= UFP_UP; + if (to->velocity[0]) + bits |= UFP_VELOCITYXY; + if (to->velocity[1]) + bits |= UFP_VELOCITYXY; + if (to->velocity[2]) + bits |= UFP_VELOCITYZ; +// if (to->msec) +// bits |= UFP_MSEC; + + return bits; +} + +static unsigned int MSGFTE_DeltaCalcBits(entity_state_t *from, entity_state_t *to) +{ + unsigned int bits = 0; + + if (from->pmovetype != to->pmovetype) + bits |= UF_PREDINFO|UF_MOVETYPE; +/* if (from->weaponframe != to->weaponframe) + bits |= UF_PREDINFO|UF_WEAPONFRAME_OLD; +*/// if (to->pmovetype) //we don't support prediction, but we still need to network the player's velocity for bob etc. + { + if (SVFTE_DeltaPredCalcBits(from, to)) + bits |= UF_PREDINFO; + + //moving players get extra data forced upon them which is not deltatracked + if ((bits & UF_PREDINFO) && (from->velocity[0] || from->velocity[1] || from->velocity[2])) + { + //if we've got player movement then write the origin anyway, to cover packetloss + bits |= UF_ORIGINXY | UF_ORIGINZ; + //and force angles too, if its not us +// if (host_client != svs.clients + to->number-1) +// bits |= UF_ANGLESXZ | UF_ANGLESY; + } + } + + + + + if (to->origin[0] != from->origin[0]) + bits |= UF_ORIGINXY; + if (to->origin[1] != from->origin[1]) + bits |= UF_ORIGINXY; + if (to->origin[2] != from->origin[2]) + bits |= UF_ORIGINZ; + + if (to->angles[0] != from->angles[0]) + bits |= UF_ANGLESXZ; + if (to->angles[1] != from->angles[1]) + bits |= UF_ANGLESY; + if (to->angles[2] != from->angles[2]) + bits |= UF_ANGLESXZ; + + + if (to->modelindex != from->modelindex) + bits |= UF_MODEL; + if (to->frame != from->frame) + bits |= UF_FRAME; + if (to->skin != from->skin) + bits |= UF_SKIN; + if (to->colormap != from->colormap) + bits |= UF_COLORMAP; + if (to->effects != from->effects) + bits |= UF_EFFECTS; + if (to->eflags != from->eflags) + bits |= UF_FLAGS; +// if (to->solidsize != from->solidsize) +// bits |= UF_SOLID; + + if (to->scale != from->scale) + bits |= UF_SCALE; + if (to->alpha != from->alpha) + bits |= UF_ALPHA; +// if (to->fatness != from->fatness) +// bits |= UF_FATNESS; + +// if (to->hexen2flags != from->hexen2flags || to->abslight != from->abslight) +// bits |= UF_DRAWFLAGS; + +// if (to->bonecount != from->bonecount || (to->bonecount && memcmp(frombonedata+from->boneoffset, tobonedata+to->boneoffset, to->bonecount*sizeof(short)*7))) +// bits |= UF_BONEDATA; +// if (!to->bonecount && (to->basebone != from->basebone || to->baseframe != from->baseframe)) +// bits |= UF_BONEDATA; + + if (to->colormod[0]!=from->colormod[0]||to->colormod[1]!=from->colormod[1]||to->colormod[2]!=from->colormod[2]) + bits |= UF_COLORMOD; + +// if (to->glowsize!=from->glowsize||to->glowcolour!=from->glowcolour||to->glowmod[0]!=from->glowmod[0]||to->glowmod[1]!=from->glowmod[1]||to->glowmod[2]!=from->glowmod[2]) +// bits |= UF_GLOW; + + if (to->tagentity != from->tagentity || to->tagindex != from->tagindex) + bits |= UF_TAGINFO; + +// if (to->light[0] != from->light[0] || to->light[1] != from->light[1] || to->light[2] != from->light[2] || to->light[3] != from->light[3] || to->lightstyle != from->lightstyle || to->lightpflags != from->lightpflags) +// bits |= UF_LIGHT; + + if (to->traileffectnum != from->traileffectnum || to->emiteffectnum != from->emiteffectnum) + bits |= UF_TRAILEFFECT; + +// if (to->modelindex2 != from->modelindex2) +// bits |= UF_MODELINDEX2; + +// if (to->gravitydir[0] != from->gravitydir[0] || to->gravitydir[1] != from->gravitydir[1]) +// bits |= UF_GRAVITYDIR; + + return bits; +} + +#define MSG_WriteEntity MSG_WriteShort +static void MSGFTE_WriteEntityUpdate(unsigned int bits, entity_state_t *state, sizebuf_t *msg, unsigned int pext2, unsigned int protocolflags) +{ + unsigned int predbits = 0; + if (bits & UF_MOVETYPE) + { + bits &= ~UF_MOVETYPE; + predbits |= UFP_MOVETYPE; + } + if (pext2 & PEXT2_PREDINFO) + { + if (bits & UF_VIEWANGLES) + { + bits &= ~UF_VIEWANGLES; + bits |= UF_PREDINFO; + predbits |= UFP_VIEWANGLE; + } + } + else + { + if (bits & UF_VIEWANGLES) + { + bits &= ~UF_VIEWANGLES; + bits |= UF_PREDINFO; + } + if (bits & UF_WEAPONFRAME_OLD) + { + bits &= ~UF_WEAPONFRAME_OLD; + predbits |= UFP_WEAPONFRAME_OLD; + } + } + +// if (!(pext2 & PEXT2_NEWSIZEENCODING)) //was added at the same time + bits &= ~UF_BONEDATA; + + /*check if we need more precision for some things*/ + if ((bits & UF_MODEL) && state->modelindex > 255) + bits |= UF_16BIT; +// if ((bits & UF_SKIN) && state->skin > 255) +// bits |= UF_16BIT; + if ((bits & UF_FRAME) && state->frame > 255) + bits |= UF_16BIT; + + /*convert effects bits to higher lengths if needed*/ + if (bits & UF_EFFECTS) + { + if (state->effects & 0xffff0000) /*both*/ + bits |= UF_EFFECTS | UF_EFFECTS2; + else if (state->effects & 0x0000ff00) /*2 only*/ + bits = (bits & ~UF_EFFECTS) | UF_EFFECTS2; + } + if (bits & 0xff000000) + bits |= UF_EXTEND3; + if (bits & 0x00ff0000) + bits |= UF_EXTEND2; + if (bits & 0x0000ff00) + bits |= UF_EXTEND1; + + MSG_WriteByte(msg, (bits>>0) & 0xff); + if (bits & UF_EXTEND1) + MSG_WriteByte(msg, (bits>>8) & 0xff); + if (bits & UF_EXTEND2) + MSG_WriteByte(msg, (bits>>16) & 0xff); + if (bits & UF_EXTEND3) + MSG_WriteByte(msg, (bits>>24) & 0xff); + + if (bits & UF_FRAME) + { + if (bits & UF_16BIT) + MSG_WriteShort(msg, state->frame); + else + MSG_WriteByte(msg, state->frame); + } + if (bits & UF_ORIGINXY) + { + MSG_WriteCoord(msg, state->origin[0], protocolflags); + MSG_WriteCoord(msg, state->origin[1], protocolflags); + } + if (bits & UF_ORIGINZ) + MSG_WriteCoord(msg, state->origin[2], protocolflags); + + if ((bits & UF_PREDINFO) && !(pext2 & PEXT2_PREDINFO)) + { /*if we have pred info, (always) use more precise angles*/ + if (bits & UF_ANGLESXZ) + { + MSG_WriteAngle16(msg, state->angles[0], protocolflags); + MSG_WriteAngle16(msg, state->angles[2], protocolflags); + } + if (bits & UF_ANGLESY) + MSG_WriteAngle16(msg, state->angles[1], protocolflags); + } + else + { + if (bits & UF_ANGLESXZ) + { + MSG_WriteAngle(msg, state->angles[0], protocolflags); + MSG_WriteAngle(msg, state->angles[2], protocolflags); + } + if (bits & UF_ANGLESY) + MSG_WriteAngle(msg, state->angles[1], protocolflags); + } + + if ((bits & (UF_EFFECTS|UF_EFFECTS2)) == (UF_EFFECTS|UF_EFFECTS2)) + MSG_WriteLong(msg, state->effects); + else if (bits & UF_EFFECTS2) + MSG_WriteShort(msg, state->effects); + else if (bits & UF_EFFECTS) + MSG_WriteByte(msg, state->effects); + + if (bits & UF_PREDINFO) + { + /*movetype is set above somewhere*/ + predbits |= SVFTE_DeltaPredCalcBits(NULL, state); + + MSG_WriteByte(msg, predbits); +/* + if (predbits & UFP_FORWARD) + MSG_WriteShort(msg, state->movement[0]); + if (predbits & UFP_SIDE) + MSG_WriteShort(msg, state->movement[1]); + if (predbits & UFP_UP) + MSG_WriteShort(msg, state->movement[2]); +*/ if (predbits & UFP_MOVETYPE) + MSG_WriteByte(msg, state->pmovetype); + if (predbits & UFP_VELOCITYXY) + { + MSG_WriteShort(msg, state->velocity[0]); + MSG_WriteShort(msg, state->velocity[1]); + } + if (predbits & UFP_VELOCITYZ) + MSG_WriteShort(msg, state->velocity[2]); +/* if (predbits & UFP_MSEC) + MSG_WriteByte(msg, state->msec); + if (pext2 & PEXT2_PREDINFO) + { + if (predbits & UFP_VIEWANGLE) + { //if we have pred info, use more precise angles + if (bits & UF_ANGLESXZ) + { + MSG_WriteShort(msg, state->vangle[0]); + MSG_WriteShort(msg, state->vangle[2]); + } + if (bits & UF_ANGLESY) + MSG_WriteShort(msg, state->vangle[1]); + } + } + else + { + if (predbits & UFP_WEAPONFRAME_OLD) + { + if (state->weaponframe > 127) + { + MSG_WriteByte(msg, 128 | (state->weaponframe & 127)); + MSG_WriteByte(msg, state->weaponframe>>7); + } + else + MSG_WriteByte(msg, state->weaponframe); + } + } +*/ + } + + if (bits & UF_MODEL) + { + if (bits & UF_16BIT) + MSG_WriteShort(msg, state->modelindex); + else + MSG_WriteByte(msg, state->modelindex); + } + if (bits & UF_SKIN) + { + if (bits & UF_16BIT) + MSG_WriteShort(msg, state->skin); + else + MSG_WriteByte(msg, state->skin); + } + if (bits & UF_COLORMAP) + MSG_WriteByte(msg, state->colormap & 0xff); + + if (bits & UF_SOLID) + { +/* if (pext2 & PEXT2_NEWSIZEENCODING) + { + if (!state->solidsize) + MSG_WriteByte(msg, 0); + else if (state->solidsize == ES_SOLID_BSP) + MSG_WriteByte(msg, 1); + else if (state->solidsize == ES_SOLID_HULL1) + MSG_WriteByte(msg, 2); + else if (state->solidsize == ES_SOLID_HULL2) + MSG_WriteByte(msg, 3); + else if (!ES_SOLID_HAS_EXTRA_BITS(state->solidsize)) + { + MSG_WriteByte(msg, 16); + MSG_WriteSize16(msg, state->solidsize); + } + else + { + MSG_WriteByte(msg, 32); + MSG_WriteLong(msg, state->solidsize); + } + } + else + MSG_WriteSize16(msg, state->solidsize); +*/ } + + if (bits & UF_FLAGS) + MSG_WriteByte(msg, state->eflags); + + if (bits & UF_ALPHA) + MSG_WriteByte(msg, (state->alpha-1)&0xff); + if (bits & UF_SCALE) + MSG_WriteByte(msg, state->scale); +/* if (bits & UF_BONEDATA) + { + short *bonedata; + int i; + byte bfl = 0; + if (state->bonecount && boneptr) + bfl |= 0x80; + if (state->basebone || state->baseframe) + bfl |= 0x40; + MSG_WriteByte(msg, bfl); + if (bfl & 0x80) + { + //this is NOT finalized + MSG_WriteByte(msg, state->bonecount); + bonedata = (short*)(boneptr + state->boneoffset); + for (i = 0; i < state->bonecount*7; i++) + MSG_WriteShort(msg, bonedata[i]); + } + if (bfl & 0x40) + { + MSG_WriteByte(msg, state->basebone); + MSG_WriteShort(msg, state->baseframe); + } + } + if (bits & UF_DRAWFLAGS) + { + MSG_WriteByte(msg, state->drawflags); + if ((state->drawflags & MLS_MASK) == MLS_ABSLIGHT) + MSG_WriteByte(msg, state->abslight); + } +*/ if (bits & UF_TAGINFO) + { + MSG_WriteEntity(msg, state->tagentity); + MSG_WriteByte(msg, state->tagindex); + } +/* if (bits & UF_LIGHT) + { + MSG_WriteShort (msg, state->light[0]); + MSG_WriteShort (msg, state->light[1]); + MSG_WriteShort (msg, state->light[2]); + MSG_WriteShort (msg, state->light[3]); + MSG_WriteByte (msg, state->lightstyle); + MSG_WriteByte (msg, state->lightpflags); + } +*/ if (bits & UF_TRAILEFFECT) + { + if (state->emiteffectnum) + { //3 spare bits. so that's nice (this is guarenteed to be 14 bits max due to precaches using the upper two bits). + MSG_WriteShort(msg, (state->traileffectnum&0x3fff)|0x8000); + MSG_WriteShort(msg, state->emiteffectnum&0x3fff); + } + else + MSG_WriteShort(msg, state->traileffectnum&0x3fff); + } + + if (bits & UF_COLORMOD) + { + MSG_WriteByte(msg, state->colormod[0]); + MSG_WriteByte(msg, state->colormod[1]); + MSG_WriteByte(msg, state->colormod[2]); + } +/* if (bits & UF_GLOW) + { + MSG_WriteByte(msg, state->glowsize); + MSG_WriteByte(msg, state->glowcolour); + MSG_WriteByte(msg, state->glowmod[0]); + MSG_WriteByte(msg, state->glowmod[1]); + MSG_WriteByte(msg, state->glowmod[2]); + } + if (bits & UF_FATNESS) + MSG_WriteByte(msg, state->fatness); + if (bits & UF_MODELINDEX2) + { + if (bits & UF_16BIT) + MSG_WriteShort(msg, state->modelindex2); + else + MSG_WriteByte(msg, state->modelindex2); + } + + if (bits & UF_GRAVITYDIR) + { + MSG_WriteByte(msg, state->gravitydir[0]); + MSG_WriteByte(msg, state->gravitydir[1]); + } +*/ +} + +static struct entity_num_state_s *snapshot_entstate; +static size_t snapshot_numents; +static size_t snapshot_maxents; + +void SVFTE_DestroyFrames(client_t *client) +{ + if (client->previousentities) + free(client->previousentities); + client->previousentities = NULL; + client->numpreviousentities = 0; + client->maxpreviousentities = 0; + + + if (client->pendingentities_bits) + free(client->pendingentities_bits); + client->pendingentities_bits = NULL; + client->numpendingentities = 0; + + while(client->numframes > 0) + { + client->numframes--; + free(client->frames[client->numframes].ents); + } + if (client->frames) + free(client->frames); + client->frames = NULL; + + client->lastacksequence = 0; +} +static void SVFTE_SetupFrames(client_t *client) +{ + size_t fr; + //the client will clear out their stats on receipt of the svc_serverinfo packet. + //we won't send any reliables until they receive it + //so it should be enough to just clear these here, and they'll get their new stats with the first entity update once they're spawned + memset(client->oldstats_i, 0, sizeof(client->oldstats_i)); + memset(client->oldstats_f, 0, sizeof(client->oldstats_f)); + client->lastmovemessage = 0; //it'll clear this too + + if (!client->protocol_pext2) + { + SVFTE_DestroyFrames(client); + return; + } + + client->numframes = 64; //must be power-of-two + client->frames = malloc(sizeof(*client->frames) * client->numframes); + client->lastacksequence = (int)0x80000000; + memset(client->frames, 0, sizeof(*client->frames) * client->numframes); + for (fr = 0; fr < client->numframes; fr++) + client->frames[fr].sequence = client->lastacksequence; + + client->numpendingentities = sv.num_edicts; + client->pendingentities_bits = calloc(client->numpendingentities, sizeof(*client->pendingentities_bits)); + + client->pendingentities_bits[0] = UF_REMOVE; +} +static void SVFTE_DroppedFrame(client_t *client, int sequence) +{ + int i; + struct deltaframe_s *frame = &client->frames[sequence&(client->numframes-1)]; + if (frame->sequence != sequence) + return; //this frame was stale... client is running too far behind. we'll probably be spamming resends as a result. + frame->sequence = -1; + //flag their stats for resend + for (i = 0; i < MAX_CL_STATS/32; i++) + client->resendstats[i] |= frame->resendstats[i]; + //flag their various entities as needing a resend too. + for (i = 0; i < frame->numents; i++) + client->pendingentities_bits[frame->ents[i].num] |= frame->ents[i].bits; + +} +void SVFTE_Ack(client_t *client, int sequence) +{ //any gaps in the sequence need to considered dropped + struct deltaframe_s *frame; + int dropseq = client->lastacksequence+1; + if (!client->numframes) + return; //client shouldn't be using this. + if (sequence == -1) + client->pendingentities_bits[0] |= UF_REMOVE; //client wants a full resend. which might happen from it just starting to record a demo, saving it from writing all the deltas out. + if (sequence < client->lastacksequence) + { +// else Con_SafePrintf("dupe or stale ack (%s, %i->%i)\n", client->name, client->lastacksequence, sequence); + return; //panic + } + if ((unsigned)(dropseq-sequence) >= client->numframes) + dropseq = sequence - client->numframes; + while(dropseq < sequence) + SVFTE_DroppedFrame(client, dropseq++); + client->lastacksequence = sequence; + + frame = &client->frames[sequence&(client->numframes-1)]; + if (frame->sequence >= 0) + { + frame->sequence = -1; + host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] = sv.time - frame->timestamp; + host_client->num_pings++; + } +} +static void SVFTE_WriteStats(client_t *client, sizebuf_t *msg) +{ + int statsi[MAX_CL_STATS]; + float statsf[MAX_CL_STATS]; + int i; + struct deltaframe_s *frame; + int sequence = NET_QSocketGetSequenceOut(client->netconnection); + + frame = &client->frames[sequence&(client->numframes-1)]; + + if (frame->sequence == sequence-(int)client->numframes) //client is getting behind... this may get really spammy, lets hope it clears up at some point + SVFTE_DroppedFrame(client, frame->sequence); + + //figure out the current values in a nice easy way (yay for copying to make arrays easier!) + SV_CalcStats(client, statsi, statsf); + + for (i = 0; i < MAX_CL_STATS; i++) + { + //small cleanup + if (!statsi[i]) + statsi[i] = statsf[i]; + else + statsf[i] = 0;//statsi[i]; + + //if it changed flag for sending + if (statsi[i] != client->oldstats_i[i] || statsf[i] != client->oldstats_f[i]) + { + client->oldstats_i[i] = statsi[i]; + client->oldstats_f[i] = statsf[i]; + client->resendstats[i/32] |= 1u<<(i&31); + } + + //if its flagged then unflag it, log it, and send it + if (client->resendstats[i/32] & (1u<<(i&31))) + { + client->resendstats[i/32] &= ~(1u<<(i&31)); + frame->resendstats[i/32] |= 1u<<(i&31); + + if ((double)statsi[i] != statsf[i] && statsf[i]) + { //didn't round nicely, so send as a float + MSG_WriteByte(msg, svcfte_updatestatfloat); + MSG_WriteByte(msg, i); + MSG_WriteFloat(msg, statsf[i]); + } + else + { + if (statsi[i] < 0 || statsi[i] > 255) + { //needs to be big + MSG_WriteByte(msg, svc_updatestat); + MSG_WriteByte(msg, i); + MSG_WriteLong(msg, statsi[i]); + } + else + { //can be fairly small + MSG_WriteByte(msg, svcdp_updatestatbyte); + MSG_WriteByte(msg, i); + MSG_WriteByte(msg, statsi[i]); + } + } + } + } +} +static void SVFTE_CalcEntityDeltas(client_t *client) +{ + struct entity_num_state_s *olds, *news, *oldstop, *newstop; + + if ((int)client->numpendingentities < sv.num_edicts) + { + int newmax = sv.num_edicts+64; + client->pendingentities_bits = realloc(client->pendingentities_bits, sizeof(*client->pendingentities_bits) * newmax); + memset(client->pendingentities_bits+client->numpendingentities, 0, sizeof(*client->pendingentities_bits)*(newmax-client->numpendingentities)); + client->numpendingentities = newmax; + } + + //if we're clearing the list and starting from scratch, just wipe all lingering state + if (client->pendingentities_bits[0] & UF_REMOVE) + { + client->numpreviousentities = 0; + client->pendingentities_bits[0] = UF_REMOVE; + } + + news = snapshot_entstate; + newstop = news + snapshot_numents; + olds = client->previousentities; + oldstop = olds+client->numpreviousentities; + + //we have two sets of entity state, pvs culled etc already. + //figure out which flags changed, + for (;;) + { + if (olds==oldstop && news==newstop) + break; + if (news==newstop || (olds!=oldstop && olds->num < news->num)) + { + //old ent is no longer visible, so flag for removal. + client->pendingentities_bits[olds->num] = UF_REMOVE; + olds++; + } + else if (olds==oldstop || (news!=newstop && news->num < olds->num)) + { + //new ent is new this frame, so reset everything. + client->pendingentities_bits[news->num] = UF_RESET | UF_RESET2; + //don't need to calc the other bits here, resets are enough + news++; + } + else + { //simple entity delta + //its flagged for removing, that's weird... must be some killer packetloss. turn that back into a reset or something + if (client->pendingentities_bits[news->num] & UF_REMOVE) + client->pendingentities_bits[news->num] = (client->pendingentities_bits[news->num] & ~UF_REMOVE) | UF_RESET2; + client->pendingentities_bits[news->num] |= MSGFTE_DeltaCalcBits(&olds->state, &news->state); + news++; + olds++; + } + } + + //now we know what flags to apply, the client needs a copy of that state for the next frame too. + //outgoing data can just read off these states too, instead of needing to hit the edicts memory (which may be spread over multiple allocations, yay cache). + //to avoid a potentially large memcopy, I'm just going to swap these buffers. + olds = client->previousentities; + oldstop = olds + client->maxpreviousentities; + + client->previousentities = snapshot_entstate; + client->numpreviousentities = snapshot_numents; + client->maxpreviousentities = snapshot_maxents; + + snapshot_entstate = olds; + snapshot_numents = 0; + snapshot_maxents = oldstop-olds; +} +static void SVFTE_WriteEntitiesToClient(client_t *client, sizebuf_t *msg, size_t overflowsize) +{ + struct entity_num_state_s *state, *stateend; + unsigned int bits, logbits; + size_t entnum; + int sequence = NET_QSocketGetSequenceOut(client->netconnection); + size_t origmaxsize = msg->maxsize; + size_t rollbacksize; //I'm too lazy to figure out sizes (especially if someone updates this for bone states or whatever) + struct deltaframe_s *frame = &client->frames[sequence&(client->numframes-1)]; + frame->sequence = sequence; //so we know that it wasn't stale later. + frame->timestamp = sv.time; + + msg->maxsize = overflowsize; + + state = client->previousentities; + stateend = state + client->numpreviousentities; + + MSG_WriteByte(msg, svcfte_updateentities); + + frame->numents = 0; + if (client->protocol_pext2 & PEXT2_PREDINFO) + MSG_WriteShort(msg, (client->lastmovemessage&0xffff)); + MSG_WriteFloat(msg, sv.time); //should be the time the last physics frame was run. + for (entnum = client->snapshotresume; entnum < client->numpendingentities; entnum++) + { + bits = client->pendingentities_bits[entnum]; + if (!(bits & ~UF_RESET2)) + continue; //nothing to send (if reset2 is still set, then leave it pending until there's more data + + rollbacksize = msg->cursize; + client->pendingentities_bits[entnum] = 0; + logbits = 0; + if (bits & UF_REMOVE) + { + if (entnum > 0x3fff) + { + MSG_WriteShort(msg, 0xc000|(entnum&0x3fff)); + MSG_WriteShort(msg, entnum>>14); + } + else + MSG_WriteShort(msg, 0x8000|entnum); + logbits = UF_REMOVE; + } + else + { + while (statenum < entnum) + state++; + if (statenum == entnum) + { + if (bits & UF_RESET2) + { + /*if reset2, then this is the second packet sent to the client and should have a forced reset (but which isn't tracked)*/ + logbits = bits & ~(UF_RESET|UF_RESET2); + bits = UF_RESET | MSGFTE_DeltaCalcBits(&EDICT_NUM(entnum)->baseline, &state->state); +// Con_Printf("RESET2 %i @ %i\n", j, sequence); + } + else if (bits & UF_RESET) + { + /*flag the entity for the next packet, so we always get two resets when it appears, to reduce the effects of packetloss on seeing rockets etc*/ + client->pendingentities_bits[entnum] = UF_RESET2; + bits = UF_RESET | MSGFTE_DeltaCalcBits(&EDICT_NUM(entnum)->baseline, &state->state); + logbits = UF_RESET; +// Con_Printf("RESET %i @ %i\n", j, sequence); + } + else + logbits = bits; + + if (entnum >= 0x4000) + { + MSG_WriteShort(msg, 0x4000|(entnum&0x3fff)); + MSG_WriteShort(msg, entnum>>14); + } + else + MSG_WriteShort(msg, entnum); +// SV_EmitDeltaEntIndex(msg, j, false, true); + MSGFTE_WriteEntityUpdate(bits, &state->state, msg, client->protocol_pext2, sv.protocolflags); + } + } + + if ((size_t)msg->cursize + 2 > origmaxsize) + { + msg->cursize = rollbacksize; //roll back + client->pendingentities_bits[entnum] |= logbits; //make sure those bits get re-applied later. + break; + } + if (frame->numents == frame->maxents) + { + frame->maxents += 64; + frame->ents = realloc(frame->ents, sizeof(*frame->ents)*frame->maxents); + } + frame->ents[frame->numents].num = entnum; + frame->ents[frame->numents].bits = logbits; + frame->numents++; + } + msg->maxsize = origmaxsize; + MSG_WriteShort(msg, 0); //eom + + //remember how far we got, so we can keep things flushed, instead of only updating the first N entities. + client->snapshotresume = (entnumnumpendingentities?entnum:0); + + + if (msg->cursize > 1024 && dev_peakstats.packetsize <= 1024) + Con_DWarning ("%i byte packet exceeds standard limit of 1024.\n", msg->cursize); + dev_stats.packetsize = msg->cursize; + dev_peakstats.packetsize = q_max(msg->cursize, dev_peakstats.packetsize); +} + +/* +SV_BuildEntityState +copies edict state into a more compact entity_state_t with all the extension fields etc sorted out and neatened up for network precision. +note: ignores viewmodelforclient and other client-specific stuff. +*/ +void SV_BuildEntityState(edict_t *ent, entity_state_t *state) +{ + eval_t *val; + state->eflags = 0; + VectorCopy(ent->v.origin, state->origin); + VectorCopy(ent->v.angles, state->angles); + state->modelindex = ent->v.modelindex; + state->frame = ent->v.frame; + state->colormap = ent->v.colormap; + state->skin = ent->v.skin; + if ((val = GetEdictFieldValue(ent, pr_extfields.scale)) && val->_float) + state->scale = val->_float*16; + else + state->scale = 16; + if ((val = GetEdictFieldValue(ent, pr_extfields.alpha))) + state->alpha = ENTALPHA_ENCODE(val->_float); + else + state->alpha = ent->alpha; + if ((val = GetEdictFieldValue(ent, pr_extfields.colormod)) && (val->vector[0]||val->vector[1]||val->vector[2])) + { + state->colormod[0] = val->vector[0]*32; + state->colormod[1] = val->vector[1]*32; + state->colormod[2] = val->vector[2]*32; + } + else + state->colormod[0] = state->colormod[1] = state->colormod[2] = 32; + state->traileffectnum = GetEdictFieldValue(ent, pr_extfields.traileffectnum)->_float; + state->emiteffectnum = GetEdictFieldValue(ent, pr_extfields.emiteffectnum)->_float; + if ((val = GetEdictFieldValue(ent, pr_extfields.tag_entity)) && val->edict) + state->tagentity = NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)); + else + state->tagentity = 0; + if ((val = GetEdictFieldValue(ent, pr_extfields.tag_index))) + state->tagindex = val->_float; + else + state->tagindex = 0; + state->effects = ent->v.effects; + if ((val = GetEdictFieldValue(ent, pr_extfields.modelflags))) + state->effects |= ((unsigned int)val->_float)<<24; + if (!ent->v.movetype || ent->v.movetype == MOVETYPE_STEP) + state->eflags |= EFLAGS_STEP; + + state->pmovetype = 0; + state->velocity[0] = state->velocity[1] = state->velocity[2] = 0; +} + +byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel); +static void SVFTE_BuildSnapshotForClient (client_t *client) +{ + unsigned int e, i; + byte *pvs; + vec3_t org; + edict_t *ent, *parent; + unsigned int maxentities = client->limit_entities; + edict_t *clent = client->edict; + eval_t *val; + unsigned char eflags; + int proged = EDICT_TO_PROG(clent); + + struct entity_num_state_s *ents = snapshot_entstate; + size_t numents = 0; + size_t maxents = snapshot_maxents; + int emiteffect; + +// find the client's PVS + VectorAdd (clent->v.origin, clent->v.view_ofs, org); + pvs = SV_FatPVS (org, sv.worldmodel); + + if (maxentities > (unsigned int)sv.num_edicts) + maxentities = (unsigned int)sv.num_edicts; + +// send over all entities (excpet the client) that touch the pvs + ent = NEXT_EDICT(sv.edicts); + for (e=1 ; e_float; + if (ent != clent) // clent is ALLWAYS sent + { + // ignore ents without visible models + if ((!ent->v.modelindex || !PR_GetString(ent->v.model)[0]) && !emiteffect) + continue; + + val = GetEdictFieldValue(ent, pr_extfields.viewmodelforclient); + if (val && val->edict == proged) + eflags |= EFLAGS_VIEWMODEL; + else if (val && val->edict) + continue; + else + { + //attached entities should use the pvs of the parent rather than the child (because the child will typically be bugging out around '0 0 0', so won't be useful) + parent = ent; + while ((val = GetEdictFieldValue(parent, pr_extfields.tag_entity)) && val->edict) + parent = PROG_TO_EDICT(val->edict); + if (parent->num_leafs) + { + // ignore if not touching a PV leaf + for (i=0 ; i < parent->num_leafs ; i++) + if (pvs[parent->leafnums[i] >> 3] & (1 << (parent->leafnums[i]&7) )) + break; + + // ericw -- added ent->num_leafs < MAX_ENT_LEAFS condition. + // + // if ent->num_leafs == MAX_ENT_LEAFS, the ent is visible from too many leafs + // for us to say whether it's in the PVS, so don't try to vis cull it. + // this commonly happens with rotators, because they often have huge bboxes + // spanning the entire map, or really tall lifts, etc. + if (i == parent->num_leafs && parent->num_leafs < MAX_ENT_LEAFS) + continue; // not visible + } + } + } + + //okay, we care about this entity. + + if (numents == maxents) + { + maxents += 64; + ents = realloc(ents, maxents*sizeof(*ents)); + } + + ents[numents].num = e; + SV_BuildEntityState(ent, &ents[numents].state); + if ((unsigned int)ents[numents].state.modelindex >= client->limit_models) + ents[numents].state.modelindex = 0; + if (ent == clent) //add velocity, but we only care for the local player (should add prediction for other entities some time too). + { + ents[numents].state.pmovetype = 0;//ent->v.movetype; //fixme: we don't do prediction, so don't tell the client that it can try + if ((int)ent->v.flags & FL_ONGROUND) + eflags |= EFLAGS_ONGROUND; + ents[numents].state.velocity[0] = ent->v.velocity[0]*8; + ents[numents].state.velocity[1] = ent->v.velocity[1]*8; + ents[numents].state.velocity[2] = ent->v.velocity[2]*8; + } + else if (ent->alpha == ENTALPHA_ZERO && !ent->v.effects) //don't send invisible entities unless they have effects + continue; +// val = GetEdictFieldValue(ent, pr_extfields.exteriormodelforclient); +// if (val && val->edict == proged) +// eflags |= EFLAGS_EXTERIORMODEL; + //EFLAGS_VIEWMODEL was handled above + ents[numents].state.eflags |= eflags; + + numents++; + } + + snapshot_entstate = ents; + snapshot_numents = numents; + snapshot_maxents = maxents; +} + +void MSG_WriteStaticOrBaseLine(sizebuf_t *buf, int idx, entity_state_t *state, unsigned int protocol_pext2, unsigned int protocol, unsigned int protocolflags) +{ + int i; + if (protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + { + if (idx>=0) + { + MSG_WriteByte(buf, svcfte_spawnbaseline2); + MSG_WriteShort(buf, idx); + } + else + MSG_WriteByte(buf, svcfte_spawnstatic2); + MSGFTE_WriteEntityUpdate(MSGFTE_DeltaCalcBits(&nullentitystate, state), state, buf, protocol_pext2, protocolflags); + } + else + { + int bits = 0; + if (protocol == PROTOCOL_VERSION_DP7) + { + if ((state->modelindex & 0xFF00) || (state->frame & 0xFF00)) + bits = B_LARGEMODEL|B_LARGEFRAME; + //no alpha etc, no idea how that's meant to work with static ents. + if (idx>=0) + { + MSG_WriteByte (buf, bits?svcdp_spawnbaseline2:svc_spawnbaseline); + MSG_WriteEntity (buf, idx); + } + else + MSG_WriteByte (buf, bits?svcdp_spawnstatic2:svc_spawnstatic); + } + else + { + if (protocol == PROTOCOL_FITZQUAKE) //still want to send baseline in PROTOCOL_NETQUAKE, so reset these values + { + if (state->modelindex & 0xFF00) + bits |= B_LARGEMODEL; + if (state->frame & 0xFF00) + bits |= B_LARGEFRAME; + if (state->alpha != ENTALPHA_DEFAULT) + bits |= B_ALPHA; + } + if (idx>=0) + { + MSG_WriteByte (buf, bits?svc_spawnbaseline2:svc_spawnbaseline); + MSG_WriteEntity (buf, idx); + } + else + MSG_WriteByte (buf, bits?svc_spawnstatic2:svc_spawnstatic); + + if (bits) + MSG_WriteByte (buf, bits); + } + + if ((bits & B_LARGEMODEL) || protocol == PROTOCOL_VERSION_BJP3) + MSG_WriteShort (buf, state->modelindex); + else + MSG_WriteByte (buf, state->modelindex); + + if (bits & B_LARGEFRAME) + MSG_WriteShort (buf, state->frame); + else + MSG_WriteByte (buf, state->frame); + + MSG_WriteByte (buf, state->colormap); + MSG_WriteByte (buf, state->skin); + for (i=0 ; i<3 ; i++) + { + MSG_WriteCoord(buf, state->origin[i], protocolflags); + MSG_WriteAngle(buf, state->angles[i], protocolflags); + } + if (bits & B_ALPHA) + MSG_WriteByte (buf, state->alpha); + } +} + + +static void SV_Pext_f(void); + /* =============== SV_Protocol_f =============== */ -void SV_Protocol_f (void) +static void SV_Protocol_f (void) { int i; + const char *s; + int prot, pext2; + + prot = sv_protocol; + pext2 = sv_protocol_pext2; switch (Cmd_Argc()) { case 1: - Con_Printf ("\"sv_protocol\" is \"%i\"\n", sv_protocol); + //"FTE+15" or "15", just to be explicit about it + Con_Printf ("\"sv_protocol\" is \"%s%i\"\n", sv_protocol_pext2?"fte":"", sv_protocol); break; case 2: - i = atoi(Cmd_Argv(1)); - if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) - Con_Printf ("sv_protocol must be %i or %i or %i\n", PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ); + s = Cmd_Argv(1); + if (!q_strncasecmp(s, "FTE", 3)) + { + s += 3; + if (*s == '+' || *s == '-') + s++; + pext2 = PEXT2_SUPPORTED_SERVER; + } + else if (!q_strncasecmp(s, "+", 3)) + { + s += 1; + pext2 = PEXT2_SUPPORTED_SERVER; + } + else if (!q_strncasecmp(s, "Base", 4)) + { + s+= 4; + if (*s == '+' || *s == '-') + s++; + pext2 = 0; + } + else if (*s == '-') + { + s++; + pext2 = 0; + } + + i = strtol(s, (char**)&s, 0); + if (*s == '-') + pext2 = 0; + else if (*s == '+') + pext2 = PEXT2_SUPPORTED_SERVER; + + if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ && i != PROTOCOL_VERSION_BJP3) + Con_Printf ("sv_protocol must be %i or %i or %i or %i.\nProtocol may be prefixed with FTE+ or Base- to enable/disable FTE extensions.\n", PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ, PROTOCOL_VERSION_BJP3); else { sv_protocol = i; + sv_protocol_pext2 = pext2; if (sv.active) - Con_Printf ("changes will not take effect until the next level load.\n"); + { + if (prot == sv_protocol && pext2 == sv_protocol_pext2) + Con_Printf ("specified protocol already active.\n"); + else + Con_Printf ("changes will not take effect until the next level load.\n"); + } } break; default: @@ -78,6 +1197,7 @@ void SV_Init (void) extern cvar_t sv_gravity; extern cvar_t sv_nostep; extern cvar_t sv_freezenonclients; + extern cvar_t sv_gameplayfix_spawnbeforethinks; extern cvar_t sv_friction; extern cvar_t sv_edgefriction; extern cvar_t sv_stopspeed; @@ -86,6 +1206,12 @@ void SV_Init (void) extern cvar_t sv_idealpitchscale; extern cvar_t sv_aim; extern cvar_t sv_altnoclip; //johnfitz + extern cvar_t sv_public; //spike + extern cvar_t sv_reportheartbeats; //spike + extern cvar_t com_protocolname; //spike + extern cvar_t net_masters[]; //spike + extern cvar_t rcon_password; //spike, proquake-compatible rcon + sv.edicts = NULL; // ericw -- sv.edicts switched to use malloc() @@ -103,13 +1229,28 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_aim); Cvar_RegisterVariable (&sv_nostep); Cvar_RegisterVariable (&sv_freezenonclients); + Cvar_RegisterVariable (&sv_gameplayfix_spawnbeforethinks); + Cvar_RegisterVariable (&pr_checkextension); Cvar_RegisterVariable (&sv_altnoclip); //johnfitz + if (isDedicated) + sv_public.string = "1"; + else + sv_public.string = "0"; + Cvar_RegisterVariable (&sv_public); + Cvar_RegisterVariable (&sv_reportheartbeats); + Cvar_RegisterVariable (&com_protocolname); + for (i = 0; net_masters[i].name; i++) + Cvar_RegisterVariable (&net_masters[i]); + Cvar_RegisterVariable (&rcon_password); + + Cmd_AddCommand_ClientCommand("pext", SV_Pext_f); Cmd_AddCommand ("sv_protocol", &SV_Protocol_f); //johnfitz for (i=0 ; i 255) + if (volume < 0) Host_Error ("SV_StartSound: volume = %i", volume); + else if (volume > 255) + { + volume = 255; + Con_Printf ("SV_StartSound: volume = %i\n", volume); + } if (attenuation < 0 || attenuation > 4) Host_Error ("SV_StartSound: attenuation = %f", attenuation); - if (channel < 0 || channel > 7) + if (channel < 0 || channel > 255) Host_Error ("SV_StartSound: channel = %i", channel); + else if (channel > 7) + Con_DPrintf ("SV_StartSound: channel = %i\n", channel); if (sv.datagram.cursize > MAX_DATAGRAM-16) return; @@ -224,46 +1376,61 @@ void SV_StartSound (edict_t *entity, int channel, const char *sample, int volume field_mask |= SND_ATTENUATION; //johnfitz -- PROTOCOL_FITZQUAKE - if (ent >= 8192) - { - if (sv.protocol == PROTOCOL_NETQUAKE) - return; //don't send any info protocol can't support - else - field_mask |= SND_LARGEENTITY; - } - if (sound_num >= 256 || channel >= 8) - { - if (sv.protocol == PROTOCOL_NETQUAKE) - return; //don't send any info protocol can't support - else - field_mask |= SND_LARGESOUND; - } + if (ent >= 8192 || channel >= 8) + field_mask |= SND_LARGEENTITY; + if (sound_num >= 256) + field_mask |= SND_LARGESOUND; //johnfitz -// directed messages go only to the entity the are targeted on - MSG_WriteByte (&sv.datagram, svc_sound); - MSG_WriteByte (&sv.datagram, field_mask); - if (field_mask & SND_VOLUME) - MSG_WriteByte (&sv.datagram, volume); - if (field_mask & SND_ATTENUATION) - MSG_WriteByte (&sv.datagram, attenuation*64); - - //johnfitz -- PROTOCOL_FITZQUAKE - if (field_mask & SND_LARGEENTITY) + for (p = 0; p < svs.maxclients; p++) { - MSG_WriteShort (&sv.datagram, ent); - MSG_WriteByte (&sv.datagram, channel); - } - else - MSG_WriteShort (&sv.datagram, (ent<<3) | channel); - if (field_mask & SND_LARGESOUND) - MSG_WriteShort (&sv.datagram, sound_num); - else - MSG_WriteByte (&sv.datagram, sound_num); - //johnfitz + cl = &svs.clients[p]; + if (!cl->active || !cl->spawned) + continue; - for (i = 0; i < 3; i++) - MSG_WriteCoord (&sv.datagram, entity->v.origin[i]+0.5*(entity->v.mins[i]+entity->v.maxs[i]), sv.protocolflags); + if (ent >= cl->limit_entities) + continue; + if (sound_num >= cl->limit_sounds) + continue; + if ((field_mask & (SND_LARGEENTITY|SND_LARGESOUND)) && (!cl->protocol_pext2 || sv.protocol == PROTOCOL_NETQUAKE)) + continue; + + // directed messages go only to the entity the are targeted on + MSG_WriteByte (&cl->datagram, svc_sound); + MSG_WriteByte (&cl->datagram, field_mask); + if (field_mask & SND_VOLUME) + MSG_WriteByte (&cl->datagram, volume); + if (field_mask & SND_ATTENUATION) + MSG_WriteByte (&cl->datagram, attenuation*64); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (field_mask & SND_LARGEENTITY) + { + if ((cl->protocol_pext2 & PEXT2_REPLACEMENTDELTAS) && ent > 0x7fff) + { + MSG_WriteShort(&cl->datagram, (ent>>8) | 0x8000); + MSG_WriteByte(&cl->datagram, ent & 0xff); + } + else + MSG_WriteShort (&cl->datagram, ent); + MSG_WriteByte (&cl->datagram, channel); + } + else + MSG_WriteShort (&cl->datagram, (ent<<3) | channel); + if ((field_mask & SND_LARGESOUND) || sv.protocol == PROTOCOL_VERSION_BJP3) + MSG_WriteShort (&cl->datagram, sound_num); + else + MSG_WriteByte (&cl->datagram, sound_num); + //johnfitz + + for (i = 0; i < 3; i++) + { + if (origin) + MSG_WriteCoord (&cl->datagram, origin[i], sv.protocolflags); + else + MSG_WriteCoord (&cl->datagram, entity->v.origin[i]+0.5*(entity->v.mins[i]+entity->v.maxs[i]), sv.protocolflags); + } + } } /* @@ -286,21 +1453,125 @@ void SV_SendServerinfo (client_t *client) { const char **s; char message[2048]; - int i; //johnfitz + unsigned int i; //johnfitz + qboolean cantruncate; + SV_VoiceInitClient(client); + + client->spawned = false; // need prespawn, spawn, etc + + //assume some safe defaults if we early out. + client->limit_unreliable = 1024; + client->limit_reliable = 8192; + client->limit_entities = 0; + client->limit_models = 0; + client->limit_sounds = 0; + + if (!sv_protocol_pext2) + { //server disabled pext completely, don't bother trying. + //make sure we try reenabling it again on the next map though. mwahaha. + client->pextknown = false; + } + else if (!client->pextknown) + { + MSG_WriteByte (&client->message, svc_stufftext); + MSG_WriteString (&client->message, "cmd pext\n"); + client->sendsignon = true; + return; + } + client->protocol_pext2 &= sv_protocol_pext2; + + if (!(client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + client->protocol_pext2 &= ~PEXT2_PREDINFO; //stats can't be deltaed if there's no deltas, so just pretend its not supported on its own. + + client->limit_entities = (sv_protocol_pext2&&NET_QSocketGetProQuakeAngleHack(client->netconnection))?2048:600; //vanilla sucks. proquake supports more so assume we can use that limit if angles are also available (but only if we're allowing other non-vanilla extensions) + client->limit_models = 256; //single byte + client->limit_sounds = 256; //single byte + + //now we know their protocol, pick some real defaults + if (sv.protocol != PROTOCOL_NETQUAKE || client->protocol_pext2) + { + client->limit_unreliable = DATAGRAM_MTU;//some safe ethernet limit. these clients should accept pretty much anything, but any routers will not. + client->limit_reliable = MAX_DATAGRAM; //quite large, ip allows 16 bits + client->limit_entities = sv.max_edicts; //we don't really know, 8k is probably a save guess but could be 32k, 65k, or even more... + client->limit_models = MAX_MODELS; //not really sure, client's problem until >14bits + client->limit_sounds = MAX_SOUNDS; //not really sure, client's problem until >14bits + + if (!Q_strcmp(NET_QSocketGetTrueAddressString(client->netconnection), "LOCAL")) + { //override some other limits for localhost, because we can probably get away with it. + //only do this if we're using extensions, so we don't break demos + client->limit_unreliable = client->limit_reliable = MAX_DATAGRAM; + } + } + if (client->limit_entities > 0x8000 && !(client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) + client->limit_entities = 0x8000; //pext2 changes the encoding of entities to support 23 bits instead of dpp7's 15bits or vanilla's 16bits, but our writeentity is lazy. + + + //unfortunately we can't split this up, so if its oversized, we'll just let the client complain instead of always kicking them + client->message.maxsize = sizeof(client->msgbuf); + if (client->message.maxsize > (int)client->limit_reliable) + client->message.maxsize = client->limit_reliable; + + NET_QSocketSetMSS(client->netconnection, client->limit_unreliable); + + if (client->message.cursize) + { //try and flush the reliable NOW, in case the qc is evil + if (NET_CanSendMessage (host_client->netconnection)) + { + if (NET_SendMessage (host_client->netconnection, &host_client->message) != -1) + { + SZ_Clear (&host_client->message); + host_client->last_message = realtime; + } + } + } + + cantruncate = client->message.cursize == 0; +retry: MSG_WriteByte (&client->message, svc_print); - sprintf (message, "%c\nFITZQUAKE %1.2f SERVER (%i CRC)\n", 2, FITZQUAKE_VERSION, pr_crc); //johnfitz -- include fitzquake version +// sprintf (message, "%c\nFITZQUAKE %1.2f SERVER (%i CRC)\n", 2, FITZQUAKE_VERSION, pr_crc); //johnfitz -- include fitzquake version + sprintf (message, "%c\n"ENGINE_NAME_AND_VER" Server (%i CRC)\n", 2, pr_crc); //spike -- quakespasm has moved on, and has its own server capabilities now. Advertising = good, right? MSG_WriteString (&client->message,message); +// lack of serverinfo means any csqc info might as well be sent the lame dp way +/* if (sv.csqc_progsize) + { + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va("csqc_progname %s\n", sv.csqc_progname)); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va("csqc_progsize %i\n", sv.csqc_progsize)); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va("csqc_progcrc %i\n", sv.csqc_progcrc)); + }*/ + //let clients know that we support downloads + //if (client->protocol_pext2 || sv.protocol == PROTOCOL_VERSION_DP7) + { + MSG_WriteByte (&client->message, svc_stufftext); + MSG_WriteString (&client->message, "cl_serverextension_download 1\n"); + } + MSG_WriteByte (&client->message, svc_serverinfo); + + if (client->protocol_pext2) + { //pext stuff takes the form of modifiers to an underlaying protocol + MSG_WriteLong (&client->message, PROTOCOL_FTE_PEXT2); + MSG_WriteLong (&client->message, client->protocol_pext2); //active extensions that the client needs to look out for + } MSG_WriteLong (&client->message, sv.protocol); //johnfitz -- sv.protocol instead of PROTOCOL_VERSION - if (sv.protocol == PROTOCOL_RMQ) { // mh - now send protocol flags so that the client knows the protocol features to expect MSG_WriteLong (&client->message, sv.protocolflags); } - + + if (client->protocol_pext2 & PEXT2_PREDINFO) + { + //if multiple gamedirs were used, we should list all the active ones eg: "id1;hipnotic;rogue;quoth;mod". + //fixme: engine-specific forced gamedirs like id1/ or qw/ or fte/ are redundant, so don't bother listing them + //we don't really track that stuff, so I'm just going to report the last one + MSG_WriteString(&client->message, COM_GetGameNames(false)); + } + MSG_WriteByte (&client->message, svs.maxclients); if (!coop.value && deathmatch.value) @@ -311,14 +1582,12 @@ void SV_SendServerinfo (client_t *client) MSG_WriteString (&client->message, PR_GetString(sv.edicts->v.message)); //johnfitz -- only send the first 256 model and sound precaches if protocol is 15 - for (i=0,s = sv.model_precache+1 ; *s; s++,i++) - if (sv.protocol != PROTOCOL_NETQUAKE || i < 256) - MSG_WriteString (&client->message, *s); + for (i=1,s = sv.model_precache+1 ; *s && i < client->limit_models; s++,i++) + MSG_WriteString (&client->message, *s); MSG_WriteByte (&client->message, 0); - for (i=0,s = sv.sound_precache+1 ; *s ; s++,i++) - if (sv.protocol != PROTOCOL_NETQUAKE || i < 256) - MSG_WriteString (&client->message, *s); + for (i=1,s = sv.sound_precache+1 ; *s && i < client->limit_sounds; s++,i++) + MSG_WriteString (&client->message, *s); MSG_WriteByte (&client->message, 0); //johnfitz @@ -335,7 +1604,105 @@ void SV_SendServerinfo (client_t *client) MSG_WriteByte (&client->message, 1); client->sendsignon = true; - client->spawned = false; // need prespawn, spawn, etc + + SVFTE_SetupFrames(client); + + if (client->message.overflowed && client->limit_sounds > 64 && cantruncate) + { + if (client->limit_models > client->limit_sounds) + client->limit_models /= 2; + else + client->limit_sounds /= 2; + SZ_Clear(&client->message); + Con_Printf("Serverinfo too large for %s, truncating.\n", NET_QSocketGetTrueAddressString(client->netconnection)); + goto retry; + } + + //try and flush the reliable NOW, in case the qc is evil + if (NET_CanSendMessage (client->netconnection)) + { + if (NET_SendMessage (client->netconnection, &client->message) != -1) + { + SZ_Clear (&client->message); + client->last_message = realtime; + client->sendsignon = false; + } + } + + //protocol 15 is too limited. let people know that they'll get a crappy experience. + if (client->limit_entities <= (unsigned)sv.num_edicts) + { + Con_Warning("Protocol limitation (entities) for %s\n", NET_QSocketGetTrueAddressString(client->netconnection)); + MSG_WriteByte (&client->message, svc_print); + MSG_WriteByte (&client->message, 2); + MSG_WriteString (&client->message, "WARNING: "); + MSG_WriteByte (&client->message, svc_print); + MSG_WriteString (&client->message, "The protocol in use is too limited. You will not be able to see all entities\n"); + } + if (client->limit_models < MAX_MODELS && sv.model_precache[client->limit_models]) + { + Con_Warning("Protocol limitation (models) for %s\n", NET_QSocketGetTrueAddressString(client->netconnection)); + MSG_WriteByte (&client->message, svc_print); + MSG_WriteByte (&client->message, 2); + MSG_WriteString (&client->message, "WARNING: "); + MSG_WriteByte (&client->message, svc_print); + MSG_WriteString (&client->message, "The protocol in use is too limited. You will not be able to see all models\n"); + } + if (client->limit_sounds < MAX_SOUNDS && sv.sound_precache[client->limit_sounds]) + { + Con_Warning("Protocol limitation (sounds) for %s\n", NET_QSocketGetTrueAddressString(client->netconnection)); + MSG_WriteByte (&client->message, svc_print); + MSG_WriteByte (&client->message, 2); + MSG_WriteString (&client->message, "WARNING: "); + MSG_WriteByte (&client->message, svc_print); + MSG_WriteString (&client->message, "The protocol in use is too limited. You will not be able to hear all sounds\n"); + } +} + +void SV_Pext_f(void) +{ + //this only makes sense on the server. the clientside part only takes the form of 'cmd pext', for compat with clients that don't support this. + if (cmd_source == src_command) + { + if (!cls.state) + { + Con_Printf ("Not connected\n"); + return; + } + Con_Printf ("Current Protocols:\n"); + if (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + Con_Printf (" Replacement Entity Deltas\n"); + if (cl.protocol_pext2 & PEXT2_PREDINFO) + Con_Printf (" Replacement Stats ('predinfo')\n"); + if (cl.protocol == PROTOCOL_NETQUAKE) + Con_Printf (" vanilla(15)\n"); + else if (cl.protocol == PROTOCOL_FITZQUAKE) + Con_Printf (" fitzquake(666)\n"); + else if (cl.protocol == PROTOCOL_RMQ) + Con_Printf (" rmq(999)\n"); + else + Con_Printf (" unknown protocol(%i)\n", cl.protocol); + return; + } + + if (!host_client->pextknown && !host_client->spawned) + { + int i; + int key; + int value; + for (i = 1; i < Cmd_Argc(); i+=2) + { + key = strtoul(Cmd_Argv(i), NULL, 0); + value = strtoul(Cmd_Argv(i+1), NULL, 0); + + if (key == PROTOCOL_FTE_PEXT2) + host_client->protocol_pext2 = value & PEXT2_SUPPORTED_SERVER; + //else some other extension that we don't know + } + + host_client->pextknown = true; + SV_SendServerinfo(host_client); + } } /* @@ -357,7 +1724,10 @@ void SV_ConnectClient (int clientnum) client = svs.clients + clientnum; - Con_DPrintf ("Client %s connected\n", NET_QSocketGetAddressString(client->netconnection)); + if (client->netconnection) + Con_DPrintf ("Client %s connected\n", NET_QSocketGetTrueAddressString(client->netconnection)); + else + Con_DPrintf ("Bot connected\n"); edictnum = clientnum+1; @@ -365,6 +1735,7 @@ void SV_ConnectClient (int clientnum) // set up the client_t netconnection = client->netconnection; + net_activeconnections++; if (sv.loadgame) memcpy (spawn_parms, client->spawn_parms, sizeof(spawn_parms)); @@ -379,6 +1750,13 @@ void SV_ConnectClient (int clientnum) client->message.maxsize = sizeof(client->msgbuf); client->message.allowoverflow = true; // we can catch it + client->datagram.data = client->datagram_buf; + client->datagram.maxsize = sizeof(client->datagram_buf); + client->datagram.allowoverflow = true; //simply ignored on overflow + + client->pextknown = false; + client->protocol_pext2 = 0; + if (sv.loadgame) memcpy (client->spawn_parms, spawn_parms, sizeof(spawn_parms)); else @@ -424,8 +1802,6 @@ void SV_CheckForNewClients (void) svs.clients[i].netconnection = ret; SV_ConnectClient (i); - - net_activeconnections++; } } @@ -534,7 +1910,7 @@ qboolean SV_VisibleToClient (edict_t *client, edict_t *test, qmodel_t *worldmode { byte *pvs; vec3_t org; - int i; + unsigned int i; VectorAdd (client->v.origin, client->v.view_ofs, org); pvs = SV_FatPVS (org, worldmodel); @@ -554,14 +1930,24 @@ SV_WriteEntitiesToClient ============= */ -void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) +void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg) { - int e, i; + edict_t *clent = client->edict; + unsigned int e, i, maxedict=sv.num_edicts; int bits; byte *pvs; vec3_t org; float miss; edict_t *ent; + eval_t *val; + int maxsize = msg->maxsize; + + //try to avoid sounds getting lost. flickering entities are weird, but missing sounds+particles are just eerie. + maxsize -= client->datagram.cursize; + maxsize -= sv.datagram.cursize; + + if (maxedict > client->limit_entities) + maxedict = client->limit_entities; // find the client's PVS VectorAdd (clent->v.origin, clent->v.view_ofs, org); @@ -569,7 +1955,7 @@ void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) // send over all entities (excpet the client) that touch the pvs ent = NEXT_EDICT(sv.edicts); - for (e=1 ; e255 entities if protocol is 15 - if (sv.protocol == PROTOCOL_NETQUAKE && (int)ent->v.modelindex & 0xFF00) + if ((unsigned int)ent->v.modelindex >= client->limit_models) continue; // ignore if not touching a PV leaf @@ -599,7 +1985,7 @@ void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) //johnfitz -- max size for protocol 15 is 18 bytes, not 16 as originally //assumed here. And, for protocol 85 the max size is actually 24 bytes. - if (msg->cursize + 24 > msg->maxsize) + if (msg->cursize + 24 > maxsize) { //johnfitz -- less spammy overflow message if (!dev_overflows.packetsize || dev_overflows.packetsize + CONSOLE_RESPAM_TIME < realtime ) @@ -649,20 +2035,23 @@ void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) bits |= U_MODEL; //johnfitz -- alpha - if (pr_alpha_supported) - { - // TODO: find a cleaner place to put this code - eval_t *val; - val = GetEdictFieldValue(ent, "alpha"); - if (val) - ent->alpha = ENTALPHA_ENCODE(val->_float); - } + // TODO: find a cleaner place to put this code + val = GetEdictFieldValue(ent, pr_extfields.alpha); + if (val) + ent->alpha = ENTALPHA_ENCODE(val->_float); //don't send invisible entities unless they have effects if (ent->alpha == ENTALPHA_ZERO && !ent->v.effects) continue; //johnfitz + //spike -- PROTOCOL_VERSION_BJP3 + if (sv.protocol == PROTOCOL_VERSION_BJP3) + { + //alpha+fullbright can be sent, but they're too hideous... + if (ent->baseline.alpha != ent->alpha) bits |= U_TRANS; + } + else //johnfitz -- PROTOCOL_FITZQUAKE if (sv.protocol != PROTOCOL_NETQUAKE) { @@ -690,12 +2079,18 @@ void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) if (bits & U_MOREBITS) MSG_WriteByte (msg, bits>>8); - //johnfitz -- PROTOCOL_FITZQUAKE - if (bits & U_EXTEND1) - MSG_WriteByte(msg, bits>>16); - if (bits & U_EXTEND2) - MSG_WriteByte(msg, bits>>24); - //johnfitz + //spike -- nehahra protocols are awkward + if (sv.protocol == PROTOCOL_VERSION_BJP3) + ; + else + { + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & U_EXTEND1) + MSG_WriteByte(msg, bits>>16); + if (bits & U_EXTEND2) + MSG_WriteByte(msg, bits>>24); + //johnfitz + } if (bits & U_LONGENTITY) MSG_WriteShort (msg,e); @@ -703,7 +2098,12 @@ void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) MSG_WriteByte (msg,e); if (bits & U_MODEL) - MSG_WriteByte (msg, ent->v.modelindex); + { + if (sv.protocol == PROTOCOL_VERSION_BJP3) + MSG_WriteShort(msg, ent->v.modelindex); + else + MSG_WriteByte (msg, ent->v.modelindex); + } if (bits & U_FRAME) MSG_WriteByte (msg, ent->v.frame); if (bits & U_COLORMAP) @@ -725,16 +2125,35 @@ void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) if (bits & U_ANGLE3) MSG_WriteAngle(msg, ent->v.angles[2], sv.protocolflags); - //johnfitz -- PROTOCOL_FITZQUAKE - if (bits & U_ALPHA) - MSG_WriteByte(msg, ent->alpha); - if (bits & U_FRAME2) - MSG_WriteByte(msg, (int)ent->v.frame >> 8); - if (bits & U_MODEL2) - MSG_WriteByte(msg, (int)ent->v.modelindex >> 8); - if (bits & U_LERPFINISH) - MSG_WriteByte(msg, (byte)(Q_rint((ent->v.nextthink-sv.time)*255))); - //johnfitz + //spike -- nehahra protocols are awkward + if (sv.protocol == PROTOCOL_VERSION_BJP3) + { + //this protocol is shite + if ((int)ent->v.effects & EF_FULLBRIGHT) + { + MSG_WriteFloat(msg, 2); + MSG_WriteFloat(msg, ENTALPHA_DECODE(ent->alpha)); + MSG_WriteFloat(msg, 1); + } + else if (bits & U_TRANS) + { + MSG_WriteFloat(msg, 1); + MSG_WriteFloat(msg, ENTALPHA_DECODE(ent->alpha)); + } + } + else + { + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & U_ALPHA) + MSG_WriteByte(msg, ent->alpha); + if (bits & U_FRAME2) + MSG_WriteByte(msg, (int)ent->v.frame >> 8); + if (bits & U_MODEL2) + MSG_WriteByte(msg, (int)ent->v.modelindex >> 8); + if (bits & U_LERPFINISH) + MSG_WriteByte(msg, (byte)(Q_rint((ent->v.nextthink-sv.time)*255))); + //johnfitz + } } //johnfitz -- devstats @@ -766,17 +2185,14 @@ void SV_CleanupEnts (void) /* ================== -SV_WriteClientdataToMessage +SV_WriteDamageToMessage ================== */ -void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) +void SV_WriteDamageToMessage(edict_t *ent, sizebuf_t *msg) { - int bits; - int i; edict_t *other; - int items; - eval_t *val; + int i; // // send a damage message @@ -807,6 +2223,25 @@ void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) MSG_WriteAngle (msg, ent->v.angles[i], sv.protocolflags ); ent->v.fixangle = 0; } +} + +/* +================== +SV_WriteClientdataToMessage + +================== +*/ +void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) +{ + edict_t *ent = client->edict; + int bits; + int i; + int items; + eval_t *val; + unsigned int weaponmodelindex = SV_ModelIndex(PR_GetString(ent->v.weaponmodel)); + + if (weaponmodelindex >= client->limit_models) + weaponmodelindex = 0; bits = 0; @@ -818,7 +2253,7 @@ void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) // stuff the sigil bits into the high bits of items for sbar, or else // mix in items2 - val = GetEdictFieldValue(ent, "items2"); + val = GetEdictFieldValue(ent, pr_extfields.items2); if (val) items = (int)ent->v.items | ((int)val->_float << 23); @@ -853,7 +2288,7 @@ void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) //johnfitz -- PROTOCOL_FITZQUAKE if (sv.protocol != PROTOCOL_NETQUAKE) { - if (bits & SU_WEAPON && SV_ModelIndex(PR_GetString(ent->v.weaponmodel)) & 0xFF00) bits |= SU_WEAPON2; + if (bits & SU_WEAPON && weaponmodelindex & 0xFF00) bits |= SU_WEAPON2; if ((int)ent->v.armorvalue & 0xFF00) bits |= SU_ARMOR2; if ((int)ent->v.currentammo & 0xFF00) bits |= SU_AMMO2; if ((int)ent->v.ammo_shells & 0xFF00) bits |= SU_SHELLS2; @@ -899,7 +2334,12 @@ void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) if (bits & SU_ARMOR) MSG_WriteByte (msg, ent->v.armorvalue); if (bits & SU_WEAPON) - MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weaponmodel))); + { + if (sv.protocol == PROTOCOL_VERSION_BJP3) + MSG_WriteShort (msg, weaponmodelindex); + else + MSG_WriteByte (msg, weaponmodelindex); + } MSG_WriteShort (msg, ent->v.health); MSG_WriteByte (msg, ent->v.currentammo); @@ -926,7 +2366,7 @@ void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) //johnfitz -- PROTOCOL_FITZQUAKE if (bits & SU_WEAPON2) - MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weaponmodel)) >> 8); + MSG_WriteByte (msg, weaponmodelindex >> 8); if (bits & SU_ARMOR2) MSG_WriteByte (msg, (int)ent->v.armorvalue >> 8); if (bits & SU_AMMO2) @@ -953,34 +2393,86 @@ SV_SendClientDatagram */ qboolean SV_SendClientDatagram (client_t *client) { - byte buf[MAX_DATAGRAM]; + byte buf[MAX_DATAGRAM+1000]; sizebuf_t msg; + if (!client->netconnection) + { + //botclient, shouldn't be sent anything. + SZ_Clear(&client->datagram); + return true; + } + + msg.allowoverflow = false; msg.data = buf; - msg.maxsize = sizeof(buf); + msg.maxsize = client->limit_unreliable; msg.cursize = 0; + if (client->download.file) + msg.maxsize /= 2; //make sure there's space for download data - //johnfitz -- if client is nonlocal, use smaller max size so packets aren't fragmented - if (Q_strcmp(NET_QSocketGetAddressString(client->netconnection), "LOCAL") != 0) - msg.maxsize = DATAGRAM_MTU; - //johnfitz + host_client = client; + if (client->spawned) + { + sv_player = client->edict; - MSG_WriteByte (&msg, svc_time); - MSG_WriteFloat (&msg, sv.time); + if (client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + { + SV_WriteDamageToMessage(client->edict, &msg); + if (!(client->protocol_pext2 & PEXT2_PREDINFO)) + SV_WriteClientdataToMessage (client, &msg); + else + SVFTE_WriteStats(client, &msg); + if (!client->snapshotresume) + { + SVFTE_BuildSnapshotForClient(client); + SVFTE_CalcEntityDeltas(client); + } + SVFTE_WriteEntitiesToClient(client, &msg, sizeof(buf)); //must always write some data, or the stats will break -// add the client specific data to the datagram - SV_WriteClientdataToMessage (client->edict, &msg); + //this delta protocol doesn't wipe old state just because there's a new packet. + //the server isn't required to sync with the client frames either + //so we can just spam multiple packets to keep our udp data under the MTU + while (client->snapshotresume) + { + NET_SendUnreliableMessage (client->netconnection, &msg); + SZ_Clear(&msg); + SVFTE_WriteEntitiesToClient(client, &msg, sizeof(buf)); + } + } + else + { + MSG_WriteByte (&msg, svc_time); + MSG_WriteFloat (&msg, sv.time); + if (client->protocol_pext2 & PEXT2_PREDINFO) + MSG_WriteShort(&msg, (client->lastmovemessage&0xffff)); - SV_WriteEntitiesToClient (client->edict, &msg); + // add the client specific data to the datagram + SV_WriteDamageToMessage (client->edict, &msg); + SV_WriteClientdataToMessage (client, &msg); + + SV_WriteEntitiesToClient (client, &msg); + } + + // copy the private datagram if there is space + if (msg.cursize + client->datagram.cursize < msg.maxsize && !client->datagram.overflowed) + SZ_Write(&msg, client->datagram.data, client->datagram.cursize); + client->datagram.overflowed = false; + SZ_Clear(&client->datagram); + // copy the server datagram if there is space + if (msg.cursize + sv.datagram.cursize < msg.maxsize) + SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize); + } + + SV_VoiceSendPacket(client, &msg); + + msg.maxsize = client->limit_unreliable; + Host_AppendDownloadData(client, &msg); -// copy the server datagram if there is space - if (msg.cursize + sv.datagram.cursize < msg.maxsize) - SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize); // send the datagram - if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) + if (msg.cursize && NET_SendUnreliableMessage (client->netconnection, &msg) == -1) { - SV_DropClient (true);// if the message couldn't send, kick off + SV_DropClient (false);// if the message couldn't send, kick off return false; } @@ -1004,7 +2496,7 @@ void SV_UpdateToReliableMessages (void) { for (j=0, client = svs.clients ; jactive) + if (!client->knowntoqc) continue; MSG_WriteByte (&client->message, svc_updatefrags); MSG_WriteByte (&client->message, i); @@ -1046,10 +2538,109 @@ void SV_SendNop (client_t *client) MSG_WriteChar (&msg, svc_nop); if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) - SV_DropClient (true); // if the message couldn't send, kick off + SV_DropClient (false); // if the message couldn't send, kick off client->last_message = realtime; } +int SV_SendPrespawnParticlePrecaches(int idx) +{ + size_t maxsize = host_client->message.maxsize; //we can go quite large + if (!host_client->protocol_pext2) + return -1; //unsupported by this client. + for (; idx < MAX_PARTICLETYPES; idx++) + { + if (!sv.particle_precache[idx]) + continue; + if (host_client->message.cursize + 4+strlen(sv.particle_precache[idx]) > maxsize) + break; + MSG_WriteByte(&host_client->message, svcdp_precache); + MSG_WriteShort(&host_client->message, 0x4000 | idx); + MSG_WriteString(&host_client->message, sv.particle_precache[idx]); + } + if (idx == MAX_PARTICLETYPES) + return -1; + return idx; +} +int SV_SendPrespawnStatics(int idx) +{ + entity_state_t *svent; + int maxsize = host_client->message.maxsize - 128; //we can go quite large + + while (1) + { + if (idx >= sv.num_statics) + return -1; + svent = &sv.static_entities[idx]; + + if (host_client->message.cursize > maxsize) + break; + idx++; + + if (svent->modelindex >= host_client->limit_models) + continue; + if (memcmp(&nullentitystate, svent, sizeof(nullentitystate))) + MSG_WriteStaticOrBaseLine(&host_client->message, -1, svent, host_client->protocol_pext2, sv.protocol, sv.protocolflags); + } + return idx; +} +int SV_SendAmbientSounds(int idx) +{ + struct ambientsound_s *snd; + int maxsize = host_client->message.maxsize - 128; //we can go quite large + qboolean large; + size_t i; + + while (1) + { + if (idx >= sv.num_ambients) + return -1; + snd = &sv.ambientsounds[idx]; + + if (host_client->message.cursize > maxsize) + break; + idx++; + + if (snd->soundindex >= host_client->limit_sounds) + continue; + + large = (snd->soundindex > 255); + if (large) + MSG_WriteByte (&host_client->message,svc_spawnstaticsound2); //johnfitz -- PROTOCOL_FITZQUAKE + else + MSG_WriteByte (&host_client->message,svc_spawnstaticsound); + for (i = 0; i < 3; i++) + MSG_WriteCoord(&host_client->message, snd->origin[i], sv.protocolflags); + if (large) + MSG_WriteShort(&host_client->message, snd->soundindex); + else + MSG_WriteByte (&host_client->message, snd->soundindex); + MSG_WriteByte (&host_client->message, snd->volume*255); + MSG_WriteByte (&host_client->message, snd->attenuation*64); + } + return idx; +} +int SV_SendPrespawnBaselines(int idx) +{ + edict_t *svent; + int maxsize = host_client->message.maxsize - 128; //we can go quite large + + while (1) + { + if (idx >= sv.num_edicts) + return -1; + svent = EDICT_NUM(idx); + + if (host_client->message.cursize > maxsize) + break; + + if (memcmp(&nullentitystate, &svent->baseline, sizeof(nullentitystate))) + MSG_WriteStaticOrBaseLine(&host_client->message, idx, &svent->baseline, host_client->protocol_pext2, sv.protocol, sv.protocolflags); + + idx++; + } + return idx; +} + /* ======================= SV_SendClientMessages @@ -1068,12 +2659,9 @@ void SV_SendClientMessages (void) if (!host_client->active) continue; - if (host_client->spawned) - { - if (!SV_SendClientDatagram (host_client)) - continue; - } - else + if (!SV_SendClientDatagram (host_client)) + continue; + if (!host_client->spawned) { // the player isn't totally in the game yet // send small keepalive messages if too much time has passed @@ -1086,6 +2674,52 @@ void SV_SendClientMessages (void) SV_SendNop (host_client); continue; // don't send out non-signon messages } + if (host_client->sendsignon == 2) + { + host_client->signonidx = SV_SendPrespawnParticlePrecaches(host_client->signonidx); + if (host_client->signonidx < 0) + { + host_client->signonidx = 0; + host_client->sendsignon++; + } + } + if (host_client->sendsignon == 3) + { + host_client->signonidx = SV_SendPrespawnBaselines(host_client->signonidx); + if (host_client->signonidx < 0) + { + host_client->signonidx = 0; + host_client->sendsignon++; + } + } + if (host_client->sendsignon == 4) + { + host_client->signonidx = SV_SendPrespawnStatics(host_client->signonidx); + if (host_client->signonidx < 0) + { + host_client->signonidx = 0; + host_client->sendsignon++; + } + } + if (host_client->sendsignon == 5) + { + host_client->signonidx = SV_SendAmbientSounds(host_client->signonidx); + if (host_client->signonidx < 0) + { + host_client->signonidx = 0; + host_client->sendsignon++; + } + } + if (host_client->sendsignon == 6) + { + if (host_client->message.cursize+sv.signon.cursize+2 < host_client->message.maxsize) + { + SZ_Write (&host_client->message, sv.signon.data, sv.signon.cursize); + MSG_WriteByte (&host_client->message, svc_signonnum); + MSG_WriteByte (&host_client->message, 2); + host_client->sendsignon = true; + } + } } // check for an overflowed message. Should only happen @@ -1093,8 +2727,8 @@ void SV_SendClientMessages (void) // changes level if (host_client->message.overflowed) { - SV_DropClient (true); - host_client->message.overflowed = false; + SZ_Clear(&host_client->message); + SV_DropClient (false); continue; } @@ -1112,10 +2746,11 @@ void SV_SendClientMessages (void) { if (NET_SendMessage (host_client->netconnection , &host_client->message) == -1) - SV_DropClient (true); // if the message couldn't send, kick off + SV_DropClient (false); // if the message couldn't send, kick off SZ_Clear (&host_client->message); host_client->last_message = realtime; - host_client->sendsignon = false; + if (host_client->sendsignon == true) + host_client->sendsignon = false; } } } @@ -1162,12 +2797,11 @@ SV_CreateBaseline */ void SV_CreateBaseline (void) { - int i; edict_t *svent; int entnum; - int bits; //johnfitz -- PROTOCOL_FITZQUAKE + eval_t *val; - for (entnum = 0; entnum < sv.num_edicts ; entnum++) + for (entnum = 0; entnum < sv.num_edicts; entnum++) { // get the current server version svent = EDICT_NUM(entnum); @@ -1193,69 +2827,18 @@ void SV_CreateBaseline (void) { svent->baseline.colormap = 0; svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svent->v.model)); - svent->baseline.alpha = svent->alpha; //johnfitz -- alpha support + val = GetEdictFieldValue(svent, pr_extfields.alpha); + if (val) + svent->baseline.alpha = ENTALPHA_ENCODE(val->_float); + else + svent->baseline.alpha = svent->alpha; //johnfitz -- alpha support } - //johnfitz -- PROTOCOL_FITZQUAKE - bits = 0; - if (sv.protocol == PROTOCOL_NETQUAKE) //still want to send baseline in PROTOCOL_NETQUAKE, so reset these values - { - if (svent->baseline.modelindex & 0xFF00) - svent->baseline.modelindex = 0; - if (svent->baseline.frame & 0xFF00) - svent->baseline.frame = 0; - svent->baseline.alpha = ENTALPHA_DEFAULT; - } - else //decide which extra data needs to be sent - { - if (svent->baseline.modelindex & 0xFF00) - bits |= B_LARGEMODEL; - if (svent->baseline.frame & 0xFF00) - bits |= B_LARGEFRAME; - if (svent->baseline.alpha != ENTALPHA_DEFAULT) - bits |= B_ALPHA; - } - //johnfitz - - // - // add to the message - // - //johnfitz -- PROTOCOL_FITZQUAKE - if (bits) - MSG_WriteByte (&sv.signon, svc_spawnbaseline2); - else - MSG_WriteByte (&sv.signon, svc_spawnbaseline); - //johnfitz - - MSG_WriteShort (&sv.signon,entnum); - - //johnfitz -- PROTOCOL_FITZQUAKE - if (bits) - MSG_WriteByte (&sv.signon, bits); - - if (bits & B_LARGEMODEL) - MSG_WriteShort (&sv.signon, svent->baseline.modelindex); - else - MSG_WriteByte (&sv.signon, svent->baseline.modelindex); - - if (bits & B_LARGEFRAME) - MSG_WriteShort (&sv.signon, svent->baseline.frame); - else - MSG_WriteByte (&sv.signon, svent->baseline.frame); - //johnfitz - - MSG_WriteByte (&sv.signon, svent->baseline.colormap); - MSG_WriteByte (&sv.signon, svent->baseline.skin); - for (i=0 ; i<3 ; i++) - { - MSG_WriteCoord(&sv.signon, svent->baseline.origin[i], sv.protocolflags); - MSG_WriteAngle(&sv.signon, svent->baseline.angles[i], sv.protocolflags); - } - - //johnfitz -- PROTOCOL_FITZQUAKE - if (bits & B_ALPHA) - MSG_WriteByte (&sv.signon, svent->baseline.alpha); - //johnfitz + //Spike -- baselines are now generated on a per-client basis. + //FIXME: should merge the above with other edict->entity_state copies (updates, baselines, spawnstatics) + //1) this allows per-client extensions. + //2) this avoids pre-generating a single signon buffer, splitting it over multiple packets. + // thereby allowing more than 3k or so entities } } @@ -1281,7 +2864,7 @@ void SV_SendReconnect (void) NET_SendToAll (&msg, 5.0); if (!isDedicated) - Cmd_ExecuteString ("reconnect\n", src_command); + Cmd_ExecuteString ("reconnect\n", src_server); } @@ -1386,6 +2969,10 @@ void SV_SpawnServer (const char *server) sv.datagram.cursize = 0; sv.datagram.data = sv.datagram_buf; + sv.multicast.maxsize = sizeof(sv.multicast_buf); + sv.multicast.cursize = 0; + sv.multicast.data = sv.multicast_buf; + sv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf); sv.reliable_datagram.cursize = 0; sv.reliable_datagram.data = sv.reliable_datagram_buf; @@ -1411,7 +2998,7 @@ void SV_SpawnServer (const char *server) q_strlcpy (sv.name, server, sizeof(sv.name)); q_snprintf (sv.modelname, sizeof(sv.modelname), "maps/%s.bsp", server); sv.worldmodel = Mod_ForName (sv.modelname, false); - if (!sv.worldmodel) + if (!sv.worldmodel || sv.worldmodel->type != mod_brush) { Con_Printf ("Couldn't spawn server %s\n", sv.modelname); sv.active = false; @@ -1427,6 +3014,12 @@ void SV_SpawnServer (const char *server) sv.sound_precache[0] = dummy; sv.model_precache[0] = dummy; sv.model_precache[1] = sv.modelname; + if (sv.worldmodel->numsubmodels > MAX_MODELS) + { + Con_Printf ("too many inline models %s\n", sv.modelname); + sv.active = false; + return; + } for (i=1 ; inumsubmodels ; i++) { sv.model_precache[1+i] = localmodels[i]; diff --git a/Quake/sv_phys.c b/Quake/sv_phys.c index eb5019a4..8c4fc5e6 100644 --- a/Quake/sv_phys.c +++ b/Quake/sv_phys.c @@ -47,6 +47,7 @@ cvar_t sv_gravity = {"sv_gravity","800",CVAR_NOTIFY|CVAR_SERVERINFO}; cvar_t sv_maxvelocity = {"sv_maxvelocity","2000",CVAR_NONE}; cvar_t sv_nostep = {"sv_nostep","0",CVAR_NONE}; cvar_t sv_freezenonclients = {"sv_freezenonclients","0",CVAR_NONE}; +cvar_t sv_gameplayfix_spawnbeforethinks = {"sv_gameplayfix_spawnbeforethinks","0",CVAR_NONE}; #define MOVE_EPSILON 0.01 @@ -388,7 +389,7 @@ void SV_AddGravity (edict_t *ent) float ent_gravity; eval_t *val; - val = GetEdictFieldValue(ent, "gravity"); + val = GetEdictFieldValue(ent, pr_extfields.gravity); if (val && val->_float) ent_gravity = val->_float; else @@ -453,6 +454,7 @@ void SV_PushMove (edict_t *pusher, float movetime) edict_t **moved_edict; //johnfitz -- dynamically allocate vec3_t *moved_from; //johnfitz -- dynamically allocate int mark; //johnfitz + float solid_backup; if (!pusher->v.velocity[0] && !pusher->v.velocity[1] && !pusher->v.velocity[2]) { @@ -519,13 +521,23 @@ void SV_PushMove (edict_t *pusher, float movetime) moved_edict[num_moved] = check; num_moved++; - // try moving the contacted entity - pusher->v.solid = SOLID_NOT; - SV_PushEntity (check, move); - pusher->v.solid = SOLID_BSP; + //QIP fix for end.bsp + solid_backup = pusher->v.solid; + if ( solid_backup == SOLID_BSP // everything that blocks: bsp models = map brushes = doors, plats, etc. + || solid_backup == SOLID_BBOX // normally boxes + || solid_backup == SOLID_SLIDEBOX ) // normally monsters + { + // try moving the contacted entity + pusher->v.solid = SOLID_NOT; + SV_PushEntity (check, move); + pusher->v.solid = solid_backup; + + // if it is still inside the pusher, block + block = SV_TestEntityPosition (check); + } + else + block = NULL; - // if it is still inside the pusher, block - block = SV_TestEntityPosition (check); if (block) { // fail the move if (check->v.mins[0] == check->v.maxs[0]) @@ -907,6 +919,9 @@ void SV_Physics_Client (edict_t *ent, int num) if ( ! svs.clients[num-1].active ) return; // unconnected slot + if (!svs.clients[num-1].knowntoqc && sv_gameplayfix_spawnbeforethinks.value) + return; //don't spam prethinks before we called putclientinserver. + // // call standard client pre-think // @@ -956,7 +971,7 @@ void SV_Physics_Client (edict_t *ent, int num) break; default: - Sys_Error ("SV_Physics_client: bad movetype %i", (int)ent->v.movetype); + Host_EndGame ("SV_Physics_client: bad movetype %i", (int)ent->v.movetype); } // @@ -1034,7 +1049,7 @@ void SV_CheckWaterTransition (edict_t *ent) { if (ent->v.watertype == CONTENTS_EMPTY) { // just crossed into water - SV_StartSound (ent, 0, "misc/h2ohit1.wav", 255, 1); + SV_StartSound (ent, NULL, 0, "misc/h2ohit1.wav", 255, 1); } ent->v.watertype = cont; ent->v.waterlevel = 1; @@ -1043,7 +1058,7 @@ void SV_CheckWaterTransition (edict_t *ent) { if (ent->v.watertype != CONTENTS_EMPTY) { // just crossed into water - SV_StartSound (ent, 0, "misc/h2ohit1.wav", 255, 1); + SV_StartSound (ent, NULL, 0, "misc/h2ohit1.wav", 255, 1); } ent->v.watertype = CONTENTS_EMPTY; ent->v.waterlevel = cont; @@ -1112,6 +1127,55 @@ void SV_Physics_Toss (edict_t *ent) SV_CheckWaterTransition (ent); } + + + + + +/* +============= +SV_Physics_Follow + +Entities that are "stuck" to another entity +============= +*/ +static void SV_Physics_Follow (edict_t *ent) +{ + vec3_t vf, vr, vu, angles, v; + edict_t *e; + + // regular thinking + if (!SV_RunThink (ent)) + return; + + // LordHavoc: implemented rotation on MOVETYPE_FOLLOW objects + e = PROG_TO_EDICT(ent->v.aiment); + if (e->v.angles[0] == ent->v.punchangle[0] && e->v.angles[1] == ent->v.punchangle[1] && e->v.angles[2] == ent->v.punchangle[2]) + { + // quick case for no rotation + VectorAdd(e->v.origin, ent->v.view_ofs, ent->v.origin); + } + else + { + angles[0] = -ent->v.punchangle[0]; + angles[1] = ent->v.punchangle[1]; + angles[2] = ent->v.punchangle[2]; + AngleVectors (angles, vf, vr, vu); + v[0] = ent->v.view_ofs[0] * vf[0] + ent->v.view_ofs[1] * vr[0] + ent->v.view_ofs[2] * vu[0]; + v[1] = ent->v.view_ofs[0] * vf[1] + ent->v.view_ofs[1] * vr[1] + ent->v.view_ofs[2] * vu[1]; + v[2] = ent->v.view_ofs[0] * vf[2] + ent->v.view_ofs[1] * vr[2] + ent->v.view_ofs[2] * vu[2]; + angles[0] = -e->v.angles[0]; + angles[1] = e->v.angles[1]; + angles[2] = e->v.angles[2]; + AngleVectors (angles, vf, vr, vu); + ent->v.origin[0] = v[0] * vf[0] + v[1] * vf[1] + v[2] * vf[2] + e->v.origin[0]; + ent->v.origin[1] = v[0] * vr[0] + v[1] * vr[1] + v[2] * vr[2] + e->v.origin[1]; + ent->v.origin[2] = v[0] * vu[0] + v[1] * vu[1] + v[2] * vu[2] + e->v.origin[2]; + } + VectorAdd (e->v.angles, ent->v.v_angle, ent->v.angles); + SV_LinkEdict (ent, true); +} + /* =============================================================================== @@ -1151,7 +1215,7 @@ void SV_Physics_Step (edict_t *ent) if ( (int)ent->v.flags & FL_ONGROUND ) // just hit ground { if (hitsound) - SV_StartSound (ent, 0, "demon/dland2.wav", 255, 1); + SV_StartSound (ent, NULL, 0, "demon/dland2.wav", 255, 1); } } @@ -1190,9 +1254,9 @@ void SV_Physics (void) ent = sv.edicts; if (sv_freezenonclients.value) - entity_cap = svs.maxclients + 1; // Only run physics on clients and the world + entity_cap = svs.maxclients + 1; // Only run physics on clients and the world else - entity_cap = sv.num_edicts; + entity_cap = sv.num_edicts; //for (i=0 ; iv.movetype == MOVETYPE_FLY || ent->v.movetype == MOVETYPE_FLYMISSILE) SV_Physics_Toss (ent); + else if (ent->v.movetype == MOVETYPE_EXT_FOLLOW) + SV_Physics_Follow (ent); + else if (ent->v.movetype == MOVETYPE_WALK) + { + if (SV_RunThink (ent)) + { + if (!SV_CheckWater (ent) && ! ((int)ent->v.flags & FL_WATERJUMP) ) + SV_AddGravity (ent); + SV_CheckStuck (ent); + SV_WalkMove (ent); + } + } else - Sys_Error ("SV_Physics: bad movetype %i", (int)ent->v.movetype); + Host_EndGame ("SV_Physics: bad movetype %i", (int)ent->v.movetype); } if (pr_global_struct->force_retouch) pr_global_struct->force_retouch--; + + if (pr_extfuncs.endframe) + { + pr_global_struct->self = EDICT_TO_PROG(sv.edicts); + pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + pr_global_struct->time = sv.time; + PR_ExecuteProgram (pr_extfuncs.endframe); + } + if (!sv_freezenonclients.value) sv.time += host_frametime; } diff --git a/Quake/sv_user.c b/Quake/sv_user.c index c3e4cb56..3bd8fc63 100644 --- a/Quake/sv_user.c +++ b/Quake/sv_user.c @@ -439,37 +439,92 @@ void SV_ReadClientMove (usercmd_t *move) { int i; vec3_t angle; - int bits; + int buttonbits; + int newimpulse; + eval_t *eval; + qboolean drop = false; + float timestamp; + vec3_t movevalues; + int sequence; + eval_t *val; -// read ping time - host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] - = sv.time - MSG_ReadFloat (); - host_client->num_pings++; + if (host_client->protocol_pext2 & PEXT2_PREDINFO) + { + i = (unsigned short)MSG_ReadShort(); + sequence = (host_client->lastmovemessage & 0xffff0000) | (i&0xffff); -// read current angles + //tollerance of a few old frames, so we can have redundancy for packetloss + if (sequence+0x100 < host_client->lastmovemessage) + sequence += 0x10000; + + if (sequence <= host_client->lastmovemessage) + drop = true; + } + else + sequence = 0; + + //read the data + timestamp = MSG_ReadFloat(); for (i=0 ; i<3 ; i++) - //johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE - if (sv.protocol == PROTOCOL_NETQUAKE) + { + if (sv.protocol == PROTOCOL_NETQUAKE && !(host_client->protocol_pext2 & PEXT2_PREDINFO) && !NET_QSocketGetProQuakeAngleHack(host_client->netconnection)) angle[i] = MSG_ReadAngle (sv.protocolflags); else - angle[i] = MSG_ReadAngle16 (sv.protocolflags); - //johnfitz + angle[i] = MSG_ReadAngle16 (sv.protocolflags); //johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE + } + movevalues[0] = MSG_ReadShort (); + movevalues[1] = MSG_ReadShort (); + movevalues[2] = MSG_ReadShort (); + buttonbits = MSG_ReadByte(); + newimpulse = MSG_ReadByte(); + if (drop) + return; //okay, we don't care about that then + +// calc ping times + host_client->lastmovemessage = sequence; + if (!(host_client->protocol_pext2 & PEXT2_PREDINFO)) + { + host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] + = sv.time - timestamp; + host_client->num_pings++; + } //otherwise time is still useful for determining the input frame's time value + + // read movement VectorCopy (angle, host_client->edict->v.v_angle); - -// read movement - move->forwardmove = MSG_ReadShort (); - move->sidemove = MSG_ReadShort (); - move->upmove = MSG_ReadShort (); + move->forwardmove = movevalues[0]; + move->sidemove = movevalues[1]; + move->upmove = movevalues[2]; // read buttons - bits = MSG_ReadByte (); - host_client->edict->v.button0 = bits & 1; - host_client->edict->v.button2 = (bits & 2)>>1; + host_client->edict->v.button0 = (buttonbits & 1)>>0; + //button1 was meant to be 'use', but got reused by too many mods to get implemented now + host_client->edict->v.button2 = (buttonbits & 2)>>1; + if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button3))) + val->_float = (buttonbits & 4)>>2; + if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button4))) + val->_float = (buttonbits & 8)>>3; + if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button5))) + val->_float = (buttonbits & 0x10)>>4; + if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button6))) + val->_float = (buttonbits & 0x20)>>5; + if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button7))) + val->_float = (buttonbits & 0x40)>>6; + if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button8))) + val->_float = (buttonbits & 0x80)>>7; - i = MSG_ReadByte (); - if (i) - host_client->edict->v.impulse = i; + if (newimpulse) + host_client->edict->v.impulse = newimpulse; + + eval = GetEdictFieldValue(host_client->edict, pr_extfields.movement); + if (eval) + { + eval->vector[0] = move->forwardmove; + eval->vector[1] = move->sidemove; + eval->vector[2] = move->upmove; + } + + //FIXME: attempt to apply physics command now, if the mod has custom physics+csqc-prediction } /* @@ -481,110 +536,76 @@ Returns false if the client should be killed */ qboolean SV_ReadClientMessage (void) { - int ret; int ccmd; const char *s; - do + MSG_BeginReading (); + + while (1) { -nextmsg: - ret = NET_GetMessage (host_client->netconnection); - if (ret == -1) + if (!host_client->active) + return false; // a command caused an error + + if (msg_badread) { - Sys_Printf ("SV_ReadClientMessage: NET_GetMessage failed\n"); + Sys_Printf ("SV_ReadClientMessage: badread\n"); return false; } - if (!ret) - return true; - MSG_BeginReading (); + ccmd = MSG_ReadChar (); - while (1) + switch (ccmd) { - if (!host_client->active) - return false; // a command caused an error + case -1: + return true; //msg_badread, meaning we just hit eof. - if (msg_badread) - { - Sys_Printf ("SV_ReadClientMessage: badread\n"); - return false; + default: + Sys_Printf ("SV_ReadClientMessage: unknown command char\n"); + return false; + + case clc_nop: +// Sys_Printf ("clc_nop\n"); + break; + + case clc_stringcmd: + s = MSG_ReadString (); + if (q_strncasecmp(s, "spawn", 5) && q_strncasecmp(s, "begin", 5) && q_strncasecmp(s, "prespawn", 8) && pr_extfuncs.parseclientcommand) + { //the spawn/begin/prespawn are because of numerous mods that disobey the rules. + //at a minimum, we must be able to join the server, so that we can see any sprints/bprints (because dprint sucks, yes there's proper ways to deal with this, but moders don't always know them). + client_t *ohc = host_client; + G_INT(OFS_PARM0) = PR_SetEngineString(s); + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(host_client->edict); + PR_ExecuteProgram(pr_extfuncs.parseclientcommand); + host_client = ohc; } + else + Cmd_ExecuteString (s, src_client); + break; - ccmd = MSG_ReadChar (); + case clc_disconnect: +// Sys_Printf ("SV_ReadClientMessage: client disconnected\n"); + return false; - switch (ccmd) - { - case -1: - goto nextmsg; // end of message + case clc_move: + if (!host_client->spawned) + return true; //this is to suck up any stale moves on map changes, so we don't get confused (quite so easily) when protocols are changed between maps + SV_ReadClientMove (&host_client->cmd); + break; - default: - Sys_Printf ("SV_ReadClientMessage: unknown command char\n"); - return false; + case clcdp_ackframe: + SVFTE_Ack(host_client, MSG_ReadLong()); + break; - case clc_nop: -// Sys_Printf ("clc_nop\n"); - break; + case clcdp_ackdownloaddata: + Host_DownloadAck(host_client); + break; - case clc_stringcmd: - s = MSG_ReadString (); - ret = 0; - if (q_strncasecmp(s, "status", 6) == 0) - ret = 1; - else if (q_strncasecmp(s, "god", 3) == 0) - ret = 1; - else if (q_strncasecmp(s, "notarget", 8) == 0) - ret = 1; - else if (q_strncasecmp(s, "fly", 3) == 0) - ret = 1; - else if (q_strncasecmp(s, "name", 4) == 0) - ret = 1; - else if (q_strncasecmp(s, "noclip", 6) == 0) - ret = 1; - else if (q_strncasecmp(s, "setpos", 6) == 0) - ret = 1; - else if (q_strncasecmp(s, "say", 3) == 0) - ret = 1; - else if (q_strncasecmp(s, "say_team", 8) == 0) - ret = 1; - else if (q_strncasecmp(s, "tell", 4) == 0) - ret = 1; - else if (q_strncasecmp(s, "color", 5) == 0) - ret = 1; - else if (q_strncasecmp(s, "kill", 4) == 0) - ret = 1; - else if (q_strncasecmp(s, "pause", 5) == 0) - ret = 1; - else if (q_strncasecmp(s, "spawn", 5) == 0) - ret = 1; - else if (q_strncasecmp(s, "begin", 5) == 0) - ret = 1; - else if (q_strncasecmp(s, "prespawn", 8) == 0) - ret = 1; - else if (q_strncasecmp(s, "kick", 4) == 0) - ret = 1; - else if (q_strncasecmp(s, "ping", 4) == 0) - ret = 1; - else if (q_strncasecmp(s, "give", 4) == 0) - ret = 1; - else if (q_strncasecmp(s, "ban", 3) == 0) - ret = 1; - - if (ret == 1) - Cmd_ExecuteString (s, src_client); - else - Con_DPrintf("%s tried to %s\n", host_client->name, s); - break; - - case clc_disconnect: -// Sys_Printf ("SV_ReadClientMessage: client disconnected\n"); - return false; - - case clc_move: - SV_ReadClientMove (&host_client->cmd); - break; - } + case clcfte_voicechat: + SV_VoiceReadPacket(host_client); + break; } - } while (ret == 1); + } return true; } @@ -599,6 +620,31 @@ void SV_RunClients (void) { int i; + //receive from clients first + //Spike -- reworked this to query the network code for an active connection. + //this allows the network code to serve multiple clients with the same listening port. + //this solves server-side nats, which is important for coop etc. + while(1) + { + struct qsocket_s *sock = NET_GetServerMessage(); + if (!sock) + break; //no more this frame + + for (i=0, host_client = svs.clients ; inetconnection == sock) + { + sv_player = host_client->edict; + if (!SV_ReadClientMessage ()) + { + SV_DropClient (false); // client misbehaved... + break; + } + } + } + } + + //then do the per-frame stuff for (i=0, host_client = svs.clients ; iactive) @@ -606,12 +652,6 @@ void SV_RunClients (void) sv_player = host_client->edict; - if (!SV_ReadClientMessage ()) - { - SV_DropClient (false); // client misbehaved... - continue; - } - if (!host_client->spawned) { // clear client movement until a new packet is received @@ -619,6 +659,23 @@ void SV_RunClients (void) continue; } + if (!host_client->netconnection) + { + //botclients can't receive packets. don't even try. + //not sure where to put this code, but here seems sane enough. + //fill in the user's desired stuff according to a few things. + eval_t *ev = GetEdictFieldValue(host_client->edict, pr_extfields.movement); + if (ev) //.movement normally works the other way around. oh well. + { + host_client->cmd.forwardmove = ev->vector[0]; + host_client->cmd.sidemove = ev->vector[1]; + host_client->cmd.upmove = ev->vector[2]; + } + host_client->cmd.viewangles[0] = host_client->edict->v.v_angle[0]; + host_client->cmd.viewangles[1] = host_client->edict->v.v_angle[1]; + host_client->cmd.viewangles[2] = host_client->edict->v.v_angle[2]; + } + // always pause in single player if in console or menus if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) ) SV_ClientThink (); diff --git a/Quake/sys.h b/Quake/sys.h index dd023638..fa4f6d56 100644 --- a/Quake/sys.h +++ b/Quake/sys.h @@ -33,8 +33,9 @@ void Sys_Init (void); // returns the file size or -1 if file is not present. // the file should be in BINARY mode for stupid OSs that care int Sys_FileOpenRead (const char *path, int *hndl); - int Sys_FileOpenWrite (const char *path); +int Sys_FileOpenStdio (FILE *file); + void Sys_FileClose (int handle); void Sys_FileSeek (int handle, int position); int Sys_FileRead (int handle, void *dest, int count); diff --git a/Quake/sys_sdl_unix.c b/Quake/sys_sdl_unix.c index d2036778..100912ad 100644 --- a/Quake/sys_sdl_unix.c +++ b/Quake/sys_sdl_unix.c @@ -119,6 +119,14 @@ int Sys_FileOpenWrite (const char *path) return i; } +int Sys_FileOpenStdio (FILE *file) +{ + int i; + i = findhandle (); + sys_handles[i] = file; + return i; +} + void Sys_FileClose (int handle) { fclose (sys_handles[handle]); @@ -382,6 +390,7 @@ void Sys_Error (const char *error, ...) va_end (argptr); fputs (errortxt1, stderr); + Con_Redirect(NULL); Host_Shutdown (); fputs (errortxt2, stderr); fputs (text, stderr); diff --git a/Quake/sys_sdl_win.c b/Quake/sys_sdl_win.c index 039c0e38..a0c4882a 100644 --- a/Quake/sys_sdl_win.c +++ b/Quake/sys_sdl_win.c @@ -118,6 +118,14 @@ int Sys_FileOpenWrite (const char *path) return i; } +int Sys_FileOpenStdio (FILE *file) +{ + int i; + i = findhandle (); + sys_handles[i] = file; + return i; +} + void Sys_FileClose (int handle) { fclose (sys_handles[handle]); @@ -304,6 +312,8 @@ void Sys_Error (const char *error, ...) q_vsnprintf (text, sizeof(text), error, argptr); va_end (argptr); + Con_Redirect(NULL); + if (isDedicated) WriteFile (houtput, errortxt1, strlen(errortxt1), &dummy, NULL); /* SDL will put these into its own stderr log, @@ -338,7 +348,14 @@ void Sys_Printf (const char *fmt, ...) if (isDedicated) { - WriteFile(houtput, text, strlen(text), &dummy, NULL); + if (*text == 1 || *text == 2) + { //mostly for Con_[D]Warning + SetConsoleTextAttribute(houtput, FOREGROUND_RED); + WriteFile(houtput, text+1, strlen(text+1), &dummy, NULL); + SetConsoleTextAttribute(houtput, FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED); + } + else + WriteFile(houtput, text, strlen(text), &dummy, NULL); } else { diff --git a/Quake/view.c b/Quake/view.c index 27ed0699..9d70ad90 100644 --- a/Quake/view.c +++ b/Quake/view.c @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" +extern qboolean premul_hud; /* The view is allowed to move slightly from it's true position for bobbing, @@ -208,7 +209,7 @@ void V_DriftPitch (void) return; } - delta = cl.idealpitch - cl.viewangles[PITCH]; + delta = cl.statsf[STAT_IDEALPITCH] - cl.viewangles[PITCH]; if (!delta) { @@ -312,7 +313,7 @@ void V_ParseDamage (void) // // calculate view angle kicks // - ent = &cl_entities[cl.viewentity]; + ent = &cl.entities[cl.viewentity]; VectorSubtract (from, ent->origin, from); VectorNormalize (from); @@ -352,10 +353,20 @@ When you run over an item, the server sends this command */ void V_BonusFlash_f (void) { - 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; + if (Cmd_Argc() >= 5) + { + 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; + } + 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; + } } /* @@ -533,16 +544,19 @@ void V_PolyBlend (void) GL_DisableMultitexture(); - glDisable (GL_ALPHA_TEST); glDisable (GL_TEXTURE_2D); glDisable (GL_DEPTH_TEST); glEnable (GL_BLEND); + if (premul_hud) + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + else + glDisable (GL_ALPHA_TEST); glMatrixMode(GL_PROJECTION); - glLoadIdentity (); + glLoadIdentity (); glOrtho (0, 1, 1, 0, -99999, 99999); glMatrixMode(GL_MODELVIEW); - glLoadIdentity (); + glLoadIdentity (); glColor4fv (v_blend); @@ -553,10 +567,15 @@ void V_PolyBlend (void) glVertex2f (0, 1); glEnd (); - glDisable (GL_BLEND); glEnable (GL_DEPTH_TEST); glEnable (GL_TEXTURE_2D); - glEnable (GL_ALPHA_TEST); + if (premul_hud) + glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + else + { + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); + } } /* @@ -642,7 +661,7 @@ void V_BoundOffsets (void) { entity_t *ent; - ent = &cl_entities[cl.viewentity]; + ent = &cl.entities[cl.viewentity]; // absolutely bound refresh reletive to entity clipping hull // so the view can never be inside a solid wall @@ -687,7 +706,7 @@ void V_CalcViewRoll (void) { float side; - side = V_CalcRoll (cl_entities[cl.viewentity].angles, cl.velocity); + side = V_CalcRoll (cl.entities[cl.viewentity].angles, cl.velocity); r_refdef.viewangles[ROLL] += side; if (v_dmg_time > 0) @@ -716,7 +735,7 @@ void V_CalcIntermissionRefdef (void) float old; // ent is the player model (visible when out of body) - ent = &cl_entities[cl.viewentity]; + ent = &cl.entities[cl.viewentity]; // view is the weapon model (only visible from inside body) view = &cl.viewent; @@ -750,7 +769,7 @@ void V_CalcRefdef (void) V_DriftPitch (); // ent is the player model (visible when out of body) - ent = &cl_entities[cl.viewentity]; + ent = &cl.entities[cl.viewentity]; // view is the weapon model (only visible from inside body) view = &cl.viewent; @@ -764,7 +783,7 @@ void V_CalcRefdef (void) // refresh position VectorCopy (ent->origin, r_refdef.vieworg); - r_refdef.vieworg[2] += cl.viewheight + bob; + r_refdef.vieworg[2] += cl.stats[STAT_VIEWHEIGHT] + bob; // never let it sit exactly on a node line, because a water plane can // dissapear when viewed with the eye exactly on it. @@ -796,7 +815,7 @@ void V_CalcRefdef (void) CalcGunAngle (); VectorCopy (ent->origin, view->origin); - view->origin[2] += cl.viewheight; + view->origin[2] += cl.stats[STAT_VIEWHEIGHT]; for (i=0 ; i<3 ; i++) view->origin[i] += forward[i]*bob*0.4; diff --git a/Quake/wad.c b/Quake/wad.c index aae5f38f..458d2060 100644 --- a/Quake/wad.c +++ b/Quake/wad.c @@ -125,7 +125,6 @@ lumpinfo_t *W_GetLumpinfo (const char *name) return lump_p; } - Con_SafePrintf ("W_GetLumpinfo: %s not found\n", name); //johnfitz -- was Sys_Error return NULL; } @@ -135,7 +134,11 @@ void *W_GetLumpName (const char *name) lump = W_GetLumpinfo (name); - if (!lump) return NULL; //johnfitz + if (!lump) + { + Con_SafePrintf ("W_GetLumpName: %s not found\n", name); //johnfitz -- was Sys_Error + return NULL; //johnfitz + } return (void *)(wad_base + lump->filepos); } diff --git a/Quake/world.c b/Quake/world.c index 475cef65..bd5de451 100644 --- a/Quake/world.c +++ b/Quake/world.c @@ -601,6 +601,9 @@ LINE TESTING IN HULLS ================== SV_RecursiveHullCheck +Spike -- note that the pointcontents in this function are completely redundant. +This function should instead return the state of the contents of the mid position. +This would avoid all redundant recursion. ================== */ qboolean SV_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace) @@ -818,6 +821,16 @@ void SV_ClipToLinks ( areanode_t *node, moveclip_t *clip ) if (clip->passedict && clip->passedict->v.size[0] && !touch->v.size[0]) continue; // points never interact + if (pr_checkextension.value) + { + //corpses are nonsolid to slidebox + if (clip->passedict->v.solid == SOLID_SLIDEBOX && touch->v.solid == SOLID_EXT_CORPSE) + continue; + //corpses ignore slidebox or corpses + if (clip->passedict->v.solid == SOLID_EXT_CORPSE && (touch->v.solid == SOLID_SLIDEBOX || touch->v.solid == SOLID_EXT_CORPSE)) + continue; + } + // might intersect, so do an exact clip if (clip->trace.allsolid) return; diff --git a/Windows/VisualStudio/quakespasm-sdl2.vcproj b/Windows/VisualStudio/quakespasm-sdl2.vcproj old mode 100644 new mode 100755 index 64b21656..4da32c49 --- a/Windows/VisualStudio/quakespasm-sdl2.vcproj +++ b/Windows/VisualStudio/quakespasm-sdl2.vcproj @@ -373,6 +373,10 @@ RelativePath="..\..\Quake\common.c" > + + @@ -501,6 +505,10 @@ RelativePath="..\..\Quake\pr_cmds.c" > + + @@ -521,6 +529,10 @@ RelativePath="..\..\Quake\r_part.c" > + + @@ -537,6 +549,10 @@ RelativePath="..\..\Quake\snd_codec.c" > + + diff --git a/Windows/VisualStudio/quakespasm.vcproj b/Windows/VisualStudio/quakespasm.vcproj old mode 100644 new mode 100755 index 308e6ffe..808dacdc --- a/Windows/VisualStudio/quakespasm.vcproj +++ b/Windows/VisualStudio/quakespasm.vcproj @@ -633,6 +633,22 @@ RelativePath="..\..\Quake\zone.c" > + + + + + + + +