diff options
| -rw-r--r-- | datasrc/network.py | 12 | ||||
| -rw-r--r-- | src/game/client/components/binds.cpp | 1 | ||||
| -rw-r--r-- | src/game/client/components/controls.cpp | 3 | ||||
| -rw-r--r-- | src/game/client/components/hud.cpp | 49 | ||||
| -rw-r--r-- | src/game/client/components/hud.h | 5 | ||||
| -rw-r--r-- | src/game/client/components/menus_settings.cpp | 5 | ||||
| -rw-r--r-- | src/game/client/components/spectator.cpp | 172 | ||||
| -rw-r--r-- | src/game/client/components/spectator.h | 37 | ||||
| -rw-r--r-- | src/game/client/gameclient.cpp | 28 | ||||
| -rw-r--r-- | src/game/client/gameclient.h | 5 | ||||
| -rw-r--r-- | src/game/server/entities/character.cpp | 2 | ||||
| -rw-r--r-- | src/game/server/gamecontext.cpp | 56 | ||||
| -rw-r--r-- | src/game/server/player.cpp | 39 | ||||
| -rw-r--r-- | src/game/server/player.h | 41 |
14 files changed, 389 insertions, 66 deletions
diff --git a/datasrc/network.py b/datasrc/network.py index 3b1afa77..e30b1a9b 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -26,6 +26,8 @@ enum FLAG_ATSTAND=-2, FLAG_TAKEN, + + SPEC_FREEVIEW=-1, }; ''' @@ -173,6 +175,12 @@ Objects = [ NetIntAny("m_ColorBody"), NetIntAny("m_ColorFeet"), ]), + + NetObject("SpectatorInfo", [ + NetIntRange("m_SpectatorID", 'SPEC_FREEVIEW', 'MAX_CLIENTS-1'), + NetIntAny("m_X"), + NetIntAny("m_Y"), + ]), ## Events @@ -273,6 +281,10 @@ Messages = [ NetMessage("Cl_SetTeam", [ NetIntRange("m_Team", 'TEAM_SPECTATORS', 'TEAM_BLUE'), ]), + + NetMessage("Cl_SetSpectatorMode", [ + NetIntRange("m_SpectatorID", 'SPEC_FREEVIEW', 'MAX_CLIENTS-1'), + ]), NetMessage("Cl_StartInfo", [ NetStringStrict("m_pName"), diff --git a/src/game/client/components/binds.cpp b/src/game/client/components/binds.cpp index 7f473ef4..746389bd 100644 --- a/src/game/client/components/binds.cpp +++ b/src/game/client/components/binds.cpp @@ -99,6 +99,7 @@ void CBinds::SetDefaults() Bind(KEY_MOUSE_1, "+fire"); Bind(KEY_MOUSE_2, "+hook"); Bind(KEY_LSHIFT, "+emote"); + Bind(KEY_RSHIFT, "+spectate"); Bind('1', "+weapon1"); Bind('2', "+weapon2"); diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index df9a6bf0..f67caa8e 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -201,6 +201,9 @@ void CControls::OnRender() // update target pos if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED || m_pClient->m_Snap.m_Spectate)) m_TargetPos = m_pClient->m_LocalCharacterPos + m_MousePos; + + if(m_pClient->m_Snap.m_Spectate && m_pClient->m_Snap.m_pSpectatorInfo) + m_MousePos = m_pClient->m_Snap.m_SpectatorPos; } bool CControls::OnMouseMove(float x, float y) diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index ca100f9d..aff76bc8 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -30,7 +30,6 @@ void CHud::OnReset() void CHud::RenderGameTimer() { float Half = 300.0f*Graphics()->ScreenAspect()/2.0f; - Graphics()->MapScreen(0, 0, 300.0f*Graphics()->ScreenAspect(), 300.0f); if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_SUDDENDEATH)) { @@ -340,8 +339,11 @@ void CHud::RenderCursor() Graphics()->QuadsEnd(); } -void CHud::RenderHealthAndAmmo() +void CHud::RenderHealthAndAmmo(const CNetObj_Character *pCharacter) { + if(!pCharacter) + return; + //mapscreen_to_group(gacenter_x, center_y, layers_game_group()); float x = 5; @@ -351,15 +353,14 @@ void CHud::RenderHealthAndAmmo() // render gui stuff Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); - Graphics()->MapScreen(0,0,m_Width,300); Graphics()->QuadsBegin(); // if weaponstage is active, put a "glow" around the stage ammo - RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[m_pClient->m_Snap.m_pLocalCharacter->m_Weapon%NUM_WEAPONS].m_pSpriteProj); + RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[pCharacter->m_Weapon%NUM_WEAPONS].m_pSpriteProj); IGraphics::CQuadItem Array[10]; int i; - for (i = 0; i < min(m_pClient->m_Snap.m_pLocalCharacter->m_AmmoCount, 10); i++) + for (i = 0; i < min(pCharacter->m_AmmoCount, 10); i++) Array[i] = IGraphics::CQuadItem(x+i*12,y+24,10,10); Graphics()->QuadsDrawTL(Array, i); Graphics()->QuadsEnd(); @@ -369,7 +370,7 @@ void CHud::RenderHealthAndAmmo() // render health RenderTools()->SelectSprite(SPRITE_HEALTH_FULL); - for(; h < min(m_pClient->m_Snap.m_pLocalCharacter->m_Health, 10); h++) + for(; h < min(pCharacter->m_Health, 10); h++) Array[h] = IGraphics::CQuadItem(x+h*12,y,10,10); Graphics()->QuadsDrawTL(Array, h); @@ -382,7 +383,7 @@ void CHud::RenderHealthAndAmmo() // render armor meter h = 0; RenderTools()->SelectSprite(SPRITE_ARMOR_FULL); - for(; h < min(m_pClient->m_Snap.m_pLocalCharacter->m_Armor, 10); h++) + for(; h < min(pCharacter->m_Armor, 10); h++) Array[h] = IGraphics::CQuadItem(x+h*12,y+12,10,10); Graphics()->QuadsDrawTL(Array, h); @@ -394,19 +395,39 @@ void CHud::RenderHealthAndAmmo() Graphics()->QuadsEnd(); } +void CHud::RenderSpectatorHud() +{ + // draw the box + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.4f); + RenderTools()->DrawRoundRectExt(m_Width-180.0f, m_Height-15.0f, 180.0f, 15.0f, 5.0f, CUI::CORNER_TL); + Graphics()->QuadsEnd(); + + // draw the text + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Spectate"), m_pClient->m_Snap.m_pSpectatorInfo && m_pClient->m_Snap.m_pSpectatorInfo->m_SpectatorID != SPEC_FREEVIEW ? + m_pClient->m_aClients[m_pClient->m_Snap.m_pSpectatorInfo->m_SpectatorID].m_aName : Localize("Free-View")); + TextRender()->Text(0, m_Width-174.0f, m_Height-13.0f, 8.0f, aBuf, -1); +} + void CHud::OnRender() { if(!m_pClient->m_Snap.m_pGameInfoObj) return; - m_Width = 300*Graphics()->ScreenAspect(); + m_Width = 300.0f*Graphics()->ScreenAspect(); + m_Height = 300.0f; + Graphics()->MapScreen(0.0f, 0.0f, m_Width, m_Height); - bool Spectate = false; - if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pLocalInfo->m_Team == TEAM_SPECTATORS) - Spectate = true; - - if(m_pClient->m_Snap.m_pLocalCharacter && !Spectate && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - RenderHealthAndAmmo(); + if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) + RenderHealthAndAmmo(m_pClient->m_Snap.m_pLocalCharacter); + else if(m_pClient->m_Snap.m_Spectate) + { + if(m_pClient->m_Snap.m_pSpectatorInfo && m_pClient->m_Snap.m_pSpectatorInfo->m_SpectatorID != SPEC_FREEVIEW) + RenderHealthAndAmmo(&m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_pSpectatorInfo->m_SpectatorID].m_Cur); + RenderSpectatorHud(); + } RenderGameTimer(); RenderSuddenDeath(); diff --git a/src/game/client/components/hud.h b/src/game/client/components/hud.h index f1f3dc0a..75702d51 100644 --- a/src/game/client/components/hud.h +++ b/src/game/client/components/hud.h @@ -6,7 +6,7 @@ class CHud : public CComponent { - float m_Width; + float m_Width, m_Height; float m_AverageFPS; void RenderCursor(); @@ -15,10 +15,11 @@ class CHud : public CComponent void RenderConnectionWarning(); void RenderTeambalanceWarning(); void RenderVoting(); - void RenderHealthAndAmmo(); + void RenderHealthAndAmmo(const CNetObj_Character *pCharacter); void RenderGameTimer(); void RenderSuddenDeath(); void RenderScoreHud(); + void RenderSpectatorHud(); void RenderWarmupTimer(); void MapscreenToGroup(float CenterX, float CenterY, struct CMapItemGroup *PGroup); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 9accd6af..4bdf0fd3 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -322,6 +322,7 @@ static CKeyInfo gs_aKeys[] = { "Team chat", "chat team", 0 }, { "Show chat", "+show_chat", 0 }, { "Emoticon", "+emote", 0 }, + { "Spectator mode", "+spectate", 0 }, { "Console", "toggle_local_console", 0 }, { "Remote console", "toggle_remote_console", 0 }, { "Screenshot", "screenshot", 0 }, @@ -331,7 +332,7 @@ static CKeyInfo gs_aKeys[] = Localize("Move left");Localize("Move right");Localize("Jump");Localize("Fire");Localize("Hook");Localize("Hammer"); Localize("Pistol");Localize("Shotgun");Localize("Grenade");Localize("Rifle");Localize("Next weapon");Localize("Prev. weapon"); Localize("Vote yes");Localize("Vote no");Localize("Chat");Localize("Team chat");Localize("Show chat");Localize("Emoticon"); - Localize("Console");Localize("Remote console");Localize("Screenshot");Localize("Scoreboard"); + Localize("Spectate");Localize("Console");Localize("Remote console");Localize("Screenshot");Localize("Scoreboard"); */ const int g_KeyCount = sizeof(gs_aKeys) / sizeof(CKeyInfo); @@ -458,7 +459,7 @@ void CMenus::RenderSettingsControls(CUIRect MainView) TextRender()->Text(0, MiscSettings.x, MiscSettings.y, 14.0f*UI()->Scale(), Localize("Miscellaneous"), -1); MiscSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &MiscSettings); - UiDoGetButtons(17, 22, MiscSettings); + UiDoGetButtons(17, 23, MiscSettings); } // defaults diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp new file mode 100644 index 00000000..18035248 --- /dev/null +++ b/src/game/client/components/spectator.cpp @@ -0,0 +1,172 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include <engine/graphics.h> +#include <engine/textrender.h> +#include <engine/shared/config.h> + +#include <game/generated/client_data.h> +#include <game/generated/protocol.h> + +#include <game/client/animstate.h> +#include <game/client/render.h> + +#include "spectator.h" + + +void CSpectator::ConKeySpectator(IConsole::IResult *pResult, void *pUserData) +{ + CSpectator *pSelf = (CSpectator *)pUserData; + if(pSelf->m_pClient->m_Snap.m_Spectate) + pSelf->m_Active = pResult->GetInteger(0) != 0; +} + +void CSpectator::ConSpectate(IConsole::IResult *pResult, void *pUserData) +{ + ((CSpectator *)pUserData)->Spectate(pResult->GetInteger(0)); +} + +CSpectator::CSpectator() +{ + OnReset(); +} + +void CSpectator::OnConsoleInit() +{ + Console()->Register("+spectate", "", CFGFLAG_CLIENT, ConKeySpectator, this, "Open spectator mode selector"); + Console()->Register("spectate", "i", CFGFLAG_CLIENT, ConSpectate, this, "Switch spectator mode"); +} + +bool CSpectator::OnMouseMove(float x, float y) +{ + if(!m_Active) + return false; + + m_SelectorMouse += vec2(x,y); + return true; +} + +void CSpectator::OnRelease() +{ + OnReset(); +} + +void CSpectator::OnRender() +{ + if(!m_Active) + { + if(m_WasActive) + { + if(m_SelectedSpectatorID != NO_SELECTION) + Spectate(m_SelectedSpectatorID); + OnReset(); + } + return; + } + + m_WasActive = true; + m_SelectedSpectatorID = NO_SELECTION; + + // draw background + float Width = 400*3.0f*Graphics()->ScreenAspect(); + float Height = 400*3.0f; + + Graphics()->MapScreen(0, 0, Width, Height); + + Graphics()->BlendNormal(); + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f); + RenderTools()->DrawRoundRect(Width/2.0f-300.0f, Height/2.0f-300.0f, 600.0f, 600.0f, 20.0f); + Graphics()->QuadsEnd(); + + // clamp mouse position to selector area + m_SelectorMouse.x = clamp(m_SelectorMouse.x, -280.0f, 280.0f); + m_SelectorMouse.y = clamp(m_SelectorMouse.y, -280.0f, 280.0f); + + // draw selections + float FontSize = 20.0f; + float StartY = -190.0f; + float LineHeight = 60.0f; + bool Selected = false; + + int SpectatorID = m_pClient->m_Snap.m_pSpectatorInfo ? m_pClient->m_Snap.m_pSpectatorInfo->m_SpectatorID : SPEC_FREEVIEW; + if(SpectatorID == SPEC_FREEVIEW) + { + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); + RenderTools()->DrawRoundRect(Width/2.0f-280.0f, Height/2.0f-280.0f, 270.0f, 60.0f, 20.0f); + Graphics()->QuadsEnd(); + } + + if(m_SelectorMouse.x >= -280.0f && m_SelectorMouse.x <= -10.0f && + m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) + { + m_SelectedSpectatorID = SPEC_FREEVIEW; + Selected = true; + } + TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected?1.0f:0.5f); + TextRender()->Text(0, Width/2.0f-240.0f, Height/2.0f-265.0f, FontSize, Localize("Free-View"), -1); + + float x = -270.0f, y = StartY; + for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i) + { + if(!m_pClient->m_Snap.m_paPlayerInfos[i] || m_pClient->m_Snap.m_paPlayerInfos[i]->m_Team == TEAM_SPECTATORS) + continue; + + if(++Count%9 == 0) + { + x += 290.0f; + y = StartY; + } + + if(SpectatorID == i) + { + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); + RenderTools()->DrawRoundRect(Width/2.0f+x-10.0f, Height/2.0f+y-10.0f, 270.0f, 60.0f, 20.0f); + Graphics()->QuadsEnd(); + } + + Selected = false; + if(m_SelectorMouse.x >= x-10.0f && m_SelectorMouse.x <= x+260.0f && + m_SelectorMouse.y >= y-10.0f && m_SelectorMouse.y <= y+50.0f) + { + m_SelectedSpectatorID = i; + Selected = true; + } + TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected?1.0f:0.5f); + TextRender()->Text(0, Width/2.0f+x+50.0f, Height/2.0f+y+5.0f, FontSize, m_pClient->m_aClients[i].m_aName, 220.0f); + + CTeeRenderInfo TeeInfo = m_pClient->m_aClients[i].m_RenderInfo; + RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(Width/2.0f+x+20.0f, Height/2.0f+y+20.0f)); + + y += LineHeight; + } + + // draw cursor + Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + IGraphics::CQuadItem QuadItem(m_SelectorMouse.x+Width/2.0f, m_SelectorMouse.y+Height/2.0f, 48.0f, 48.0f); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); +} + +void CSpectator::OnReset() +{ + m_WasActive = false; + m_Active = false; + m_SelectedSpectatorID = NO_SELECTION; +} + +void CSpectator::Spectate(int SpectatorID) +{ + if(m_pClient->m_Snap.m_pSpectatorInfo && m_pClient->m_Snap.m_pSpectatorInfo->m_SpectatorID == SpectatorID) + return; + + CNetMsg_Cl_SetSpectatorMode Msg; + Msg.m_SpectatorID = SpectatorID; + Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); +} diff --git a/src/game/client/components/spectator.h b/src/game/client/components/spectator.h new file mode 100644 index 00000000..607780f5 --- /dev/null +++ b/src/game/client/components/spectator.h @@ -0,0 +1,37 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef GAME_CLIENT_COMPONENTS_SPECTATOR_H +#define GAME_CLIENT_COMPONENTS_SPECTATOR_H +#include <base/vmath.h> + +#include <game/client/component.h> + +class CSpectator : public CComponent +{ + enum + { + NO_SELECTION=-2, + }; + + bool m_Active; + bool m_WasActive; + + int m_SelectedSpectatorID; + vec2 m_SelectorMouse; + + static void ConKeySpectator(IConsole::IResult *pResult, void *pUserData); + static void ConSpectate(IConsole::IResult *pResult, void *pUserData); + +public: + CSpectator(); + + virtual void OnConsoleInit(); + virtual bool OnMouseMove(float x, float y); + virtual void OnRender(); + virtual void OnRelease(); + virtual void OnReset(); + + void Spectate(int SpectatorID); +}; + +#endif diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index f80d0266..0d847162 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -44,6 +44,7 @@ #include "components/scoreboard.h" #include "components/skins.h" #include "components/sounds.h" +#include "components/spectator.h" #include "components/voting.h" CGameClient g_GameClient; @@ -69,6 +70,7 @@ static CSounds gs_Sounds; static CEmoticon gs_Emoticon; static CDamageInd gsDamageInd; static CVoting gs_Voting; +static CSpectator gs_Spectator; static CPlayers gs_Players; static CNamePlates gs_NamePlates; @@ -139,6 +141,7 @@ void CGameClient::OnConsoleInit() m_All.Add(&m_pParticles->m_RenderGeneral); m_All.Add(m_pDamageind); m_All.Add(&gs_Hud); + m_All.Add(&gs_Spectator); m_All.Add(&gs_Emoticon); m_All.Add(&gs_KillMessages); m_All.Add(m_pChat); @@ -156,6 +159,7 @@ void CGameClient::OnConsoleInit() m_Input.Add(m_pChat); // chat has higher prio due to tha you can quit it by pressing esc m_Input.Add(m_pMotd); // for pressing esc to remove it m_Input.Add(m_pMenus); + m_Input.Add(&gs_Spectator); m_Input.Add(&gs_Emoticon); m_Input.Add(m_pControls); m_Input.Add(m_pBinds); @@ -336,8 +340,9 @@ void CGameClient::OnReset() } -void CGameClient::UpdateLocalCharacterPos() +void CGameClient::UpdatePositions() { + // local character position if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) { if(!m_Snap.m_pLocalCharacter || (m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) @@ -353,6 +358,16 @@ void CGameClient::UpdateLocalCharacterPos() vec2(m_Snap.m_pLocalPrevCharacter->m_X, m_Snap.m_pLocalPrevCharacter->m_Y), vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y), Client()->IntraGameTick()); } + + // spectator position + if(m_Snap.m_Spectate && m_Snap.m_pSpectatorInfo) + { + if(m_Snap.m_pPrevSpectatorInfo) + m_Snap.m_SpectatorPos = mix(vec2(m_Snap.m_pPrevSpectatorInfo->m_X, m_Snap.m_pPrevSpectatorInfo->m_Y), + vec2(m_Snap.m_pSpectatorInfo->m_X, m_Snap.m_pSpectatorInfo->m_Y), Client()->IntraGameTick()); + else + m_Snap.m_SpectatorPos = vec2(m_Snap.m_pSpectatorInfo->m_X, m_Snap.m_pSpectatorInfo->m_Y); + } } @@ -393,8 +408,8 @@ void CGameClient::OnRender() return;*/ - // update the local character position - UpdateLocalCharacterPos(); + // update the local character and spectate position + UpdatePositions(); // dispatch all input to systems DispatchInput(); @@ -716,11 +731,11 @@ void CGameClient::OnNewSnapshot() else if(Item.m_Type == NETOBJTYPE_CHARACTER) { const void *pOld = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_CHARACTER, Item.m_ID); + m_Snap.m_aCharacters[Item.m_ID].m_Cur = *((const CNetObj_Character *)pData); if(pOld) { m_Snap.m_aCharacters[Item.m_ID].m_Active = true; m_Snap.m_aCharacters[Item.m_ID].m_Prev = *((const CNetObj_Character *)pOld); - m_Snap.m_aCharacters[Item.m_ID].m_Cur = *((const CNetObj_Character *)pData); if(m_Snap.m_aCharacters[Item.m_ID].m_Prev.m_Tick) Evolve(&m_Snap.m_aCharacters[Item.m_ID].m_Prev, Client()->PrevGameTick()); @@ -728,6 +743,11 @@ void CGameClient::OnNewSnapshot() Evolve(&m_Snap.m_aCharacters[Item.m_ID].m_Cur, Client()->GameTick()); } } + else if(Item.m_Type == NETOBJTYPE_SPECTATORINFO) + { + m_Snap.m_pSpectatorInfo = (const CNetObj_SpectatorInfo *)pData; + m_Snap.m_pPrevSpectatorInfo = (const CNetObj_SpectatorInfo *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_SPECTATORINFO, Item.m_ID); + } else if(Item.m_Type == NETOBJTYPE_GAMEINFO) { static bool s_GameOver = 0; diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 3681b301..5e8b9391 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -49,7 +49,7 @@ class CGameClient : public IGameClient void DispatchInput(); void ProcessEvents(); - void UpdateLocalCharacterPos(); + void UpdatePositions(); int m_PredictedTick; int m_LastNewPredictedTick; @@ -109,6 +109,8 @@ public: const CNetObj_Character *m_pLocalCharacter; const CNetObj_Character *m_pLocalPrevCharacter; const CNetObj_PlayerInfo *m_pLocalInfo; + const CNetObj_SpectatorInfo *m_pSpectatorInfo; + const CNetObj_SpectatorInfo *m_pPrevSpectatorInfo; const CNetObj_Flag *m_paFlags[2]; const CNetObj_GameInfo *m_pGameInfoObj; const CNetObj_GameData *m_pGameDataObj; @@ -121,6 +123,7 @@ public: int m_NumPlayers; int m_aTeamSize[2]; bool m_Spectate; + vec2 m_SpectatorPos; // struct CCharacterInfo diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index ddd416d1..32a9523e 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -822,7 +822,7 @@ void CCharacter::Snap(int SnappingClient) pCharacter->m_Direction = m_Input.m_Direction; - if(m_pPlayer->GetCID() == SnappingClient) + if(m_pPlayer->GetCID() == SnappingClient || SnappingClient == -1 || m_pPlayer->GetCID() == GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID) { pCharacter->m_Health = m_Health; pCharacter->m_Armor = m_Armor; diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 44d9b6fc..7f8a075f 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -389,7 +389,10 @@ void CGameContext::OnTick() for(int i = 0; i < MAX_CLIENTS; i++) { if(m_apPlayers[i]) + { m_apPlayers[i]->Tick(); + m_apPlayers[i]->PostTick(); + } } // update voting @@ -454,7 +457,7 @@ void CGameContext::OnTick() SendChat(-1, CGameContext::CHAT_ALL, "Vote passed"); if(m_apPlayers[m_VoteCreator]) - m_apPlayers[m_VoteCreator]->m_Last_VoteCall = 0; + m_apPlayers[m_VoteCreator]->m_LastVoteCall = 0; } else if(m_VoteEnforce == VOTE_ENFORCE_NO || time_get() > m_VoteCloseTime) { @@ -548,6 +551,13 @@ void CGameContext::OnClientDrop(int ClientID) (void)m_pController->CheckTeamBalance(); m_VoteUpdate = true; + + // update spectator modes + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_apPlayers[i] && m_apPlayers[i]->m_SpectatorID == ClientID) + m_apPlayers[i]->m_SpectatorID = SPEC_FREEVIEW; + } } void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) @@ -572,10 +582,10 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) else Team = CGameContext::CHAT_ALL; - if(g_Config.m_SvSpamprotection && pPlayer->m_Last_Chat && pPlayer->m_Last_Chat+Server()->TickSpeed() > Server()->Tick()) + if(g_Config.m_SvSpamprotection && pPlayer->m_LastChat && pPlayer->m_LastChat+Server()->TickSpeed() > Server()->Tick()) return; - pPlayer->m_Last_Chat = Server()->Tick(); + pPlayer->m_LastChat = Server()->Tick(); // check for invalid chars unsigned char *pMessage = (unsigned char *)pMsg->m_pMessage; @@ -590,11 +600,11 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) } else if(MsgID == NETMSGTYPE_CL_CALLVOTE) { - if(g_Config.m_SvSpamprotection && pPlayer->m_Last_VoteTry && pPlayer->m_Last_VoteTry+Server()->TickSpeed()*3 > Server()->Tick()) + if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry+Server()->TickSpeed()*3 > Server()->Tick()) return; int64 Now = Server()->Tick(); - pPlayer->m_Last_VoteTry = Now; + pPlayer->m_LastVoteTry = Now; if(pPlayer->GetTeam() == TEAM_SPECTATORS) { SendChatTarget(ClientID, "Spectators aren't allowed to start a vote."); @@ -607,8 +617,8 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) return; } - int Timeleft = pPlayer->m_Last_VoteCall + Server()->TickSpeed()*60 - Now; - if(pPlayer->m_Last_VoteCall && Timeleft > 0) + int Timeleft = pPlayer->m_LastVoteCall + Server()->TickSpeed()*60 - Now; + if(pPlayer->m_LastVoteCall && Timeleft > 0) { char aChatmsg[512] = {0}; str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote", (Timeleft/Server()->TickSpeed())+1); @@ -715,7 +725,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) pPlayer->m_Vote = 1; pPlayer->m_VotePos = m_VotePos = 1; m_VoteCreator = ClientID; - pPlayer->m_Last_VoteCall = Now; + pPlayer->m_LastVoteCall = Now; } } else if(MsgID == NETMSGTYPE_CL_VOTE) @@ -738,7 +748,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) { CNetMsg_Cl_SetTeam *pMsg = (CNetMsg_Cl_SetTeam *)pRawMsg; - if(pPlayer->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && pPlayer->m_Last_SetTeam && pPlayer->m_Last_SetTeam+Server()->TickSpeed()*3 > Server()->Tick())) + if(pPlayer->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && pPlayer->m_LastSetTeam && pPlayer->m_LastSetTeam+Server()->TickSpeed()*3 > Server()->Tick())) return; // Switch team on given client and kill/respawn him @@ -746,7 +756,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) { if(m_pController->CanChangeTeam(pPlayer, pMsg->m_Team)) { - pPlayer->m_Last_SetTeam = Server()->Tick(); + pPlayer->m_LastSetTeam = Server()->Tick(); if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS) m_VoteUpdate = true; pPlayer->SetTeam(pMsg->m_Team); @@ -762,14 +772,28 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) SendBroadcast(aBuf, ClientID); } } + else if (MsgID == NETMSGTYPE_CL_SETSPECTATORMODE && !m_World.m_Paused) + { + CNetMsg_Cl_SetSpectatorMode *pMsg = (CNetMsg_Cl_SetSpectatorMode *)pRawMsg; + + if(pPlayer->GetTeam() != TEAM_SPECTATORS || pPlayer->m_SpectatorID == pMsg->m_SpectatorID || ClientID == pMsg->m_SpectatorID || + (g_Config.m_SvSpamprotection && pPlayer->m_LastSetSpectatorMode && pPlayer->m_LastSetSpectatorMode+Server()->TickSpeed()*3 > Server()->Tick())) + return; + + pPlayer->m_LastSetSpectatorMode = Server()->Tick(); + if(pMsg->m_SpectatorID != SPEC_FREEVIEW && !m_apPlayers[pMsg->m_SpectatorID]) + SendChatTarget(ClientID, "Invalid spectator id used"); + else + pPlayer->m_SpectatorID = pMsg->m_SpectatorID; + } else if (MsgID == NETMSGTYPE_CL_CHANGEINFO || MsgID == NETMSGTYPE_CL_STARTINFO) { CNetMsg_Cl_ChangeInfo *pMsg = (CNetMsg_Cl_ChangeInfo *)pRawMsg; - if(g_Config.m_SvSpamprotection && pPlayer->m_Last_ChangeInfo && pPlayer->m_Last_ChangeInfo+Server()->TickSpeed()*5 > Server()->Tick()) + if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && pPlayer->m_LastChangeInfo+Server()->TickSpeed()*5 > Server()->Tick()) return; - pPlayer->m_Last_ChangeInfo = Server()->Tick(); + pPlayer->m_LastChangeInfo = Server()->Tick(); pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor; pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody; @@ -818,19 +842,19 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) { CNetMsg_Cl_Emoticon *pMsg = (CNetMsg_Cl_Emoticon *)pRawMsg; - if(g_Config.m_SvSpamprotection && pPlayer->m_Last_Emote && pPlayer->m_Last_Emote+Server()->TickSpeed()*3 > Server()->Tick()) + if(g_Config.m_SvSpamprotection && pPlayer->m_LastEmote && pPlayer->m_LastEmote+Server()->TickSpeed()*3 > Server()->Tick()) return; - pPlayer->m_Last_Emote = Server()->Tick(); + pPlayer->m_LastEmote = Server()->Tick(); SendEmoticon(ClientID, pMsg->m_Emoticon); } else if (MsgID == NETMSGTYPE_CL_KILL && !m_World.m_Paused) { - if(pPlayer->m_Last_Kill && pPlayer->m_Last_Kill+Server()->TickSpeed()*3 > Server()->Tick()) + if(pPlayer->m_LastKill && pPlayer->m_LastKill+Server()->TickSpeed()*3 > Server()->Tick()) return; - pPlayer->m_Last_Kill = Server()->Tick(); + pPlayer->m_LastKill = Server()->Tick(); pPlayer->KillCharacter(WEAPON_SELF); pPlayer->m_RespawnTick = Server()->Tick()+Server()->TickSpeed()*3; } diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 573d996d..7d2f4ad9 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -18,6 +18,7 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team) Character = 0; this->m_ClientID = ClientID; m_Team = GameServer()->m_pController->ClampTeam(Team); + m_SpectatorID = SPEC_FREEVIEW; m_LastActionTick = Server()->Tick(); } @@ -77,6 +78,23 @@ void CPlayer::Tick() TryRespawn(); } +void CPlayer::PostTick() +{ + // update latency value + if(m_PlayerFlags&PLAYERFLAG_SCOREBOARD) + { + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS) + m_aActLatency[i] = GameServer()->m_apPlayers[i]->m_Latency.m_Min; + } + } + + // update view pos for spectators + if(m_Team == TEAM_SPECTATORS && m_SpectatorID != SPEC_FREEVIEW && GameServer()->m_apPlayers[m_SpectatorID]) + m_ViewPos = GameServer()->m_apPlayers[m_SpectatorID]->m_ViewPos; +} + void CPlayer::Snap(int SnappingClient) { #ifdef CONF_DEBUG @@ -99,12 +117,6 @@ void CPlayer::Snap(int SnappingClient) if(!pPlayerInfo) return; - // update latency value - if(SnappingClient != -1 && m_Team != -1 && GameServer()->m_apPlayers[SnappingClient]->m_PlayerFlags&PLAYERFLAG_SCOREBOARD) - { - GameServer()->m_apPlayers[SnappingClient]->m_aActLatency[m_ClientID] = m_Latency.m_Min; - } - pPlayerInfo->m_Latency = SnappingClient == -1 ? m_Latency.m_Min : GameServer()->m_apPlayers[SnappingClient]->m_aActLatency[m_ClientID]; pPlayerInfo->m_Local = 0; pPlayerInfo->m_ClientID = m_ClientID; @@ -112,7 +124,18 @@ void CPlayer::Snap(int SnappingClient) pPlayerInfo->m_Team = m_Team; if(m_ClientID == SnappingClient) - pPlayerInfo->m_Local = 1; + pPlayerInfo->m_Local = 1; + + if(m_ClientID == SnappingClient && m_Team == TEAM_SPECTATORS && m_SpectatorID != SPEC_FREEVIEW) + { + CNetObj_SpectatorInfo *pSpectatorInfo = static_cast<CNetObj_SpectatorInfo *>(Server()->SnapNewItem(NETOBJTYPE_SPECTATORINFO, m_ClientID, sizeof(CNetObj_SpectatorInfo))); + if(!pSpectatorInfo) + return; + + pSpectatorInfo->m_SpectatorID = m_SpectatorID; + pSpectatorInfo->m_X = m_ViewPos.x; + pSpectatorInfo->m_Y = m_ViewPos.y; + } } void CPlayer::OnDisconnect() @@ -146,7 +169,7 @@ void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput) if(!Character && m_Team != TEAM_SPECTATORS && (NewInput->m_Fire&1)) m_Spawning = true; - if(!Character && m_Team == TEAM_SPECTATORS) + if(!Character && m_Team == TEAM_SPECTATORS && m_SpectatorID == SPEC_FREEVIEW) m_ViewPos = vec2(NewInput->m_TargetX, NewInput->m_TargetY); // check for activity diff --git a/src/game/server/player.h b/src/game/server/player.h index f4d82d24..c638fae8 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -25,6 +25,7 @@ public: int GetCID() const { return m_ClientID; }; void Tick(); + void PostTick(); void Snap(int SnappingClient); void OnDirectInput(CNetObj_PlayerInput *NewInput); @@ -43,18 +44,22 @@ public: // used for snapping to just update latency if the scoreboard is active int m_aActLatency[MAX_CLIENTS]; + + // used for spectator mode + int m_SpectatorID; // int m_Vote; int m_VotePos; // - int m_Last_VoteCall; - int m_Last_VoteTry; - int m_Last_Chat; - int m_Last_SetTeam; - int m_Last_ChangeInfo; - int m_Last_Emote; - int m_Last_Kill; + int m_LastVoteCall; + int m_LastVoteTry; + int m_LastChat; + int m_LastSetTeam; + int m_LastSetSpectatorMode; + int m_LastChangeInfo; + int m_LastEmote; + int m_LastKill; // TODO: clean this up struct @@ -76,6 +81,17 @@ public: int m_TargetX; int m_TargetY; } m_LatestActivity; + + // network latency calculations + struct + { + int m_Accum; + int m_AccumMin; + int m_AccumMax; + int m_Avg; + int m_Min; + int m_Max; + } m_Latency; private: CCharacter *Character; @@ -88,17 +104,6 @@ private: bool m_Spawning; int m_ClientID; int m_Team; - - // network latency calculations - struct - { - int m_Accum; - int m_AccumMin; - int m_AccumMax; - int m_Avg; - int m_Min; - int m_Max; - } m_Latency; }; #endif |