1
0
Fork 0
forked from fte/fteqw

added demo_nudge command.

attempt to fix an issue with bc formats being rejected by d3d if they're not a multiple of 4.
added doreset argument to refreshhostcache builtin, for mods to forget all servers when rescanning.
stop fteqcc from reporting uninitialised locals etc inside the wrong file.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5251 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2018-05-06 16:09:07 +00:00
parent 23afe95b70
commit 8c4d39268e
32 changed files with 323 additions and 52 deletions

View file

@ -378,6 +378,10 @@ void CL_ProgressDemoTime(void)
return; return;
} }
if (cl.demopausedtilltime >= realtime)
return;
cl.demopausedtilltime = 0;
cl.demonudge = 0;
if (cl_demospeed.value >= 0 && cls.state == ca_active) if (cl_demospeed.value >= 0 && cls.state == ca_active)
demtime += host_frametime*cl_demospeed.value; demtime += host_frametime*cl_demospeed.value;
else else
@ -448,6 +452,51 @@ void CL_DemoJump_f(void)
cls.demoseeking = true; cls.demoseeking = true;
} }
void CL_DemoNudge_f(void)
{
extern cvar_t cl_demospeed;
int move = atoi(Cmd_Argv(1));
int newnudge;
if (!cls.demoplayback)
{
Con_Printf("not playing a demo, cannot nudge.\n");
return;
}
if (!move)
move = 1;
newnudge = cl.demonudge + move;
if (newnudge <= -(int)countof(cl.inframes))
newnudge = 1-(int)countof(cl.inframes);
if (newnudge < 0)
{ //if we're nudging to a past frame, make sure that its actually valid.
for(;-(int)countof(cl.inframes) < newnudge && newnudge < 0;)
{
int i = cls.netchan.incoming_sequence+newnudge;
if (i < 0)
break;
if (cl.inframes[i&UPDATE_MASK].frameid == i && !cl.inframes[i&UPDATE_MASK].invalid)
{
cl.demonudge = newnudge;
break;
}
if (move < 0)
newnudge--;
else
newnudge++;
}
if (!newnudge)
cl.demonudge = newnudge;
}
else
cl.demonudge = newnudge;
cl.demopausedtilltime = realtime + 3;
}
/* /*
==================== ====================
CL_GetDemoMessage CL_GetDemoMessage
@ -537,13 +586,13 @@ qboolean CL_GetDemoMessage (void)
#endif #endif
if (cls.demoplayback == DPB_NETQUAKE && cls.signon == 4/*SIGNONS*/) if (cls.demoplayback == DPB_NETQUAKE && cls.signon == 4/*SIGNONS*/)
{ {
if (!demtime) /*if (!demtime)
{ {
cl.gametime = 0; cl.gametime = 0;
cl.gametimemark = demtime; cl.gametimemark = demtime;
olddemotime = 0; olddemotime = 0;
return 0; return 0;
} }*/
cls.netchan.last_received = realtime; cls.netchan.last_received = realtime;
if (cls.demoseeking) if (cls.demoseeking)
{ {
@ -553,6 +602,8 @@ qboolean CL_GetDemoMessage (void)
return 0; return 0;
} }
} }
else if (cl.demonudge > 0)
cl.demonudge--;
else if ((cls.timedemo && host_framecount == demoframe) || (!cls.timedemo && demtime < cl.gametime && cl.gametime))// > dem_lasttime+demtime) else if ((cls.timedemo && host_framecount == demoframe) || (!cls.timedemo && demtime < cl.gametime && cl.gametime))// > dem_lasttime+demtime)
{ {
if (demtime <= cl.gametime-1) if (demtime <= cl.gametime-1)
@ -688,6 +739,11 @@ readnext:
cls.td_starttime = Sys_DoubleTime(); cls.td_starttime = Sys_DoubleTime();
demtime = demotime; // warp demtime = demotime; // warp
} }
else if (cl.demonudge > 0)
{
cl.demonudge--;
demtime = demotime; // warp
}
else if (!(cl.paused&~4) && cls.state >= ca_onserver) else if (!(cl.paused&~4) && cls.state >= ca_onserver)
{ // always grab until fully connected { // always grab until fully connected
if (demtime + 1.0 < demotime) if (demtime + 1.0 < demotime)

View file

@ -3680,7 +3680,12 @@ void CL_TransitionEntities (void)
} }
//force our emulated time to as late as we can, if we're not using interpolation, which has the effect of disabling all interpolation //force our emulated time to as late as we can, if we're not using interpolation, which has the effect of disabling all interpolation
if (nolerp) if (cl.demonudge < 0)
{
servertime = cl.inframes[(cls.netchan.incoming_sequence+cl.demonudge)&UPDATE_MASK].packet_entities.servertime;
nolerp = true;
}
else if (nolerp)
servertime = cl.inframes[cls.netchan.incoming_sequence&UPDATE_MASK].packet_entities.servertime; servertime = cl.inframes[cls.netchan.incoming_sequence&UPDATE_MASK].packet_entities.servertime;
else else
servertime = cl.servertime; servertime = cl.servertime;
@ -4969,6 +4974,7 @@ void CL_LinkPlayers (void)
float predictmsmult = 1000*cl_predict_players_frac.value; float predictmsmult = 1000*cl_predict_players_frac.value;
int modelindex2; int modelindex2;
extern cvar_t cl_demospeed; extern cvar_t cl_demospeed;
int displayseq;
if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED) if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED)
return; return;
@ -4982,7 +4988,11 @@ void CL_LinkPlayers (void)
if (playertime > realtime) if (playertime > realtime)
playertime = realtime; playertime = realtime;
frame = &cl.inframes[cl.validsequence&UPDATE_MASK]; if (cl.demonudge < 0)
displayseq = cl.lerpentssequence;
else
displayseq = cl.validsequence;
frame = &cl.inframes[displayseq&UPDATE_MASK];
predictplayers = cl_predict_players.ival; predictplayers = cl_predict_players.ival;
if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV) if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
@ -4993,7 +5003,7 @@ void CL_LinkPlayers (void)
{ {
nametagseen[j] = false; nametagseen[j] = false;
if (state->messagenum != cl.validsequence) if (state->messagenum != displayseq)
{ {
#ifdef CSQC_DAT #ifdef CSQC_DAT
CSQC_DeltaPlayer(j, NULL); CSQC_DeltaPlayer(j, NULL);

View file

@ -4340,6 +4340,7 @@ void CL_Init (void)
Cmd_AddCommand ("qtvlist", CL_QTVList_f); Cmd_AddCommand ("qtvlist", CL_QTVList_f);
Cmd_AddCommand ("qtvdemos", CL_QTVDemos_f); Cmd_AddCommand ("qtvdemos", CL_QTVDemos_f);
Cmd_AddCommandD ("demo_jump", CL_DemoJump_f, "Jump to a specified time in a demo. Prefix with a + or - for a relative offset. Seeking backwards will restart the demo and the fast forward, which can take some time in long demos."); Cmd_AddCommandD ("demo_jump", CL_DemoJump_f, "Jump to a specified time in a demo. Prefix with a + or - for a relative offset. Seeking backwards will restart the demo and the fast forward, which can take some time in long demos.");
Cmd_AddCommandD ("demo_nudge", CL_DemoNudge_f, "Nudge the demo by one frame. Argument should be +1 or -1. Nudging backwards is limited.");
Cmd_AddCommandAD ("timedemo", CL_TimeDemo_f, CL_DemoList_c, NULL); Cmd_AddCommandAD ("timedemo", CL_TimeDemo_f, CL_DemoList_c, NULL);
Cmd_AddCommand ("crashme_endgame", CL_CrashMeEndgame_f); Cmd_AddCommand ("crashme_endgame", CL_CrashMeEndgame_f);

View file

@ -222,7 +222,7 @@ unsigned int Master_TotalCount(void);
unsigned int Master_NumPolled(void); //progress indicator unsigned int Master_NumPolled(void); //progress indicator
unsigned int Master_NumAlive(void); unsigned int Master_NumAlive(void);
void Master_SetupSockets(void); void Master_SetupSockets(void);
void MasterInfo_Refresh(void); void MasterInfo_Refresh(qboolean doreset);
void Master_QueryServer(serverinfo_t *server); void Master_QueryServer(serverinfo_t *server);
void MasterInfo_WriteServers(void); void MasterInfo_WriteServers(void);

View file

@ -1461,8 +1461,9 @@ static int CL_LoadSounds(int stage, qboolean dontactuallyload)
void Sound_CheckDownload(const char *s) void Sound_CheckDownload(const char *s)
{ {
#ifndef QUAKETC
char mangled[512]; char mangled[512];
#endif
if (*s == '*') //q2 sexed sound if (*s == '*') //q2 sexed sound
return; return;
@ -1473,11 +1474,13 @@ void Sound_CheckDownload(const char *s)
if (CL_CheckFile(s)) if (CL_CheckFile(s))
return; //we have it already return; //we have it already
#if !defined(QUAKETC) && defined(AVAIL_OGGVORBIS)
//the things I do for nexuiz... *sigh* //the things I do for nexuiz... *sigh*
COM_StripExtension(s, mangled, sizeof(mangled)); COM_StripExtension(s, mangled, sizeof(mangled));
COM_DefaultExtension(mangled, ".ogg", sizeof(mangled)); COM_DefaultExtension(mangled, ".ogg", sizeof(mangled));
if (CL_CheckFile(mangled)) if (CL_CheckFile(mangled))
return; return;
#endif
//check with the sound/ prefix //check with the sound/ prefix
s = va("sound/%s",s); s = va("sound/%s",s);
@ -1485,12 +1488,13 @@ void Sound_CheckDownload(const char *s)
if (CL_CheckFile(s)) if (CL_CheckFile(s))
return; //we have it already return; //we have it already
#if !defined(QUAKETC) && defined(AVAIL_OGGVORBIS)
//the things I do for nexuiz... *sigh* //the things I do for nexuiz... *sigh*
COM_StripExtension(s, mangled, sizeof(mangled)); COM_StripExtension(s, mangled, sizeof(mangled));
COM_DefaultExtension(mangled, ".ogg", sizeof(mangled)); COM_DefaultExtension(mangled, ".ogg", sizeof(mangled));
if (CL_CheckFile(mangled)) if (CL_CheckFile(mangled))
return; return;
#endif
//download the one the server said. //download the one the server said.
CL_CheckOrEnqueDownloadFile(s, NULL, 0); CL_CheckOrEnqueDownloadFile(s, NULL, 0);
} }
@ -7932,9 +7936,26 @@ void CLNQ_ParseServerMessage (void)
CL_SetStatNumeric (0, i, j, j); CL_SetStatNumeric (0, i, j, j);
break; break;
case svcdp_updatestatbyte: case svcdp_updatestatbyte:
//case svcneh_fog:
if (CPNQ_IS_BJP || cls.protocol_nq == PROTOCOL_VERSION_NEHD)
{
CL_ResetFog(0);
if (MSG_ReadByte())
{
cl.fog[0].density = MSG_ReadFloat();
cl.fog[0].colour[0] = SRGBf(MSG_ReadByte()/255.0f);
cl.fog[0].colour[1] = SRGBf(MSG_ReadByte()/255.0f);
cl.fog[0].colour[2] = SRGBf(MSG_ReadByte()/255.0f);
cl.fog[0].time += 0.25; //change fairly fast, but not instantly
}
cl.fog_locked = !!cl.fog[0].density;
}
else
{
i = MSG_ReadByte (); i = MSG_ReadByte ();
j = MSG_ReadByte (); j = MSG_ReadByte ();
CL_SetStatNumeric (0, i, j, j); CL_SetStatNumeric (0, i, j, j);
}
break; break;
case svcfte_updatestatstring: case svcfte_updatestatstring:
i = MSG_ReadByte(); i = MSG_ReadByte();
@ -8086,7 +8107,7 @@ void CLNQ_ParseServerMessage (void)
cls.signon = 4; cls.signon = 4;
CLNQ_SignonReply (); CLNQ_SignonReply ();
} }
//well, it's really any protocol, but we're only going to support version 5. //well, it's really any protocol, but we're only going to support version 5 (through 7).
CLDP_ParseDarkPlaces5Entities(); CLDP_ParseDarkPlaces5Entities();
break; break;
case svcdp_spawnbaseline2: case svcdp_spawnbaseline2:

View file

@ -651,7 +651,7 @@ void CL_CalcClientTime(void)
if (max) if (max)
{ {
extern cvar_t cl_demospeed; extern cvar_t cl_demospeed;
if (cls.demoplayback && cl_demospeed.value >= 0 && cls.state == ca_active) if (cls.demoplayback && cl_demospeed.value > 0 && cls.state == ca_active)
cl.servertime += host_frametime*cl_demospeed.value; cl.servertime += host_frametime*cl_demospeed.value;
else else
cl.servertime += host_frametime; cl.servertime += host_frametime;

View file

@ -419,10 +419,13 @@ void SCR_CenterPrint (int pnum, const char *str, qboolean skipgamecode)
cvar_t *var; cvar_t *var;
var = Cvar_FindVar ("scr_centerprinttext"); var = Cvar_FindVar ("scr_centerprinttext");
if (!var) if (!var)
Cvar_Get("scr_centerprinttext", "", 0, "Script Notifications"); var = Cvar_Get("scr_centerprinttext", "", 0, "Script Notifications");
if (var)
{
Cvar_Set(var, str); Cvar_Set(var, str);
Cbuf_AddText("f_centerprint\n", RESTRICT_LOCAL); Cbuf_AddText("f_centerprint\n", RESTRICT_LOCAL);
} }
}
p = &scr_centerprint[pnum]; p = &scr_centerprint[pnum];
p->flags = 0; p->flags = 0;
@ -550,7 +553,10 @@ void SCR_CenterPrint (int pnum, const char *str, qboolean skipgamecode)
for (i = 0; i < p->charcount; i++) for (i = 0; i < p->charcount; i++)
{ {
if (p->string[i] == CON_LINKSTART) if (p->string[i] == CON_LINKSTART)
{
p->flags |= CPRINT_CURSOR; p->flags |= CPRINT_CURSOR;
break;
}
} }
} }

