diff --git a/engine/client/cl_cam.c b/engine/client/cl_cam.c index ce69c81eb..ccb358030 100644 --- a/engine/client/cl_cam.c +++ b/engine/client/cl_cam.c @@ -136,6 +136,20 @@ static float CL_TrackScore(player_info_t *pl, char *rule) } return score; } +static qboolean CL_MayTrack(int seat, int player) +{ + if (player < 0) + return false; + if (!cl.players[player].userid || !cl.players[player].name[0] || cl.players[player].spectator) + return false; + for (seat--; seat >= 0; seat--) + { + //not valid if an earlier view is tracking it. + if (Cam_TrackNum(&cl.playerview[seat]) == player) + return false; + } + return true; +} //returns the player with the highest frags static int CL_FindHighTrack(int seat, char *rule) { @@ -146,7 +160,7 @@ static int CL_FindHighTrack(int seat, char *rule) //set a default to the currently tracked player, to reuse the current player we're tracking if someone lower equalises. j = cl.playerview[seat].cam_spec_track; - if (j >= 0 && cl.players[j].userid && cl.players[j].name[0] && !cl.players[j].spectator) + if (CL_MayTrack(seat, j)) max = CL_TrackScore(&cl.players[j], rule); else { @@ -158,23 +172,16 @@ static int CL_FindHighTrack(int seat, char *rule) { s = &cl.players[i]; score = CL_TrackScore(s, rule); - if (s->userid && s->name[0] && !s->spectator && score > max) + if (score > max) { if (j == i) //this was our default. continue; - //skip it if an earlier seat is watching it already - for (k = 0; k < seat; k++) - { - if (Cam_TrackNum(&cl.playerview[k]) == i) - break; - } + if (!CL_MayTrack(seat, i)) + continue; if (cl.teamplay && seat && cl.playerview[0].cam_spec_track >= 0 && strcmp(cl.players[cl.playerview[0].cam_spec_track].team, s->team)) continue; //when using multiview, keep tracking the team - if (k == seat) - { - max = CL_TrackScore(s, rule); - j = i; - } + max = CL_TrackScore(s, rule); + j = i; } } return j; @@ -224,7 +231,7 @@ qboolean Cam_DrawViewModel(playerview_t *pv) int Cam_TrackNum(playerview_t *pv) { - if (pv->cam_state == CAM_EYECAM) + if (cl.spectator && pv->cam_state == CAM_EYECAM) return pv->cam_spec_track; return -1; } diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index b1cb8b86c..55c8b0dde 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -1419,7 +1419,7 @@ void CL_Record_f (void) { MSG_WriteByte (&buf, svc_updateentertime); MSG_WriteByte (&buf, i); - MSG_WriteFloat (&buf, player->entertime); + MSG_WriteFloat (&buf, realtime - player->realentertime); //seconds since } if (*player->userinfo) diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index be5963614..c7977fbbf 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -59,6 +59,14 @@ int CL_TargettedSplit(qboolean nowrap) char *c; int pnum; int mod; + + //explicitly targetted at some seat number from the server + if (Cmd_ExecLevel > RESTRICT_SERVER) + return Cmd_ExecLevel - RESTRICT_SERVER-1; + if (Cmd_ExecLevel == RESTRICT_SERVER) + return 0; + + //locally executed command. if (nowrap) mod = MAX_SPLITS; else @@ -1579,6 +1587,23 @@ qboolean CLQW_SendCmd (sizebuf_t *buf) if (cl.sendprespawn) buf->cursize = st; //don't send movement commands while we're still supposedly downloading. mvdsv does not like that. + else + { + //FIXME: user a timer. + if (!cls.netchan.message.cursize && cl.allocated_client_slots > 1 && cl.splitclients && (cls.fteprotocolextensions & PEXT_SPLITSCREEN)) + { + int targ = cl_splitscreen.ival+1; + if (targ > MAX_SPLITS) + targ = MAX_SPLITS; + if (cl.splitclients < targ) + { + char buffer[2048]; + CL_SendClientCommand(true, "addseat %i %s", cl.splitclients, COM_QuotedString(cls.userinfo[cl.splitclients], buffer, sizeof(buffer), false)); + } + else if (cl.splitclients > targ) + CL_SendClientCommand(true, "addseat %i", targ); + } + } return dontdrop; } diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 2be1f2a3d..d08491417 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -6099,6 +6099,24 @@ void CLQW_ParseServerMessage (void) Cbuf_Execute (); // make sure any stuffed commands are done CLQW_ParseServerData (); break; + case svc_signonnum: + cl.splitclients = MSG_ReadByte(); + for (i = 0; i < cl.splitclients && i < 4; i++) + { + cl.playerview[i].playernum = MSG_ReadByte(); + cl.playerview[i].viewentity = cl.playerview[i].playernum+1; + } + if (i < cl.splitclients) + { + Con_Printf("Server sent us too many seats!\n"); + for (; i < cl.splitclients; i++) + { //svcfte_choosesplitclient has a modulo that is also broken, but at least there's no parse errors this way + MSG_ReadByte(); +// CL_SendClientCommand(true, va("%i drop", i+1)); + } + cl.splitclients = MAX_SPLITS; + } + break; #ifdef PEXT_SETVIEW case svc_setview: if (!(cls.fteprotocolextensions & PEXT_SETVIEW)) @@ -6224,7 +6242,7 @@ void CLQW_ParseServerMessage (void) i = MSG_ReadByte (); if (i >= MAX_CLIENTS) Host_EndGame ("CL_ParseServerMessage: svc_updateentertime > MAX_SCOREBOARD"); - cl.players[i].entertime = cl.servertime - MSG_ReadFloat (); + cl.players[i].realentertime = realtime - MSG_ReadFloat (); break; case svc_spawnbaseline: diff --git a/engine/client/cl_plugin.inc b/engine/client/cl_plugin.inc index 9c199b241..6e97129b3 100644 --- a/engine/client/cl_plugin.inc +++ b/engine/client/cl_plugin.inc @@ -464,7 +464,7 @@ typedef struct { char name[PLUGMAX_SCOREBOARDNAME]; int ping; int pl; - int starttime; + float activetime; int userid; int spectator; char userinfo[1024]; @@ -501,7 +501,7 @@ static qintptr_t VARGS Plug_GetPlayerInfo(void *offset, quintptr_t mask, const q Q_strncpyz(out->name, cl.players[i].name, PLUGMAX_SCOREBOARDNAME); out->ping = cl.players[i].ping; out->pl = cl.players[i].pl; - out->starttime = cl.players[i].entertime; + out->activetime = realtime - cl.players[i].realentertime; out->userid = cl.players[i].userid; out->spectator = cl.players[i].spectator; Q_strncpyz(out->userinfo, cl.players[i].userinfo, sizeof(out->userinfo)); diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index d4d54efb0..41844053b 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -552,7 +552,7 @@ void CL_CalcClientTime(void) } else// if (cls.protocol != CP_QUAKE3) { - float oldst = realtime; +// float oldst = realtime; if (cls.demoplayback && cls.timedemo) { //more deterministic. one frame is drawn per demo packet parsed. so sync to it as closely as possible. @@ -574,7 +574,7 @@ void CL_CalcClientTime(void) { float min, max; - oldst = cl.servertime; +// oldst = cl.servertime; max = cl.gametime; min = cl.oldgametime; @@ -624,7 +624,7 @@ void CL_CalcClientTime(void) } } cl.time = cl.servertime; - if (oldst == 0) +/* if (oldst == 0) { int i; for (i = 0; i < cl.allocated_client_slots; i++) @@ -632,9 +632,11 @@ void CL_CalcClientTime(void) cl.players[i].entertime += cl.servertime; } } +*/ return; } +#if 0 if (cls.protocol == CP_NETQUAKE || (cls.demoplayback && cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV)) { float want; @@ -669,6 +671,7 @@ void CL_CalcClientTime(void) if (cl.time > realtime) cl.time = realtime; } +#endif } static void CL_DecodeStateSize(unsigned short solid, int modelindex, vec3_t mins, vec3_t maxs) @@ -894,7 +897,10 @@ void CL_PredictMovePNum (int seat) pv->nolocalplayer = !!(cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) || (cls.protocol != CP_QUAKEWORLD); if (!cl.spectator) //just in case + { pv->cam_state = CAM_FREECAM; + pv->cam_spec_track = -1; + } #ifdef Q2CLIENT if (cls.protocol == CP_QUAKE2) diff --git a/engine/client/client.h b/engine/client/client.h index a10870b87..122e6652c 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -159,7 +159,7 @@ typedef struct player_info_s // scoreboard information char name[MAX_SCOREBOARDNAME]; char team[MAX_INFO_KEY]; - float entertime; + float realentertime; //pegged against realtime, to cope with potentially not knowing the server's time when we first receive this message int frags; int ping; qbyte pl; @@ -826,7 +826,7 @@ typedef struct double matchgametimestart; enum { - MATCH_DONTKNOW, + MATCH_DONTKNOW, //assumed to be in progress. MATCH_COUNTDOWN, MATCH_STANDBY, MATCH_INPROGRESS diff --git a/engine/client/keys.c b/engine/client/keys.c index 0376f94ef..31432e6f2 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -419,7 +419,7 @@ int Con_ExecuteLine(console_t *con, char *line) waschat = true; if (keydown[K_CTRL]) Cbuf_AddText ("say_team ", RESTRICT_LOCAL); - else if (keydown[K_SHIFT]) + else if (keydown[K_SHIFT] || *line == ' ') Cbuf_AddText ("say ", RESTRICT_LOCAL); else waschat = false; diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 47a233bdf..c05d1050b 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -2581,6 +2581,7 @@ typedef struct MV_BONES, MV_SHADER } mode; + int surfaceidx; int skingroup; int framegroup; double framechangetime; @@ -2619,9 +2620,7 @@ static unsigned int genhsv(float h_, float s, float v) ((int)(r*255)<<16) | ((int)(g*255)<<8) | ((int)(b*255)<<0); -}; -const char *Mod_FrameNameForNum(model_t *model, int num); -const char *Mod_SkinNameForNum(model_t *model, int num); +} #include "com_mesh.h" #ifdef SKELETALMODELS @@ -2650,6 +2649,7 @@ static void M_ModelViewerDraw(int x, int y, struct menucustom_s *c, struct menu_ entity_t ent; vec3_t fwd, rgt, up; const char *fname; + shader_t *shader; vec2_t fs = {8,8}; modelview_t *mods = c->dptr; @@ -2713,15 +2713,27 @@ static void M_ModelViewerDraw(int x, int y, struct menucustom_s *c, struct menu_ y = 0; - fname = Mod_FrameNameForNum(ent.model, mods->framegroup); + fname = Mod_SurfaceNameForNum(ent.model, mods->surfaceidx); if (!fname) - fname = "Unknown Frame"; - Draw_FunString(0, y, va("%i: %s", mods->framegroup, fname)); + fname = "Unknown Surface"; + Draw_FunString(0, y, va("Surf %i: %s", mods->surfaceidx, fname)); y+=8; - fname = Mod_SkinNameForNum(ent.model, mods->skingroup); + + { + char *fname; + int numframes = 0; + float duration = 0; + qboolean loop = false; + if (!Mod_FrameInfoForNum(ent.model, mods->surfaceidx, mods->framegroup, &fname, &numframes, &duration, &loop)) + fname = "Unknown Frame"; + Draw_FunString(0, y, va("Frame%i: %s (%i poses, %f secs, %s)", mods->framegroup, fname, numframes, duration, loop?"looped":"unlooped")); + y+=8; + } + fname = Mod_SkinNameForNum(ent.model, mods->surfaceidx, mods->skingroup); if (!fname) fname = "Unknown Skin"; - Draw_FunString(0, y, va("%i: %s", mods->skingroup, fname)); + shader = Mod_ShaderForSkin(ent.model, mods->surfaceidx, mods->skingroup); + Draw_FunString(0, y, va("Skin %i: (%s) %s", mods->skingroup, fname, shader?shader->name:"NO SHADER")); y+=8; switch(mods->mode) @@ -2759,7 +2771,7 @@ static void M_ModelViewerDraw(int x, int y, struct menucustom_s *c, struct menu_ { if (!mods->shadertext) { - char *body = Shader_GetShaderBody(Mod_ShaderForSkin(ent.model, mods->skingroup)); + char *body = Shader_GetShaderBody(Mod_ShaderForSkin(ent.model, mods->surfaceidx, mods->skingroup)); mods->shadertext = Z_StrDup(body); } R_DrawTextField(r_refdef.grect.x, r_refdef.grect.y+16, r_refdef.grect.width, r_refdef.grect.height-16, mods->shadertext, CON_WHITEMASK, CPRINT_TALIGN|CPRINT_LALIGN, font_default, fs); @@ -2814,17 +2826,33 @@ static qboolean M_ModelViewerKey(struct menucustom_s *c, struct menu_s *m, int k { mods->skingroup += 1; mods->skinchangetime = realtime; + Z_Free(mods->shadertext); + mods->shadertext = NULL; } else if (key == K_PGDN) { mods->framegroup = max(0, mods->framegroup-1); mods->framechangetime = realtime; + Z_Free(mods->shadertext); + mods->shadertext = NULL; } else if (key == K_PGUP) { mods->framegroup += 1; mods->framechangetime = realtime; } + else if (key == K_INS) + { + mods->surfaceidx = max(0, mods->surfaceidx-1); + Z_Free(mods->shadertext); + mods->shadertext = NULL; + } + else if (key == K_DEL) + { + mods->surfaceidx += 1; + Z_Free(mods->shadertext); + mods->shadertext = NULL; + } else return false; return true; diff --git a/engine/client/merged.h b/engine/client/merged.h index 98ea4df8f..3e7fc7068 100644 --- a/engine/client/merged.h +++ b/engine/client/merged.h @@ -147,9 +147,9 @@ extern const char *Mod_FixName (const char *modname, const char *worldname); char *Mod_ParseWorldspawnKey (const char *ents, const char *key, char *buffer, size_t sizeofbuffer); extern void Mod_Think (void); -extern int Mod_SkinNumForName (struct model_s *model, const char *name); -extern int Mod_FrameNumForName (struct model_s *model, const char *name); -extern float Mod_GetFrameDuration (struct model_s *model, int framenum); +extern int Mod_SkinNumForName (struct model_s *model, int surfaceidx, const char *name); +extern int Mod_FrameNumForName (struct model_s *model, int surfaceidx, const char *name); +extern float Mod_GetFrameDuration (struct model_s *model, int surfaceidx, int framenum); #undef FNC diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index c30c3e771..c121abb22 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -3090,11 +3090,16 @@ static void QCBUILTIN PF_cs_getplayerkey (pubprogfuncs_t *prinst, struct globalv ret = buffer; sprintf(ret, "%i", cl.players[pnum].pl); } - else if (!strcmp(keyname, "entertime")) //packet loss + else if (!strcmp(keyname, "activetime")) //packet loss { ret = buffer; - sprintf(ret, "%i", (int)cl.players[pnum].entertime); + sprintf(ret, "%f", realtime - cl.players[pnum].realentertime); } +// else if (!strcmp(keyname, "entertime")) //packet loss +// { +// ret = buffer; +// sprintf(ret, "%i", (int)cl.players[pnum].entertime); +// } else if (!strcmp(keyname, "topcolor_rgb")) //packet loss { unsigned int col = cl.players[pnum].ttopcolor; diff --git a/engine/client/pr_skelobj.c b/engine/client/pr_skelobj.c index 56093b7a7..a543d8ae4 100644 --- a/engine/client/pr_skelobj.c +++ b/engine/client/pr_skelobj.c @@ -35,9 +35,6 @@ qc must build the skeletal object still, which fills the skeletal object from th #include "quakedef.h" -qboolean Mod_FrameInfoForNum(model_t *model, int num, char **name, int *numframes, float *duration, qboolean *loop); -const char *Mod_SkinNameForNum(model_t *model, int num); - #if defined(RAGDOLL) || defined(SKELETALOBJECTS) #include "pr_common.h" @@ -870,7 +867,7 @@ void skel_generateragdoll_f(void) int numframes; float duration; qboolean loop; - if (!Mod_FrameInfoForNum(mod, i, &fname, &numframes, &duration, &loop)) + if (!Mod_FrameInfoForNum(mod, 0, i, &fname, &numframes, &duration, &loop)) break; VFS_PUTS(f, va("//%i %s (%i frames) (%f secs)%s", i, fname, numframes, duration, loop?" (loop)":"")); } @@ -882,7 +879,7 @@ void skel_generateragdoll_f(void) for (i = 0; i < 32768; i++) { const char *sname; - sname = Mod_SkinNameForNum(mod, i); + sname = Mod_SkinNameForNum(mod, 0, i); if (!sname) break; VFS_PUTS(f, va("//%i %s", i, sname)); @@ -2193,17 +2190,15 @@ void QCBUILTIN PF_gettagindex (pubprogfuncs_t *prinst, struct globalvars_s *pr_g G_FLOAT(OFS_RETURN) = 0; } -const char *Mod_FrameNameForNum(model_t *model, int num); -const char *Mod_SkinNameForNum(model_t *model, int num); - //string(float modidx, float framenum) frametoname void QCBUILTIN PF_frametoname (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { world_t *w = prinst->parms->user; unsigned int modelindex = G_FLOAT(OFS_PARM0); unsigned int skinnum = G_FLOAT(OFS_PARM1); + int surfaceidx = 0; model_t *mod = w->Get_CModel(w, modelindex); - const char *n = Mod_FrameNameForNum(mod, skinnum); + const char *n = Mod_FrameNameForNum(mod, surfaceidx, skinnum); if (n) RETURN_TSTRING(n); @@ -2215,11 +2210,12 @@ void QCBUILTIN PF_frameforname (pubprogfuncs_t *prinst, struct globalvars_s *pr_ { world_t *w = prinst->parms->user; unsigned int modelindex = G_FLOAT(OFS_PARM0); + int surfaceidx = 0; char *str = PF_VarString(prinst, 1, pr_globals); model_t *mod = w->Get_CModel(w, modelindex); if (mod) - G_FLOAT(OFS_RETURN) = Mod_FrameNumForName(mod, str); + G_FLOAT(OFS_RETURN) = Mod_FrameNumForName(mod, surfaceidx, str); else G_FLOAT(OFS_RETURN) = -1; } @@ -2228,10 +2224,11 @@ void QCBUILTIN PF_frameduration (pubprogfuncs_t *prinst, struct globalvars_s *pr world_t *w = prinst->parms->user; unsigned int modelindex = G_FLOAT(OFS_PARM0); unsigned int framenum = G_FLOAT(OFS_PARM1); + int surfaceidx = 0; model_t *mod = w->Get_CModel(w, modelindex); if (mod) - G_FLOAT(OFS_RETURN) = Mod_GetFrameDuration(mod, framenum); + G_FLOAT(OFS_RETURN) = Mod_GetFrameDuration(mod, surfaceidx, framenum); else G_FLOAT(OFS_RETURN) = 0; } @@ -2242,8 +2239,9 @@ void QCBUILTIN PF_skintoname (pubprogfuncs_t *prinst, struct globalvars_s *pr_gl world_t *w = prinst->parms->user; unsigned int modelindex = G_FLOAT(OFS_PARM0); unsigned int skinnum = G_FLOAT(OFS_PARM1); + int surfaceidx = 0; model_t *mod = w->Get_CModel(w, modelindex); - const char *n = Mod_SkinNameForNum(mod, skinnum); + const char *n = Mod_SkinNameForNum(mod, surfaceidx, skinnum); if (n) RETURN_TSTRING(n); @@ -2256,10 +2254,11 @@ void QCBUILTIN PF_skinforname (pubprogfuncs_t *prinst, struct globalvars_s *pr_g world_t *w = prinst->parms->user; unsigned int modelindex = G_FLOAT(OFS_PARM0); char *str = PF_VarString(prinst, 1, pr_globals); + int surfaceidx = 0; model_t *mod = w->Get_CModel(w, modelindex); if (mod) - G_FLOAT(OFS_RETURN) = Mod_SkinNumForName(mod, str); + G_FLOAT(OFS_RETURN) = Mod_SkinNumForName(mod, surfaceidx, str); else #endif G_FLOAT(OFS_RETURN) = -1; diff --git a/engine/client/render.h b/engine/client/render.h index 7f7e2783a..58ed971e0 100644 --- a/engine/client/render.h +++ b/engine/client/render.h @@ -432,9 +432,9 @@ skinfile_t *Mod_LookupSkin(skinid_t id); void Mod_Init (qboolean initial); void Mod_Shutdown (qboolean final); int Mod_TagNumForName(struct model_s *model, const char *name); -int Mod_SkinNumForName(struct model_s *model, const char *name); -int Mod_FrameNumForName(struct model_s *model, const char *name); -float Mod_GetFrameDuration(struct model_s *model, int frameno); +int Mod_SkinNumForName(struct model_s *model, int surfaceidx, const char *name); +int Mod_FrameNumForName(struct model_s *model, int surfaceidx, const char *name); +float Mod_GetFrameDuration(struct model_s *model, int surfaceidx, int frameno); void Mod_ResortShaders(void); void Mod_ClearAll (void); diff --git a/engine/client/sbar.c b/engine/client/sbar.c index fcac1eff9..30bad9eeb 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -3113,10 +3113,7 @@ ping time frags name }) #define COLUMN_TIME COLUMN(time, 4*8, \ { \ - if (cl.intermission) \ - total = cl.completed_time - s->entertime; \ - else \ - total = cl.servertime - s->entertime; \ + total = realtime - s->realentertime; \ minutes = (int)total/60; \ sprintf (num, "%4i", minutes); \ Draw_FunStringWidth(x, y, num, 4*8, false, false); \ diff --git a/engine/common/com_mesh.c b/engine/common/com_mesh.c index 28fc492e0..2dcf4f492 100644 --- a/engine/common/com_mesh.c +++ b/engine/common/com_mesh.c @@ -4169,7 +4169,7 @@ int Mod_TagNumForName(model_t *model, const char *name) return 0; } -int Mod_FrameNumForName(model_t *model, const char *name) +int Mod_FrameNumForName(model_t *model, int surfaceidx, const char *name) { galiasanimation_t *group; galiasinfo_t *inf; @@ -4186,17 +4186,22 @@ int Mod_FrameNumForName(model_t *model, const char *name) inf = Mod_Extradata(model); - group = inf->ofsanimations; - for (i = 0; i < inf->numanimations; i++, group++) + while(surfaceidx-->0 && inf) + inf = inf->nextsurf; + if (inf) { - if (!strcmp(group->name, name)) - return i; + group = inf->ofsanimations; + for (i = 0; i < inf->numanimations; i++, group++) + { + if (!strcmp(group->name, name)) + return i; + } } return -1; } #ifndef SERVERONLY -int Mod_SkinNumForName(model_t *model, const char *name) +int Mod_SkinNumForName(model_t *model, int surfaceidx, const char *name) { int i; galiasinfo_t *inf; @@ -4206,18 +4211,22 @@ int Mod_SkinNumForName(model_t *model, const char *name) return -1; inf = Mod_Extradata(model); - skin = inf->ofsskins; - for (i = 0; i < inf->numskins; i++, skin++) + while(surfaceidx-->0 && inf) + inf = inf->nextsurf; + if (inf) { - if (!strcmp(skin->name, name)) - return i; + skin = inf->ofsskins; + for (i = 0; i < inf->numskins; i++, skin++) + { + if (!strcmp(skin->name, name)) + return i; + } } - return -1; } #endif -const char *Mod_FrameNameForNum(model_t *model, int num) +const char *Mod_FrameNameForNum(model_t *model, int surfaceidx, int num) { galiasanimation_t *group; galiasinfo_t *inf; @@ -4229,13 +4238,16 @@ const char *Mod_FrameNameForNum(model_t *model, int num) inf = Mod_Extradata(model); - if (num >= inf->numanimations) + while(surfaceidx-->0 && inf) + inf = inf->nextsurf; + + if (!inf || num >= inf->numanimations) return NULL; group = inf->ofsanimations; return group[num].name; } -qboolean Mod_FrameInfoForNum(model_t *model, int num, char **name, int *numframes, float *duration, qboolean *loop) +qboolean Mod_FrameInfoForNum(model_t *model, int surfaceidx, int num, char **name, int *numframes, float *duration, qboolean *loop) { galiasanimation_t *group; galiasinfo_t *inf; @@ -4247,7 +4259,10 @@ qboolean Mod_FrameInfoForNum(model_t *model, int num, char **name, int *numframe inf = Mod_Extradata(model); - if (num >= inf->numanimations) + while(surfaceidx-->0 && inf) + inf = inf->nextsurf; + + if (!inf || num >= inf->numanimations) return false; group = inf->ofsanimations; @@ -4259,7 +4274,7 @@ qboolean Mod_FrameInfoForNum(model_t *model, int num, char **name, int *numframe } #ifndef SERVERONLY -shader_t *Mod_ShaderForSkin(model_t *model, int num) +shader_t *Mod_ShaderForSkin(model_t *model, int surfaceidx, int num) { galiasinfo_t *inf; galiasskin_t *skin; @@ -4268,13 +4283,16 @@ shader_t *Mod_ShaderForSkin(model_t *model, int num) return NULL; inf = Mod_Extradata(model); - if (num >= inf->numskins) + while(surfaceidx-->0 && inf) + inf = inf->nextsurf; + + if (!inf || num >= inf->numskins) return NULL; skin = inf->ofsskins; return skin[num].frame[0].shader; } #endif -const char *Mod_SkinNameForNum(model_t *model, int num) +const char *Mod_SkinNameForNum(model_t *model, int surfaceidx, int num) { #ifdef SERVERONLY return NULL; @@ -4286,17 +4304,39 @@ const char *Mod_SkinNameForNum(model_t *model, int num) return NULL; inf = Mod_Extradata(model); - if (num >= inf->numskins) + while(surfaceidx-->0 && inf) + inf = inf->nextsurf; + if (!inf || num >= inf->numskins) return NULL; skin = inf->ofsskins; - if (!*skin[num].name) - return skin[num].frame[0].shadername; - else +// if (!*skin[num].name) +// return skin[num].frame[0].shadername; +// else return skin[num].name; #endif } -float Mod_GetFrameDuration(model_t *model, int frameno) +const char *Mod_SurfaceNameForNum(model_t *model, int num) +{ +#ifdef SERVERONLY + return NULL; +#else + galiasinfo_t *inf; + + if (!model || model->type != mod_alias) + return NULL; + inf = Mod_Extradata(model); + + while(num-->0 && inf) + inf = inf->nextsurf; + if (inf) + return inf->surfacename; + else + return NULL; +#endif +} + +float Mod_GetFrameDuration(model_t *model, int surfaceidx, int frameno) { galiasinfo_t *inf; galiasanimation_t *group; @@ -4304,12 +4344,20 @@ float Mod_GetFrameDuration(model_t *model, int frameno) if (!model || model->type != mod_alias) return 0; inf = Mod_Extradata(model); + + while(surfaceidx-->0 && inf) + inf = inf->nextsurf; - group = inf->ofsanimations; - if (frameno < 0 || frameno >= inf->numanimations) - return 0; - group += frameno; - return group->numposes/group->rate; + if (inf) + { + group = inf->ofsanimations; + if (frameno >= 0 && frameno < inf->numanimations) + { + group += frameno; + return group->numposes/group->rate; + } + } + return 0; } @@ -6476,6 +6524,7 @@ galiasinfo_t *Mod_ParseIQMMeshModel(model_t *mod, char *buffer, size_t fsize) skin++; Q_strncpyz(skinframe[j].shadername, strings+mesh[i].material, sizeof(skinframe[j].shadername)); + skinframe++; } #endif diff --git a/engine/common/com_mesh.h b/engine/common/com_mesh.h index 74febeddf..72f27df32 100644 --- a/engine/common/com_mesh.h +++ b/engine/common/com_mesh.h @@ -209,8 +209,12 @@ void Mod_DestroyMesh(galiasinfo_t *galias); void Alias_FlushCache(void); void Alias_Shutdown(void); void Alias_Register(void); -shader_t *Mod_ShaderForSkin(model_t *model, int num); -const char *Mod_SkinNameForNum(model_t *model, int num); +shader_t *Mod_ShaderForSkin(model_t *model, int surfaceidx, int num); +const char *Mod_SkinNameForNum(model_t *model, int surfaceidx, int num); +const char *Mod_SurfaceNameForNum(model_t *model, int num); +const char *Mod_FrameNameForNum(model_t *model, int surfaceidx, int num); +const char *Mod_SkinNameForNum(model_t *model, int surfaceidx, int num); +qboolean Mod_FrameInfoForNum(model_t *model, int surfaceidx, int num, char **name, int *numframes, float *duration, qboolean *loop); void Mod_DoCRC(model_t *mod, char *buffer, int buffersize); diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 6358adef8..1a98909ae 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -1226,6 +1226,7 @@ qboolean Cvar_Command (int level) char *str; char buffer[65536]; int olev; + int seat; // check variables v = Cvar_FindVar (Cmd_Argv(0)); @@ -1243,6 +1244,7 @@ qboolean Cvar_Command (int level) Con_Printf ("Server tried setting %s cvar\n", v->name); return true; } + // perform a variable print or set if (Cmd_Argc() == 1) { @@ -1291,10 +1293,21 @@ qboolean Cvar_Command (int level) Con_Printf ("Cvar %s may not be set via the console\n", v->name); return true; } + #ifndef SERVERONLY - if (level > RESTRICT_SERVER) - { //directed at a secondary player. - CL_SendClientCommand(true, "%i setinfo %s %s", level - RESTRICT_SERVER-1, v->name, COM_QuotedString(str, buffer, sizeof(buffer), false)); + seat = CL_TargettedSplit(true); + if (v->flags & CVAR_USERINFO) + { + if (Cmd_FromGamecode() && cls.protocol == CP_QUAKEWORLD) + { //don't bother even changing the cvar locally, just update the server's version. + //fixme: quake2/quake3 latching. + if (seat) + CL_SendClientCommand(true, "%i setinfo %s \"%s\"", seat+1, v->name, str); + else + CL_SendClientCommand(true, "setinfo %s \"%s\"", v->name, str); + } + else + CL_SetInfo(seat, v->name, str); return true; } diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index feab40c87..1a5030366 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -33,6 +33,7 @@ struct wedict_s /*the above is shared with qclib*/ link_t area; pvscache_t pvsinfo; + int lastruntime; #ifdef USERBE entityode_t ode; diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index 2026b411f..e557f4f51 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -6008,7 +6008,10 @@ QCC_ref_t *QCC_PR_ParseRefArrayPointer (QCC_ref_t *retbuf, QCC_ref_t *r, pbool a char *tname; unsigned int i; if (!idx.cast && t->type == ev_pointer && !arraysize) + { t = t->aux_type; + dereference = true; + } tname = t->name; if (t->type == ev_struct || t->type == ev_union) @@ -10072,6 +10075,10 @@ pbool QCC_CheckUninitialised(int firststatement, int laststatement) QCC_type_t *type = pr_scope->type; int err; + //assume all, because we don't care for optimisations once we know we're not going to compile anything (removes warning about uninitialised unknown variables/typos). + if (pr_error_count) + return true; + for (i = 0; i < type->num_parms; i++) { paramend += type->params[i].type->size; diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 70a0364fb..cc43d56b3 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -3512,6 +3512,8 @@ static void QCBUILTIN PF_spawnclient (pubprogfuncs_t *prinst, struct globalvars_ svs.clients[i].datagram.allowoverflow = true; svs.clients[i].datagram.maxsize = 0; + svs.clients[i].edict = EDICT_NUM(prinst, i+1); + SV_SetUpClientEdict (&svs.clients[i], svs.clients[i].edict); RETURN_EDICT(prinst, svs.clients[i].edict); diff --git a/engine/server/server.h b/engine/server/server.h index b47e4cecf..8baced320 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -1021,6 +1021,7 @@ int SV_ModelIndex (const char *name); void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg); void SVQW_WriteDelta (entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qboolean force, unsigned int protext); +client_t *SV_AddSplit(client_t *controller, char *info, int id); void SV_GetNewSpawnParms(client_t *cl); void SV_SaveSpawnparms (void); void SV_SaveSpawnparmsClient(client_t *client, float *transferparms); //if transferparms, calls SetTransferParms instead, and does not modify the player. diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 1bde94a6d..cdefe7a4f 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -94,6 +94,8 @@ cvar_t allow_download_configs = CVARD("allow_download_configs", "0", "1 allows cvar_t allow_download_copyrighted = CVARD("allow_download_copyrighted", "0", "0 blocks download of packages that are considered copyrighted. Specifically, this means packages with a leading 'pak' prefix on the filename.\nIf you take your copyrights seriously, you should also set allow_download_pakmaps 0 and allow_download_pakcontents 0."); cvar_t allow_download_other = CVARD("allow_download_other", "0", "0 blocks downloading of any file that was not covered by any of the directory download blocks."); +extern cvar_t sv_allow_splitscreen; + cvar_t sv_serverip = CVARD("sv_serverip", "", "Set this cvar to the server's public ip address if the server is behind a firewall and cannot detect its own public address. Providing a port is required if the firewall/nat remaps it, but is otherwise optional."); cvar_t sv_public = CVAR("sv_public", "0"); cvar_t sv_listen_qw = CVARAF("sv_listen_qw", "1", "sv_listen", 0); @@ -1891,6 +1893,101 @@ void SV_UserDNSResolved(void *ctx, void *data, size_t idx, size_t uid) Z_Free(data); } +client_t *SV_AddSplit(client_t *controller, char *info, int id) +{ + client_t *cl, *prev; + int i; + int curclients; + + if (!(controller->fteprotocolextensions & PEXT_SPLITSCREEN)) + { + SV_PrintToClient(controller, PRINT_HIGH, "Your client doesn't support splitscreen\n"); + return NULL; + } + + for (curclients = 0, prev = cl = controller; cl; cl = cl->controlled) + { + prev = cl; + curclients++; + } + + if (id && curclients != id) + return NULL; //this would be weird. + + if (curclients >= 16) + return NULL; //protocol limit on stats. + if (curclients >= MAX_SPLITS) + return NULL; + //only allow splitscreen if its explicitly allowed. unless its the local client in which case its always allowed. + //wouldn't it be awesome if we could always allow it for spectators? the join command makes that awkward, though I suppose we could just drop the extras in that case. + if (!sv_allow_splitscreen.ival && controller->netchan.remote_address.type != NA_LOOPBACK) + return NULL; + + for (i=0,cl=svs.clients ; istate == cs_free) + { + break; + } + } + if (i == sv.allocated_client_slots) + { + SV_PrintToClient(controller, PRINT_HIGH, "not enough free player slots\n"); + return NULL; + } + + cl->spectator = controller->spectator; + cl->netchan.remote_address = controller->netchan.remote_address; + cl->zquake_extensions = controller->zquake_extensions; + cl->fteprotocolextensions = controller->fteprotocolextensions; + cl->fteprotocolextensions2 = controller->fteprotocolextensions2; + cl->penalties = controller->penalties; + cl->protocol = controller->protocol; + + + Q_strncatz(cl->guid, va("%s:%i", controller->guid, curclients), sizeof(cl->guid)); + cl->name = cl->namebuf; + cl->team = cl->teambuf; + + nextuserid++; // so every client gets a unique id + cl->userid = nextuserid; + + cl->playerclass = 0; + cl->pendingentbits = NULL; + cl->edict = EDICT_NUM(svprogfuncs, i+1); + + prev->controlled = cl; + prev = cl; + cl->controller = prev->controller?prev->controller:host_client; + cl->controlled = NULL; + + Q_strncpyS (cl->userinfo, info, sizeof(cl->userinfo)-1); + cl->userinfo[sizeof(cl->userinfo)-1] = '\0'; + + if (controller->spectator) + { + Info_RemoveKey (cl->userinfo, "spectator"); + //this is a hint rather than a game breaker should it fail. + Info_SetValueForStarKey (cl->userinfo, "*spectator", "1", sizeof(cl->userinfo)); + } + else + Info_RemoveKey (cl->userinfo, "*spectator"); + + SV_ExtractFromUserinfo (cl, true); + SV_GetNewSpawnParms(cl); + + cl->state = controller->state; + + if (cl->state >= cs_connected) + { + cl->sendinfo = true; + SV_SetUpClientEdict(cl, cl->edict); + } + if (cl->state >= cs_spawned) + SV_Begin_Core(cl); + return cl; +} + /* ================== SVC_DirectConnect @@ -2806,7 +2903,13 @@ client_t *SVC_DirectConnect(void) Info_SetValueForStarKey (cl->userinfo, "*spectator", "1", sizeof(cl->userinfo)); } - newcl->fteprotocolextensions &= ~PEXT_SPLITSCREEN; + //only advertise PEXT_SPLITSCREEN when splitscreen is allowed, to avoid spam. this might mean people need to reconnect after its enabled. oh well. + if (!sv_allow_splitscreen.ival) + newcl->fteprotocolextensions &= ~PEXT_SPLITSCREEN; + + for (clients = 1; clients < numssclients; clients++) + SV_AddSplit(newcl, userinfo[clients], clients); +#if 0 for (clients = 1; clients < numssclients; clients++) { for (i=0,cl=svs.clients ; ifteprotocolextensions |= PEXT_SPLITSCREEN; - temp.frameunion.frames = cl->frameunion.frames; //don't touch these. temp.edict = cl->edict; memcpy(cl, newcl, sizeof(client_t)); @@ -2869,6 +2970,7 @@ client_t *SVC_DirectConnect(void) SV_EvaluatePenalties(cl); } +#endif newcl->controller = NULL; if (!redirect) diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 7d5a75797..c62fc976c 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -2083,9 +2083,10 @@ void WPhys_RunEntity (world_t *w, wedict_t *ent) else #endif { - if ((unsigned int)ent->v->lastruntime == w->framenum) + if (ent->lastruntime == w->framenum) return; - ent->v->lastruntime = w->framenum; + ent->lastruntime = w->framenum; + ent->v->lastruntime = w->physicstime; #ifndef CLIENTONLY svent = NULL; #endif @@ -2439,7 +2440,7 @@ qboolean SV_Physics (void) old_bot_time = newbottime; for (i = 1; i <= sv.allocated_client_slots; i++) { - if (svs.clients[i-1].state && svs.clients[i-1].protocol == SCP_BAD) + if (svs.clients[i-1].state > cs_zombie && svs.clients[i-1].protocol == SCP_BAD) { //then this is a bot oldhost = host_client; oldplayer = sv_player; diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index 450292d5a..59c760c0b 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -837,6 +837,9 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int } } + if (client->penalties & BAN_BLIND) + continue; + if (pnum >= 0) { if (pnum != j) @@ -844,31 +847,32 @@ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int } else if (svprogfuncs) { - if (!((int)client->edict->xv->dimension_see & dimension_mask)) - continue; - - if (!mask) //no pvs? broadcast. - goto inrange; - - if (client->penalties & BAN_BLIND) - continue; - - if (to == MULTICAST_PHS_R || to == MULTICAST_PHS) + client_t *seat = client; + for(seat = client; seat; seat = seat->controlled) { - vec3_t delta; - VectorSubtract(origin, client->edict->v->origin, delta); - if (DotProduct(delta, delta) <= 1024*1024) - goto inrange; - } - - { - vec3_t pos; - VectorAdd(client->edict->v->origin, client->edict->v->view_ofs, pos); - cluster = sv.world.worldmodel->funcs.ClusterForPoint (sv.world.worldmodel, pos); - if (cluster>= 0 && !(mask[cluster>>3] & (1<<(cluster&7)) ) ) - { - // Con_Printf ("PVS supressed multicast\n"); + if (!((int)seat->edict->xv->dimension_see & dimension_mask)) continue; + + if (!mask) //no pvs? broadcast. + goto inrange; + + if (to == MULTICAST_PHS_R || to == MULTICAST_PHS) + { + vec3_t delta; + VectorSubtract(origin, seat->edict->v->origin, delta); + if (DotProduct(delta, delta) <= 1024*1024) + goto inrange; + } + + { + vec3_t pos; + VectorAdd(seat->edict->v->origin, seat->edict->v->view_ofs, pos); + cluster = sv.world.worldmodel->funcs.ClusterForPoint (sv.world.worldmodel, pos); + if (cluster>= 0 && !(mask[cluster>>3] & (1<<(cluster&7)) ) ) + { + // Con_Printf ("PVS supressed multicast\n"); + continue; + } } } } diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index bd3efcfaf..90a68e70f 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -3655,6 +3655,48 @@ void SV_Pause_f (void) } +static void SV_UpdateSeats(client_t *controller) +{ + client_t *cl, *prev; + int curclients; + + for (curclients = 0, cl = controller; cl; cl = cl->controlled) + { + prev = cl; + curclients++; + } + + ClientReliableWrite_Begin(controller, svc_signonnum, 2+curclients); + ClientReliableWrite_Byte(controller, curclients); + for (curclients = 0, cl = controller; cl; cl = cl->controlled, curclients++) + { + ClientReliableWrite_Byte(controller, cl - svs.clients); + } + + for (curclients = 0, cl = controller; cl; cl = cl->controlled, curclients++) + { +// send a fixangle over the reliable channel to make sure it gets there +// Never send a roll angle, because savegames can catch the server +// in a state where it is expecting the client to correct the angle +// and it won't happen if the game was just loaded, so you wind up +// with a permanent head tilt + ClientReliableWrite_Begin(controller, svcfte_choosesplitclient, 2+curclients); + ClientReliableWrite_Byte (controller, curclients); + ClientReliableWrite_Byte (controller, svc_setangle); + if (cl->edict->v->fixangle) + { + ClientReliableWrite_Angle(controller, cl->edict->v->angles[0]); + ClientReliableWrite_Angle(controller, cl->edict->v->angles[1]); + ClientReliableWrite_Angle(controller, 0);//cl->edict->v->angles[2]); + } + else + { + ClientReliableWrite_Angle(controller, cl->edict->v->v_angle[0]); + ClientReliableWrite_Angle(controller, cl->edict->v->v_angle[1]); + ClientReliableWrite_Angle(controller, 0);//cl->edict->v->v_angle[2]); + } + } +} /* ================= @@ -3666,6 +3708,7 @@ The client is going to disconnect, so remove the connection immediately void SV_Drop_f (void) { extern cvar_t sv_fullredirect; + client_t *prev; SV_EndRedirect (); if (!host_client->drop) @@ -3681,6 +3724,26 @@ void SV_Drop_f (void) } host_client->drop = true; } + + //if splitscreen, orphan the dropper + if (host_client->controller) + { + for (prev = host_client->controller; prev; prev = prev->controlled) + { + if (prev->controlled == host_client) + { + prev->controlled = host_client->controlled; + host_client->netchan.remote_address.type = NA_INVALID; //so the remaining client doesn't get the kick too. + host_client->protocol = SCP_BAD; //make it a bit like a bot, so we don't try sending any datagrams/reliables at someone that isn't able to receive anything. + + SV_UpdateSeats(host_client->controller); + host_client->controller->joinobservelockeduntil = realtime + 3; + host_client->controlled = NULL; + host_client->controller = NULL; + break; + } + } + } } /* @@ -4494,6 +4557,78 @@ void SV_SetUpClientEdict (client_t *cl, edict_t *ent) ent->v->frags = 0; cl->connection_started = realtime; } + +//dynamically add/remove a splitscreen client +static void Cmd_AddSeat_f(void) +{ + client_t *cl, *prev; + qboolean changed = false; + //don't allow an altseat to add. paranoia. + if (host_client->controller) + return; + + if (host_client->state != cs_spawned) + return; + + if (!(host_client->fteprotocolextensions & PEXT_SPLITSCREEN)) + return; + + if (Cmd_Argc()>1) + { + int num = atoi(Cmd_Argv(1)); + int count; + + if (!num || host_client->joinobservelockeduntil > realtime) + return; + host_client->joinobservelockeduntil = realtime + 2; + + for (count = 1, prev = host_client, cl = host_client->controlled; cl; cl = cl->controlled) + { + if (count >= num) + { + for(; cl; cl = prev->controlled) + { + //unlink it + prev->controlled = cl->controlled; + cl->controller = NULL; + cl->controlled = NULL; + + //make it into a pseudo-bot + cl->netchan.remote_address.type = NA_INVALID; //so the remaining client doesn't get the kick too. + cl->protocol = SCP_BAD; //make it a bit like a bot, so we don't try sending any datagrams/reliables at someone that isn't able to receive anything. + + //okay, it can get lost now. + cl->drop = true; + } + host_client->controller->joinobservelockeduntil = realtime + 3; + changed = true; + break; + } + prev = cl; + count++; + } + + if (!changed && count <= num) + changed = !!SV_AddSplit(host_client, Cmd_Argv(2), num); + } + else + { + cl = NULL; +/* if (host_client->joinobservelockeduntil > realtime) + { + SV_TPrintToClient(host_client, PRINT_HIGH, va("Please wait %.1g more seconds\n", host_client->joinobservelockeduntil-realtime)); + return; + } + host_client->joinobservelockeduntil = realtime + 2; + + cl = SV_AddSplit(host_client, host_client->userinfo, 0); +*/ + } + + if (cl || changed) + SV_UpdateSeats(host_client); +} + /* ================== Cmd_Join_f @@ -4825,6 +4960,9 @@ void Cmd_FPSList_f(void) void SV_EnableClientsCSQC(void) { + if (host_client->controller) + return; + #ifdef PEXT_CSQC // if ((host_client->fteprotocolextensions & PEXT_CSQC) || atoi(Cmd_Argv(1))) { @@ -5314,6 +5452,7 @@ ucmd_t ucmds[] = {"nextdl", SV_NextDownload_f, true}, /*quakeworld specific things*/ + {"addseat", Cmd_AddSeat_f}, {"join", Cmd_Join_f}, {"observe", Cmd_Observe_f}, {"snap", SV_NoSnap_f},