| 1 | #include "nameplates.h" |
| 2 | |
| 3 | #include <engine/graphics.h> |
| 4 | #include <engine/shared/config.h> |
| 5 | #include <engine/shared/protocol7.h> |
| 6 | #include <engine/textrender.h> |
| 7 | |
| 8 | #include <generated/client_data.h> |
| 9 | |
| 10 | #include <game/client/animstate.h> |
| 11 | #include <game/client/gameclient.h> |
| 12 | #include <game/client/prediction/entities/character.h> |
| 13 | |
| 14 | #include <memory> |
| 15 | #include <vector> |
| 16 | |
| 17 | enum class EHookStrongWeakState |
| 18 | { |
| 19 | WEAK, |
| 20 | NEUTRAL, |
| 21 | STRONG |
| 22 | }; |
| 23 | |
| 24 | class CNamePlateData |
| 25 | { |
| 26 | public: |
| 27 | bool m_InGame; |
| 28 | ColorRGBA m_Color; |
| 29 | bool m_ShowName; |
| 30 | char m_aName[std::max<size_t>(a: MAX_NAME_LENGTH, b: protocol7::MAX_NAME_ARRAY_SIZE)]; |
| 31 | bool m_ShowFriendMark; |
| 32 | bool m_ShowClientId; |
| 33 | int m_ClientId; |
| 34 | float m_FontSizeClientId; |
| 35 | bool m_ClientIdSeparateLine; |
| 36 | float m_FontSize; |
| 37 | bool m_ShowClan; |
| 38 | char m_aClan[std::max<size_t>(a: MAX_CLAN_LENGTH, b: protocol7::MAX_CLAN_ARRAY_SIZE)]; |
| 39 | float m_FontSizeClan; |
| 40 | bool m_ShowDirection; |
| 41 | bool m_DirLeft; |
| 42 | bool m_DirJump; |
| 43 | bool m_DirRight; |
| 44 | float m_FontSizeDirection; |
| 45 | bool m_ShowHookStrongWeak; |
| 46 | EHookStrongWeakState m_HookStrongWeakState; |
| 47 | bool m_ShowHookStrongWeakId; |
| 48 | int m_HookStrongWeakId; |
| 49 | float m_FontSizeHookStrongWeak; |
| 50 | }; |
| 51 | |
| 52 | // Part Types |
| 53 | |
| 54 | static constexpr float DEFAULT_PADDING = 5.0f; |
| 55 | |
| 56 | class CNamePlatePart |
| 57 | { |
| 58 | protected: |
| 59 | vec2 m_Size = vec2(0.0f, 0.0f); |
| 60 | vec2 m_Padding = vec2(DEFAULT_PADDING, DEFAULT_PADDING); |
| 61 | bool m_NewLine = false; // Whether this part is a new line (doesn't do anything else) |
| 62 | bool m_Visible = true; // Whether this part is visible |
| 63 | bool m_ShiftOnInvis = false; // Whether when not visible will still take up space |
| 64 | CNamePlatePart(CGameClient &This) {} |
| 65 | |
| 66 | public: |
| 67 | virtual void Update(CGameClient &This, const CNamePlateData &Data) {} |
| 68 | virtual void Reset(CGameClient &This) {} |
| 69 | virtual void Render(CGameClient &This, vec2 Pos) const {} |
| 70 | vec2 Size() const { return m_Size; } |
| 71 | vec2 Padding() const { return m_Padding; } |
| 72 | bool NewLine() const { return m_NewLine; } |
| 73 | bool Visible() const { return m_Visible; } |
| 74 | bool ShiftOnInvis() const { return m_ShiftOnInvis; } |
| 75 | CNamePlatePart() = delete; |
| 76 | virtual ~CNamePlatePart() = default; |
| 77 | }; |
| 78 | |
| 79 | using PartsVector = std::vector<std::unique_ptr<CNamePlatePart>>; |
| 80 | |
| 81 | static constexpr ColorRGBA s_OutlineColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f); |
| 82 | |
| 83 | class CNamePlatePartText : public CNamePlatePart |
| 84 | { |
| 85 | protected: |
| 86 | STextContainerIndex m_TextContainerIndex; |
| 87 | virtual bool UpdateNeeded(CGameClient &This, const CNamePlateData &Data) = 0; |
| 88 | virtual void UpdateText(CGameClient &This, const CNamePlateData &Data) = 0; |
| 89 | ColorRGBA m_Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); |
| 90 | CNamePlatePartText(CGameClient &This) : |
| 91 | CNamePlatePart(This) |
| 92 | { |
| 93 | Reset(This); |
| 94 | } |
| 95 | |
| 96 | public: |
| 97 | void Update(CGameClient &This, const CNamePlateData &Data) override |
| 98 | { |
| 99 | if(!UpdateNeeded(This, Data) && m_TextContainerIndex.Valid()) |
| 100 | return; |
| 101 | |
| 102 | // Set flags |
| 103 | unsigned int Flags = ETextRenderFlags::TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE; |
| 104 | if(Data.m_InGame) |
| 105 | Flags |= ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGNMENT; // Prevent jittering from rounding |
| 106 | This.TextRender()->SetRenderFlags(Flags); |
| 107 | |
| 108 | if(Data.m_InGame) |
| 109 | { |
| 110 | // Create text at standard zoom |
| 111 | float ScreenX0, ScreenY0, ScreenX1, ScreenY1; |
| 112 | This.Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1); |
| 113 | This.Graphics()->MapScreenToInterface(CenterX: This.m_Camera.m_Center.x, CenterY: This.m_Camera.m_Center.y); |
| 114 | This.TextRender()->DeleteTextContainer(TextContainerIndex&: m_TextContainerIndex); |
| 115 | UpdateText(This, Data); |
| 116 | This.Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1); |
| 117 | } |
| 118 | else |
| 119 | { |
| 120 | UpdateText(This, Data); |
| 121 | } |
| 122 | |
| 123 | This.TextRender()->SetRenderFlags(0); |
| 124 | |
| 125 | if(!m_TextContainerIndex.Valid()) |
| 126 | { |
| 127 | m_Visible = false; |
| 128 | return; |
| 129 | } |
| 130 | |
| 131 | const STextBoundingBox Container = This.TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: m_TextContainerIndex); |
| 132 | m_Size = vec2(Container.m_W, Container.m_H); |
| 133 | } |
| 134 | void Reset(CGameClient &This) override |
| 135 | { |
| 136 | This.TextRender()->DeleteTextContainer(TextContainerIndex&: m_TextContainerIndex); |
| 137 | } |
| 138 | void Render(CGameClient &This, vec2 Pos) const override |
| 139 | { |
| 140 | if(!m_TextContainerIndex.Valid()) |
| 141 | return; |
| 142 | |
| 143 | ColorRGBA OutlineColor, Color; |
| 144 | Color = m_Color; |
| 145 | OutlineColor = s_OutlineColor.WithMultipliedAlpha(alpha: m_Color.a); |
| 146 | This.TextRender()->RenderTextContainer(TextContainerIndex: m_TextContainerIndex, |
| 147 | TextColor: Color, TextOutlineColor: OutlineColor, |
| 148 | X: Pos.x - Size().x / 2.0f, Y: Pos.y - Size().y / 2.0f); |
| 149 | } |
| 150 | }; |
| 151 | |
| 152 | class CNamePlatePartIcon : public CNamePlatePart |
| 153 | { |
| 154 | protected: |
| 155 | IGraphics::CTextureHandle m_Texture; |
| 156 | float m_Rotation = 0.0f; |
| 157 | ColorRGBA m_Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); |
| 158 | CNamePlatePartIcon(CGameClient &This) : |
| 159 | CNamePlatePart(This) {} |
| 160 | |
| 161 | public: |
| 162 | void Render(CGameClient &This, vec2 Pos) const override |
| 163 | { |
| 164 | IGraphics::CQuadItem QuadItem(Pos.x - Size().x / 2.0f, Pos.y - Size().y / 2.0f, Size().x, Size().y); |
| 165 | This.Graphics()->TextureSet(Texture: m_Texture); |
| 166 | This.Graphics()->QuadsBegin(); |
| 167 | This.Graphics()->SetColor(m_Color); |
| 168 | This.Graphics()->QuadsSetRotation(Angle: m_Rotation); |
| 169 | This.Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1); |
| 170 | This.Graphics()->QuadsEnd(); |
| 171 | This.Graphics()->QuadsSetRotation(Angle: 0.0f); |
| 172 | } |
| 173 | }; |
| 174 | |
| 175 | class CNamePlatePartSprite : public CNamePlatePart |
| 176 | { |
| 177 | protected: |
| 178 | IGraphics::CTextureHandle m_Texture; |
| 179 | int m_Sprite = -1; |
| 180 | int m_SpriteFlags = 0; |
| 181 | float m_Rotation = 0.0f; |
| 182 | ColorRGBA m_Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); |
| 183 | CNamePlatePartSprite(CGameClient &This) : |
| 184 | CNamePlatePart(This) {} |
| 185 | |
| 186 | public: |
| 187 | void Render(CGameClient &This, vec2 Pos) const override |
| 188 | { |
| 189 | This.Graphics()->TextureSet(Texture: m_Texture); |
| 190 | This.Graphics()->QuadsSetRotation(Angle: m_Rotation); |
| 191 | This.Graphics()->QuadsBegin(); |
| 192 | This.Graphics()->SetColor(m_Color); |
| 193 | This.Graphics()->SelectSprite(Id: m_Sprite, Flags: m_SpriteFlags); |
| 194 | This.Graphics()->DrawSprite(x: Pos.x, y: Pos.y, ScaledWidth: Size().x, ScaledHeight: Size().y); |
| 195 | This.Graphics()->QuadsEnd(); |
| 196 | This.Graphics()->QuadsSetRotation(Angle: 0.0f); |
| 197 | } |
| 198 | }; |
| 199 | |
| 200 | // Part Definitions |
| 201 | |
| 202 | class CNamePlatePartNewLine : public CNamePlatePart |
| 203 | { |
| 204 | public: |
| 205 | CNamePlatePartNewLine(CGameClient &This) : |
| 206 | CNamePlatePart(This) |
| 207 | { |
| 208 | m_NewLine = true; |
| 209 | } |
| 210 | }; |
| 211 | |
| 212 | enum Direction |
| 213 | { |
| 214 | DIRECTION_LEFT, |
| 215 | DIRECTION_UP, |
| 216 | DIRECTION_RIGHT |
| 217 | }; |
| 218 | |
| 219 | class CNamePlatePartDirection : public CNamePlatePartIcon |
| 220 | { |
| 221 | private: |
| 222 | int m_Direction; |
| 223 | |
| 224 | public: |
| 225 | CNamePlatePartDirection(CGameClient &This, Direction Dir) : |
| 226 | CNamePlatePartIcon(This) |
| 227 | { |
| 228 | m_Texture = g_pData->m_aImages[IMAGE_ARROW].m_Id; |
| 229 | m_Direction = Dir; |
| 230 | switch(m_Direction) |
| 231 | { |
| 232 | case DIRECTION_LEFT: |
| 233 | m_Rotation = pi; |
| 234 | break; |
| 235 | case DIRECTION_UP: |
| 236 | m_Rotation = pi / -2.0f; |
| 237 | break; |
| 238 | case DIRECTION_RIGHT: |
| 239 | m_Rotation = 0.0f; |
| 240 | break; |
| 241 | } |
| 242 | } |
| 243 | void Update(CGameClient &This, const CNamePlateData &Data) override |
| 244 | { |
| 245 | if(!Data.m_ShowDirection) |
| 246 | { |
| 247 | m_ShiftOnInvis = false; |
| 248 | m_Visible = false; |
| 249 | return; |
| 250 | } |
| 251 | m_ShiftOnInvis = true; // Only shift (horizontally) the other parts if directions as a whole is visible |
| 252 | m_Size = vec2(Data.m_FontSizeDirection, Data.m_FontSizeDirection); |
| 253 | m_Padding.y = m_Size.y / 2.0f; |
| 254 | switch(m_Direction) |
| 255 | { |
| 256 | case DIRECTION_LEFT: |
| 257 | m_Visible = Data.m_DirLeft; |
| 258 | break; |
| 259 | case DIRECTION_UP: |
| 260 | m_Visible = Data.m_DirJump; |
| 261 | break; |
| 262 | case DIRECTION_RIGHT: |
| 263 | m_Visible = Data.m_DirRight; |
| 264 | break; |
| 265 | } |
| 266 | m_Color.a = Data.m_Color.a; |
| 267 | } |
| 268 | }; |
| 269 | |
| 270 | class CNamePlatePartClientId : public CNamePlatePartText |
| 271 | { |
| 272 | private: |
| 273 | int m_ClientId = -1; |
| 274 | static_assert(MAX_CLIENTS <= 999, "Make this buffer bigger" ); |
| 275 | char m_aText[5] = "" ; |
| 276 | float m_FontSize = -INFINITY; |
| 277 | bool m_ClientIdSeparateLine = false; |
| 278 | |
| 279 | protected: |
| 280 | bool UpdateNeeded(CGameClient &This, const CNamePlateData &Data) override |
| 281 | { |
| 282 | m_Visible = Data.m_ShowClientId && (Data.m_ClientIdSeparateLine == m_ClientIdSeparateLine); |
| 283 | if(!m_Visible) |
| 284 | return false; |
| 285 | m_Color = Data.m_Color; |
| 286 | return m_FontSize != Data.m_FontSizeClientId || m_ClientId != Data.m_ClientId; |
| 287 | } |
| 288 | void UpdateText(CGameClient &This, const CNamePlateData &Data) override |
| 289 | { |
| 290 | m_FontSize = Data.m_FontSizeClientId; |
| 291 | m_ClientId = Data.m_ClientId; |
| 292 | if(m_ClientIdSeparateLine) |
| 293 | str_format(buffer: m_aText, buffer_size: sizeof(m_aText), format: "%d" , m_ClientId); |
| 294 | else |
| 295 | str_format(buffer: m_aText, buffer_size: sizeof(m_aText), format: "%d:" , m_ClientId); |
| 296 | CTextCursor Cursor; |
| 297 | Cursor.m_FontSize = m_FontSize; |
| 298 | This.TextRender()->CreateOrAppendTextContainer(TextContainerIndex&: m_TextContainerIndex, pCursor: &Cursor, pText: m_aText); |
| 299 | } |
| 300 | |
| 301 | public: |
| 302 | CNamePlatePartClientId(CGameClient &This, bool ClientIdSeparateLine) : |
| 303 | CNamePlatePartText(This) |
| 304 | { |
| 305 | m_ClientIdSeparateLine = ClientIdSeparateLine; |
| 306 | } |
| 307 | }; |
| 308 | |
| 309 | class CNamePlatePartFriendMark : public CNamePlatePartText |
| 310 | { |
| 311 | private: |
| 312 | float m_FontSize = -INFINITY; |
| 313 | |
| 314 | protected: |
| 315 | bool UpdateNeeded(CGameClient &This, const CNamePlateData &Data) override |
| 316 | { |
| 317 | m_Visible = Data.m_ShowFriendMark; |
| 318 | if(!m_Visible) |
| 319 | return false; |
| 320 | m_Color.a = Data.m_Color.a; |
| 321 | return m_FontSize != Data.m_FontSize; |
| 322 | } |
| 323 | void UpdateText(CGameClient &This, const CNamePlateData &Data) override |
| 324 | { |
| 325 | m_FontSize = Data.m_FontSize; |
| 326 | CTextCursor Cursor; |
| 327 | This.TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
| 328 | Cursor.m_FontSize = m_FontSize; |
| 329 | This.TextRender()->CreateOrAppendTextContainer(TextContainerIndex&: m_TextContainerIndex, pCursor: &Cursor, pText: FontIcons::FONT_ICON_HEART); |
| 330 | This.TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
| 331 | } |
| 332 | |
| 333 | public: |
| 334 | CNamePlatePartFriendMark(CGameClient &This) : |
| 335 | CNamePlatePartText(This) |
| 336 | { |
| 337 | m_Color = ColorRGBA(1.0f, 0.0f, 0.0f); |
| 338 | } |
| 339 | }; |
| 340 | |
| 341 | class CNamePlatePartName : public CNamePlatePartText |
| 342 | { |
| 343 | private: |
| 344 | char m_aText[std::max<size_t>(a: MAX_NAME_LENGTH, b: protocol7::MAX_NAME_ARRAY_SIZE)] = "" ; |
| 345 | float m_FontSize = -INFINITY; |
| 346 | |
| 347 | protected: |
| 348 | bool UpdateNeeded(CGameClient &This, const CNamePlateData &Data) override |
| 349 | { |
| 350 | m_Visible = Data.m_ShowName; |
| 351 | if(!m_Visible) |
| 352 | return false; |
| 353 | m_Color = Data.m_Color; |
| 354 | return m_FontSize != Data.m_FontSize || str_comp(a: m_aText, b: Data.m_aName) != 0; |
| 355 | } |
| 356 | void UpdateText(CGameClient &This, const CNamePlateData &Data) override |
| 357 | { |
| 358 | m_FontSize = Data.m_FontSize; |
| 359 | str_copy(dst: m_aText, src: Data.m_aName, dst_size: sizeof(m_aText)); |
| 360 | CTextCursor Cursor; |
| 361 | Cursor.m_FontSize = m_FontSize; |
| 362 | This.TextRender()->CreateOrAppendTextContainer(TextContainerIndex&: m_TextContainerIndex, pCursor: &Cursor, pText: m_aText); |
| 363 | } |
| 364 | |
| 365 | public: |
| 366 | CNamePlatePartName(CGameClient &This) : |
| 367 | CNamePlatePartText(This) {} |
| 368 | }; |
| 369 | |
| 370 | class CNamePlatePartClan : public CNamePlatePartText |
| 371 | { |
| 372 | private: |
| 373 | char m_aText[std::max<size_t>(a: MAX_CLAN_LENGTH, b: protocol7::MAX_CLAN_ARRAY_SIZE)] = "" ; |
| 374 | float m_FontSize = -INFINITY; |
| 375 | |
| 376 | protected: |
| 377 | bool UpdateNeeded(CGameClient &This, const CNamePlateData &Data) override |
| 378 | { |
| 379 | m_Visible = Data.m_ShowClan; |
| 380 | if(!m_Visible && Data.m_aClan[0] != '\0') |
| 381 | return false; |
| 382 | m_Color = Data.m_Color; |
| 383 | return m_FontSize != Data.m_FontSizeClan || str_comp(a: m_aText, b: Data.m_aClan) != 0; |
| 384 | } |
| 385 | void UpdateText(CGameClient &This, const CNamePlateData &Data) override |
| 386 | { |
| 387 | m_FontSize = Data.m_FontSizeClan; |
| 388 | str_copy(dst: m_aText, src: Data.m_aClan, dst_size: sizeof(m_aText)); |
| 389 | CTextCursor Cursor; |
| 390 | Cursor.m_FontSize = m_FontSize; |
| 391 | This.TextRender()->CreateOrAppendTextContainer(TextContainerIndex&: m_TextContainerIndex, pCursor: &Cursor, pText: m_aText); |
| 392 | } |
| 393 | |
| 394 | public: |
| 395 | CNamePlatePartClan(CGameClient &This) : |
| 396 | CNamePlatePartText(This) {} |
| 397 | }; |
| 398 | |
| 399 | class CNamePlatePartHookStrongWeak : public CNamePlatePartSprite |
| 400 | { |
| 401 | protected: |
| 402 | void Update(CGameClient &This, const CNamePlateData &Data) override |
| 403 | { |
| 404 | m_Visible = Data.m_ShowHookStrongWeak; |
| 405 | if(!m_Visible) |
| 406 | return; |
| 407 | m_Size = vec2(Data.m_FontSizeHookStrongWeak + DEFAULT_PADDING, Data.m_FontSizeHookStrongWeak + DEFAULT_PADDING); |
| 408 | switch(Data.m_HookStrongWeakState) |
| 409 | { |
| 410 | case EHookStrongWeakState::STRONG: |
| 411 | m_Sprite = SPRITE_HOOK_STRONG; |
| 412 | m_Color = color_cast<ColorRGBA>(hsl: ColorHSLA(6401973)); |
| 413 | break; |
| 414 | case EHookStrongWeakState::NEUTRAL: |
| 415 | m_Sprite = SPRITE_HOOK_ICON; |
| 416 | m_Color = ColorRGBA(1.0f, 1.0f, 1.0f); |
| 417 | break; |
| 418 | case EHookStrongWeakState::WEAK: |
| 419 | m_Sprite = SPRITE_HOOK_WEAK; |
| 420 | m_Color = color_cast<ColorRGBA>(hsl: ColorHSLA(41131)); |
| 421 | break; |
| 422 | } |
| 423 | m_Color.a = Data.m_Color.a; |
| 424 | } |
| 425 | |
| 426 | public: |
| 427 | CNamePlatePartHookStrongWeak(CGameClient &This) : |
| 428 | CNamePlatePartSprite(This) |
| 429 | { |
| 430 | m_Texture = g_pData->m_aImages[IMAGE_STRONGWEAK].m_Id; |
| 431 | m_Padding = vec2(0.0f, 0.0f); |
| 432 | } |
| 433 | }; |
| 434 | |
| 435 | class CNamePlatePartHookStrongWeakId : public CNamePlatePartText |
| 436 | { |
| 437 | private: |
| 438 | int m_StrongWeakId = -1; |
| 439 | static_assert(MAX_CLIENTS <= 999, "Make this buffer bigger" ); |
| 440 | char m_aText[4] = "" ; |
| 441 | float m_FontSize = -INFINITY; |
| 442 | |
| 443 | protected: |
| 444 | bool UpdateNeeded(CGameClient &This, const CNamePlateData &Data) override |
| 445 | { |
| 446 | m_Visible = Data.m_ShowHookStrongWeakId; |
| 447 | if(!m_Visible) |
| 448 | return false; |
| 449 | switch(Data.m_HookStrongWeakState) |
| 450 | { |
| 451 | case EHookStrongWeakState::STRONG: |
| 452 | m_Color = color_cast<ColorRGBA>(hsl: ColorHSLA(6401973)); |
| 453 | break; |
| 454 | case EHookStrongWeakState::NEUTRAL: |
| 455 | m_Color = ColorRGBA(1.0f, 1.0f, 1.0f); |
| 456 | break; |
| 457 | case EHookStrongWeakState::WEAK: |
| 458 | m_Color = color_cast<ColorRGBA>(hsl: ColorHSLA(41131)); |
| 459 | break; |
| 460 | } |
| 461 | m_Color.a = Data.m_Color.a; |
| 462 | return m_FontSize != Data.m_FontSizeHookStrongWeak || m_StrongWeakId != Data.m_HookStrongWeakId; |
| 463 | } |
| 464 | void UpdateText(CGameClient &This, const CNamePlateData &Data) override |
| 465 | { |
| 466 | m_FontSize = Data.m_FontSizeHookStrongWeak; |
| 467 | m_StrongWeakId = Data.m_HookStrongWeakId; |
| 468 | str_format(buffer: m_aText, buffer_size: sizeof(m_aText), format: "%d" , m_StrongWeakId); |
| 469 | CTextCursor Cursor; |
| 470 | Cursor.m_FontSize = m_FontSize; |
| 471 | This.TextRender()->CreateOrAppendTextContainer(TextContainerIndex&: m_TextContainerIndex, pCursor: &Cursor, pText: m_aText); |
| 472 | } |
| 473 | |
| 474 | public: |
| 475 | CNamePlatePartHookStrongWeakId(CGameClient &This) : |
| 476 | CNamePlatePartText(This) {} |
| 477 | }; |
| 478 | |
| 479 | // Name Plates |
| 480 | |
| 481 | class CNamePlate |
| 482 | { |
| 483 | private: |
| 484 | bool m_Inited = false; |
| 485 | bool m_InGame = false; |
| 486 | PartsVector m_vpParts; |
| 487 | void RenderLine(CGameClient &This, |
| 488 | vec2 Pos, vec2 Size, |
| 489 | PartsVector::iterator Start, PartsVector::iterator End) |
| 490 | { |
| 491 | Pos.x -= Size.x / 2.0f; |
| 492 | for(auto PartIt = Start; PartIt != End; ++PartIt) |
| 493 | { |
| 494 | const CNamePlatePart &Part = **PartIt; |
| 495 | if(Part.Visible()) |
| 496 | { |
| 497 | Part.Render(This, Pos: vec2( |
| 498 | Pos.x + (Part.Padding().x + Part.Size().x) / 2.0f, |
| 499 | Pos.y - std::max(a: Size.y, b: Part.Padding().y + Part.Size().y) / 2.0f)); |
| 500 | } |
| 501 | if(Part.Visible() || Part.ShiftOnInvis()) |
| 502 | Pos.x += Part.Size().x + Part.Padding().x; |
| 503 | } |
| 504 | } |
| 505 | template<typename PartType, typename... ArgsType> |
| 506 | void AddPart(CGameClient &This, ArgsType &&...Args) |
| 507 | { |
| 508 | m_vpParts.push_back(std::make_unique<PartType>(This, std::forward<ArgsType>(Args)...)); |
| 509 | } |
| 510 | void Init(CGameClient &This) |
| 511 | { |
| 512 | if(m_Inited) |
| 513 | return; |
| 514 | m_Inited = true; |
| 515 | |
| 516 | AddPart<CNamePlatePartDirection>(This, Args: DIRECTION_LEFT); |
| 517 | AddPart<CNamePlatePartDirection>(This, Args: DIRECTION_UP); |
| 518 | AddPart<CNamePlatePartDirection>(This, Args: DIRECTION_RIGHT); |
| 519 | AddPart<CNamePlatePartNewLine>(This); |
| 520 | |
| 521 | AddPart<CNamePlatePartFriendMark>(This); |
| 522 | AddPart<CNamePlatePartClientId>(This, Args: false); |
| 523 | AddPart<CNamePlatePartName>(This); |
| 524 | AddPart<CNamePlatePartNewLine>(This); |
| 525 | |
| 526 | AddPart<CNamePlatePartClan>(This); |
| 527 | AddPart<CNamePlatePartNewLine>(This); |
| 528 | |
| 529 | AddPart<CNamePlatePartClientId>(This, Args: true); |
| 530 | AddPart<CNamePlatePartNewLine>(This); |
| 531 | |
| 532 | AddPart<CNamePlatePartHookStrongWeak>(This); |
| 533 | AddPart<CNamePlatePartHookStrongWeakId>(This); |
| 534 | } |
| 535 | |
| 536 | public: |
| 537 | CNamePlate() = default; |
| 538 | CNamePlate(CGameClient &This, const CNamePlateData &Data) |
| 539 | { |
| 540 | // Convenience constructor |
| 541 | Update(This, Data); |
| 542 | } |
| 543 | void Reset(CGameClient &This) |
| 544 | { |
| 545 | for(auto &Part : m_vpParts) |
| 546 | Part->Reset(This); |
| 547 | } |
| 548 | void Update(CGameClient &This, const CNamePlateData &Data) |
| 549 | { |
| 550 | Init(This); |
| 551 | m_InGame = Data.m_InGame; |
| 552 | for(auto &Part : m_vpParts) |
| 553 | Part->Update(This, Data); |
| 554 | } |
| 555 | void Render(CGameClient &This, const vec2 &PositionBottomMiddle) |
| 556 | { |
| 557 | dbg_assert(m_Inited, "Tried to render uninited nameplate" ); |
| 558 | vec2 Position = PositionBottomMiddle; |
| 559 | // X: Total width including padding of line, Y: Max height of line parts |
| 560 | vec2 LineSize = vec2(0.0f, 0.0f); |
| 561 | bool Empty = true; |
| 562 | auto Start = m_vpParts.begin(); |
| 563 | for(auto PartIt = m_vpParts.begin(); PartIt != m_vpParts.end(); ++PartIt) |
| 564 | { |
| 565 | CNamePlatePart &Part = **PartIt; |
| 566 | if(Part.NewLine()) |
| 567 | { |
| 568 | if(!Empty) |
| 569 | { |
| 570 | RenderLine(This, Pos: Position, Size: LineSize, Start, End: std::next(x: PartIt)); |
| 571 | Position.y -= LineSize.y; |
| 572 | } |
| 573 | Start = std::next(x: PartIt); |
| 574 | LineSize = vec2(0.0f, 0.0f); |
| 575 | } |
| 576 | else if(Part.Visible() || Part.ShiftOnInvis()) |
| 577 | { |
| 578 | Empty = false; |
| 579 | LineSize.x += Part.Size().x + Part.Padding().x; |
| 580 | LineSize.y = std::max(a: LineSize.y, b: Part.Size().y + Part.Padding().y); |
| 581 | } |
| 582 | } |
| 583 | RenderLine(This, Pos: Position, Size: LineSize, Start, End: m_vpParts.end()); |
| 584 | This.Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
| 585 | } |
| 586 | vec2 Size() const |
| 587 | { |
| 588 | dbg_assert(m_Inited, "Tried to get size of uninited nameplate" ); |
| 589 | // X: Total width including padding of line, Y: Max height of line parts |
| 590 | vec2 LineSize = vec2(0.0f, 0.0f); |
| 591 | float WMax = 0.0f; |
| 592 | float HTotal = 0.0f; |
| 593 | bool Empty = true; |
| 594 | for(auto PartIt = m_vpParts.begin(); PartIt != m_vpParts.end(); ++PartIt) // NOLINT(modernize-loop-convert) For consistency with Render |
| 595 | { |
| 596 | CNamePlatePart &Part = **PartIt; |
| 597 | if(Part.NewLine()) |
| 598 | { |
| 599 | if(!Empty) |
| 600 | { |
| 601 | if(LineSize.x > WMax) |
| 602 | WMax = LineSize.x; |
| 603 | HTotal += LineSize.y; |
| 604 | } |
| 605 | LineSize = vec2(0.0f, 0.0f); |
| 606 | } |
| 607 | else if(Part.Visible() || Part.ShiftOnInvis()) |
| 608 | { |
| 609 | Empty = false; |
| 610 | LineSize.x += Part.Size().x + Part.Padding().x; |
| 611 | LineSize.y = std::max(a: LineSize.y, b: Part.Size().y + Part.Padding().y); |
| 612 | } |
| 613 | } |
| 614 | if(LineSize.x > WMax) |
| 615 | WMax = LineSize.x; |
| 616 | HTotal += LineSize.y; |
| 617 | return vec2(WMax, HTotal); |
| 618 | } |
| 619 | }; |
| 620 | |
| 621 | class CNamePlates::CNamePlatesData |
| 622 | { |
| 623 | public: |
| 624 | CNamePlate m_aNamePlates[MAX_CLIENTS]; |
| 625 | }; |
| 626 | |
| 627 | void CNamePlates::RenderNamePlateGame(vec2 Position, const CNetObj_PlayerInfo *pPlayerInfo, float Alpha) |
| 628 | { |
| 629 | // Get screen edges to avoid rendering offscreen |
| 630 | float ScreenX0, ScreenY0, ScreenX1, ScreenY1; |
| 631 | Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1); |
| 632 | |
| 633 | // Assume that the name plate fits into a 800x800 box placed directly above the tee |
| 634 | ScreenX0 -= 400; |
| 635 | ScreenX1 += 400; |
| 636 | ScreenY1 += 800; |
| 637 | if(!(in_range(a: Position.x, lower: ScreenX0, upper: ScreenX1) && in_range(a: Position.y, lower: ScreenY0, upper: ScreenY1))) |
| 638 | return; |
| 639 | |
| 640 | CNamePlateData Data; |
| 641 | |
| 642 | const auto &ClientData = GameClient()->m_aClients[pPlayerInfo->m_ClientId]; |
| 643 | const bool OtherTeam = GameClient()->IsOtherTeam(ClientId: pPlayerInfo->m_ClientId); |
| 644 | |
| 645 | Data.m_InGame = true; |
| 646 | |
| 647 | Data.m_ShowName = pPlayerInfo->m_Local ? g_Config.m_ClNamePlatesOwn : g_Config.m_ClNamePlates; |
| 648 | str_copy(dst&: Data.m_aName, src: GameClient()->m_aClients[pPlayerInfo->m_ClientId].m_aName); |
| 649 | Data.m_ShowFriendMark = Data.m_ShowName && g_Config.m_ClNamePlatesFriendMark && GameClient()->m_aClients[pPlayerInfo->m_ClientId].m_Friend; |
| 650 | Data.m_ShowClientId = Data.m_ShowName && (g_Config.m_Debug || g_Config.m_ClNamePlatesIds); |
| 651 | Data.m_FontSize = 18.0f + 20.0f * g_Config.m_ClNamePlatesSize / 100.0f; |
| 652 | |
| 653 | Data.m_ClientId = pPlayerInfo->m_ClientId; |
| 654 | Data.m_ClientIdSeparateLine = g_Config.m_ClNamePlatesIdsSeparateLine; |
| 655 | Data.m_FontSizeClientId = Data.m_ClientIdSeparateLine ? (18.0f + 20.0f * g_Config.m_ClNamePlatesIdsSize / 100.0f) : Data.m_FontSize; |
| 656 | |
| 657 | Data.m_ShowClan = Data.m_ShowName && g_Config.m_ClNamePlatesClan; |
| 658 | str_copy(dst&: Data.m_aClan, src: GameClient()->m_aClients[pPlayerInfo->m_ClientId].m_aClan); |
| 659 | Data.m_FontSizeClan = 18.0f + 20.0f * g_Config.m_ClNamePlatesClanSize / 100.0f; |
| 660 | |
| 661 | Data.m_FontSizeHookStrongWeak = 18.0f + 20.0f * g_Config.m_ClNamePlatesStrongSize / 100.0f; |
| 662 | Data.m_FontSizeDirection = 18.0f + 20.0f * g_Config.m_ClDirectionSize / 100.0f; |
| 663 | |
| 664 | if(g_Config.m_ClNamePlatesAlways == 0) |
| 665 | Alpha *= std::clamp(val: 1.0f - std::pow(x: distance(a: GameClient()->m_Controls.m_aTargetPos[g_Config.m_ClDummy], b: Position) / 200.0f, y: 16.0f), lo: 0.0f, hi: 1.0f); |
| 666 | if(OtherTeam) |
| 667 | Alpha *= (float)g_Config.m_ClShowOthersAlpha / 100.0f; |
| 668 | |
| 669 | Data.m_Color = ColorRGBA(1.0f, 1.0f, 1.0f); |
| 670 | if(g_Config.m_ClNamePlatesTeamcolors) |
| 671 | { |
| 672 | if(GameClient()->IsTeamPlay()) |
| 673 | { |
| 674 | if(ClientData.m_Team == TEAM_RED) |
| 675 | Data.m_Color = ColorRGBA(1.0f, 0.5f, 0.5f); |
| 676 | else if(ClientData.m_Team == TEAM_BLUE) |
| 677 | Data.m_Color = ColorRGBA(0.7f, 0.7f, 1.0f); |
| 678 | } |
| 679 | else |
| 680 | { |
| 681 | const int Team = GameClient()->m_Teams.Team(ClientId: pPlayerInfo->m_ClientId); |
| 682 | if(Team) |
| 683 | Data.m_Color = GameClient()->GetDDTeamColor(DDTeam: Team, Lightness: 0.75f); |
| 684 | } |
| 685 | } |
| 686 | Data.m_Color.a = Alpha; |
| 687 | |
| 688 | int ShowDirectionConfig = g_Config.m_ClShowDirection; |
| 689 | #if defined(CONF_VIDEORECORDER) |
| 690 | if(IVideo::Current()) |
| 691 | ShowDirectionConfig = g_Config.m_ClVideoShowDirection; |
| 692 | #endif |
| 693 | Data.m_DirLeft = Data.m_DirJump = Data.m_DirRight = false; |
| 694 | switch(ShowDirectionConfig) |
| 695 | { |
| 696 | case 0: // Off |
| 697 | Data.m_ShowDirection = false; |
| 698 | break; |
| 699 | case 1: // Others |
| 700 | Data.m_ShowDirection = !pPlayerInfo->m_Local; |
| 701 | break; |
| 702 | case 2: // Everyone |
| 703 | Data.m_ShowDirection = true; |
| 704 | break; |
| 705 | case 3: // Only self |
| 706 | Data.m_ShowDirection = pPlayerInfo->m_Local; |
| 707 | break; |
| 708 | default: |
| 709 | dbg_assert_failed("ShowDirectionConfig invalid" ); |
| 710 | } |
| 711 | if(Data.m_ShowDirection) |
| 712 | { |
| 713 | if(Client()->State() != IClient::STATE_DEMOPLAYBACK && |
| 714 | pPlayerInfo->m_ClientId == GameClient()->m_aLocalIds[!g_Config.m_ClDummy]) |
| 715 | { |
| 716 | const auto &InputData = GameClient()->m_Controls.m_aInputData[!g_Config.m_ClDummy]; |
| 717 | Data.m_DirLeft = InputData.m_Direction == -1; |
| 718 | Data.m_DirJump = InputData.m_Jump == 1; |
| 719 | Data.m_DirRight = InputData.m_Direction == 1; |
| 720 | } |
| 721 | else if(Client()->State() != IClient::STATE_DEMOPLAYBACK && pPlayerInfo->m_Local) // Always render local input when not in demo playback |
| 722 | { |
| 723 | const auto &InputData = GameClient()->m_Controls.m_aInputData[g_Config.m_ClDummy]; |
| 724 | Data.m_DirLeft = InputData.m_Direction == -1; |
| 725 | Data.m_DirJump = InputData.m_Jump == 1; |
| 726 | Data.m_DirRight = InputData.m_Direction == 1; |
| 727 | } |
| 728 | else |
| 729 | { |
| 730 | const auto &Character = GameClient()->m_Snap.m_aCharacters[pPlayerInfo->m_ClientId]; |
| 731 | Data.m_DirLeft = Character.m_Cur.m_Direction == -1; |
| 732 | Data.m_DirJump = Character.m_Cur.m_Jumped & 1; |
| 733 | Data.m_DirRight = Character.m_Cur.m_Direction == 1; |
| 734 | } |
| 735 | } |
| 736 | |
| 737 | Data.m_ShowHookStrongWeak = false; |
| 738 | Data.m_HookStrongWeakState = EHookStrongWeakState::NEUTRAL; |
| 739 | Data.m_ShowHookStrongWeakId = false; |
| 740 | Data.m_HookStrongWeakId = 0; |
| 741 | |
| 742 | const bool Following = (GameClient()->m_Snap.m_SpecInfo.m_Active && !GameClient()->m_MultiViewActivated && GameClient()->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW); |
| 743 | if(GameClient()->m_Snap.m_LocalClientId != -1 || Following) |
| 744 | { |
| 745 | const int SelectedId = Following ? GameClient()->m_Snap.m_SpecInfo.m_SpectatorId : GameClient()->m_Snap.m_LocalClientId; |
| 746 | const CGameClient::CSnapState::CCharacterInfo &Selected = GameClient()->m_Snap.m_aCharacters[SelectedId]; |
| 747 | const CGameClient::CSnapState::CCharacterInfo &Other = GameClient()->m_Snap.m_aCharacters[pPlayerInfo->m_ClientId]; |
| 748 | |
| 749 | if((Selected.m_HasExtendedData || GameClient()->m_aClients[SelectedId].m_SpecCharPresent) && Other.m_HasExtendedData) |
| 750 | { |
| 751 | int SelectedStrongWeakId = Selected.m_HasExtendedData ? Selected.m_ExtendedData.m_StrongWeakId : 0; |
| 752 | Data.m_HookStrongWeakId = Other.m_ExtendedData.m_StrongWeakId; |
| 753 | Data.m_ShowHookStrongWeakId = g_Config.m_Debug || g_Config.m_ClNamePlatesStrong == 2; |
| 754 | if(SelectedId == pPlayerInfo->m_ClientId) |
| 755 | Data.m_ShowHookStrongWeak = Data.m_ShowHookStrongWeakId; |
| 756 | else |
| 757 | { |
| 758 | Data.m_HookStrongWeakState = SelectedStrongWeakId > Other.m_ExtendedData.m_StrongWeakId ? EHookStrongWeakState::STRONG : EHookStrongWeakState::WEAK; |
| 759 | Data.m_ShowHookStrongWeak = g_Config.m_Debug || g_Config.m_ClNamePlatesStrong > 0; |
| 760 | } |
| 761 | } |
| 762 | } |
| 763 | |
| 764 | // Check if the nameplate is actually on screen |
| 765 | CNamePlate &NamePlate = m_pData->m_aNamePlates[pPlayerInfo->m_ClientId]; |
| 766 | NamePlate.Update(This&: *GameClient(), Data); |
| 767 | NamePlate.Render(This&: *GameClient(), PositionBottomMiddle: Position - vec2(0.0f, (float)g_Config.m_ClNamePlatesOffset)); |
| 768 | } |
| 769 | |
| 770 | void CNamePlates::RenderNamePlatePreview(vec2 Position, int Dummy) |
| 771 | { |
| 772 | const float FontSize = 18.0f + 20.0f * g_Config.m_ClNamePlatesSize / 100.0f; |
| 773 | const float FontSizeClan = 18.0f + 20.0f * g_Config.m_ClNamePlatesClanSize / 100.0f; |
| 774 | |
| 775 | const float FontSizeDirection = 18.0f + 20.0f * g_Config.m_ClDirectionSize / 100.0f; |
| 776 | const float FontSizeHookStrongWeak = 18.0f + 20.0f * g_Config.m_ClNamePlatesStrongSize / 100.0f; |
| 777 | |
| 778 | CNamePlateData Data; |
| 779 | |
| 780 | Data.m_InGame = false; |
| 781 | Data.m_Color = g_Config.m_ClNamePlatesTeamcolors ? GameClient()->GetDDTeamColor(DDTeam: 13, Lightness: 0.75f) : TextRender()->DefaultTextColor(); |
| 782 | Data.m_Color.a = 1.0f; |
| 783 | |
| 784 | Data.m_ShowName = g_Config.m_ClNamePlates || g_Config.m_ClNamePlatesOwn; |
| 785 | const char *pName = Dummy == 0 ? Client()->PlayerName() : Client()->DummyName(); |
| 786 | str_copy(dst&: Data.m_aName, src: str_utf8_skip_whitespaces(str: pName)); |
| 787 | str_utf8_trim_right(param: Data.m_aName); |
| 788 | Data.m_FontSize = FontSize; |
| 789 | |
| 790 | Data.m_ShowFriendMark = Data.m_ShowName && g_Config.m_ClNamePlatesFriendMark; |
| 791 | |
| 792 | Data.m_ShowClientId = Data.m_ShowName && (g_Config.m_Debug || g_Config.m_ClNamePlatesIds); |
| 793 | Data.m_ClientId = Dummy; |
| 794 | Data.m_ClientIdSeparateLine = g_Config.m_ClNamePlatesIdsSeparateLine; |
| 795 | Data.m_FontSizeClientId = Data.m_ClientIdSeparateLine ? (18.0f + 20.0f * g_Config.m_ClNamePlatesIdsSize / 100.0f) : Data.m_FontSize; |
| 796 | |
| 797 | Data.m_ShowClan = Data.m_ShowName && g_Config.m_ClNamePlatesClan; |
| 798 | const char *pClan = Dummy == 0 ? g_Config.m_PlayerClan : g_Config.m_ClDummyClan; |
| 799 | str_copy(dst&: Data.m_aClan, src: str_utf8_skip_whitespaces(str: pClan)); |
| 800 | str_utf8_trim_right(param: Data.m_aClan); |
| 801 | if(Data.m_aClan[0] == '\0') |
| 802 | str_copy(dst&: Data.m_aClan, src: "Clan Name" ); |
| 803 | Data.m_FontSizeClan = FontSizeClan; |
| 804 | |
| 805 | Data.m_ShowDirection = g_Config.m_ClShowDirection != 0 ? true : false; |
| 806 | Data.m_DirLeft = Data.m_DirJump = Data.m_DirRight = true; |
| 807 | Data.m_FontSizeDirection = FontSizeDirection; |
| 808 | |
| 809 | Data.m_FontSizeHookStrongWeak = FontSizeHookStrongWeak; |
| 810 | Data.m_HookStrongWeakId = Data.m_ClientId; |
| 811 | Data.m_ShowHookStrongWeakId = g_Config.m_ClNamePlatesStrong == 2; |
| 812 | if(Dummy == g_Config.m_ClDummy) |
| 813 | { |
| 814 | Data.m_HookStrongWeakState = EHookStrongWeakState::NEUTRAL; |
| 815 | Data.m_ShowHookStrongWeak = Data.m_ShowHookStrongWeakId; |
| 816 | } |
| 817 | else |
| 818 | { |
| 819 | Data.m_HookStrongWeakState = Data.m_HookStrongWeakId == 2 ? EHookStrongWeakState::STRONG : EHookStrongWeakState::WEAK; |
| 820 | Data.m_ShowHookStrongWeak = g_Config.m_ClNamePlatesStrong > 0; |
| 821 | } |
| 822 | |
| 823 | CTeeRenderInfo TeeRenderInfo; |
| 824 | if(Dummy == 0) |
| 825 | { |
| 826 | TeeRenderInfo.Apply(pSkin: GameClient()->m_Skins.Find(pName: g_Config.m_ClPlayerSkin)); |
| 827 | TeeRenderInfo.ApplyColors(CustomColoredSkin: g_Config.m_ClPlayerUseCustomColor, ColorBody: g_Config.m_ClPlayerColorBody, ColorFeet: g_Config.m_ClPlayerColorFeet); |
| 828 | } |
| 829 | else |
| 830 | { |
| 831 | TeeRenderInfo.Apply(pSkin: GameClient()->m_Skins.Find(pName: g_Config.m_ClDummySkin)); |
| 832 | TeeRenderInfo.ApplyColors(CustomColoredSkin: g_Config.m_ClDummyUseCustomColor, ColorBody: g_Config.m_ClDummyColorBody, ColorFeet: g_Config.m_ClDummyColorFeet); |
| 833 | } |
| 834 | TeeRenderInfo.m_Size = 64.0f; |
| 835 | |
| 836 | CNamePlate NamePlate(*GameClient(), Data); |
| 837 | Position.y += NamePlate.Size().y / 2.0f; |
| 838 | Position.y += (float)g_Config.m_ClNamePlatesOffset / 2.0f; |
| 839 | // tee looking towards cursor, and it is happy when you touch it |
| 840 | const vec2 DeltaPosition = Ui()->MousePos() - Position; |
| 841 | const float Distance = length(a: DeltaPosition); |
| 842 | const float InteractionDistance = 20.0f; |
| 843 | const vec2 TeeDirection = Distance < InteractionDistance ? normalize(v: vec2(DeltaPosition.x, maximum(a: DeltaPosition.y, b: 0.5f))) : normalize(v: DeltaPosition); |
| 844 | const int TeeEmote = Distance < InteractionDistance ? EMOTE_HAPPY : (Dummy ? g_Config.m_ClDummyDefaultEyes : g_Config.m_ClPlayerDefaultEyes); |
| 845 | RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &TeeRenderInfo, Emote: TeeEmote, Dir: TeeDirection, Pos: Position); |
| 846 | Position.y -= (float)g_Config.m_ClNamePlatesOffset; |
| 847 | NamePlate.Render(This&: *GameClient(), PositionBottomMiddle: Position); |
| 848 | NamePlate.Reset(This&: *GameClient()); |
| 849 | } |
| 850 | |
| 851 | void CNamePlates::ResetNamePlates() |
| 852 | { |
| 853 | for(CNamePlate &NamePlate : m_pData->m_aNamePlates) |
| 854 | NamePlate.Reset(This&: *GameClient()); |
| 855 | } |
| 856 | |
| 857 | void CNamePlates::OnRender() |
| 858 | { |
| 859 | if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
| 860 | return; |
| 861 | |
| 862 | int ShowDirection = g_Config.m_ClShowDirection; |
| 863 | #if defined(CONF_VIDEORECORDER) |
| 864 | if(IVideo::Current()) |
| 865 | ShowDirection = g_Config.m_ClVideoShowDirection; |
| 866 | #endif |
| 867 | if(!g_Config.m_ClNamePlates && ShowDirection == 0) |
| 868 | return; |
| 869 | |
| 870 | for(int i = 0; i < MAX_CLIENTS; i++) |
| 871 | { |
| 872 | const CNetObj_PlayerInfo *pInfo = GameClient()->m_Snap.m_apPlayerInfos[i]; |
| 873 | if(!pInfo) |
| 874 | continue; |
| 875 | |
| 876 | // Each player can also have a spectator char whose name plate is displayed independently |
| 877 | if(GameClient()->m_aClients[i].m_SpecCharPresent) |
| 878 | { |
| 879 | const vec2 RenderPos = GameClient()->m_aClients[i].m_SpecChar; |
| 880 | RenderNamePlateGame(Position: RenderPos, pPlayerInfo: pInfo, Alpha: 0.4f); |
| 881 | } |
| 882 | // Only render name plates for active characters |
| 883 | if(GameClient()->m_Snap.m_aCharacters[i].m_Active) |
| 884 | { |
| 885 | const vec2 RenderPos = GameClient()->m_aClients[i].m_RenderPos; |
| 886 | RenderNamePlateGame(Position: RenderPos, pPlayerInfo: pInfo, Alpha: 1.0f); |
| 887 | } |
| 888 | } |
| 889 | } |
| 890 | |
| 891 | void CNamePlates::OnWindowResize() |
| 892 | { |
| 893 | ResetNamePlates(); |
| 894 | } |
| 895 | |
| 896 | CNamePlates::CNamePlates() : |
| 897 | m_pData(new CNamePlates::CNamePlatesData()) {} |
| 898 | |
| 899 | CNamePlates::~CNamePlates() |
| 900 | { |
| 901 | delete m_pData; |
| 902 | } |
| 903 | |