View file

@ -836,7 +836,7 @@ static qintptr_t UI_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con
{ {
extern qboolean NET_SendPollPacket(int len, void *data, netadr_t to); extern qboolean NET_SendPollPacket(int len, void *data, netadr_t to);
netadr_t na; netadr_t na;
MasterInfo_Refresh(); MasterInfo_Refresh(false);
if (NET_StringToAdr("255.255.255.255", PORT_Q3SERVER, &na)) if (NET_StringToAdr("255.255.255.255", PORT_Q3SERVER, &na))
NET_SendPollPacket (14, va("%c%c%c%cgetstatus\n", 255, 255, 255, 255), na); NET_SendPollPacket (14, va("%c%c%c%cgetstatus\n", 255, 255, 255, 255), na);

View file

@ -818,6 +818,8 @@ typedef struct
float oldgametime; //used as the old time to lerp cl.time from. float oldgametime; //used as the old time to lerp cl.time from.
float oldgametimemark; //if it's 0, cl.time will casually increase. float oldgametimemark; //if it's 0, cl.time will casually increase.
float demogametimebias; //mvd timings are weird. float demogametimebias; //mvd timings are weird.
int demonudge; //
float demopausedtilltime;//demo is paused until realtime>this
float minpitch; float minpitch;
float maxpitch; float maxpitch;
@ -1229,6 +1231,7 @@ void CL_QTVPoll (void);
void CL_QTVList_f (void); void CL_QTVList_f (void);
void CL_QTVDemos_f (void); void CL_QTVDemos_f (void);
void CL_DemoJump_f(void); void CL_DemoJump_f(void);
void CL_DemoNudge_f(void);
void CL_ProgressDemoTime(void); void CL_ProgressDemoTime(void);
void CL_TimeDemo_f (void); void CL_TimeDemo_f (void);
typedef struct typedef struct

View file

@ -4433,6 +4433,7 @@ typedef union
byte_vec4_t v; byte_vec4_t v;
unsigned int u; unsigned int u;
} pixel32_t; } pixel32_t;
#define etc_expandv(p,x,y,z) p.v[0]|=p.v[0]>>x,p.v[1]|=p.v[1]>>y,p.v[2]|=p.v[2]>>z
#ifdef DECOMPRESS_ETC2 #ifdef DECOMPRESS_ETC2
//FIXME: this is littleendian only... //FIXME: this is littleendian only...
static void Image_Decode_ETC2_Block_TH_Internal(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w, pixel32_t base1, pixel32_t base2, int d, qboolean tmode) static void Image_Decode_ETC2_Block_TH_Internal(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w, pixel32_t base1, pixel32_t base2, int d, qboolean tmode)
@ -4507,8 +4508,6 @@ static void Image_Decode_ETC2_Block_Internal(qbyte *fte_restrict in, pixel32_t *
unsigned char R1,G1,B1; unsigned char R1,G1,B1;
pixel32_t *out1, *out2, *out3; pixel32_t *out1, *out2, *out3;
#define etc_expandv(p,x,y,z) p.v[0]|=p.v[0]>>x,p.v[1]|=p.v[1]>>y,p.v[2]|=p.v[2]>>z
qboolean opaque; qboolean opaque;
if (alphamode) if (alphamode)
@ -4757,15 +4756,15 @@ static void Image_Decode_S3TC_Block_Internal(qbyte *fte_restrict in, pixel32_t *
} }
static void Image_Decode_BC1_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w) static void Image_Decode_BC1_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w)
{ {
Image_S3TC_Decode_Block_Internal(in, out, w, 0xff); Image_Decode_S3TC_Block_Internal(in, out, w, 0xff);
} }
static void Image_Decode_BC1A_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w) static void Image_Decode_BC1A_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w)
{ {
Image_S3TC_Decode_Block_Internal(in, out, w, 0); Image_Decode_S3TC_Block_Internal(in, out, w, 0);
} }
static void Image_Decode_BC2_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w) static void Image_Decode_BC2_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w)
{ {
Image_S3TC_Decode_Block_Internal(in+8, out, w, 0xff); Image_Decode_S3TC_Block_Internal(in+8, out, w, 0xff);
//BC2 has straight 4-bit alpha. //BC2 has straight 4-bit alpha.
#define BC2_AlphaRow() \ #define BC2_AlphaRow() \
@ -4849,8 +4848,8 @@ static void Image_Decode_RGTC_Block_Internal(qbyte *fte_restrict in, qbyte *fte_
//s3tc rgb channel, with an rgtc alpha channel that depends upon both encodings (really the origin of rgtc, but mneh). //s3tc rgb channel, with an rgtc alpha channel that depends upon both encodings (really the origin of rgtc, but mneh).
static void Image_Decode_BC3_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w) static void Image_Decode_BC3_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w)
{ {
Image_S3TC_Decode_Block_Internal(in+8, out, w, 0xff); Image_Decode_S3TC_Block_Internal(in+8, out, w, 0xff);
Image_RGTC_Decode_Block_Internal(in, out->v+3, w*4, false); Image_Decode_RGTC_Block_Internal(in, out->v+3, w*4, false);
} }
#endif #endif
static void Image_Decode_BC4U_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w) static void Image_Decode_BC4U_Block(qbyte *fte_restrict in, pixel32_t *fte_restrict out, int w)
@ -5241,7 +5240,20 @@ static void Image_ChangeFormat(struct pendingtextureinfo *mips, unsigned int fla
//if that format isn't supported/desired, try converting it. //if that format isn't supported/desired, try converting it.
if (sh_config.texfmt[mips->encoding]) if (sh_config.texfmt[mips->encoding])
{
if (sh_config.texture_allow_block_padding && mips->mipcount)
{ //direct3d is annoying, and will reject any block-compressed format with a base mip size that is not a multiple of the block size.
//its fine with weirdly sized mips though. I have no idea why there's this restriction, but whatever.
//we need to de
int blockbytes, blockwidth, blockheight;
Image_BlockSizeForEncoding(mips->encoding, &blockbytes, &blockwidth, &blockheight);
if (!(mips->mip[0].width % blockwidth) && !(mips->mip[0].height % blockheight))
return; return;
//else encoding isn't supported for this size. fall through.
}
else
return;
}
{ //various compressed formats might not be supported. { //various compressed formats might not be supported.
void *decodefunc = NULL; void *decodefunc = NULL;

View file

@ -2745,7 +2745,8 @@ void Key_Event (unsigned int devid, int key, unsigned int unicode, qboolean down
default: default:
dc = keybindings[key][modifierstate]; dc = keybindings[key][modifierstate];
//toggleconsole or +showFOO keys should do their regular bind action //toggleconsole or +showFOO keys should do their regular bind action
if (!dc || (strcmp(dc, "toggleconsole") && strncmp(dc, "+show", 5))) //demo_jump/demo_setspeed/demo_nudge should be allowed too.
if (!dc || (strcmp(dc, "toggleconsole") && strncmp(dc, "+show", 5) && strncmp(dc, "demo_", 5)))
{ {
M_ToggleMenu_f (); M_ToggleMenu_f ();
return; return;

View file

@ -1110,9 +1110,13 @@ static void SL_Remove (menu_t *menu)
static qboolean SL_DoRefresh (menuoption_t *opt, menu_t *menu, int key) static qboolean SL_DoRefresh (menuoption_t *opt, menu_t *menu, int key)
{ {
MasterInfo_Refresh(); if (key == K_MOUSE1 || key == K_MOUSE1 || key == K_ENTER || key == K_KP_ENTER)
{
MasterInfo_Refresh(false);
isrefreshing = true; isrefreshing = true;
return true; return true;
}
return false;
} }
void M_Menu_ServerList2_f(void) void M_Menu_ServerList2_f(void)
@ -1249,7 +1253,7 @@ void M_Menu_ServerList2_f(void)
if (!Master_TotalCount()) if (!Master_TotalCount())
{ {
MasterInfo_Refresh(); MasterInfo_Refresh(true);
isrefreshing = true; isrefreshing = true;
} }
@ -1309,7 +1313,7 @@ static void M_QuickConnect_PreDraw(menu_t *menu)
} }
//retry //retry
MasterInfo_Refresh(); MasterInfo_Refresh(false);
isrefreshing = true; isrefreshing = true;
} }
} }
@ -1340,7 +1344,7 @@ void M_QuickConnect_f(void)
Key_Dest_Add(kdm_emenu); Key_Dest_Add(kdm_emenu);
MasterInfo_Refresh(); MasterInfo_Refresh(false);
isrefreshing = true; isrefreshing = true;
quickconnecttimeout = Sys_DoubleTime() + 5; quickconnecttimeout = Sys_DoubleTime() + 5;

