From df6b651eeb024f2c75035831bc3072f06472b873 Mon Sep 17 00:00:00 2001 From: Spoike Date: Mon, 9 Jan 2023 05:11:34 +0000 Subject: [PATCH] Better compat with QE. EX_PROMPT now supported serverside (emulated for non-qe clients). Per-client localisation now works. Scoreboards are now a little nicer when running mods with well-defined teams (eg NQ ssqc). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6309 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_ents.c | 12 ++- engine/client/cl_input.c | 2 +- engine/client/cl_main.c | 4 +- engine/client/cl_parse.c | 14 +-- engine/client/cl_screen.c | 193 +++++++++++++++++++++++++--------- engine/client/sbar.c | 214 ++++++++++++++++++++++++++++++-------- engine/common/common.h | 4 +- engine/common/pr_bgcmd.c | 2 +- engine/common/translate.c | 53 +++++----- engine/common/translate.h | 1 + engine/server/pr_cmds.c | 189 +++++++++++++++++++++++++++++++-- engine/server/server.h | 20 ++++ engine/server/sv_main.c | 25 ++++- engine/server/sv_send.c | 66 ++++++++++++ engine/server/sv_user.c | 70 ++++++++++++- 15 files changed, 714 insertions(+), 155 deletions(-) diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index d5317b07e..6844a5db9 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -4166,12 +4166,14 @@ void CL_LinkPacketEntities (void) if (state->effects & EF_BRIGHTLIGHT) { radius = max(radius,r_brightlight_colour.vec4[3]); - VectorAdd(colour, r_brightlight_colour.vec4, colour); + if (!(state->effects & (EF_RED|EF_GREEN|EF_BLUE))) + VectorAdd(colour, r_brightlight_colour.vec4, colour); } if (state->effects & EF_DIMLIGHT) { radius = max(radius,r_dimlight_colour.vec4[3]); - VectorAdd(colour, r_dimlight_colour.vec4, colour); + if (!(state->effects & (EF_RED|EF_GREEN|EF_BLUE))) + VectorAdd(colour, r_dimlight_colour.vec4, colour); } if (state->effects & EF_BLUE) { @@ -5398,12 +5400,14 @@ void CL_LinkPlayers (void) if (state->effects & EF_BRIGHTLIGHT) { radius = max(radius,r_brightlight_colour.vec4[3]); - VectorAdd(colour, r_brightlight_colour.vec4, colour); + if (!(state->effects & (EF_RED|EF_GREEN|EF_BLUE))) + VectorAdd(colour, r_brightlight_colour.vec4, colour); } if (state->effects & EF_DIMLIGHT) { radius = max(radius,r_dimlight_colour.vec4[3]); - VectorAdd(colour, r_dimlight_colour.vec4, colour); + if (!(state->effects & (EF_RED|EF_GREEN|EF_BLUE))) + VectorAdd(colour, r_dimlight_colour.vec4, colour); } if (state->effects & EF_BLUE) { diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index b2808f5d9..fa22536f4 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -1837,7 +1837,7 @@ void CLNQ_SendMove (usercmd_t *cmd, int pnum, sizebuf_t *buf) MSG_WriteFloat (buf, cmd->fservertime); // use latest time. because ping reports! if (cls.qex) - MSG_WriteByte(buf, 4); + MSG_WriteByte(buf, 1); for (i=0 ; i<3 ; i++) { diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 096678d7d..0aa407dc7 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -4048,8 +4048,8 @@ void CLNQ_ConnectionlessPacket(void) return; } - if (length == 5 && net_from.prot == NP_DTLS) - { + if (length == 5) + { //QE strips the port entirely. cls.proquake_angles_hack = false; cls.protocol_nq = CPNQ_ID; Con_DPrintf("QuakeEx server...\n"); diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 026e473d7..ef2fad995 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -4136,11 +4136,11 @@ static void CLQEX_ParsePrompt(void) SCR_CenterPrint(0, NULL, true); return; } - + *message = 0; s = MSG_ReadString(); Q_strncatz(message+ofs, "/S/C/.", sizeof(message)-ofs); ofs += strlen(message+ofs); - TL_Reformat(message+ofs, sizeof(message)-ofs, 1, &s); + TL_Reformat(com_language, message+ofs, sizeof(message)-ofs, 1, &s); ofs += strlen(message+ofs); Q_strncatz(message+ofs, "\n", sizeof(message)-ofs); @@ -4151,7 +4151,7 @@ static void CLQEX_ParsePrompt(void) imp = MSG_ReadByte(); Q_strncatz(message+ofs, "^[[", sizeof(message)-ofs); ofs += strlen(message+ofs); - TL_Reformat(message+ofs, sizeof(message)-ofs, 1, &s); + TL_Reformat(com_language, message+ofs, sizeof(message)-ofs, 1, &s); ofs += strlen(message+ofs); Q_strncatz(message+ofs, va("]\\impulse\\%i^]\n", imp), sizeof(message)-ofs); ofs += strlen(message+ofs); @@ -4175,7 +4175,7 @@ static char *CLQEX_ReadStrings(void) for (; a < count; a++) MSG_ReadString(); //don't lose space, though we can't buffer it. - TL_Reformat(formatted, sizeof(formatted), a, arg); + TL_Reformat(com_language, formatted, sizeof(formatted), a, arg); return formatted; } @@ -7606,7 +7606,7 @@ void CLQW_ParseServerMessage (void) cl.completed_time = cl.gametime; } cl.intermissionmode = IM_NQFINALE; - SCR_CenterPrint (destsplit, TL_Translate(MSG_ReadString ()), false); + SCR_CenterPrint (destsplit, TL_Translate(com_language, MSG_ReadString ()), false); break; case svc_sellscreen: @@ -8784,8 +8784,8 @@ void CLNQ_ParseServerMessage (void) //FIXME: figure out the player index so we can kickban/mute/etc them. qbyte plcolour = MSG_ReadByte(); qbyte chatcolour = MSG_ReadByte(); - char *pcols[] = {S_COLOR_WHITE,S_COLOR_RED,S_COLOR_YELLOW, S_COLOR_CYAN}; - char *ccols[] = {S_COLOR_WHITE,S_COLOR_CYAN}; + char *pcols[] = {S_COLOR_WHITE,S_COLOR_GREEN,S_COLOR_CYAN, S_COLOR_YELLOW}; + char *ccols[] = {S_COLOR_WHITE,S_COLOR_CYAN}; //say, say_team Con_Printf("^[%s%s"/*"\\player\\%i"*/"^]: ", pcols[plcolour%countof(pcols)], MSG_ReadString()); Con_Printf("%s%s\n", ccols[chatcolour%countof(ccols)], MSG_ReadString()); break; diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index ac7ea9089..c8f20bf2d 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -327,6 +327,8 @@ typedef struct { float time_off; int erase_lines; int erase_center; + + int oldmousex, oldmousey; //so the cursorchar can be changed by keyboard without constantly getting stomped on. } cprint_t; cprint_t scr_centerprint[MAX_SPLITS]; @@ -682,7 +684,7 @@ int SCR_DrawCenterString (vrect_t *playerrect, cprint_t *p, struct font_s *font) int remaining; shader_t *pic; int ch; - int mousex,mousey; + int mousex,mousey, mousemoved; conchar_t *line_start[MAX_CPRINT_LINES]; conchar_t *line_end[MAX_CPRINT_LINES]; @@ -741,6 +743,10 @@ int SCR_DrawCenterString (vrect_t *playerrect, cprint_t *p, struct font_s *font) Font_BeginString(font, rect.x+rect.width, rect.y+rect.height, &right, &bottom); linecount = Font_LineBreaks(p->string, p->string + p->charcount, (p->flags & CPRINT_NOWRAP)?0x7fffffff:(right - left), MAX_CPRINT_LINES, line_start, line_end); + mousemoved = mousex != p->oldmousex || mousey != p->oldmousey; + p->oldmousex = mousex; + p->oldmousey = mousey; + ch = Font_CharHeight(); if (p->flags & CPRINT_TALIGN) @@ -791,9 +797,27 @@ int SCR_DrawCenterString (vrect_t *playerrect, cprint_t *p, struct font_s *font) else x = left + (right - left - Font_LineWidth(line_start[l], line_end[l]))/2; - if (mousey >= y && mousey < y+ch) + if (mousemoved && mousey >= y && mousey < y+ch) { - p->cursorchar = Font_CharAt(mousex - x, line_start[l], line_end[l]); + conchar_t *linkstart; + linkstart = Font_CharAt(mousex - x, line_start[l], line_end[l]); + + //scan backwards to find any link enclosure + if (linkstart) + for(linkstart = linkstart-1; linkstart >= line_start[l]; linkstart--) + { + if (*linkstart == CON_LINKSTART) + { + //found one + p->cursorchar = linkstart; + break; + } + if (*linkstart == CON_LINKEND) + { + //some other link ended here. don't use its start. + break; + } + } } remaining -= line_end[l]-line_start[l]; @@ -803,6 +827,25 @@ int SCR_DrawCenterString (vrect_t *playerrect, cprint_t *p, struct font_s *font) if (line_end[l] <= line_start[l]) break; } + + if (p->cursorchar && p->cursorchar >= line_start[l] && p->cursorchar < line_end[l] && *p->cursorchar == CON_LINKSTART) + { + conchar_t *linkend; + int s, e; + for (linkend = p->cursorchar; linkend < line_end[l]; linkend++) + if (*linkend == CON_LINKEND) + break; + + s = x+Font_LineWidth(line_start[l], p->cursorchar); + e = x+Font_LineWidth(line_start[l], linkend); + + //draw a 2-pixel underscore (behind the text). + R2D_ImageColours(SRGBA(0.3,0.3,0.3, 1)); //mouseover. + R2D_FillBlock((s*vid.width)/(float)vid.rotpixelwidth, ((y+Font_CharHeight()-2)*vid.height)/(float)vid.rotpixelheight, ((e - s)*vid.width)/(float)vid.rotpixelwidth, (2*vid.height)/(float)vid.rotpixelheight); + R2D_Flush(); + R2D_ImageColours(SRGBA(1, 1, 1, 1)); + } + Font_LineDraw(x, y, line_start[l], line_end[l]); } @@ -811,11 +854,59 @@ int SCR_DrawCenterString (vrect_t *playerrect, cprint_t *p, struct font_s *font) return linecount; } +static void Key_CenterPrintActivate(int pnum) +{ + char *link; + cprint_t *p = &scr_centerprint[pnum]; + link = SCR_CopyCenterPrint(p); + if (link) + { + + if (link[0] == '^' && link[1] == '[') + { + //looks like it might be a link! + char *end = NULL; + char *info; + for (info = link + 2; *info; ) + { + if (info[0] == '^' && info[1] == ']') + break; //end of tag, with no actual info, apparently + if (*info == '\\') + break; + else if (info[0] == '^' && info[1] == '^') + info+=2; + else + info++; + } + for(end = info; *end; ) + { + if (end[0] == '^' && end[1] == ']') + { + //okay, its a valid link that they clicked + *end = 0; + +#ifdef PLUGINS + if (!Plug_ConsoleLink(link+2, info, "")) +#endif +#ifdef CSQC_DAT + if (!CSQC_ConsoleLink(link+2, info)) +#endif + Key_DefaultLinkClicked(NULL, link+2, info); + + break; + } + if (end[0] == '^' && end[1] == '^') + end+=2; + else + end++; + } + } + } +} qboolean Key_Centerprint(int key, int unicode, unsigned int devid) { int pnum; cprint_t *p; - char *link; if (key == K_MOUSE1) { @@ -824,52 +915,7 @@ qboolean Key_Centerprint(int key, int unicode, unsigned int devid) { p = &scr_centerprint[pnum]; if (cl.playerview[pnum].gamerectknown == cls.framecount) - { - link = SCR_CopyCenterPrint(p); - if (link) - { - - if (link[0] == '^' && link[1] == '[') - { - //looks like it might be a link! - char *end = NULL; - char *info; - for (info = link + 2; *info; ) - { - if (info[0] == '^' && info[1] == ']') - break; //end of tag, with no actual info, apparently - if (*info == '\\') - break; - else if (info[0] == '^' && info[1] == '^') - info+=2; - else - info++; - } - for(end = info; *end; ) - { - if (end[0] == '^' && end[1] == ']') - { - //okay, its a valid link that they clicked - *end = 0; - -#ifdef PLUGINS - if (!Plug_ConsoleLink(link+2, info, "")) -#endif -#ifdef CSQC_DAT - if (!CSQC_ConsoleLink(link+2, info)) -#endif - Key_DefaultLinkClicked(NULL, link+2, info); - - break; - } - if (end[0] == '^' && end[1] == '^') - end+=2; - else - end++; - } - } - } - } + Key_CenterPrintActivate(pnum); } return true; //handled } @@ -882,6 +928,52 @@ qboolean Key_Centerprint(int key, int unicode, unsigned int devid) } return true; } + else if ((key == K_ENTER || + key == K_KP_ENTER || + key == K_GP_DIAMOND_RIGHT) && devid < countof(scr_centerprint)) + { + p = &scr_centerprint[devid]; + if (p->cursorchar) + Key_CenterPrintActivate(devid); + return true; + } + else if ((key == K_UPARROW || + key == K_LEFTARROW || + key == K_KP_UPARROW || + key == K_KP_LEFTARROW || + key == K_GP_DPAD_UP || + key == K_GP_DPAD_LEFT) && devid < countof(scr_centerprint)) + { + p = &scr_centerprint[devid]; + if (!p->cursorchar) + p->cursorchar = p->string + p->charcount; + while (--p->cursorchar >= p->string) + { + if (*p->cursorchar == CON_LINKSTART) + return true; //found one + } + p->cursorchar = NULL; + return true; + } + else if ((key == K_DOWNARROW || + key == K_RIGHTARROW || + key == K_KP_DOWNARROW || + key == K_KP_RIGHTARROW || + key == K_GP_DPAD_DOWN || + key == K_GP_DPAD_RIGHT) && devid < countof(scr_centerprint)) + { + p = &scr_centerprint[devid]; + if (!p->cursorchar) + p->cursorchar = p->string-1; + while (++p->cursorchar < p->string + p->charcount) + { + if (*p->cursorchar == CON_LINKSTART) + return true; //found one + } + p->cursorchar = NULL; //hit the end + return true; + } + return false; } @@ -895,7 +987,6 @@ void SCR_CheckDrawCenterString (void) for (pnum = 0; pnum < cl.splitclients; pnum++) { p = &scr_centerprint[pnum]; - p->cursorchar = NULL; #ifdef QUAKESTATS if (IN_DrawWeaponWheel(pnum)) diff --git a/engine/client/sbar.c b/engine/client/sbar.c index 694446e98..40f458ad7 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -126,6 +126,9 @@ static qboolean sbarfailed; #ifdef HEXEN2 static qboolean sbar_hexen2; #endif +#ifdef NQPROT +static void Sbar_CTFScores_f(void); +#endif vrect_t sbar_rect; //screen area that the sbar must fit. float sbar_rect_left; @@ -803,15 +806,6 @@ void Sbar_ShowScores (void) sb_updates = 0; } -#ifdef NQPROT -static void Sbar_CTFScores_f(void) -{ //issued via stuffcmds. -// int red = atoi(Cmd_Argv(1)); -// int blue = atoi(Cmd_Argv(2)); -// int flags = atoi(Cmd_Argv(3)); //base|carried|dropped | base|carried|dropped -} -#endif - #ifdef HEXEN2 static void Sbar_Hexen2InvLeft_f(void) { @@ -1455,9 +1449,69 @@ static void Sbar_Hexen2DrawNum (float x, float y, int num, int digits) } #endif +#ifdef NQPROT +//this stuff was added to the rerelease's ctf mod. +int cl_ctfredscore; +int cl_ctfbluescore; +int cl_ctfflags; +static void Sbar_CTFScores_f(void) +{ //issued via stuffcmds. + cl_ctfredscore = atoi(Cmd_Argv(1)); + cl_ctfbluescore = atoi(Cmd_Argv(2)); + cl_ctfflags = atoi(Cmd_Argv(3)) | 0x100; //base|carried|dropped | base|carried|dropped +} +static void Sbar_DrawCTFScores(playerview_t *pv) +{ + if (cl_ctfflags) + { + int i, x, y, sy; + static struct + { + int colour; + int *score; + const char *base, *held, *dropped; //at base, held, dropped. + int shift; + } team[] = + { + {4, &cl_ctfredscore, "gfx/redf1.lmp", "gfx/redf2.lmp", "gfx/redf3.lmp", 0}, + {13, &cl_ctfbluescore, "gfx/bluef1.lmp", "gfx/bluef2.lmp", "gfx/bluef3.lmp", 3}, + }; + + x = sbar_rect.x + sbar_rect.width - (48+2); + sy = sbar_rect.y + sbar_rect.height-sb_lines; + + if (!cl_sbar.value && sb_lines > 24 && scr_viewsize.value>=100 && !cl_hudswap.value) + x -= 42; //QW hud nudges it in by 24 to not cover ammo. + + for (i = 0, y=sy-17; i < countof(team); i++, y -= 18) + { + if (cl.players[pv->playernum].rbottomcolor == team[i].colour) + Sbar_FillPC (x-1, y-1, 48+2, 16+2, 12); + Sbar_FillPC (x+16, y, 32, 16, team[i].colour); + } + R2D_ImageColours(1.0, 1.0, 1.0, 1.0); + + for (i = 0, y=sy-17; i < countof(team); i++, y -= 18) + { + int fl = (cl_ctfflags>>team[i].shift)&7; + mpic_t *pic = NULL; + if (fl == 1) + pic = R2D_SafeCachePic(team[i].base); + else if (fl == 2) + pic = R2D_SafeCachePic(team[i].held); + else if (fl == 4) + pic = R2D_SafeCachePic(team[i].dropped); + if (pic) + R2D_ScalePic(x, y, 16, 16, pic); + } + for (i = 0, y=sy-17; i < countof(team); i++, y -= 18) + Draw_FunStringWidth (x+16, y+((16-8)/2), va("%i", *team[i].score), 32, 2, false); + } +} +#endif + //============================================================================= -int playerteam[MAX_CLIENTS]; typedef struct { char team[16+1]; int frags; @@ -1466,11 +1520,13 @@ typedef struct { int topcolour, bottomcolour; qboolean ownteam; } team_t; -team_t teams[MAX_CLIENTS]; -int teamsort[MAX_CLIENTS]; -int scoreboardteams; +static int playerteam[MAX_CLIENTS]; +static team_t teams[MAX_CLIENTS]; +static int teamsort[MAX_CLIENTS]; +static int scoreboardteams; +static qboolean consistentteams; //can hide the 'team' displays when colours are enough. -struct +static struct { unsigned char upper; short frags; @@ -1567,9 +1623,12 @@ void Sbar_SortTeams (playerview_t *pv) player_info_t *s; char t[16+1]; int ownnum; + unsigned int seen; + char *end; // request new ping times every two second scoreboardteams = 0; + consistentteams = false; if (!cl.teamplay) return; @@ -1652,6 +1711,40 @@ void Sbar_SortTeams (playerview_t *pv) teamsort[i] = teamsort[j]; teamsort[j] = k; } + + seen = 0; + for (i = 0; i < scoreboardteams; i++) + { + //make sure the colour is one of quake's valid non-fullbright colour ranges. + if (teams[i].bottomcolour < 0 || teams[i].bottomcolour > 13) + return; + + //don't allow multiple teams with the same colour but different names. + if (seen & (1<team, 4*8, false, false); \ +#define COL_TEAM_TEAM if (!consistentteams){COLUMN("team", 4*8, {Draw_FunStringWidth ( x, y, tm->team, 4*8, false, false); })} +#define COL_TEAM_TOTAL COLUMN("total", 5*8, {Draw_FunString ( x, y, va("%5i", tm->frags)); \ if (ourteam)\ {\ Draw_FunString ( x - 1*8, y, "^Ue010");\ - Draw_FunString ( x + 4*8, y, "^Ue011");\ + Draw_FunString ( x + 5*8, y, "^Ue011");\ }\ }) -#define COL_TEAM_TOTAL COLUMN("total", 5*8, {Draw_FunString ( x, y, va("%5i", tm->frags)); }) #define COL_TEAM_PLAYERS COLUMN("players", 7*8, {Draw_FunString ( x, y, va("%5i", tm->players)); }) #define ALL_TEAM_COLUMNS COL_TEAM_LOWAVGHIGH COL_TEAM_TEAM COL_TEAM_TOTAL COL_TEAM_PLAYERS @@ -3092,6 +3189,9 @@ void Sbar_TeamOverlay (playerview_t *pv) return; } + // sort the teams + Sbar_SortTeams(pv); + y = gr.y; if (scr_scoreboard_drawtitle.ival) @@ -3157,9 +3257,6 @@ void Sbar_TeamOverlay (playerview_t *pv) #undef COLUMN } -// sort the teams - Sbar_SortTeams(pv); - if (pv->spectator) trackplayer = Cam_TrackNum(pv); else @@ -3326,7 +3423,7 @@ ping time frags name Sbar_FillPC(x, y+4, 40, 4, bottom); \ } \ }) -#define COLUMN_TEAMNAME COLUMN(team, 4*8, \ +#define COLUMN_TEAMNAME COLUMN(team, (consistentteams?0:(4*8)), \ { \ if (!s->spectator) \ { \ @@ -3454,7 +3551,7 @@ void Sbar_DeathmatchOverlay (playerview_t *pv, int start) rank_width = 0; -#define COLUMN(title, cwidth, code, fill) if (rank_width+(cwidth)+8 <= gr.width) {showcolumns |= (1<= 640) - Sbar_SortTeams(pv); +// if (sbar_rect.width >= 640) + Sbar_SortTeams(pv); if (!scoreboardlines) return; // no one there? @@ -3769,7 +3866,7 @@ static void Sbar_MiniDeathmatchOverlay (playerview_t *pv) Q_strncpyz(name, s->name, sizeof(name)); // team and name - if (cl.teamplay) + if (cl.teamplay && !consistentteams) { Draw_FunStringWidth (x+48, y, s->team, 32, false, false); Draw_FunStringWidth (x+48+40, y, name, MAX_DISPLAYEDNAME*8, false, false); @@ -3778,38 +3875,67 @@ static void Sbar_MiniDeathmatchOverlay (playerview_t *pv) Draw_FunStringWidth (x+48, y, name, MAX_DISPLAYEDNAME*8, false, false); y += 8; } + x += 48; + if (cl.teamplay && !consistentteams) + x += 40; + x += MAX_DISPLAYEDNAME*8; + x += 8; // draw teams if room - if (sbar_rect.width < 640 || !cl.teamplay) + if (x + (consistentteams?40:80) > sbar_rect.x+sbar_rect.width || !cl.teamplay) return; + y = sbar_rect.y + sbar_rect.height - sb_lines; + // draw seperator - x += 208; + R2D_ImageColours(0.3, 0.3, 0.3, 1); + R2D_FillBlock(x, y, 2, sb_lines); + R2D_ImageColours(1,1,1,1); + x += 2; // for (y = sbar_rect.height - sb_lines; y < sbar_rect.height - 6; y += 2) // Draw_ColouredCharacter(x, y, CON_WHITEMASK|14); - x += 16; + x += 8; - y = sbar_rect.height - sb_lines; - for (i=0 ; i < scoreboardteams && y <= sbar_rect.height; i++) + for (i=0 ; i < scoreboardteams && y <= sbar_rect.y+sbar_rect.height; i++) { k = teamsort[i]; tm = teams + k; - // draw pings - Draw_FunStringWidth (x, y, tm->team, 32, false, false); - - // draw total - sprintf (num, "%5i", tm->frags); - Draw_FunString(x + 40, y, num); - - if (!strncmp(cl.players[pv->playernum].team, tm->team, 16)) + if (consistentteams) { - Font_BeginString(font_default, x-8, y, &px, &py); - Font_DrawChar(px, py, CON_WHITEMASK, 16|0xe000); - Font_BeginString(font_default, x+32, y, &px, &py); - Font_DrawChar(px, py, CON_WHITEMASK, 17|0xe000); - Font_EndString(font_default); + // draw total + Sbar_FillPC ( x, y+1, 48, 3, tm->topcolour); + Sbar_FillPC ( x, y+4, 48, 4, tm->bottomcolour); + R2D_ImageColours(1,1,1,1); + sprintf (num, "%i", tm->frags); + Draw_FunStringWidth(x,y,num, 40, true, false); + if (!strncmp(cl.players[pv->playernum].team, tm->team, 16)) + { + Font_BeginString(font_default, x, y, &px, &py); + Font_DrawChar(px, py, CON_WHITEMASK, 16|0xe000); + Font_BeginString(font_default, x+40, y, &px, &py); + Font_DrawChar(px, py, CON_WHITEMASK, 17|0xe000); + Font_EndString(font_default); + } + } + else + { + // draw name + Draw_FunStringWidth (x, y, tm->team, 32, false, false); + + // draw total + sprintf (num, "%5i", tm->frags); + Draw_FunString(x + 40, y, num); + + if (!strncmp(cl.players[pv->playernum].team, tm->team, 16)) + { + Font_BeginString(font_default, x-8, y, &px, &py); + Font_DrawChar(px, py, CON_WHITEMASK, 16|0xe000); + Font_BeginString(font_default, x+32, y, &px, &py); + Font_DrawChar(px, py, CON_WHITEMASK, 17|0xe000); + Font_EndString(font_default); + } } y += 8; diff --git a/engine/common/common.h b/engine/common/common.h index da3172b25..536ea67f6 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -981,8 +981,8 @@ struct po_s *PO_Create(void); void PO_Merge(struct po_s *po, vfsfile_t *file); const char *PO_GetText(struct po_s *po, const char *msg); void PO_Close(struct po_s *po); -const char *TL_Translate(const char *src); //$foo translations. -void TL_Reformat(char *out, size_t outsize, size_t numargs, const char **arg); //"{0} died\n" formatting (with $foo translation, on each arg) +const char *TL_Translate(int language, const char *src); //$foo translations. +void TL_Reformat(int language, char *out, size_t outsize, size_t numargs, const char **arg); //"{0} died\n" formatting (with $foo translation, on each arg) // // log.c diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index d51588108..853964181 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -7996,7 +7996,7 @@ qc_extension_t QSG_Extensions[] = { {"EXT_DIMENSION_GHOST"}, // {"EX_EXTENDED_EF", NULL, 0,{NULL}, "Provides EF_CANDLELIGHT..."}, // {"EX_MOVETYPE_GIB", NULL, 0,{NULL}, "Adds MOVETYPE_GIB - basically MOVETYPE_BOUNCE with gravity controlled by a cvar instead of just setting the .gravity field..."}, -// {"EX_PROMPT", NULL, 3,{"ex_prompt", "ex_promptchoice", "ex_clearprompt"}, "Engine-driven alternative to centerprint menus."}, + {"EX_PROMPT", NULL, 3,{"ex_prompt", "ex_promptchoice", "ex_clearprompt"}, "Engine-driven alternative to centerprint menus."}, {"FRIK_FILE", check_notrerelease, 11,{"stof", "fopen","fclose","fgets","fputs","strlen","strcat","substring","stov","strzone","strunzone"}}, {"FTE_CALLTIMEOFDAY", NULL, 1,{"calltimeofday"}, "Replication of mvdsv functionality (call calltimeofday to cause 'timeofday' to be called, with arguments that can be saved off to a global). Generally strftime is simpler to use."}, {"FTE_CSQC_ALTCONSOLES", NULL, 4,{"con_getset", "con_printf", "con_draw", "con_input"}, "The engine tracks multiple consoles. These may or may not be directly visible to the user."}, diff --git a/engine/common/translate.c b/engine/common/translate.c index 73fbadab3..02d9224c3 100644 --- a/engine/common/translate.c +++ b/engine/common/translate.c @@ -15,15 +15,9 @@ int com_language; char sys_language[64] = ""; static char langpath[MAX_OSPATH] = ""; struct language_s languages[MAX_LANGUAGES]; -static struct po_s *com_translations; static void QDECL TL_LanguageChanged(struct cvar_s *var, char *oldvalue) { - if (com_translations) - { - PO_Close(com_translations); - com_translations = NULL; - } com_language = TL_FindLanguage(var->string); } @@ -46,6 +40,9 @@ void TL_Shutdown(void) languages[j].name = NULL; PO_Close(languages[j].po); languages[j].po = NULL; + + PO_Close(languages[j].po_qex); + languages[j].po_qex = NULL; } } @@ -75,10 +72,10 @@ static int TL_LoadLanguage(char *lang) //keep truncating until we can find a name that works u = strrchr(lang, '_'); if (u) + { *u = 0; - else - lang = ""; - return TL_LoadLanguage(lang); + return TL_LoadLanguage(lang); + } } languages[j].name = strdup(lang); languages[j].po = NULL; @@ -503,7 +500,7 @@ const char *PO_GetText(struct po_s *po, const char *msg) } -static void PO_Merge_Rerelease(struct po_s *po, const char *fmt) +static void PO_Merge_Rerelease(struct po_s *po, const char *langname, const char *fmt) { //FOO = "CString" char line[32768]; @@ -512,22 +509,22 @@ static void PO_Merge_Rerelease(struct po_s *po, const char *fmt) char *s; vfsfile_t *file = NULL; - if (!file && *language.string) //use system locale names - file = FS_OpenVFS(va(fmt, language.string), "rb", FS_GAME); + if (!file && *langname) //use system locale names + file = FS_OpenVFS(va(fmt, langname), "rb", FS_GAME); if (!file) //make a guess { s = NULL; - if (language.string[0] && language.string[1] && (!language.string[2] || language.string[2] == '-' || language.string[2] == '_')) + if (langname[0] && langname[1] && (!langname[2] || langname[2] == '-' || langname[2] == '_')) { //try to map the user's formal locale to the rerelease's arbitrary names (at least from the perspective of anyone who doesn't speak english). - if (!strncmp(language.string, "fr", 2)) + if (!strncmp(langname, "fr", 2)) s = "french"; - else if (!strncmp(language.string, "de", 2)) + else if (!strncmp(langname, "de", 2)) s = "german"; - else if (!strncmp(language.string, "it", 2)) + else if (!strncmp(langname, "it", 2)) s = "italian"; - else if (!strncmp(language.string, "ru", 2)) + else if (!strncmp(langname, "ru", 2)) s = "russian"; - else if (!strncmp(language.string, "es", 2)) + else if (!strncmp(langname, "es", 2)) s = "spanish"; } if (s) @@ -553,18 +550,18 @@ static void PO_Merge_Rerelease(struct po_s *po, const char *fmt) } } -const char *TL_Translate(const char *src) +const char *TL_Translate(int language, const char *src) { if (*src == '$') { - if (!com_translations) + if (!languages[language].po_qex) { char lang[64], *h; vfsfile_t *f = NULL; - com_translations = PO_Create(); - PO_Merge_Rerelease(com_translations, "localization/loc_%s.txt"); + languages[language].po_qex = PO_Create(); + PO_Merge_Rerelease(languages[language].po_qex, languages[language].name, "localization/loc_%s.txt"); - Q_strncpyz(lang, language.string, sizeof(lang)); + Q_strncpyz(lang, languages[language].name, sizeof(lang)); while ((h = strchr(lang, '-'))) *h = '_'; //standardise it if (*lang) @@ -579,20 +576,20 @@ const char *TL_Translate(const char *src) } } if (f) - PO_Merge(com_translations, f); + PO_Merge(languages[language].po_qex, f); } - src = PO_GetText(com_translations, src); + src = PO_GetText(languages[language].po_qex, src); } return src; } -void TL_Reformat(char *out, size_t outsize, size_t numargs, const char **arg) +void TL_Reformat(int language, char *out, size_t outsize, size_t numargs, const char **arg) { const char *fmt; const char *a; size_t alen; fmt = (numargs>0&&arg[0])?arg[0]:""; - fmt = TL_Translate(fmt); + fmt = TL_Translate(language, fmt); outsize--; while (outsize > 0) @@ -623,7 +620,7 @@ void TL_Reformat(char *out, size_t outsize, size_t numargs, const char **arg) if (index >= numargs || !arg[index]) a = ""; else - a = TL_Translate(arg[index]); + a = TL_Translate(language, arg[index]); alen = strlen(a); if (alen > outsize) diff --git a/engine/common/translate.h b/engine/common/translate.h index 635b900bf..6b13ff491 100644 --- a/engine/common/translate.h +++ b/engine/common/translate.h @@ -12,6 +12,7 @@ struct language_s { char *name; struct po_s *po; + struct po_s *po_qex; }; extern struct language_s languages[MAX_LANGUAGES]; extern int com_language; diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 39005ef54..d204a25cc 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -10857,11 +10857,15 @@ static void QCBUILTIN PF_centerprint_qex(pubprogfuncs_t *prinst, struct globalva int args; char s[1024]; int entnum; + int lang = com_language; entnum = G_EDICTNUM(prinst, OFS_PARM0); for (args = 0; args+1 < prinst->callargc; args++) arg[args] = PR_GetStringOfs(prinst, OFS_PARM1+args*(OFS_PARM1-OFS_PARM0)); - TL_Reformat(s, sizeof(s), args, arg); + + if (entnum >= 1 && entnum <= sv.allocated_client_slots) + lang = svs.clients[entnum-1].language; + TL_Reformat(lang, s, sizeof(s), args, arg); PF_centerprint_Internal(entnum, false, s); } @@ -10893,7 +10897,6 @@ static void QCBUILTIN PF_sprint_qex(pubprogfuncs_t *prinst, struct globalvars_s for (args = 0; args+2 < prinst->callargc; args++) arg[args] = PR_GetStringOfs(prinst, OFS_PARM2+args*(OFS_PARM1-OFS_PARM0)); } - TL_Reformat(s, sizeof(s), args, arg); if (entnum < 1 || entnum > sv.allocated_client_slots) { @@ -10903,6 +10906,7 @@ static void QCBUILTIN PF_sprint_qex(pubprogfuncs_t *prinst, struct globalvars_s client = &svs.clients[entnum-1]; + TL_Reformat(client->language, s, sizeof(s), args, arg); SV_ClientPrintf (client, level, "%s", s); if (sv_specprint.ival & SPECPRINT_SPRINT) @@ -10929,7 +10933,6 @@ static void QCBUILTIN PF_bprint_qex(pubprogfuncs_t *prinst, struct globalvars_s { //TODO: send the strings to the client for localisation+reordering const char *arg[8]; int args; - char formatted[1024]; int level; if (progstype == PROG_QW) @@ -10945,8 +10948,178 @@ static void QCBUILTIN PF_bprint_qex(pubprogfuncs_t *prinst, struct globalvars_s arg[args] = PR_GetStringOfs(prinst, OFS_PARM0+args*(OFS_PARM1-OFS_PARM0)); } - TL_Reformat(formatted, sizeof(formatted), args, arg); - SV_BroadcastPrintf (level, "%s", formatted); + SV_BroadcastPrint_QexLoc (0, level, arg, args); +} + +void SV_Prompt_Input(client_t *cl, usercmd_t *ucmd) +{ + if (cl->prompt.active && !cl->qex) + { //with this serverside, we don't get autorepeat etc. + //we also can't prevent movement beyond the menu closure. + if (ucmd->forwardmove >= 100 && cl->prompt.oldmove[0] < 100) + { //up + if (cl->prompt.selected > 0) + cl->prompt.selected--; + else if (cl->prompt.numopts) + cl->prompt.selected = cl->prompt.numopts-1; //wrap. + + cl->prompt.nextsend = realtime; + } + else if (ucmd->forwardmove <= -100 && cl->prompt.oldmove[0] > -100) + { //down + cl->prompt.selected++; + if (cl->prompt.selected >= cl->prompt.numopts) + cl->prompt.selected = 0; //wrap. + cl->prompt.nextsend = realtime; + } + else if (ucmd->sidemove >= 100 && cl->prompt.oldmove[1] < 100) + { //right (use) + if (cl->prompt.selected < cl->prompt.numopts) + cl->edict->v->impulse = cl->prompt.opt[cl->prompt.selected].impulse; + } + cl->prompt.oldmove[0] = ucmd->forwardmove; + cl->prompt.oldmove[1] = ucmd->sidemove; + ucmd->forwardmove = 0; + ucmd->sidemove = 0; + } + else + { + cl->prompt.oldmove[0] = ucmd->forwardmove; + cl->prompt.oldmove[1] = ucmd->sidemove; + } +} +void SV_Prompt_Resend(client_t *client) +{ + Z_Free(client->centerprintstring); + client->centerprintstring = NULL; + + if (client->prompt.nextsend > realtime) + return; //still good. + + if (client->qex) + { //can depend on the client doing any translation stuff. + size_t sz; + sizebuf_t *msg; + size_t i; + + sz = 2; + if (client->prompt.numopts) + { + sz += strlen(client->prompt.header)+1; + for (i = 0; i < client->prompt.numopts; i++) + sz += strlen(client->prompt.opt[i].text)+2; + } + + msg = ClientReliable_StartWrite(client, sz); + + MSG_WriteByte(msg, svcqex_prompt); + MSG_WriteByte(msg, client->prompt.numopts); + if (client->prompt.numopts) + { + MSG_WriteString(msg, client->prompt.header); + for (i = 0; i < client->prompt.numopts; i++) + { + MSG_WriteString(msg, client->prompt.opt[i].text); + MSG_WriteByte(msg, client->prompt.opt[i].impulse); + } + } + + ClientReliable_FinishWrite(client); + + client->prompt.nextsend = DBL_MAX; //don't let it time out. + } + else + { //emulate via centerprints + const char *txt[4]; + size_t i; + client->prompt.nextsend = realtime + 1; //don't let it time out. + + if (client->prompt.numopts) + { + txt[0] = client->prompt.header?client->prompt.header:""; + for (i = 0; i < 3; i++) + { + if (client->prompt.selected + i - 1u < client->prompt.numopts) + txt[1+i] = client->prompt.opt[client->prompt.selected + i - 1u].text; + else + txt[1+i] = " "; + } + + //need to translate it too. + for (i = 0; i < countof(txt); i++) + txt[i] = TL_Translate(client->language, txt[i]); + + client->centerprintstring = va("%s\n%s\n^a[[ %s ]]^a\n%s", txt[0], txt[1], txt[2], txt[3]); + } + else + client->centerprintstring = client->prompt.header?client->prompt.header:""; + client->centerprintstring = Z_StrDup(client->centerprintstring); + } +} +void SV_Prompt_Clear(client_t *cl) +{ + int i; + cl->prompt.active = false; + Z_Free(cl->prompt.header); + cl->prompt.header = NULL; + cl->prompt.selected = 0; + cl->prompt.nextsend = realtime; + + for (i = 0; i < cl->prompt.numopts; i++) + Z_Free(cl->prompt.opt[i].text); + cl->prompt.numopts = cl->prompt.maxopts = 0; + Z_Free(cl->prompt.opt); + cl->prompt.opt = NULL; +} +static void QCBUILTIN PF_prompt_qex(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + int p = G_EDICT(prinst, OFS_PARM0)->entnum - 1; + client_t *cl; + const char *text = (prinst->callargc >= 2)?PR_GetStringOfs(prinst, OFS_PARM1):NULL; + unsigned int opts = (prinst->callargc >= 3)?G_FLOAT(OFS_PARM2):0; + if (p < 0 || p >= sv.allocated_client_slots) + { + PR_BIError (prinst, "PF_clearprompt_qex: not a player\n"); + return; + } + cl = &svs.clients[p]; + + SV_Prompt_Clear(cl); + if (!text) + { + SV_Prompt_Resend(cl); + return; + } + cl->prompt.active = true; + cl->prompt.maxopts = opts; + cl->prompt.numopts = 0; + Z_StrDupPtr(&cl->prompt.header, text); + cl->prompt.opt = Z_Malloc(sizeof(*cl->prompt.opt) * cl->prompt.maxopts); +} +static void QCBUILTIN PF_promptchoice_qex(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + int p = G_EDICT(prinst, OFS_PARM0)->entnum - 1; + client_t *cl; + const char *text = PR_GetStringOfs(prinst, OFS_PARM1); + int impulse = G_FLOAT(OFS_PARM2); + size_t opt; + if (p < 0 || p >= sv.allocated_client_slots) + { + PR_BIError (prinst, "PF_clearprompt_qex: not a player\n"); + return; + } + cl = &svs.clients[p]; + if (!cl->prompt.active) + { + PR_BIError (prinst, "PF_clearprompt_qex: too many options\n"); + return; + } + + opt = cl->prompt.numopts++; + if (opt >= cl->prompt.maxopts) + Z_ReallocElements((void**)&cl->prompt.opt, &cl->prompt.maxopts, opt+1, sizeof(*cl->prompt.opt)); + Z_StrDupPtr(&cl->prompt.opt[opt].text, text); + cl->prompt.opt[opt].impulse = impulse; } #define STUB ,NULL,true @@ -11215,9 +11388,9 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"ex_bot_followentity",PF_Fixme, 0, 0, 0,0, D("float(entity bot, entity goal)", "Behaviour is undocumented.")}, {"ex_CheckPlayerEXFlags",PF_CheckPlayerEXFlags_qex,0,0, 0,0, D("float(entity playerEnt)", "Behaviour is undocumented.")}, {"ex_walkpathtogoal",PF_walkpathtogoal_qex,0, 0, 0,0, D("float(float movedist, vector goal)", "Behaviour is undocumented.")}, -// {"ex_prompt", PF_prompt_qex, 0, 0, 0,0, D("void(entity player, string text, float numchoices)", "Behaviour is undocumented.")}, -// {"ex_promptchoice", PF_promptchoice_qex,0, 0, 0,0, D("void(entity player, string text, float impulse)", "Behaviour is undocumented.")}, -// {"ex_clearprompt", PF_clearprompt_qex, 0, 0, 0,0, D("void(entity player)", "Behaviour is undocumented.")}, + {"ex_prompt", PF_prompt_qex, 0, 0, 0,0, D("void(entity player, string text, float numchoices)", "Behaviour is undocumented.")}, + {"ex_promptchoice", PF_promptchoice_qex,0, 0, 0,0, D("void(entity player, string text, float impulse)", "Behaviour is undocumented.")}, + {"ex_clearprompt", PF_prompt_qex, 0, 0, 0,0, D("void(entity player)", "Behaviour is undocumented.")}, //End QuakeEx, for now. :( // Tomaz - QuakeC String Manipulation Begin diff --git a/engine/server/server.h b/engine/server/server.h index 96551ef00..a43045794 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -559,6 +559,21 @@ typedef struct client_s char *statss[MAX_CL_STATS]; char *centerprintstring; + struct + { + qboolean active; + char *header; + double nextsend; //qex is a one-off, other clients need spam. + size_t numopts, maxopts, selected; + struct + { + char *text; + int impulse; + } *opt; + + int oldmove[2]; + } prompt; + union{ //save space client_frame_t *frames; // updates can be deltad from here #ifdef Q2SERVER @@ -1303,6 +1318,7 @@ void SV_StuffcmdToClient_Unreliable(client_t *cl, const char *string); void VARGS SV_ClientPrintf (client_t *cl, int level, const char *fmt, ...) LIKEPRINTF(3); void VARGS SV_ClientTPrintf (client_t *cl, int level, translation_t text, ...); void VARGS SV_BroadcastPrintf (int level, const char *fmt, ...) LIKEPRINTF(2); +void SV_BroadcastPrint_QexLoc (unsigned int flags, int level, const char **arg, int args); void SV_BroadcastPrint (unsigned int flags, int level, const char *text); //flags exposed to ktx. #define BPRINT_IGNOREINDEMO (1<<0) // broad cast print will be not put in demo @@ -1317,6 +1333,10 @@ void SV_SendFixAngle(client_t *client, sizebuf_t *msg, int fixtype, qboolean rol void SV_BroadcastUserinfoChange(client_t *about, qboolean isbasic, const char *key, const char *newval); +void SV_Prompt_Resend(client_t *cl); +void SV_Prompt_Clear(client_t *cl); +void SV_Prompt_Input(client_t *cl, usercmd_t *ucmd); + // // sv_user.c // diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 54dc6585a..7438197c8 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -613,6 +613,7 @@ void SV_DropClient (client_t *drop) break; } + SV_Prompt_Clear(drop); if (drop->centerprintstring) Z_Free(drop->centerprintstring); drop->centerprintstring = NULL; @@ -1084,6 +1085,19 @@ void SV_FullClientUpdate (client_t *client, client_t *to) MSG_WriteByte(buf, playercolor); ClientReliable_FinishWrite(to); + if (to->qex) + { + unsigned int s1, s2; + if (client->netchan.remote_address.type == NA_LOOPBACK) + s1 = s2 = 0; //host + else + s1 = s2 = -1; //non-playfab connection + MSG_WriteByte(buf, svcqex_updatesocial); + MSG_WriteByte(buf, i); + MSG_WriteLong(buf, s1); + MSG_WriteLong(buf, s2); + } + if (to->fteprotocolextensions2 & PEXT2_PREDINFO) { char *s; @@ -1961,11 +1975,18 @@ void SV_AcceptMessage(client_t *newcl) MSG_WriteLong(&sb, 0); MSG_WriteByte(&sb, CCREP_ACCEPT); if (newcl->qex) - ; //skip any port info (as well as any proquake ident stuff. + ; //skip any port info (as well as any proquake ident stuff). else { NET_LocalAddressForRemote(svs.sockets, &net_from, &localaddr, 0); - MSG_WriteLong(&sb, ShortSwap(localaddr.port)); + if (net_from.prot == NP_DTLS + #ifdef SUPPORT_ICE + || net_from.type == NA_ICE + #endif + ) + MSG_WriteLong(&sb, 0); //send a port of 0 if we expect the client to be sane enough and/or otherwise problematic. + else + MSG_WriteLong(&sb, ShortSwap(localaddr.port)); if (newcl->proquake_angles_hack) { MSG_WriteByte(&sb, MOD_PROQUAKE); diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index afac1b7f8..7d5897dd0 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -409,6 +409,69 @@ void SV_BroadcastPrint (unsigned int flags, int level, const char *string) #endif } +void SV_BroadcastPrint_QexLoc (unsigned int flags, int level, const char **arg, int args) +{ + client_t *cl; + int i; + + char string[1024]; + + if (!(flags & BPRINT_IGNORECONSOLE)) + { + //pretend to print on the server, but not to the client's console + TL_Reformat(com_language, string, sizeof(string), args, arg); + Sys_Printf ("%s", string); // print to the system console + Log_String(LOG_CONSOLE, string); //dump into log + } + + if (!(flags & BPRINT_IGNORECLIENTS)) + { + for (i=0, cl = svs.clients ; imessagelevel) + continue; + if (!cl->state) + continue; + if (cl->protocol == SCP_BAD) + continue; + + if (cl == sv.skipbprintclient) //silence bprints about the player in ClientConnect. NQ completely wipes the buffer after clientconnect, which is what traditionally hides it. + continue; + + if (cl->controller) + continue; + + if (cl->qex) + { //get the damn client to do it. + int size = 3; + for (i = 0; i < args; i++) + size += strlen(arg[i])+1; + ClientReliableWrite_Begin (cl, svcqex_locprint, size); + ClientReliableWrite_Short (cl, args); + for (i = 0; i < args; i++) + ClientReliableWrite_String (cl, arg[i]); + } + else + { + TL_Reformat(cl->language, string, sizeof(string), args, arg); + SV_PrintToClient(cl, level, string); + } + } + } + +#ifdef MVD_RECORDING + if (sv.mvdrecording && !(flags&BPRINT_IGNOREINDEMO)) + { + sizebuf_t *msg; + TL_Reformat(com_language, string, sizeof(string), args, arg); + msg = MVDWrite_Begin (dem_all, 0, strlen(string)+3); + MSG_WriteByte (msg, svc_print); + MSG_WriteByte (msg, level); + MSG_WriteString (msg, string); + } +#endif +} + void VARGS SV_BroadcastPrintf (int level, const char *fmt, ...) { va_list argptr; @@ -1640,6 +1703,9 @@ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) { SV_WriteEntityDataToMessage(split, msg, pnum); + if (split->prompt.active) + SV_Prompt_Resend(split); + if (split->centerprintstring && ! client->num_backbuf) { SV_WriteCenterPrint(split, split->centerprintstring); diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index e8c99a4af..4e97244fd 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -4068,6 +4068,35 @@ void SV_PushFloodProt(client_t *client) client->lastspoke = realtime; } +#ifdef NQPROT +static void SV_SendQEXChat(client_t *to, int clcolour, int chatcolour, const char *sendername, const char *message) +{ + if (to->controller) + to = to->controller; + + switch (to->protocol) + { + case SCP_BAD: //bot + break; + case SCP_QUAKE2: + case SCP_QUAKE3: + case SCP_QUAKEWORLD: + break; //doesn't make sense. + case SCP_DARKPLACES6: + case SCP_DARKPLACES7: + case SCP_NETQUAKE: + case SCP_BJP3: + case SCP_FITZ666: + ClientReliableWrite_Begin (to, svcqex_chat, 3 + strlen(sendername)+strlen(message)); + ClientReliableWrite_Byte (to, clcolour); + ClientReliableWrite_Byte (to, chatcolour); + ClientReliableWrite_String (to, sendername); + ClientReliableWrite_String (to, message); + break; + } +} +#endif + /* ================== SV_Say @@ -4100,7 +4129,7 @@ void SV_Say (qboolean team) memset(sent, 0, sizeof(sent)); - if (team) + if (1)//team) { Q_strncpyz (t1, InfoBuf_ValueForKey(&host_client->userinfo, "team"), sizeof(t1)); } @@ -4223,7 +4252,25 @@ void SV_Say (qboolean team) else sent[cln] = true; - SV_ClientPrintf(client, PRINT_CHAT, "%s", text); +#ifdef NQPROT + if (client->qex) + { //white, green, cyan, yellow + int c = 0; + if (client == host_client) + c = 3; //yellow for yourself. + else + { + t2 = InfoBuf_ValueForKey (&client->userinfo, "team"); + if (strcmp(t1, t2)) + c = 1; //green for other team. should probably be red but that's not an option. + else + c = 2; //cyan for same team. + } + SV_SendQEXChat(client, c, !!team, host_client->name, p); + } + else +#endif + SV_ClientPrintf(client, PRINT_CHAT, "%s", text); } #ifdef MVD_RECORDING sv.mvdrecording = mvdrecording; @@ -8113,6 +8160,7 @@ static double SVFTE_ExecuteClientMove(client_t *controller) ran=true; } + SV_Prompt_Input(split, &split->lastcmd); SV_RunCmd (&split->lastcmd, false); split->lastruncmd = split->lastcmd.servertime; } @@ -8123,6 +8171,7 @@ static double SVFTE_ExecuteClientMove(client_t *controller) if (split->lastcmd.impulse) split->edict->v->impulse = split->lastcmd.impulse; + SV_Prompt_Input(split, &split->lastcmd); SV_SetEntityButtons(split->edict, split->lastcmd.buttons); split->lastcmd.buttons = 0; } @@ -8376,6 +8425,7 @@ void SV_ExecuteClientMessage (client_t *cl) if (newcmd.impulse)// && SV_FilterImpulse(newcmd.impulse, host_client->trustlevel)) split->edict->v->impulse = newcmd.impulse; + SV_Prompt_Input(split, &newcmd); SV_SetEntityButtons(split->edict, newcmd.buttons); } else @@ -8388,18 +8438,26 @@ void SV_ExecuteClientMessage (client_t *cl) { while (net_drop > 2) { + SV_Prompt_Input(split, &split->lastcmd); SV_RunCmd (&split->lastcmd, false); net_drop--; } if (net_drop > 1) + { + SV_Prompt_Input(split, &oldest); SV_RunCmd (&oldest, false); + } if (net_drop > 0) + { + SV_Prompt_Input(split, &oldcmd); SV_RunCmd (&oldcmd, false); + } } + SV_Prompt_Input(split, &newcmd); SV_RunCmd (&newcmd, false); - host_client->lastruncmd = sv.time*1000; + split->lastruncmd = sv.time*1000; - if (!SV_PlayerPhysicsQC || host_client->spectator) + if (!SV_PlayerPhysicsQC || split->spectator) SV_PostRunCmd(); } @@ -8741,7 +8799,7 @@ void SVNQ_ReadClientMove (qboolean forceangle16, qboolean quakeex) frame = &host_client->frameunion.frames[host_client->netchan.incoming_acknowledged & UPDATE_MASK]; if (quakeex) - ; + ; //sequence is a separate clc (should have already been sent) else if (host_client->protocol == SCP_DARKPLACES7) host_client->last_sequence = MSG_ReadLong (); else if (host_client->fteprotocolextensions2 & PEXT2_PREDINFO) @@ -8888,6 +8946,8 @@ void SVNQ_ReadClientMove (qboolean forceangle16, qboolean quakeex) } } + SV_Prompt_Input(host_client, &cmd); + /*host_client->edict->v->v_angle[0] = SHORT2ANGLE(cmd.angles[0]); host_client->edict->v->v_angle[1] = SHORT2ANGLE(cmd.angles[1]); host_client->edict->v->v_angle[2] = SHORT2ANGLE(cmd.angles[2]);*/