From bc4bd883b449b8fcbe53e7e38be9e7abafd610c7 Mon Sep 17 00:00:00 2001 From: Marco Cawthorne Date: Mon, 8 Jan 2024 15:31:12 -0800 Subject: [PATCH] Integrate various in-progress spectating improvements from Nuclide --- src/client/hud.qc | 82 ++------------------- src/client/progs.src | 1 + src/client/vgui_chooseteam.qc | 2 +- src/client/vgui_spectator.qc | 108 ++++++++++++++++++++++++++++ src/server/gamerules_deathmatch.qc | 71 ++++-------------- src/server/gamerules_multiplayer.qc | 32 ++++++--- src/shared/item_c4bomb.qc | 3 +- 7 files changed, 150 insertions(+), 149 deletions(-) create mode 100644 src/client/vgui_spectator.qc diff --git a/src/client/hud.qc b/src/client/hud.qc index 240ecf4..4732f44 100644 --- a/src/client/hud.qc +++ b/src/client/hud.qc @@ -751,89 +751,17 @@ HUD_Draw(void) HUD_PlayerNames(); } -/* specatator main entry */ -string g_specmodes[] = { - "Free Camera", - "Third Person", - "First Person" -}; - -#define SPEC_SEP_COL [0.204,0.196,0.114] -#define SPEC_FG_COL [0.561,0.561,0.212] void HUD_DrawSpectator(void) { - vector vecPos = g_vec_null; - float flSep; - string strText; - int iMinutes, iSeconds; - Textmenu_Draw(); Obituary_Draw(); - Textmenu_Draw(); + if (!g_specHUD) + g_specHUD = spawn(CSSpectateHUD); - /* parts on top and bottom */ - drawfill(video_mins, [video_res[0], 32], [0,0,0], 0.75f, DRAWFLAG_NORMAL); - drawfill(video_mins + [0, video_res[1]-32], [video_res[0], 32], [0,0,0], 0.75f, DRAWFLAG_NORMAL); + g_specHUD.SetPos(video_mins); + g_specHUD.SetSize(video_res); + g_specHUD.Draw(); - /* tracking player box */ - drawrect(g_hudmins + [(g_hudres[0] / 2) - 111, g_hudres[1]-26], [222,20], 1.0f, SPEC_SEP_COL, 1.0f, DRAWFLAG_NORMAL); - NSClientSpectator spec = (NSClientSpectator)pSeat->m_ePlayer; - strText = strcat(HUD_GetChatColorHEX(getplayerkeyfloat(spec.spec_ent - 1, "*team")), getplayerkeyvalue(spec.spec_ent - 1, "name")); - vecPos[0] = g_hudmins[0] + (g_hudres[0] / 2) - (stringwidth(strText, TRUE, [12,12]) / 2); - vecPos[1] = g_hudres[1]-21; - drawstring(vecPos, strText, [12,12], [1,1,1], 1.0f, DRAWFLAG_NORMAL); - - /* tracking mode box */ - drawrect(g_hudmins + [(g_hudres[0] / 2) + 128, g_hudres[1]-26], [172,20], 1.0f, SPEC_SEP_COL, 1.0f, DRAWFLAG_NORMAL); - strText = g_specmodes[spec.spec_mode]; - vecPos[0] = (g_hudmins[0] + (g_hudres[0] / 2) + 214) - (stringwidth(strText, TRUE, [12,12]) / 2); - vecPos[1] = g_hudres[1]-21; - drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); - - if (spec.spec_mode == SPECMODE_FIRSTPERSON) { - entity oself = self; - self = findfloat(world, ::entnum, spec.spec_ent); - if (self) - Cstrike_DrawCrosshair(); - self = oself; - } - - /* money */ - strText = sprintf("$ %i", getstati(STAT_MONEY)); - flSep = stringwidth(strText, TRUE, [12,12]); - - if (flSep < 42) - flSep = 42; - - flSep = g_hudmins[0] + (g_hudres[0] - flSep) - 10; - - vecPos[0] = flSep + 8; - vecPos[1] = g_hudmins[1] + 3; - drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); - - /* score/money separator */ - drawfill([flSep,1], [4,30], SPEC_SEP_COL, 1.0f, DRAWFLAG_NORMAL); - - /* team scores */ - drawfont = Font_GetID(FONT_CON); - strText = sprintf("Terrorist Forces: %s", serverkey("teamscore_1")); - vecPos[0] = flSep - stringwidth(strText, TRUE, [12,12]) - 2; - vecPos[1] = g_hudmins[1] + 3; - drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); - - strText = sprintf("CT Forces: %s", serverkey("teamscore_2")); - vecPos[0] = flSep - stringwidth(strText, TRUE, [12,12]) - 2; - vecPos[1] = g_hudmins[1] + 16; - drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); - - /* time display */ - drawpic([flSep + 8, 15], "gfx/vgui/640_timer", [14, 14], [1,1,1], 1.0f, DRAWFLAG_NORMAL); - iMinutes = getstatf(STAT_GAMETIME) / 60; - iSeconds = getstatf(STAT_GAMETIME) - 60 * iMinutes; - strText = sprintf("%i:%02i", iMinutes, iSeconds); - vecPos[0] = flSep + 8 + 17; - vecPos[1] = g_hudmins[1] + 17; - drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); } diff --git a/src/client/progs.src b/src/client/progs.src index 5588170..28a8602 100644 --- a/src/client/progs.src +++ b/src/client/progs.src @@ -34,6 +34,7 @@ crosshair.qc ../../../valve/src/client/obituary.qc ../../../valve/src/client/hud_dmgnotify.qc hud_ammonotify.qc +vgui_spectator.qc hud.qc hud_weaponselect.qc ../../../valve/src/client/hud_sprite.qc diff --git a/src/client/vgui_chooseteam.qc b/src/client/vgui_chooseteam.qc index f3135e2..f3ed81d 100644 --- a/src/client/vgui_chooseteam.qc +++ b/src/client/vgui_chooseteam.qc @@ -90,7 +90,7 @@ VGUI_ChooseTeam(void) } static void VGUI_ChooseTeam_Spec(void) { - //sendevent("JoinAuto", ""); + sendevent("JoinSpectator", ""); winChooseTeam.Hide(); } diff --git a/src/client/vgui_spectator.qc b/src/client/vgui_spectator.qc new file mode 100644 index 0000000..0865717 --- /dev/null +++ b/src/client/vgui_spectator.qc @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023 Marco Cawthorne + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define SPEC_SEP_COL [0.204,0.196,0.114] +#define SPEC_FG_COL [0.561,0.561,0.212] + +class +CSSpectateHUD:VGUIWidget +{ + void CSSpectateHUD(void); + + virtual void Draw(void); +}; +CSSpectateHUD g_specHUD; + +void +CSSpectateHUD::CSSpectateHUD(void) +{ + +} + +void +CSSpectateHUD::Draw(void) +{ + vector vecPos = g_vec_null; + float flSep; + string strText; + int iMinutes, iSeconds; + NSClientSpectator spec = (NSClientSpectator)pSeat->m_ePlayer; + + /* parts on top and bottom */ + drawfill(m_vecOrigin, [m_vecSize[0], 32], [0,0,0], 0.75f, DRAWFLAG_NORMAL); + drawfill(m_vecOrigin + [0, m_vecSize[1]-32], [m_vecSize[0], 32], [0,0,0], 0.75f, DRAWFLAG_NORMAL); + + /* tracking player box */ + drawrect(g_hudmins + [(g_hudres[0] / 2) - 111, g_hudres[1]-26], [222,20], 1.0f, SPEC_SEP_COL, 1.0f, DRAWFLAG_NORMAL); + strText = strcat(HUD_GetChatColorHEX(getplayerkeyfloat(spec.spec_ent - 1, "*team")), getplayerkeyvalue(spec.spec_ent - 1, "name")); + vecPos[0] = g_hudmins[0] + (g_hudres[0] / 2) - (stringwidth(strText, TRUE, [12,12]) / 2); + vecPos[1] = g_hudres[1]-21; + drawstring(vecPos, strText, [12,12], [1,1,1], 1.0f, DRAWFLAG_NORMAL); + + /* tracking mode box */ + drawrect(g_hudmins + [(g_hudres[0] / 2) + 128, g_hudres[1]-26], [172,20], 1.0f, SPEC_SEP_COL, 1.0f, DRAWFLAG_NORMAL); + strText = g_specmodes[spec.spec_mode]; + vecPos[0] = (g_hudmins[0] + (g_hudres[0] / 2) + 214) - (stringwidth(strText, TRUE, [12,12]) / 2); + vecPos[1] = g_hudres[1]-21; + drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); + +#if 0 + if (spec.spec_mode == SPECMODE_FIRSTPERSON) { + entity oself = self; + self = findfloat(world, ::entnum, spec.spec_ent); + if (self) + Cstrike_DrawCrosshair(); + self = oself; + } +#endif + + /* money */ + strText = sprintf("$ %i", getstati(STAT_MONEY)); + flSep = stringwidth(strText, TRUE, [12,12]); + + if (flSep < 42) + flSep = 42; + + flSep = g_hudmins[0] + (g_hudres[0] - flSep) - 10; + + vecPos[0] = flSep + 8; + vecPos[1] = g_hudmins[1] + 3; + drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); + + /* score/money separator */ + drawfill([flSep,1], [4,30], SPEC_SEP_COL, 1.0f, DRAWFLAG_NORMAL); + + /* team scores */ + drawfont = Font_GetID(FONT_CON); + strText = sprintf("Terrorist Forces: %s", serverkey("teamscore_1")); + vecPos[0] = flSep - stringwidth(strText, TRUE, [12,12]) - 2; + vecPos[1] = g_hudmins[1] + 3; + drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); + + strText = sprintf("CT Forces: %s", serverkey("teamscore_2")); + vecPos[0] = flSep - stringwidth(strText, TRUE, [12,12]) - 2; + vecPos[1] = g_hudmins[1] + 16; + drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); + + /* time display */ + drawpic([flSep + 8, 15], "gfx/vgui/640_timer", [14, 14], [1,1,1], 1.0f, DRAWFLAG_NORMAL); + iMinutes = getstatf(STAT_GAMETIME) / 60; + iSeconds = getstatf(STAT_GAMETIME) - 60 * iMinutes; + strText = sprintf("%i:%02i", iMinutes, iSeconds); + vecPos[0] = flSep + 8 + 17; + vecPos[1] = g_hudmins[1] + 17; + drawstring(vecPos, strText, [12,12], SPEC_FG_COL, 1.0f, DRAWFLAG_NORMAL); +} diff --git a/src/server/gamerules_deathmatch.qc b/src/server/gamerules_deathmatch.qc index 7211ce5..9b6838c 100644 --- a/src/server/gamerules_deathmatch.qc +++ b/src/server/gamerules_deathmatch.qc @@ -14,9 +14,6 @@ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -const string mp_teamlist_fallback = "robo;hgrunt"; -var string autocvar_mp_teamlist = mp_teamlist_fallback; - string CSDeathmatchRules::Title(void) { @@ -60,25 +57,11 @@ CSDeathmatchRules::InitPostEnts(void) forceinfokey(world, "scorepoints", "0"); if (IsTeamplay() == true) { - int c; - - /* get the segments from our cvar */ - m_strTeamList = autocvar_mp_teamlist; - c = tokenizebyseparator(m_strTeamList, ";"); - - /* if we've got less than 2 teams, use the fallback... */ - if (c < 2) { - m_strTeamList = mp_teamlist_fallback; - c = tokenizebyseparator(m_strTeamList, ";"); - } - - forceinfokey(world, "teams", itos(c)); - - /* initialize all dem teams */ - for (int i = 0; i < c; i++) { - forceinfokey(world, sprintf("team_%i", i+1i), argv(i)); - forceinfokey(world, sprintf("teamscore_%i", i+1i), "0"); - } + forceinfokey(world, "teams", "2"); + forceinfokey(world, "team_1", "Counter-Terrorist"); + forceinfokey(world, "teamscore_1", "0"); + forceinfokey(world, "team_2", "Terrorist"); + forceinfokey(world, "teamscore_2", "0"); } else { forceinfokey(world, "teams", "0"); } @@ -167,7 +150,6 @@ void CSDeathmatchRules::PlayerSpawn(NSClientPlayer pp) { player pl = (player)pp; - string playerModel; /* this is where the mods want to deviate */ entity spot; @@ -190,11 +172,13 @@ CSDeathmatchRules::PlayerSpawn(NSClientPlayer pp) /* TODO: this should sort us into the lowest team */ if (playerTeam == 0) { playerTeam = 1i + (int)floor(random(0, (float)teamCount)); /* teams start at 1 after all */ - pl.SetTeam(playerTeam); + pl.SetTeam(random() < 0.5 ? TEAM_CT : TEAM_T); } - /* assign our player model */ - playerModel = sprintf("models/player/%s/%s.mdl", argv(playerTeam - 1i), argv(playerTeam - 1i)); + if (playerTeam == TEAM_T) + pl.charmodel = floor(random(1,5)); + else + pl.charmodel = floor(random(5,9)); } else { pl.charmodel = rint(random(1,9)); } @@ -228,12 +212,8 @@ CSDeathmatchRules::PlayerSpawn(NSClientPlayer pp) pl.model = "models/player/vip/vip.mdl"; } - /* fallback is always models/player.mdl for Half-Life */ - if not (whichpack(playerModel)) { - playerModel = "models/player.mdl"; - } - pl.SetModel(playerModel); + pl.SetModel(pl.model); pl.SetSize(VEC_HULL_MIN, VEC_HULL_MAX); pl.ClearVelocity(); pl.gravity = __NULL__; @@ -306,31 +286,4 @@ CSDeathmatchRules::CSDeathmatchRules(void) /* these lines do nothing but tell the server to register those cvars */ autocvar(timelimit, 15, "Timelimit for multiplayer rounds"); autocvar(fraglimit, 15, "Points limit for multiplayer rounds"); -} - -void -CSEv_HLDM_Chooseteam_s(string teamName) -{ - CSGameRules rules = (CSGameRules)g_grMode; - player pl = (player)self; - - if (!teamName) - return; - if (rules.IsMultiplayer() == false) - return; - if (rules.IsTeamplay() == false) - return; - if (pl.IsDead() == true) - return; - - CSDeathmatchRules mprules = (CSDeathmatchRules)rules; - int c = tokenizebyseparator(mprules.m_strTeamList, ";"); - - for (int i = 0; i < c; i++) { - if (argv(i) == teamName) { - pl.SetTeam((float)i + 1); - Damage_Apply(pl, pl, 100, 0, DMG_SKIP_ARMOR); - return; - } - } -} +} \ No newline at end of file diff --git a/src/server/gamerules_multiplayer.qc b/src/server/gamerules_multiplayer.qc index 4ff4e4e..5be30a4 100644 --- a/src/server/gamerules_multiplayer.qc +++ b/src/server/gamerules_multiplayer.qc @@ -294,7 +294,7 @@ CSMultiplayerRules::TimerBegin(float tleft, int mode) RoundOver(TEAM_T, 3600, FALSE); return; } - + } else if (mode == GAME_END) { g_cs_gamestate = GAME_END; } else if (mode == GAME_COMMENCING) { @@ -397,7 +397,7 @@ CSMultiplayerRules::BuyingPossible(NSClientPlayer pl) if (pl.health <= 0) { return (false); } - + if (g_cs_gamestate == GAME_ACTIVE) { if (((autocvar_mp_roundtime * 60) - g_cs_gametime) > autocvar_mp_buytime) { centerprint(pl, sprintf("%d seconds have passed...\nYou can't buy anything now!", autocvar_mp_buytime)); @@ -409,12 +409,12 @@ CSMultiplayerRules::BuyingPossible(NSClientPlayer pl) centerprint(pl, "You are the VIP...\nYou can't buy anything!\n"); return (false); } - + if (g_cstrike_buying == BUY_NEITHER) { centerprint(pl, "Sorry, you aren't meant\nto be buying anything.\n"); return (false); } - + if (g_cstrike_buying != BUY_BOTH) { if (g_cstrike_buying == BUY_CT && pl.team == TEAM_T) { centerprint(pl, "Terrorists aren't allowed to\nbuy anything on this map!\n"); @@ -424,12 +424,12 @@ CSMultiplayerRules::BuyingPossible(NSClientPlayer pl) return (false); } } - + if (!(pl.gflags & GF_BUYZONE)) { centerprint(pl, "Sorry, you aren't in a buyzone.\n"); return (false); } - + return (true); } @@ -463,7 +463,7 @@ CSMultiplayerRules::RestartRound(int iWipe) if (autocvar_fcs_swaponround > 0) if (m_iSwapTeamRoundCounter >= autocvar_fcs_swaponround) { m_iSwapTeamRoundCounter = 0; - + for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) { player pl = (player)eFind; @@ -577,11 +577,11 @@ CSMultiplayerRules::RestartRound(int iWipe) if (g_cs_bombzones > 0) { int iRandomT = floor(random(1, (float)g_cs_alive_t + 1)); int iPickT = 0; - + for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) { if (eFind.team == TEAM_T) { iPickT++; - + if (iPickT == iRandomT) { MakeBomber((player)eFind); } @@ -820,14 +820,14 @@ CSMultiplayerRules::PlayerFindSpawn(float t) if (t == TEAM_T) { m_eLastTSpawn = find(m_eLastTSpawn, ::classname, "info_player_deathmatch"); - + if (m_eLastTSpawn == world) { m_eLastTSpawn = find(m_eLastTSpawn, ::classname, "info_player_deathmatch"); } point = m_eLastTSpawn; } else if (t == TEAM_CT) { m_eLastCTSpawn = find(m_eLastCTSpawn, ::classname, "info_player_start"); - + if (m_eLastCTSpawn == world) { m_eLastCTSpawn = find(m_eLastCTSpawn, ::classname, "info_player_start"); } @@ -1121,8 +1121,10 @@ CSMultiplayerRules::CSMultiplayerRules(void) forceinfokey(world, "teams", "2"); forceinfokey(world, "team_1", "Terrorist"); forceinfokey(world, "teamscore_1", "0"); + forceinfokey(world, "teamcolor_1", "1 0 0" ); forceinfokey(world, "team_2", "Counter-Terrorist"); forceinfokey(world, "teamscore_2", "0"); + forceinfokey(world, "teamcolor_2", "0.25 0.25 1" ); m_iEscapedTerrorists = 0; } @@ -1227,3 +1229,11 @@ CSEv_JoinAuto(void) CSEv_JoinTeam_f(floor(random(5,9))); } } + +void +CSEv_JoinSpectator(void) +{ + NSClientPlayer pl = (NSClientPlayer)self; + ClientKill(); + pl.MakeSpectator(); +} diff --git a/src/shared/item_c4bomb.qc b/src/shared/item_c4bomb.qc index e02b859..5d35a8c 100644 --- a/src/shared/item_c4bomb.qc +++ b/src/shared/item_c4bomb.qc @@ -106,7 +106,7 @@ item_c4::Logic(void) /* we need to check if the user has changed every frame. */ if (!m_eUser.button5) { ClearProgress(); - + /* clear user */ m_eUser = world; m_flDefusalState = 0.0f; @@ -189,6 +189,7 @@ item_c4::OnRemoveEntity(void) m_flBeepTime = 0.0f; m_flDefusalState = 0; g_cs_bombbeingdefused = FALSE; + g_cs_bombplanted = false; } void