View file

@ -351,7 +351,10 @@ qboolean Media_NamedTrack(const char *track, const char *looptrack)
static char *ext[] = static char *ext[] =
{ {
"", "",
#ifdef AVAIL_OGGVORBIS #if defined(AVAIL_OGGOPUS) || defined(FTE_TARGET_WEB)
".opus",
#endif
#if defined(AVAIL_OGGVORBIS) || defined(FTE_TARGET_WEB)
".ogg", ".ogg",
#endif #endif
#if defined(AVAIL_MP3_ACM) || defined(FTE_TARGET_WEB) #if defined(AVAIL_MP3_ACM) || defined(FTE_TARGET_WEB)

View file

@ -1106,8 +1106,13 @@ void M_Menu_MediaFiles_f (void)
info->ext[info->numext] = ".wav"; info->ext[info->numext] = ".wav";
info->command[info->numext] = "media_add"; info->command[info->numext] = "media_add";
info->numext++; info->numext++;
#if defined(AVAIL_OGGOPUS) || defined(FTE_TARGET_WEB)
info->ext[info->numext] = ".opus";
info->command[info->numext] = "media_add";
info->numext++;
#endif
#if defined(AVAIL_OGGVORBIS) || defined(FTE_TARGET_WEB) #if defined(AVAIL_OGGVORBIS) || defined(FTE_TARGET_WEB)
info->ext[info->numext] = ".ogg"; //will this ever be added properly? info->ext[info->numext] = ".ogg";
info->command[info->numext] = "media_add"; info->command[info->numext] = "media_add";
info->numext++; info->numext++;
#endif #endif

View file

@ -1468,7 +1468,7 @@ void M_Keydown (int key, int unicode)
if (key == K_MOUSE1) //mouse clicks are deferred until the release event. this is for touch screens and aiming. if (key == K_MOUSE1) //mouse clicks are deferred until the release event. this is for touch screens and aiming.
menu_mousedown = true; menu_mousedown = true;
else if (key == K_LSHIFT || key == K_RSHIFT || key == K_LALT || key == K_RALT || key == K_LCTRL || key == K_RCTRL) else if (key == K_LSHIFT || key == K_RSHIFT || key == K_LALT || key == K_RALT || key == K_LCTRL || key == K_RCTRL)
; ; //modifiers are sent on up events instead.
else else
M_Complex_Key (key, unicode); M_Complex_Key (key, unicode);
return; return;

View file

@ -2618,11 +2618,18 @@ void MasterInfo_WriteServers(void)
} }
//poll master servers for server lists. //poll master servers for server lists.
void MasterInfo_Refresh(void) void MasterInfo_Refresh(qboolean doreset)
{ {
master_t *mast; master_t *mast;
serverinfo_t *info;
qboolean loadedone; qboolean loadedone;
if (doreset)
{
for (info = firstserver; info; info = info->next)
info->status &= ~1; //hide until we get a new response from it.
}
loadedone = false; loadedone = false;
loadedone |= Master_LoadMasterList("masters.txt", false, MT_MASTERUDP, MP_QUAKEWORLD, 5); //fte listing loadedone |= Master_LoadMasterList("masters.txt", false, MT_MASTERUDP, MP_QUAKEWORLD, 5); //fte listing

View file

@ -376,7 +376,6 @@ void QCBUILTIN PF_cl_findkeysforcommand (pubprogfuncs_t *prinst, struct globalva
RETURN_TSTRING(keyname); RETURN_TSTRING(keyname);
} }
void QCBUILTIN PF_cl_findkeysforcommandex (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) void QCBUILTIN PF_cl_findkeysforcommandex (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{ {
const char *cmdname = PR_GetStringOfs(prinst, OFS_PARM0); const char *cmdname = PR_GetStringOfs(prinst, OFS_PARM0);
@ -384,9 +383,7 @@ void QCBUILTIN PF_cl_findkeysforcommandex (pubprogfuncs_t *prinst, struct global
int keynums[256]; int keynums[256];
int keymods[countof(keynums)]; int keymods[countof(keynums)];
char keyname[512]; char keyname[512];
int i, count; int i, count = M_FindKeysForBind(bindmap, cmdname, keynums, keymods, countof(keynums));
count = M_FindKeysForBind(bindmap, cmdname, keynums, keymods, countof(keynums));
keyname[0] = '\0'; keyname[0] = '\0';
@ -797,7 +794,8 @@ void QCBUILTIN PF_cl_sethostcachesort(pubprogfuncs_t *prinst, struct globalvars_
//void refreshhostcache(void) = #620; //void refreshhostcache(void) = #620;
void QCBUILTIN PF_cl_refreshhostcache(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) void QCBUILTIN PF_cl_refreshhostcache(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{ {
MasterInfo_Refresh(); qboolean doreset = (prinst->callargc>=1)?G_FLOAT(OFS_PARM0):false;
MasterInfo_Refresh(doreset);
} }
//float gethostcachenumber(float fld, float hostnr) = #621; //float gethostcachenumber(float fld, float hostnr) = #621;
void QCBUILTIN PF_cl_gethostcachenumber(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) void QCBUILTIN PF_cl_gethostcachenumber(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)

View file

@ -873,7 +873,15 @@ static void S_LoadSoundWorker (void *ctx, void *ctxdata, size_t a, size_t b)
//Con_Printf ("S_LoadSound: %x\n", (int)stackbuf); //Con_Printf ("S_LoadSound: %x\n", (int)stackbuf);
// load it in // load it in
const char *prefixes[] = {"sound/", ""}; const char *prefixes[] = {"sound/", ""};
const char *extensions[] = {".wav", ".ogg"}; const char *extensions[] = {
".wav",
#ifdef AVAIL_OGGOPUS
".opus",
#endif
#ifdef AVAIL_OGGVORBIS
".ogg",
#endif
};
char altname[sizeof(namebuffer)]; char altname[sizeof(namebuffer)];
char orig[16]; char orig[16];
size_t pre, ex; size_t pre, ex;

View file

@ -772,7 +772,7 @@ void V_CalcBlend (float *hw_blend)
a2 = pv->cshifts[j].percent / 255.0; //don't allow modification of this one. a2 = pv->cshifts[j].percent / 255.0; //don't allow modification of this one.
} }
if (!a2) if (a2 <= 0)
continue; continue;
if (j == CSHIFT_SERVER) if (j == CSHIFT_SERVER)

View file

@ -318,6 +318,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#define svcfitz_spawnstatic2 43 #define svcfitz_spawnstatic2 43
#define svcfitz_spawnstaticsound2 44 #define svcfitz_spawnstaticsound2 44
//nehahra svcs
#define svcneh_skyboxsize 50 // [coord] size (default is 4096)
#define svcneh_fog 51 // [byte] enable <optional past this point, only included if enable is true> [float] density [byte] red [byte] green [byte] blue
//DP extended svcs //DP extended svcs
#define svcdp_downloaddata 50 #define svcdp_downloaddata 50
#define svcdp_updatestatbyte 51 #define svcdp_updatestatbyte 51

View file

@ -35,12 +35,12 @@ qboolean D3D9_LoadTextureMips(image_t *tex, const struct pendingtextureinfo *mip
fmt = D3DFMT_R5G6B5; fmt = D3DFMT_R5G6B5;
break; break;
case PTI_RGBA4444://not supported on d3d9 case PTI_RGBA4444://not supported on d3d9
return false; break;
case PTI_ARGB4444: case PTI_ARGB4444:
fmt = D3DFMT_A4R4G4B4; fmt = D3DFMT_A4R4G4B4;
break; break;
case PTI_RGBA5551://not supported on d3d9 case PTI_RGBA5551://not supported on d3d9
return false; break;
case PTI_ARGB1555: case PTI_ARGB1555:
fmt = D3DFMT_A1R5G5B5; fmt = D3DFMT_A1R5G5B5;
break; break;
@ -159,6 +159,8 @@ qboolean D3D9_LoadTextureMips(image_t *tex, const struct pendingtextureinfo *mip
} }
} }
//Microsoft's code will reject any dxt texture with a mip[0] width/height that is not a multiple of 4.
if (FAILED(IDirect3DDevice9_CreateTexture(pD3DDev9, mips->mip[0].width, mips->mip[0].height, mipcount, 0, fmt, D3DPOOL_MANAGED, &dt, NULL))) if (FAILED(IDirect3DDevice9_CreateTexture(pD3DDev9, mips->mip[0].width, mips->mip[0].height, mipcount, 0, fmt, D3DPOOL_MANAGED, &dt, NULL)))
return false; return false;
dbt = (IDirect3DBaseTexture9*)dt; dbt = (IDirect3DBaseTexture9*)dt;

View file

@ -597,6 +597,7 @@ void D3D9Shader_Init(void)
IDirect3DDevice9_GetDeviceCaps(pD3DDev9, &caps); IDirect3DDevice9_GetDeviceCaps(pD3DDev9, &caps);
sh_config.texture_allow_block_padding = false; //microsoft blocks this.
if (caps.TextureCaps & D3DPTEXTURECAPS_POW2) if (caps.TextureCaps & D3DPTEXTURECAPS_POW2)
{ //this flag is a LIMITATION, not a capability. { //this flag is a LIMITATION, not a capability.
sh_config.texture_non_power_of_two = false; sh_config.texture_non_power_of_two = false;

View file

@ -856,6 +856,7 @@ static qboolean initD3D11Device(HWND hWnd, rendererstate_t *info, PFN_D3D11_CREA
memset(&sh_config, 0, sizeof(sh_config)); memset(&sh_config, 0, sizeof(sh_config));
sh_config.texture_non_power_of_two = flevel>=D3D_FEATURE_LEVEL_10_0; //npot MUST be supported on all d3d10+ cards. sh_config.texture_non_power_of_two = flevel>=D3D_FEATURE_LEVEL_10_0; //npot MUST be supported on all d3d10+ cards.
sh_config.texture_non_power_of_two_pic = true; //always supported in d3d11, supposedly, even with d3d9 devices. sh_config.texture_non_power_of_two_pic = true; //always supported in d3d11, supposedly, even with d3d9 devices.
sh_config.texture_allow_block_padding = false; //microsoft blocks this.
sh_config.npot_rounddown = false; sh_config.npot_rounddown = false;
if (flevel>=D3D_FEATURE_LEVEL_11_0) if (flevel>=D3D_FEATURE_LEVEL_11_0)
sh_config.texture2d_maxsize = 16384; sh_config.texture2d_maxsize = 16384;

View file

@ -504,6 +504,7 @@ void D3D8Shader_Init(void)
IDirect3DDevice8_GetDeviceCaps(pD3DDev8, &caps); IDirect3DDevice8_GetDeviceCaps(pD3DDev8, &caps);
sh_config.texture_allow_block_padding = false; //microsoft blocks this.
if (caps.TextureCaps & D3DPTEXTURECAPS_POW2) if (caps.TextureCaps & D3DPTEXTURECAPS_POW2)
{ //this flag is a LIMITATION, not a capability. { //this flag is a LIMITATION, not a capability.
sh_config.texture_non_power_of_two = false; sh_config.texture_non_power_of_two = false;

View file

@ -668,6 +668,11 @@ void GL_CheckExtensions (void *(*getglfunction) (char *name))
Con_DPrintf("Anisotropic filter extension found (%dx max).\n",gl_config.ext_texture_filter_anisotropic); Con_DPrintf("Anisotropic filter extension found (%dx max).\n",gl_config.ext_texture_filter_anisotropic);
} }
if ((!gl_config.gles && gl_config.glversion >= 2) || GL_CheckExtension("GL_ARB_texture_non_power_of_two"))
sh_config.texture_allow_block_padding = true; //gl2/npot explicitly relaxes this restriction
else
sh_config.texture_allow_block_padding = false; //gles does not support padded sizes, even with gles3. This is especially true if we're running atop d3d-via-webgl.
if (!gl_config.gles && gl_config.glversion >= 3) if (!gl_config.gles && gl_config.glversion >= 3)
{ //GL_ARB_texture_non_power_of_two is supposed to be mandatory in gl2+ and thus checking for it is redundant and not forwards-compatible { //GL_ARB_texture_non_power_of_two is supposed to be mandatory in gl2+ and thus checking for it is redundant and not forwards-compatible
//geforcefx apparently software emulates it, so only activate it unconditionally on gl3+ hardware. //geforcefx apparently software emulates it, so only activate it unconditionally on gl3+ hardware.

View file

@ -767,6 +767,7 @@ typedef struct
unsigned int texturecube_maxsize; unsigned int texturecube_maxsize;
qboolean texture_non_power_of_two; //full support for npot qboolean texture_non_power_of_two; //full support for npot
qboolean texture_non_power_of_two_pic; //npot only works with clamp-to-edge mipless images. qboolean texture_non_power_of_two_pic; //npot only works with clamp-to-edge mipless images.
qboolean texture_allow_block_padding; //mip 0 of compressed formats can be any size, with implicit padding.
qboolean npot_rounddown; //memory limited systems can say that they want to use less ram. qboolean npot_rounddown; //memory limited systems can say that they want to use less ram.
qboolean tex_env_combine; qboolean tex_env_combine;
qboolean nv_tex_env_combine4; qboolean nv_tex_env_combine4;

View file

@ -1206,6 +1206,8 @@ pbool PDECL ED_ParseEval (pubprogfuncs_t *ppf, eval_t *eval, int type, const cha
break; break;
case ev_entity: case ev_entity:
if (!strncmp(s, "entity ", 7)) //cope with etos weirdness.
s += 7;
eval->edict = atoi (s); eval->edict = atoi (s);
break; break;

View file

@ -10156,12 +10156,12 @@ void QCC_PR_ParseStatement (void)
if (QCC_PR_CheckKeyword(keyword_return, "return")) if (QCC_PR_CheckKeyword(keyword_return, "return"))
{ {
/*if (pr_classtype) /*
{ accumulate behaviour requires the ability to just run code without explicit returns.
e = QCC_PR_GetDef(NULL, "__oself", pr_scope, false, 0); return = foo; sets the value that will be returned when the function finally exits, without returning now.
e2 = QCC_PR_GetDef(NULL, "self", NULL, false, 0); return; returns that value now, without execing later accumulations.
QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STORE_ENT], e, QCC_PR_DummyDef(pr_classtype, "self", pr_scope, 0, e2->ofs, false), NULL)); return 5; also returns now.
}*/ */
if (QCC_PR_CheckToken (";")) if (QCC_PR_CheckToken (";"))
{ {
@ -10186,6 +10186,9 @@ void QCC_PR_ParseStatement (void)
// if (opt_return_only) // if (opt_return_only)
// QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_DONE], nullsref, nullsref, NULL)); // QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_DONE], nullsref, nullsref, NULL));
// else // else
if (pr_scope->type->aux_type->type == ev_vector) //make sure bad returns don't return junk in the y+z members.
QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], QCC_MakeVectorConst(0,0,0), nullsref, NULL));
else
QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], nullsref, nullsref, NULL)); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], nullsref, nullsref, NULL));
return; return;
} }
@ -12252,6 +12255,7 @@ void QCC_PR_FinaliseFunction(QCC_function_t *f)
QCC_PR_ResumeFunction(f); QCC_PR_ResumeFunction(f);
pr_token_line_last = f->line_end; pr_token_line_last = f->line_end;
s_filen = f->filen;
if (f->returndef.cast) if (f->returndef.cast)
{ {

View file

@ -10849,7 +10849,7 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
{"sethostcachemasknumber",PF_Fixme, 0, 0, 0, 617, "void(float mask, float fld, float num, float op)"}, {"sethostcachemasknumber",PF_Fixme, 0, 0, 0, 617, "void(float mask, float fld, float num, float op)"},
{"resorthostcache", PF_Fixme, 0, 0, 0, 618, "void()"}, {"resorthostcache", PF_Fixme, 0, 0, 0, 618, "void()"},
{"sethostcachesort",PF_Fixme, 0, 0, 0, 619, "void(float fld, float descending)"}, {"sethostcachesort",PF_Fixme, 0, 0, 0, 619, "void(float fld, float descending)"},
{"refreshhostcache",PF_Fixme, 0, 0, 0, 620, "void()"}, {"refreshhostcache",PF_Fixme, 0, 0, 0, 620, "void(optional float dopurge)"},
{"gethostcachenumber",PF_Fixme, 0, 0, 0, 621, "float(float fld, float hostnr)"}, {"gethostcachenumber",PF_Fixme, 0, 0, 0, 621, "float(float fld, float hostnr)"},
{"gethostcacheindexforkey",PF_Fixme, 0, 0, 0, 622, "float(string key)"}, {"gethostcacheindexforkey",PF_Fixme, 0, 0, 0, 622, "float(string key)"},
{"addwantedhostcachekey",PF_Fixme, 0, 0, 0, 623, "void(string key)"}, {"addwantedhostcachekey",PF_Fixme, 0, 0, 0, 623, "void(string key)"},

View file

@ -4304,6 +4304,7 @@ qboolean VK_Init(rendererstate_t *info, const char **sysextnames, qboolean (*cre
sh_config.minver = -1; sh_config.minver = -1;
sh_config.maxver = -1; sh_config.maxver = -1;
sh_config.texture_allow_block_padding = true;
sh_config.texture_non_power_of_two = true; //is this always true? sh_config.texture_non_power_of_two = true; //is this always true?
sh_config.texture_non_power_of_two_pic = true; //probably true... sh_config.texture_non_power_of_two_pic = true; //probably true...
sh_config.npot_rounddown = false; sh_config.npot_rounddown = false;

109
specs/bspx.txt Normal file
View file

@ -0,0 +1,109 @@
BSPX is a format originally invented by Tonik, I believe, and is already implemented in both ezQuake and FTE.
It is not so much a format itself, but more of an extensible way to shove additional lumps within an existing BSP.
Typically these additional lumps will provide supplemental information or replace existing info.
BSPX itself can logically be applied to the BSP file format of Quake, Quake II, or Quake III.
BSPX itself can be defined in just the following two structures:
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;
The bspx_header_t struct can be found immediately following the BSP's standard header. It is only valid if the standard header specifies no lump as starting at that location, and if the magic id matches.
The engine can then walk the lump list looking for lumps that it recognises. Unknown lumps MUST be ignored.
These lumps are currently defined:
RGBLIGHTING:
(applies to fte, ezquake, qss)
This is equivelent to the information stored in a .lit file (sans header), and must contain the same number of samples as the lightmap lump would normally contain, because it doesn't make much sense otherwise.
Presence of this lump permits omitting the regular mono lightmap to save space, but doing so will harm compatibility.
LIGHTING_E5BGR9:
(applies to fte)
This is a more advanced alternative to RGBLIGHTING.
Each luxel is a E5BGR9 int32 packed value (ie: on little-endian machines, the exponent is the high 5 bits), resulting in what is effectively a memory-efficient floating point rgb value.
This lightmap format virtually removes all oversaturation limits, and promises greater precision in dark areas too.
This format is directly supported on ALL OpenGL[ES] 3.0+ gpus (aka: GL_EXT_texture_shared_exponent).
As a floating point format, a logical value of 1.0 is considered as identity (instead of 255 being an [overbright] multiplier of 2.0).
Lighting values are always assumed to be linear.
LIGHTINGDIR:
(applies to fte)
This lump contains surface-space light directions (read: deluxemap), equivelent to fte's .lux file, or dp's .dlit files (sans header).
(as unorm values, these need to be biased before use).
If bumpmaps or specular maps are not available then the effects of this may not be significant/noticable.
LMSHIFT:
(applies to fte, qss)
This is a series of per-surface bytes. Each byte provides the (1<<shift) ratio of texels-per-luxel of the corresponding surface.
Note that the engine's per-surface lightmap size limit still applies, but any engine that supports LMSHIFT must support up to 256 luxels in either axis (note that 1 luxel is reserved for edges, so a shift of 0 requires that surfaces be subdivided to at least a 255qu boundary while a shift of 4 reduces the required subdivisions on an infinitely sized surface by a factor of 256, or to a max extent of 4080qu).
As a result, the subdivide size argument of qbsp can limit the minimum shift value (maximum lightmap scale).
LMOFFSET:
(applies to fte, qss)
This replaces the lightmap offset value of the surface lumps. Should only be used in conjunction with LMSHIFT.
This allows a bsp to contain both scaled surfaces and unscaled ones, without looking broken in engines that use either mode.
LMSTYLE:
(applies to fte, qss)
This replaces the four lightstyle indexes of each surface lump entry. Should only be used in conjunction with LMSHIFT.
VERTEXNORMALS:
(applies to fte)
This lump contains a series of vec3_t values. One per vertex lump entry.
Surfaces with a texinfo flag of 0x800 will use this lump in order to determine vertex normals, otherwise they will use their plane's normal.
These normals allow rtlights to respond to curved forms, while the flag prevents the need for excessive vertex+edge counts.
Verticies that are not part of any 0x800 surface will not be read, and will usually hold values of 0 (if only because it compresses better).
BRUSHLIST:
(applies to fte)
Engines that utilise this lump will no longer need to use hull-based collisions.
struct {
unsigned int ver = 1;
unsigned int modelnum; //inline model number. 0 for world, obviously.
unsigned int numbrushes; //size of following array.
unsigned int numplanes; //total count. for validation.
struct
{
vec_t mins;
vec_t maxs;
signed short contents;
unsigned short numplanes;
{
vec3_t normal;
float dist;
} planes[numplanes];
} brush[numbrushes];
} permodel[];
A submodel could be an illusionary model with no solid surfaces. Such a model will become non-solid if there is a BRUSHLIST lump that does not name the submodel.
Axial planes MUST NOT be written - they will be inferred from the brush's mins+maxs. This guarentees that brush expansion does not extend points beyond the brush itself, and saves size on maps made by lazy people.
Contents values are equal to Quake's normal CONTENT_FOO values. This outbreak of lack of imagination is brought to you by a desire to avoid politics about content masks.
CONTENTS_EMPTY = -1 //an error. pointless.
CONTENTS_SOLID = -2 //regular solidity
CONTENTS_WATER = -3 //required for pointcontents to work properly.
CONTENTS_SLIME = -4 //really I'm only listing these for completeness. only the last two are 'new'.
CONTENTS_LAVA = -5 //it burns!
CONTENTS_SKY = -6 //nothing explodes here!
CONTENTS_CLIP = -8 //borrowed from halflife, blocks only entities with size.
CONTENTS_LADDER = -16 //borrowed from halflife
Presence of this lump permits hulls 1 and 2 to be entirely omitted from the BSP (0 is still used for rendering), but doing so will harm compatibility with engines that do not understand this (presumably a solid-but-valid tree should be written, just in case). This could boost load times.
The engine is expected to insert the brushes into the bsp's various nodes. Inserting them into the leafs is suboptimal as leaf 0 will end up containing every brush...
Other features:
FTE supports mipmaps with all four offsets set to 0 as external textures. Such texture will ignore gl_load24bit, treating it as always enabled. This saves space and simplifies copyright ownership without resulting in untextured maps (when the textures are actually available, anyway).
Misc Compatibility Concerns:
mipmap lump - mipmaps with all four offsets set to 0 are external textures and contain no actual data. Note that vanilla quake will glitch out if given such a texture, although its unlikely to crash.
hulls - presence of BRUSHLIST allows hulls 1 and 2 to be ommitted. This will crash vanilla glquake but not winquake, and can be reproduced with the vanilla tools too.
lightmap sizes - winquake is limited to 18*18 lightmaps. Many quake engines support up to 256*256 now (AFTER lmscale). Note that such large lightmaps are not recommended due to all the resulting texture switches.

View file

@ -265,6 +265,7 @@ ramp <red> <green> <blue> [alpha] [scale]
Specifies a ramp index in rgb terms, regardless of palette. Specifies a ramp index in rgb terms, regardless of palette.
stains <value> stains <value>
(Not supported in QSS)
How much the effect discolours the wall upon impact. How much the effect discolours the wall upon impact.
The stained colour is based upon the colour of the particle upon impact. The stained colour is based upon the colour of the particle upon impact.
@ -359,6 +360,7 @@ spawnvel <horiz> [vert]
obsolete obsolete
viewspace [frac] viewspace [frac]
(Not supported in QSS)
Specifies that this particle type should move relative to the camera. Specifies that this particle type should move relative to the camera.
Not compatible with splitscreen. Not compatible with splitscreen.
Should not normally be used in combination with clipping/bouncing. Should not normally be used in combination with clipping/bouncing.
@ -412,6 +414,7 @@ lightshadows <castshadows>
lightcorona <intensity> <scale> lightcorona <intensity> <scale>
model <name> [options] model <name> [options]
(Not supported in QSS)
options are: options are:
frame= frame=
framestart= framestart=
@ -442,6 +445,8 @@ sound <name> [options]
Plays a sound when the effect is spawned. Only ONE sound will be used, picked randomly from the included sounds according to their weights. Plays a sound when the effect is spawned. Only ONE sound will be used, picked randomly from the included sounds according to their weights.
spawnstain <radius> <r> <g> <b> spawnstain <radius> <r> <g> <b>
(Not supported in QSS)
Controls whether a stain will be created at the same time as any particles (instead of depending upon impacts).
rainfrequency <multiplier> rainfrequency <multiplier>
Specifies the interval between spawning new particle puffs on surfaces. Specifies the interval between spawning new particle puffs on surfaces.