diff --git a/CMakeLists.txt b/CMakeLists.txt index 26184e674..8db91101e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1234,6 +1234,7 @@ set(EXPECTED_DATA gui_cursor.png gui_icons.png gui_logo.png + hud.png languages/arabic.txt languages/belarusian.txt languages/bosnian.txt @@ -1964,6 +1965,8 @@ if(CLIENT) components/emoticon.h components/flow.cpp components/flow.h + components/freezebars.cpp + components/freezebars.h components/ghost.cpp components/ghost.h components/hud.cpp diff --git a/data/hud.png b/data/hud.png new file mode 100644 index 000000000..ccf57668e Binary files /dev/null and b/data/hud.png differ diff --git a/datasrc/content.py b/datasrc/content.py index 940dc3bf7..1a60149a6 100644 --- a/datasrc/content.py +++ b/datasrc/content.py @@ -238,6 +238,7 @@ image_arrow = Image("arrow", "arrow.png") image_demobuttons2 = Image("demobuttons2", "demo_buttons2.png") image_audio_source = Image("audio_source", "editor/audio_source.png") image_strongweak = Image("strongweak", "strong_weak.png") +image_hud = Image("hud", "hud.png") container.images.Add(image_null) container.images.Add(image_game) @@ -256,6 +257,7 @@ container.images.Add(image_arrow) container.images.Add(image_demobuttons2) container.images.Add(image_audio_source) container.images.Add(image_strongweak) +container.images.Add(image_hud) container.pickups.Add(Pickup("health")) container.pickups.Add(Pickup("armor")) @@ -278,6 +280,7 @@ set_guiicons = SpriteSet("guiicons", image_guiicons, 12, 2) set_demobuttons2 = SpriteSet("demobuttons2", image_demobuttons2, 4, 1) set_audio_source = SpriteSet("audio_source", image_audio_source, 1, 1) set_strongweak = SpriteSet("strongweak", image_strongweak, 2, 1) +set_hud = SpriteSet("hud", image_hud, 14, 12) container.spritesets.Add(set_particles) container.spritesets.Add(set_game) @@ -291,6 +294,7 @@ container.spritesets.Add(set_guiicons) container.spritesets.Add(set_demobuttons2) container.spritesets.Add(set_audio_source) container.spritesets.Add(set_strongweak) +container.spritesets.Add(set_hud) container.sprites.Add(Sprite("part_slice", set_particles, 0,0,1,1)) container.sprites.Add(Sprite("part_ball", set_particles, 1,0,1,1)) @@ -362,12 +366,16 @@ container.sprites.Add(Sprite("weapon_ninja_muzzle3", set_game, 25,8,7,4)) container.sprites.Add(Sprite("pickup_health", set_game, 10,2,2,2)) container.sprites.Add(Sprite("pickup_armor", set_game, 12,2,2,2)) +container.sprites.Add(Sprite("pickup_hammer", set_game, 2,1,4,3)) +container.sprites.Add(Sprite("pickup_gun", set_game, 2,4,4,2)) +container.sprites.Add(Sprite("pickup_shotgun", set_game, 2,6,8,2)) +container.sprites.Add(Sprite("pickup_grenade", set_game, 2,8,7,2)) +container.sprites.Add(Sprite("pickup_laser", set_game, 2,12,7,3)) +container.sprites.Add(Sprite("pickup_ninja", set_game, 2,10,8,2)) container.sprites.Add(Sprite("pickup_armor_shotgun", set_game, 15,2,2,2)) container.sprites.Add(Sprite("pickup_armor_grenade", set_game, 17,2,2,2)) -container.sprites.Add(Sprite("pickup_armor_laser", set_game, 19,2,2,2)) container.sprites.Add(Sprite("pickup_armor_ninja", set_game, 10,10,2,2)) -container.sprites.Add(Sprite("pickup_weapon", set_game, 3,0,6,2)) -container.sprites.Add(Sprite("pickup_ninja", set_game, 2,10,8,2)) +container.sprites.Add(Sprite("pickup_armor_laser", set_game, 19,2,2,2)) container.sprites.Add(Sprite("flag_blue", set_game, 12,8,4,8)) container.sprites.Add(Sprite("flag_red", set_game, 16,8,4,8)) @@ -434,6 +442,35 @@ container.sprites.Add(Sprite("audio_source", set_audio_source, 0,0,1,1)) container.sprites.Add(Sprite("hook_strong", set_strongweak, 0,0,1,1)) container.sprites.Add(Sprite("hook_weak", set_strongweak, 1,0,1,1)) +container.sprites.Add(Sprite("hud_airjump", set_hud, 0,0,2,2)) +container.sprites.Add(Sprite("hud_airjump_empty", set_hud, 2,0,2,2)) +container.sprites.Add(Sprite("hud_solo", set_hud, 4,0,2,2)) +container.sprites.Add(Sprite("hud_no_collision", set_hud, 6,0,2,2)) +container.sprites.Add(Sprite("hud_endless_jump", set_hud, 8,0,2,2)) +container.sprites.Add(Sprite("hud_endless_hook", set_hud, 10,0,2,2)) +container.sprites.Add(Sprite("hud_jetpack", set_hud, 12,0,2,2)) +container.sprites.Add(Sprite("hud_freeze_bar_full_left", set_hud, 0,2,1,1)) +container.sprites.Add(Sprite("hud_freeze_bar_full", set_hud, 1,2,1,1)) +container.sprites.Add(Sprite("hud_freeze_bar_empty", set_hud, 2,2,1,1)) +container.sprites.Add(Sprite("hud_freeze_bar_empty_right", set_hud, 3,2,1,1)) +container.sprites.Add(Sprite("hud_ninja_bar_full_left", set_hud, 0,3,1,1)) +container.sprites.Add(Sprite("hud_ninja_bar_full", set_hud, 1,3,1,1)) +container.sprites.Add(Sprite("hud_ninja_bar_empty", set_hud, 2,3,1,1)) +container.sprites.Add(Sprite("hud_ninja_bar_empty_right", set_hud, 3,3,1,1)) +container.sprites.Add(Sprite("hud_no_hook_hit", set_hud, 4,2,2,2)) +container.sprites.Add(Sprite("hud_no_hammer_hit", set_hud, 6,2,2,2)) +container.sprites.Add(Sprite("hud_no_shotgun_hit", set_hud, 8,2,2,2)) +container.sprites.Add(Sprite("hud_no_grenade_hit", set_hud, 10,2,2,2)) +container.sprites.Add(Sprite("hud_no_laser_hit", set_hud, 12,2,2,2)) +container.sprites.Add(Sprite("hud_deep_frozen", set_hud, 10,4,2,2)) +container.sprites.Add(Sprite("hud_live_frozen", set_hud, 12,4,2,2)) +container.sprites.Add(Sprite("hud_teleport_grenade", set_hud, 4,4,2,2)) +container.sprites.Add(Sprite("hud_teleport_gun", set_hud, 6,4,2,2)) +container.sprites.Add(Sprite("hud_teleport_laser", set_hud, 8,4,2,2)) +container.sprites.Add(Sprite("hud_practice_mode", set_hud, 4,6,2,2)) +container.sprites.Add(Sprite("hud_dummy_hammer", set_hud, 6,6,2,2)) +container.sprites.Add(Sprite("hud_dummy_copy", set_hud, 8,6,2,2)) + anim = Animation("base") anim.body.frames.Add(AnimKeyframe(0, 0, -4, 0)) anim.back_foot.frames.Add(AnimKeyframe(0, 0, 10, 0)) diff --git a/datasrc/network.py b/datasrc/network.py index bf66afdff..81b1fe462 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -243,6 +243,17 @@ Objects = [ NetIntRange("m_StrongWeakID", 0, 'MAX_CLIENTS-1'), ]), + NetObjectEx("DDNetCharacterDisplayInfo", "character-display-info@netobj.ddnet.tw", [ + NetIntRange("m_JumpedTotal", 0, 255), + NetTick("m_NinjaActivationTick"), + NetTick("m_FreezeTick"), + NetBool("m_IsInFreeze"), + NetBool("m_IsInPracticeMode"), + NetIntAny("m_TargetX"), + NetIntAny("m_TargetY"), + NetIntAny("m_RampValue"), + ]), + NetObjectEx("DDNetPlayer", "player@netobj.ddnet.tw", [ NetIntAny("m_Flags"), NetIntRange("m_AuthLevel", "AUTHED_NO", "AUTHED_ADMIN"), diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 5d31f61a8..df427af59 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -129,6 +129,19 @@ void CGraph::Add(float v, float r, float g, float b) m_aColors[m_Index][2] = b; } +bool CGraph::InsertAt(int i, float v, float r, float g, float b) +{ + if(i < 0 || i > MAX_VALUES - 1) + { + return false; + } + m_aValues[i] = v; + m_aColors[i][0] = r; + m_aColors[i][1] = g; + m_aColors[i][2] = b; + return true; +} + void CGraph::Render(IGraphics *pGraphics, IGraphics::CTextureHandle FontTexture, float x, float y, float w, float h, const char *pDescription) { //m_pGraphics->BlendNormal(); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index a5c3fefec..d49b9530a 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -53,6 +53,7 @@ public: void ScaleMin(); void Add(float v, float r, float g, float b); + bool InsertAt(int i, float v, float r, float g, float b); void Render(IGraphics *pGraphics, IGraphics::CTextureHandle FontTexture, float x, float y, float w, float h, const char *pDescription); }; diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index d1461b0e7..0bca67a64 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -162,7 +162,9 @@ static void Mix(short *pFinalOut, unsigned Frames) float r = Voice.m_Circle.m_Radius; RangeX = r; - int Dist = (int)sqrtf((float)dx * dx + (float)dy * dy); // nasty float + // dx and dy can be larger than 46341 and thus the calculation would go beyond the limits of a integer, + // therefore we cast them into float + int Dist = (int)sqrtf((float)dx * dx + (float)dy * dy); if(Dist < r) { InVoiceField = true; diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 91a9b4c34..de32b9518 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -41,6 +41,7 @@ MACRO_CONFIG_STR(ClAssetsEntites, cl_assets_entities, 50, "default", CFGFLAG_SAV MACRO_CONFIG_STR(ClAssetGame, cl_asset_game, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for game") MACRO_CONFIG_STR(ClAssetEmoticons, cl_asset_emoticons, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for emoticons") MACRO_CONFIG_STR(ClAssetParticles, cl_asset_particles, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for particles") +MACRO_CONFIG_STR(ClAssetHud, cl_asset_hud, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for HUD") MACRO_CONFIG_STR(BrFilterString, br_filter_string, 25, "Novice", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser filtering string") MACRO_CONFIG_STR(BrExcludeString, br_exclude_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser exclusion string") @@ -304,7 +305,10 @@ MACRO_CONFIG_INT(ClRaceGhost, cl_race_ghost, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_S MACRO_CONFIG_INT(ClRaceGhostServerControl, cl_race_ghost_server_control, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Let the server start the ghost") MACRO_CONFIG_INT(ClRaceShowGhost, cl_race_show_ghost, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ghost") MACRO_CONFIG_INT(ClRaceSaveGhost, cl_race_save_ghost, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Save ghost") + +MACRO_CONFIG_INT(ClDDRaceHud, cl_ddrace_hud, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Enable DDRace HUD") MACRO_CONFIG_INT(ClDDRaceScoreBoard, cl_ddrace_scoreboard, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Enable DDRace Scoreboard") +MACRO_CONFIG_INT(ClShowFreezeBars, cl_show_freeze_bars, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show a freeze bar under frozen players to indicate the thaw time") MACRO_CONFIG_INT(SvResetPickups, sv_reset_pickups, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Whether the weapons are reset on passing the start tile or not") MACRO_CONFIG_INT(ClShowOthers, cl_show_others, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show players in other teams (2 to show own team only)") MACRO_CONFIG_INT(ClShowOthersAlpha, cl_show_others_alpha, 40, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show players in other teams (alpha value, 0 invisible, 100 fully visible)") diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index 862f05949..1f9caa2ed 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -79,6 +79,7 @@ public: fs_makedir(GetPath(TYPE_SAVE, "assets/entities", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "assets/game", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "assets/particles", aPath, sizeof(aPath))); + fs_makedir(GetPath(TYPE_SAVE, "assets/hud", aPath, sizeof(aPath))); #if defined(CONF_VIDEORECORDER) fs_makedir(GetPath(TYPE_SAVE, "videos", aPath, sizeof(aPath))); #endif diff --git a/src/game/client/components/debughud.cpp b/src/game/client/components/debughud.cpp index a85eab4c6..303b18074 100644 --- a/src/game/client/components/debughud.cpp +++ b/src/game/client/components/debughud.cpp @@ -28,25 +28,32 @@ void CDebugHud::RenderNetCorrections() /*float speed = distance(vec2(netobjects.local_prev_character->x, netobjects.local_prev_character->y), vec2(netobjects.local_character->x, netobjects.local_character->y));*/ - float Velspeed = length(vec2(m_pClient->m_Snap.m_pLocalCharacter->m_VelX / 256.0f, m_pClient->m_Snap.m_pLocalCharacter->m_VelY / 256.0f)) * 50; + const float TicksPerSecond = 50.0f; + float Velspeed = length(vec2(m_pClient->m_Snap.m_pLocalCharacter->m_VelX / 256.0f, m_pClient->m_Snap.m_pLocalCharacter->m_VelY / 256.0f)) * TicksPerSecond; + float VelspeedX = m_pClient->m_Snap.m_pLocalCharacter->m_VelX / 256.0f * TicksPerSecond; + float VelspeedY = m_pClient->m_Snap.m_pLocalCharacter->m_VelY / 256.0f * TicksPerSecond; float Ramp = VelocityRamp(Velspeed, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampCurvature); - const char *paStrings[] = {"velspeed:", "velspeed*ramp:", "ramp:", "checkpoint:", "Pos", " x:", " y:", "angle:", "netobj corrections", " num:", " on:"}; - const int Num = std::size(paStrings); + const char *apStrings[] = {"velspeed:", "velspeed.x*ramp:", "velspeed.y:", "ramp:", "checkpoint:", "Pos", " x:", " y:", "angle:", "netobj corrections", " num:", " on:"}; + const int Num = std::size(apStrings); const float LineHeight = 6.0f; const float Fontsize = 5.0f; float x = Width - 100.0f, y = 50.0f; for(int i = 0; i < Num; ++i) - TextRender()->Text(0, x, y + i * LineHeight, Fontsize, paStrings[i], -1.0f); + TextRender()->Text(0, x, y + i * LineHeight, Fontsize, apStrings[i], -1.0f); x = Width - 10.0f; char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed / 32); + str_format(aBuf, sizeof(aBuf), "%.2f Bps", Velspeed / 32); float w = TextRender()->TextWidth(0, Fontsize, aBuf, -1, -1.0f); TextRender()->Text(0, x - w, y, Fontsize, aBuf, -1.0f); y += LineHeight; - str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed / 32 * Ramp); + str_format(aBuf, sizeof(aBuf), "%.2f Bps", VelspeedX / 32 * Ramp); + w = TextRender()->TextWidth(0, Fontsize, aBuf, -1, -1.0f); + TextRender()->Text(0, x - w, y, Fontsize, aBuf, -1.0f); + y += LineHeight; + str_format(aBuf, sizeof(aBuf), "%.2f Bps", VelspeedY / 32); w = TextRender()->TextWidth(0, Fontsize, aBuf, -1, -1.0f); TextRender()->Text(0, x - w, y, Fontsize, aBuf, -1.0f); y += LineHeight; @@ -125,25 +132,81 @@ void CDebugHud::RenderTuning() Count++; } - y = y + Count * 6; + // Rander Velspeed.X*Ramp Graphs + Graphics()->MapScreen(0, 0, Graphics()->ScreenWidth(), Graphics()->ScreenHeight()); + float GraphW = Graphics()->ScreenWidth() / 4.0f; + float GraphH = Graphics()->ScreenHeight() / 6.0f; + float sp = Graphics()->ScreenWidth() / 100.0f; + float GraphX = GraphW; + float GraphY = Graphics()->ScreenHeight() - GraphH - sp; - Graphics()->TextureClear(); - Graphics()->BlendNormal(); - Graphics()->LinesBegin(); - float Height = 50.0f; - float pv = 1; - IGraphics::CLineItem Array[100]; - for(int i = 0; i < 100; i++) + CTuningParams *ClinetTuning = &m_pClient->m_Tuning[g_Config.m_ClDummy]; + const int StepSizeRampGraph = 270; + const int StepSizeZoomedInGraph = 14; + if(m_OldVelrampStart != ClinetTuning->m_VelrampStart || m_OldVelrampRange != ClinetTuning->m_VelrampRange || m_OldVelrampCurvature != ClinetTuning->m_VelrampCurvature) { - float Speed = i / 100.0f * 3000; - float Ramp = VelocityRamp(Speed, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampCurvature); - float RampedSpeed = (Speed * Ramp) / 1000.0f; - Array[i] = IGraphics::CLineItem((i - 1) * 2, y + Height - pv * Height, i * 2, y + Height - RampedSpeed * Height); - //Graphics()->LinesDraw((i-1)*2, 200, i*2, 200); - pv = RampedSpeed; + m_OldVelrampStart = ClinetTuning->m_VelrampStart; + m_OldVelrampRange = ClinetTuning->m_VelrampRange; + m_OldVelrampCurvature = ClinetTuning->m_VelrampCurvature; + + m_RampGraph.Init(0.0f, 0.0f); + m_SpeedTurningPoint = 0; + float pv = 1; + // CGraph must be fed with exactly 128 values. + for(int i = 0; i < 128; i++) + { + // This is a calculation of the speed values per second on the X axis, from 270 to 34560 in steps of 270 + float Speed = (i + 1) * StepSizeRampGraph; + float Ramp = VelocityRamp(Speed, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampCurvature); + float RampedSpeed = Speed * Ramp; + if(RampedSpeed >= pv) + { + m_RampGraph.InsertAt(i, RampedSpeed / 32, 0, 1, 0); + m_SpeedTurningPoint = Speed; + } + else + { + m_RampGraph.InsertAt(i, RampedSpeed / 32, 1, 0, 0); + } + pv = RampedSpeed; + } + m_RampGraph.ScaleMin(); + m_RampGraph.ScaleMax(); + + m_ZoomedInGraph.Init(0.0f, 0.0f); + pv = 1; + MiddleOfZoomedInGraph = m_SpeedTurningPoint; + for(int i = 0; i < 128; i++) + { + // This is a calculation of the speed values per second on the X axis, from (MiddleOfZoomedInGraph - 64 * StepSize) to (MiddleOfZoomedInGraph + 64 * StepSize) + float Speed = MiddleOfZoomedInGraph - 64 * StepSizeZoomedInGraph + i * StepSizeZoomedInGraph; + float Ramp = VelocityRamp(Speed, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_Tuning[g_Config.m_ClDummy].m_VelrampCurvature); + float RampedSpeed = Speed * Ramp; + if(RampedSpeed >= pv) + { + m_ZoomedInGraph.InsertAt(i, RampedSpeed / 32, 0, 1, 0); + m_SpeedTurningPoint = Speed; + } + else + { + m_ZoomedInGraph.InsertAt(i, RampedSpeed / 32, 1, 0, 0); + } + if(i == 0) + { + m_ZoomedInGraph.m_Min = m_ZoomedInGraph.m_MinRange = RampedSpeed; + } + pv = RampedSpeed; + } + m_ZoomedInGraph.ScaleMin(); + m_ZoomedInGraph.ScaleMax(); } - Graphics()->LinesDraw(Array, 100); - Graphics()->LinesEnd(); + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "Velspeed.X*Ramp in Bps (Velspeed %d to %d)", StepSizeRampGraph / 32, 128 * StepSizeRampGraph / 32); + m_RampGraph.Render(Graphics(), Client()->GetDebugFont(), GraphX, GraphY - GraphH - sp, GraphW, GraphH, aBuf); + str_format(aBuf, sizeof(aBuf), "Max Velspeed before it ramps off: %.2f Bps", m_SpeedTurningPoint / 32); + TextRender()->Text(0x0, GraphX, GraphY - sp - GraphH - 12, 12, aBuf, -1.0f); + str_format(aBuf, sizeof(aBuf), "Zoomed in on turning point (Velspeed %d to %d)", ((int)MiddleOfZoomedInGraph - 64 * StepSizeZoomedInGraph) / 32, ((int)MiddleOfZoomedInGraph + 64 * StepSizeZoomedInGraph) / 32); + m_ZoomedInGraph.Render(Graphics(), Client()->GetDebugFont(), GraphX, GraphY, GraphW, GraphH, aBuf); TextRender()->TextColor(1, 1, 1, 1); } diff --git a/src/game/client/components/debughud.h b/src/game/client/components/debughud.h index 0720c8d3c..67b9019d3 100644 --- a/src/game/client/components/debughud.h +++ b/src/game/client/components/debughud.h @@ -2,6 +2,8 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #ifndef GAME_CLIENT_COMPONENTS_DEBUGHUD_H #define GAME_CLIENT_COMPONENTS_DEBUGHUD_H +#include + #include class CDebugHud : public CComponent @@ -10,6 +12,14 @@ class CDebugHud : public CComponent void RenderTuning(); void RenderHint(); + CGraph m_RampGraph; + CGraph m_ZoomedInGraph; + float m_SpeedTurningPoint; + float MiddleOfZoomedInGraph; + float m_OldVelrampStart; + float m_OldVelrampRange; + float m_OldVelrampCurvature; + public: virtual int Sizeof() const override { return sizeof(*this); } virtual void OnRender() override; diff --git a/src/game/client/components/freezebars.cpp b/src/game/client/components/freezebars.cpp new file mode 100644 index 000000000..04e32d265 --- /dev/null +++ b/src/game/client/components/freezebars.cpp @@ -0,0 +1,239 @@ +#include + +#include "freezebars.h" + +void CFreezeBars::RenderFreezeBar(const int ClientID) +{ + const float FreezeBarWidth = 64.0f; + const float FreezeBarHalfWidth = 32.0f; + const float FreezeBarHight = 16.0f; + + // pCharacter contains the predicted character for local players or the last snap for players who are spectated + CCharacterCore *pCharacter = &m_pClient->m_aClients[ClientID].m_Predicted; + if(pCharacter->m_FreezeEnd <= 0.0f || !m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo || pCharacter->m_IsInFreeze) + { + return; + } + + const int Max = pCharacter->m_FreezeEnd - pCharacter->m_FreezeTick; + float FreezeProgress = clamp(Max - (Client()->GameTick(g_Config.m_ClDummy) - pCharacter->m_FreezeTick), 0, Max) / (float)Max; + if(FreezeProgress <= 0.0f) + { + return; + } + + vec2 Position = m_pClient->m_aClients[ClientID].m_RenderPos; + Position.x -= FreezeBarHalfWidth; + Position.y += 32; + + float Alpha = m_pClient->IsOtherTeam(ClientID) ? g_Config.m_ClShowOthersAlpha / 100.0f : 1.0f; + + RenderFreezeBarPos(Position.x, Position.y, FreezeBarWidth, FreezeBarHight, FreezeProgress, Alpha); +} + +void CFreezeBars::RenderFreezeBarPos(float x, const float y, const float width, const float height, float Progress, const float Alpha) +{ + Progress = clamp(Progress, 0.0f, 1.0f); + + // what percentage of the end pieces is used for the progress indicator and how much is the rest + // half of the ends are used for the progress display + const float RestPct = 0.5f; + const float ProgPct = 0.5f; + + const float EndWidth = height; // to keep the correct scale - the height of the sprite is as long as the width + const float BarHeight = height; + const float WholeBarWidth = width; + const float MiddleBarWidth = WholeBarWidth - (EndWidth * 2.0f); + const float EndProgressWidth = EndWidth * ProgPct; + const float EndRestWidth = EndWidth * RestPct; + const float ProgressBarWidth = WholeBarWidth - (EndProgressWidth * 2.0f); + const float EndProgressProportion = EndProgressWidth / ProgressBarWidth; + const float MiddleProgressProportion = MiddleBarWidth / ProgressBarWidth; + + // we cut 2% of all sides of all sprites so we don't get edge bleeding + const float CutL = 0.02f; + const float CutR = 0.98f; + const float CutT = 0.02f; + const float CutB = 0.98f; + const float Slice = 0.02f; + + // beginning piece + float BeginningPieceProgress = 1; + if(Progress <= EndProgressProportion) + { + BeginningPieceProgress = Progress / EndProgressProportion; + } + + // full + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudFreezeBarFullLeft); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // Subset: top_l, top_m, btm_m, btm_l + Graphics()->QuadsSetSubsetFree(CutL, CutT, RestPct + (ProgPct - CutL) * BeginningPieceProgress, CutT, RestPct + (ProgPct - CutL) * BeginningPieceProgress, CutB, CutL, CutB); + IGraphics::CQuadItem QuadFullBeginning(x, y, EndRestWidth + EndProgressWidth * BeginningPieceProgress, BarHeight); + Graphics()->QuadsDrawTL(&QuadFullBeginning, 1); + Graphics()->QuadsEnd(); + + // empty + if(BeginningPieceProgress < 1.0f) + { + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudFreezeBarEmptyRight); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // Subset: top_m, top_l, btm_l, btm_m | it is mirrored on the horizontal axe and rotated 180 degrees + Graphics()->QuadsSetSubsetFree(ProgPct - (ProgPct - CutL) * BeginningPieceProgress, CutT, CutL, CutT, CutL, CutB, ProgPct - (ProgPct - CutL) * BeginningPieceProgress, CutB); + IGraphics::CQuadItem QuadEmptyBeginning(x + EndRestWidth + EndProgressWidth * BeginningPieceProgress, y, EndProgressWidth * (1.0f - BeginningPieceProgress), BarHeight); + Graphics()->QuadsDrawTL(&QuadEmptyBeginning, 1); + Graphics()->QuadsEnd(); + } + + // middle piece + x += EndWidth; + + float MiddlePieceProgress = 1; + if(Progress <= EndProgressProportion + MiddleProgressProportion) + { + if(Progress <= EndProgressProportion) + { + MiddlePieceProgress = 0; + } + else + { + MiddlePieceProgress = (Progress - EndProgressProportion) / MiddleProgressProportion; + } + } + + const float FullMiddleBarWidth = MiddleBarWidth * MiddlePieceProgress; + const float EmptyMiddleBarWidth = MiddleBarWidth - FullMiddleBarWidth; + + // full freeze bar + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudFreezeBarFull); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // select the middle portion of the sprite so we don't get edge bleeding + if(FullMiddleBarWidth <= EndWidth) + { + // prevent pixel puree, select only a small slice + // Subset: top_l, top_m, btm_m, btm_l + Graphics()->QuadsSetSubsetFree(CutL, CutT, CutL + Slice, CutT, CutL + Slice, CutB, CutL, CutB); + } + else + { + // Subset: top_l, top_r, btm_r, btm_l + Graphics()->QuadsSetSubsetFree(CutL, CutT, CutR, CutT, CutR, CutB, CutL, CutB); + } + IGraphics::CQuadItem QuadFull(x, y, FullMiddleBarWidth, BarHeight); + Graphics()->QuadsDrawTL(&QuadFull, 1); + Graphics()->QuadsEnd(); + + // empty freeze bar + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudFreezeBarEmpty); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // select the middle portion of the sprite so we don't get edge bleeding + if(EmptyMiddleBarWidth <= EndWidth) + { + // prevent pixel puree, select only a small slice + // Subset: top_m, top_l, btm_l, btm_m | it is mirrored on the horizontal axe and rotated 180 degrees + Graphics()->QuadsSetSubsetFree(CutL + Slice, CutT, CutL, CutT, CutL, CutB, CutL + Slice, CutB); + } + else + { + // Subset: top_r, top_l, btm_l, btm_r | it is mirrored on the horizontal axe and rotated 180 degrees + Graphics()->QuadsSetSubsetFree(CutR, CutT, CutL, CutT, CutL, CutB, CutR, CutB); + } + + IGraphics::CQuadItem QuadEmpty(x + FullMiddleBarWidth, y, EmptyMiddleBarWidth, BarHeight); + Graphics()->QuadsDrawTL(&QuadEmpty, 1); + Graphics()->QuadsEnd(); + + // end piece + x += MiddleBarWidth; + float EndingPieceProgress = 1; + if(Progress <= 1) + { + if(Progress <= (EndProgressProportion + MiddleProgressProportion)) + { + EndingPieceProgress = 0; + } + else + { + EndingPieceProgress = (Progress - EndProgressProportion - MiddleProgressProportion) / EndProgressProportion; + } + } + if(EndingPieceProgress > 0.0f) + { + // full + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudFreezeBarFullLeft); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // Subset: top_r, top_m, btm_m, btm_r | it is mirrored on the horizontal axe and rotated 180 degrees + Graphics()->QuadsSetSubsetFree(CutR, CutT, CutR - (ProgPct - CutL) * EndingPieceProgress, CutT, CutR - (ProgPct - CutL) * EndingPieceProgress, CutB, CutR, CutB); + IGraphics::CQuadItem QuadFullEnding(x, y, EndProgressWidth * EndingPieceProgress, BarHeight); + Graphics()->QuadsDrawTL(&QuadFullEnding, 1); + Graphics()->QuadsEnd(); + } + // empty + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudFreezeBarEmptyRight); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // Subset: top_m, top_r, btm_r, btm_m + Graphics()->QuadsSetSubsetFree(ProgPct - (ProgPct - CutL) * (1.0f - EndingPieceProgress), CutT, CutR, CutT, CutR, CutB, ProgPct - (ProgPct - CutL) * (1.0f - EndingPieceProgress), CutB); + IGraphics::CQuadItem QuadEmptyEnding(x + EndProgressWidth * EndingPieceProgress, y, EndProgressWidth * (1.0f - EndingPieceProgress) + EndRestWidth, BarHeight); + Graphics()->QuadsDrawTL(&QuadEmptyEnding, 1); + Graphics()->QuadsEnd(); + + Graphics()->QuadsSetSubset(0, 0, 1, 1); + Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); +} + +inline bool CFreezeBars::IsPlayerInfoAvailable(int ClientID) const +{ + const void *pPrevInfo = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_PLAYERINFO, ClientID); + const void *pInfo = Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_PLAYERINFO, ClientID); + return pPrevInfo && pInfo; +} + +void CFreezeBars::OnRender() +{ + if(!g_Config.m_ClShowFreezeBars) + { + return; + } + // get screen edges to avoid rendering offscreen + float ScreenX0, ScreenY0, ScreenX1, ScreenY1; + Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); + // expand the edges to prevent popping in/out onscreen + // + // it is assumed that the tee with the freeze bar fit into a 240x240 box centered on the tee + // this may need to be changed or calculated differently in the future + float BorderBuffer = 120; + ScreenX0 -= BorderBuffer; + ScreenX1 += BorderBuffer; + ScreenY0 -= BorderBuffer; + ScreenY1 += BorderBuffer; + + int LocalClientID = m_pClient->m_Snap.m_LocalClientID; + + // render everyone else's freeze bar, then our own + for(int ClientID = 0; ClientID < MAX_CLIENTS; ClientID++) + { + if(ClientID == LocalClientID || !m_pClient->m_Snap.m_aCharacters[ClientID].m_Active || !IsPlayerInfoAvailable(ClientID)) + { + continue; + } + + //don't render if the tee is offscreen + vec2 *pRenderPos = &m_pClient->m_aClients[ClientID].m_RenderPos; + if(pRenderPos->x < ScreenX0 || pRenderPos->x > ScreenX1 || pRenderPos->y < ScreenY0 || pRenderPos->y > ScreenY1) + { + continue; + } + + RenderFreezeBar(ClientID); + } + if(LocalClientID != -1 && m_pClient->m_Snap.m_aCharacters[LocalClientID].m_Active && IsPlayerInfoAvailable(LocalClientID)) + { + RenderFreezeBar(LocalClientID); + } +} \ No newline at end of file diff --git a/src/game/client/components/freezebars.h b/src/game/client/components/freezebars.h new file mode 100644 index 000000000..f0f76110d --- /dev/null +++ b/src/game/client/components/freezebars.h @@ -0,0 +1,16 @@ +#ifndef GAME_CLIENT_COMPONENTS_FREEZEBARS_H +#define GAME_CLIENT_COMPONENTS_FREEZEBARS_H +#include + +class CFreezeBars : public CComponent +{ + void RenderFreezeBar(const int ClientID); + void RenderFreezeBarPos(float x, const float y, const float width, const float height, float Progress, float Alpha = 1.0f); + bool IsPlayerInfoAvailable(int ClientID) const; + +public: + virtual int Sizeof() const override { return sizeof(*this); } + virtual void OnRender() override; +}; + +#endif diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index 135797a38..5a8a58d71 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -81,30 +82,29 @@ void CHud::OnReset() void CHud::OnInit() { - Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); m_HudQuadContainerIndex = Graphics()->CreateQuadContainer(false); - PrepareHealthAmoQuads(); + Graphics()->QuadsSetSubset(0, 0, 1, 1); + PrepareAmmoHealthAndArmorQuads(); - // all cursors + // all cursors for the different weapons for(int i = 0; i < NUM_WEAPONS; ++i) { float ScaleX, ScaleY; RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[i].m_pSpriteCursor, ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); + m_CursorOffset[i] = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); } // the flags - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 8.f, 16.f); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 8.f, 16.f); + m_FlagOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 8.f, 16.f); + + PreparePlayerStateQuads(); + Graphics()->QuadContainerUpload(m_HudQuadContainerIndex); } void CHud::RenderGameTimer() { - float Half = 300.0f * Graphics()->ScreenAspect() / 2.0f; + float Half = m_Width / 2.0f; if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_SUDDENDEATH)) { @@ -127,9 +127,12 @@ void CHud::RenderGameTimer() str_time((int64_t)Time * 100, TIME_DAYS, aBuf, sizeof(aBuf)); float FontSize = 10.0f; - float w = TextRender()->TextWidth(0, FontSize, - Time >= 3600 * 24 ? "00d 00:00:00" : Time >= 3600 ? "00:00:00" : "00:00", - -1, -1.0f); + static float s_TextWidthM = TextRender()->TextWidth(0, FontSize, "00:00", -1, -1.0f); + static float s_TextWidthH = TextRender()->TextWidth(0, FontSize, "00:00:00", -1, -1.0f); + static float s_TextWidth0D = TextRender()->TextWidth(0, FontSize, "0d 00:00:00", -1, -1.0f); + static float s_TextWidth00D = TextRender()->TextWidth(0, FontSize, "00d 00:00:00", -1, -1.0f); + static float s_TextWidth000D = TextRender()->TextWidth(0, FontSize, "000d 00:00:00", -1, -1.0f); + float w = Time >= 3600 * 24 * 100 ? s_TextWidth000D : Time >= 3600 * 24 * 10 ? s_TextWidth00D : Time >= 3600 * 24 ? s_TextWidth0D : Time >= 3600 ? s_TextWidthH : s_TextWidthM; // last 60 sec red, last 10 sec blink if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60 && (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer <= 0)) { @@ -157,7 +160,7 @@ void CHud::RenderSuddenDeath() { if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_SUDDENDEATH) { - float Half = 300.0f * Graphics()->ScreenAspect() / 2.0f; + float Half = m_Width / 2.0f; const char *pText = Localize("Sudden Death"); float FontSize = 12.0f; float w = TextRender()->TextWidth(0, FontSize, pText, -1, -1.0f); @@ -171,8 +174,7 @@ void CHud::RenderScoreHud() if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)) { int GameFlags = m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags; - float Whole = 300 * Graphics()->ScreenAspect(); - float StartY = 229.0f; + float StartY = 229.0f; // the height of this display is 56, so EndY is 285 const float ScoreSingleBoxHeight = 18.0f; @@ -218,7 +220,7 @@ void CHud::RenderScoreHud() Graphics()->SetColor(1.0f, 0.0f, 0.0f, 0.25f); else Graphics()->SetColor(0.0f, 0.0f, 1.0f, 0.25f); - m_aScoreInfo[t].m_RoundRectQuadContainerIndex = RenderTools()->CreateRoundRectQuadContainer(Whole - ScoreWidthMax - ImageSize - 2 * Split, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split, ScoreSingleBoxHeight, 5.0f, CUI::CORNER_L); + m_aScoreInfo[t].m_RoundRectQuadContainerIndex = RenderTools()->CreateRoundRectQuadContainer(m_Width - ScoreWidthMax - ImageSize - 2 * Split, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split, ScoreSingleBoxHeight, 5.0f, CUI::CORNER_L); } Graphics()->TextureClear(); Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); @@ -232,7 +234,7 @@ void CHud::RenderScoreHud() TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_TextScoreContainerIndex); CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Whole - ScoreWidthMax + (ScoreWidthMax - m_aScoreInfo[t].m_ScoreTextWidth) / 2 - Split, StartY + t * 20 + (18.f - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER); + TextRender()->SetCursor(&Cursor, m_Width - ScoreWidthMax + (ScoreWidthMax - m_aScoreInfo[t].m_ScoreTextWidth) / 2 - Split, StartY + t * 20 + (18.f - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; m_aScoreInfo[t].m_TextScoreContainerIndex = TextRender()->CreateTextContainer(&Cursor, aScoreTeam[t]); } @@ -252,10 +254,9 @@ void CHud::RenderScoreHud() if(FlagCarrier[t] == FLAG_ATSTAND || (FlagCarrier[t] == FLAG_TAKEN && ((Client()->GameTick(g_Config.m_ClDummy) / BlinkTimer) & 1))) { // draw flag - Graphics()->TextureSet(t == 0 ? GameClient()->m_GameSkin.m_SpriteFlagRed : GameClient()->m_GameSkin.m_SpriteFlagBlue); - int QuadOffset = NUM_WEAPONS * 10 * 2 + 40 * 2 + NUM_WEAPONS + t; + Graphics()->TextureSet(t == 0 ? m_pClient->m_GameSkin.m_SpriteFlagRed : m_pClient->m_GameSkin.m_SpriteFlagBlue); Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); - Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, QuadOffset, Whole - ScoreWidthMax - ImageSize, StartY + 1.0f + t * 20); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_FlagOffset, m_Width - ScoreWidthMax - ImageSize, StartY + 1.0f + t * 20); } else if(FlagCarrier[t] >= 0) { @@ -272,7 +273,7 @@ void CHud::RenderScoreHud() float w = TextRender()->TextWidth(0, 8.0f, pName, -1, -1.0f); CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, minimum(Whole - w - 1.0f, Whole - ScoreWidthMax - ImageSize - 2 * Split), StartY + (t + 1) * 20.0f - 2.0f, 8.0f, TEXTFLAG_RENDER); + TextRender()->SetCursor(&Cursor, minimum(m_Width - w - 1.0f, m_Width - ScoreWidthMax - ImageSize - 2 * Split), StartY + (t + 1) * 20.0f - 2.0f, 8.0f, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; m_aScoreInfo[t].m_OptionalNameTextContainerIndex = TextRender()->CreateTextContainer(&Cursor, pName); } @@ -291,7 +292,7 @@ void CHud::RenderScoreHud() CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); - vec2 TeeRenderPos(Whole - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y); + vec2 TeeRenderPos(m_Width - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); } @@ -401,7 +402,7 @@ void CHud::RenderScoreHud() Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); else Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.25f); - m_aScoreInfo[t].m_RoundRectQuadContainerIndex = RenderTools()->CreateRoundRectQuadContainer(Whole - ScoreWidthMax - ImageSize - 2 * Split - PosSize, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split + PosSize, ScoreSingleBoxHeight, 5.0f, CUI::CORNER_L); + m_aScoreInfo[t].m_RoundRectQuadContainerIndex = RenderTools()->CreateRoundRectQuadContainer(m_Width - ScoreWidthMax - ImageSize - 2 * Split - PosSize, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split + PosSize, ScoreSingleBoxHeight, 5.0f, CUI::CORNER_L); } Graphics()->TextureClear(); Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); @@ -414,7 +415,7 @@ void CHud::RenderScoreHud() TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_TextScoreContainerIndex); CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Whole - ScoreWidthMax + (ScoreWidthMax - m_aScoreInfo[t].m_ScoreTextWidth) - Split, StartY + t * 20 + (18.f - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER); + TextRender()->SetCursor(&Cursor, m_Width - ScoreWidthMax + (ScoreWidthMax - m_aScoreInfo[t].m_ScoreTextWidth) - Split, StartY + t * 20 + (18.f - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; m_aScoreInfo[t].m_TextScoreContainerIndex = TextRender()->CreateTextContainer(&Cursor, aScore[t]); } @@ -443,7 +444,7 @@ void CHud::RenderScoreHud() float w = TextRender()->TextWidth(0, 8.0f, pName, -1, -1.0f); CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, minimum(Whole - w - 1.0f, Whole - ScoreWidthMax - ImageSize - 2 * Split - PosSize), StartY + (t + 1) * 20.0f - 2.0f, 8.0f, TEXTFLAG_RENDER); + TextRender()->SetCursor(&Cursor, minimum(m_Width - w - 1.0f, m_Width - ScoreWidthMax - ImageSize - 2 * Split - PosSize), StartY + (t + 1) * 20.0f - 2.0f, 8.0f, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; m_aScoreInfo[t].m_OptionalNameTextContainerIndex = TextRender()->CreateTextContainer(&Cursor, pName); } @@ -462,7 +463,7 @@ void CHud::RenderScoreHud() CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); - vec2 TeeRenderPos(Whole - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y); + vec2 TeeRenderPos(m_Width - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); } @@ -483,7 +484,7 @@ void CHud::RenderScoreHud() TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_TextRankContainerIndex); CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, Whole - ScoreWidthMax - ImageSize - Split - PosSize, StartY + t * 20 + (18.f - 10.f) / 2.f, 10.0f, TEXTFLAG_RENDER); + TextRender()->SetCursor(&Cursor, m_Width - ScoreWidthMax - ImageSize - Split - PosSize, StartY + t * 20 + (18.f - 10.f) / 2.f, 10.0f, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; m_aScoreInfo[t].m_TextRankContainerIndex = TextRender()->CreateTextContainer(&Cursor, aBuf); } @@ -545,9 +546,7 @@ void CHud::RenderTextInfo() static float s_TextWidth00000 = TextRender()->TextWidth(0, 12.f, "00000", -1, -1.0f); static float s_TextWidth[5] = {s_TextWidth0, s_TextWidth00, s_TextWidth000, s_TextWidth0000, s_TextWidth00000}; - int DigitIndex = (int)log10((FrameTime ? FrameTime : 1)); - if(DigitIndex > 4) - DigitIndex = 4; + int DigitIndex = GetDigitsIndex(FrameTime, 4); //TextRender()->Text(0, m_Width-10-TextRender()->TextWidth(0,12,Buf,-1,-1.0f), 5, 12, Buf, -1.0f); CTextCursor Cursor; @@ -665,31 +664,27 @@ void CHud::RenderCursor() MapscreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup()); - int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS; - - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteWeaponCursors[CurWeapon]); - // render cursor - int QuadOffset = NUM_WEAPONS * 10 * 2 + 40 * 2 + (CurWeapon); + int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS; Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); - Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, QuadOffset, m_pClient->m_Controls.m_TargetPos[g_Config.m_ClDummy].x, m_pClient->m_Controls.m_TargetPos[g_Config.m_ClDummy].y); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteWeaponCursors[CurWeapon]); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_CursorOffset[CurWeapon], m_pClient->m_Controls.m_TargetPos[g_Config.m_ClDummy].x, m_pClient->m_Controls.m_TargetPos[g_Config.m_ClDummy].y); } -void CHud::PrepareHealthAmoQuads() +void CHud::PrepareAmmoHealthAndArmorQuads() { float x = 5; float y = 5; IGraphics::CQuadItem Array[10]; + // ammo of the different weapons for(int i = 0; i < NUM_WEAPONS; ++i) { - Graphics()->QuadsSetSubset(0, 0, 1, 1); - // 0.6 for(int n = 0; n < 10; n++) Array[n] = IGraphics::CQuadItem(x + n * 12, y + 24, 10, 10); - Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); + m_AmmoOffset[i] = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); // 0.7 if(i == WEAPON_GRENADE) @@ -708,20 +703,19 @@ void CHud::PrepareHealthAmoQuads() } // health - Graphics()->QuadsSetSubset(0, 0, 1, 1); for(int i = 0; i < 10; ++i) Array[i] = IGraphics::CQuadItem(x + i * 12, y, 10, 10); - Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); + m_HealthOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); // 0.7 for(int i = 0; i < 10; ++i) Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12); Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); - Graphics()->QuadsSetSubset(0, 0, 1, 1); + // empty health for(int i = 0; i < 10; ++i) Array[i] = IGraphics::CQuadItem(x + i * 12, y, 10, 10); - Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); + m_EmptyHealthOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); // 0.7 for(int i = 0; i < 10; ++i) @@ -729,20 +723,19 @@ void CHud::PrepareHealthAmoQuads() Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); // armor meter - Graphics()->QuadsSetSubset(0, 0, 1, 1); for(int i = 0; i < 10; ++i) Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 10, 10); - Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); + m_ArmorOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); // 0.7 for(int i = 0; i < 10; ++i) Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 12, 12); Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); - Graphics()->QuadsSetSubset(0, 0, 1, 1); + // empty armor meter for(int i = 0; i < 10; ++i) Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 10, 10); - Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); + m_EmptyArmorOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); // 0.7 for(int i = 0; i < 10; ++i) @@ -750,47 +743,752 @@ void CHud::PrepareHealthAmoQuads() Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); } -void CHud::RenderHealthAndAmmo(const CNetObj_Character *pCharacter) +void CHud::RenderAmmoHealthAndArmor(const CNetObj_Character *pCharacter) { if(!pCharacter) return; - // render ammo count - // render gui stuff - bool IsSixupGameSkin = GameClient()->m_GameSkin.IsSixup(); + bool IsSixupGameSkin = m_pClient->m_GameSkin.IsSixup(); int QuadOffsetSixup = (IsSixupGameSkin ? 10 : 0); + // ammo display int CurWeapon = pCharacter->m_Weapon % NUM_WEAPONS; - int QuadOffset = (CurWeapon * 2) * 10 + QuadOffsetSixup; - - if(GameClient()->m_GameSkin.m_SpriteWeaponProjectiles[CurWeapon].IsValid()) + if(m_pClient->m_GameSkin.m_SpriteWeaponProjectiles[CurWeapon].IsValid()) { - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteWeaponProjectiles[CurWeapon]); - - Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, QuadOffset, minimum(pCharacter->m_AmmoCount, 10)); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteWeaponProjectiles[CurWeapon]); + Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_AmmoOffset[CurWeapon] + QuadOffsetSixup, minimum(pCharacter->m_AmmoCount, 10)); } - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteHealthFull); + // health display + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteHealthFull); + Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_HealthOffset + QuadOffsetSixup, minimum(pCharacter->m_Health, 10)); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteHealthEmpty); + Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_EmptyHealthOffset + QuadOffsetSixup + minimum(pCharacter->m_Health, 10), 10 - minimum(pCharacter->m_Health, 10)); - QuadOffset = NUM_WEAPONS * 10 * 2 + QuadOffsetSixup; - Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, QuadOffset, minimum(pCharacter->m_Health, 10)); + // armor display + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteArmorFull); + Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_ArmorOffset + QuadOffsetSixup, minimum(pCharacter->m_Armor, 10)); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteArmorEmpty); + Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_ArmorOffset + QuadOffsetSixup + minimum(pCharacter->m_Armor, 10), 10 - minimum(pCharacter->m_Armor, 10)); +} - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteHealthEmpty); +void CHud::PreparePlayerStateQuads() +{ + float x = 5; + float y = 5 + 24; + IGraphics::CQuadItem Array[10]; - QuadOffset += 10 * 2 + minimum(pCharacter->m_Health, 10); - if(minimum(pCharacter->m_Health, 10) < 10) - Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, QuadOffset, 10 - minimum(pCharacter->m_Health, 10)); + // Quads for displaying the available and used jumps + for(int i = 0; i < 10; ++i) + Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12); + m_AirjumpOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteArmorFull); + for(int i = 0; i < 10; ++i) + Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12); + m_AirjumpEmptyOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); - QuadOffset = NUM_WEAPONS * 10 * 2 + 20 * 2 + QuadOffsetSixup; - Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, QuadOffset, minimum(pCharacter->m_Armor, 10)); + // Quads for displaying weapons + float ScaleX, ScaleY; + const float HudWeaponScale = 0.25f; + RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_pSpriteBody, ScaleX, ScaleY); + m_WeaponHammerOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_VisualSize * ScaleY * HudWeaponScale); + RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_GUN].m_pSpriteBody, ScaleX, ScaleY); + m_WeaponGunOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_GUN].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_GUN].m_VisualSize * ScaleY * HudWeaponScale); + RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_pSpriteBody, ScaleX, ScaleY); + m_WeaponShotgunOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_VisualSize * ScaleY * HudWeaponScale); + RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_pSpriteBody, ScaleX, ScaleY); + m_WeaponGrenadeOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_VisualSize * ScaleY * HudWeaponScale); + RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_LASER].m_pSpriteBody, ScaleX, ScaleY); + m_WeaponLaserOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_LASER].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_LASER].m_VisualSize * ScaleY * HudWeaponScale); + RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_pSpriteBody, ScaleX, ScaleY); + m_WeaponNinjaOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_VisualSize * ScaleY * HudWeaponScale); - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteArmorEmpty); + // Quads for displaying capabilities + m_EndlessJumpOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_EndlessHookOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_JetpackOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_TeleportGrenadeOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_TeleportGunOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_TeleportLaserOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); - QuadOffset += 10 * 2 + minimum(pCharacter->m_Armor, 10); - if(minimum(pCharacter->m_Armor, 10) < 10) - Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, QuadOffset, 10 - minimum(pCharacter->m_Armor, 10)); + // Quads for displaying prohibited capabilities + m_SoloOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_NoCollisionOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_NoHookHitOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_NoHammerHitOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_NoShotgunHitOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_NoGrenadeHitOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_NoLaserHitOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + + // Quads for displaying freeze status + m_DeepFrozenOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_LiveFrozenOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + + // Quads for displaying dummy actions + m_DummyHammerOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + m_DummyCopyOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); + + // Quad for displaying practice mode + m_PracticeModeOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); +} + +void CHud::RenderPlayerState(const int ClientID) +{ + Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); + + // pCharacter contains the predicted character for local players or the last snap for players who are spectated + CCharacterCore *pCharacter = &m_pClient->m_aClients[ClientID].m_Predicted; + + int TotalJumpsToDisplay, AvailableJumpsToDisplay; + if(m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo) + { + bool Grounded = false; + if(Collision()->CheckPoint(pCharacter->m_Pos.x + CCharacter::ms_PhysSize / 2, + pCharacter->m_Pos.y + CCharacter::ms_PhysSize / 2 + 5)) + { + Grounded = true; + } + if(Collision()->CheckPoint(pCharacter->m_Pos.x - CCharacter::ms_PhysSize / 2, + pCharacter->m_Pos.y + CCharacter::ms_PhysSize / 2 + 5)) + { + Grounded = true; + } + + int UsedJumps = pCharacter->m_JumpedTotal; + if(pCharacter->m_Jumps > 1) + { + UsedJumps += !Grounded; + } + else if(pCharacter->m_Jumps == 1) + { + // If the player has only one jump, each jump is the last one + UsedJumps = pCharacter->m_Jumped & 2; + } + else if(pCharacter->m_Jumps == -1) + { + // The player has only one ground jump + UsedJumps = !Grounded; + } + + if(pCharacter->m_EndlessJump && UsedJumps >= abs(pCharacter->m_Jumps)) + { + UsedJumps = abs(pCharacter->m_Jumps) - 1; + } + + int UnusedJumps = abs(pCharacter->m_Jumps) - UsedJumps; + if(!(pCharacter->m_Jumped & 2) && UnusedJumps <= 0) + { + // In some edge cases when the player just got another number of jumps, UnusedJumps is not correct + UnusedJumps = 1; + } + TotalJumpsToDisplay = maximum(minimum(abs(pCharacter->m_Jumps), 10), 0); + AvailableJumpsToDisplay = maximum(minimum(UnusedJumps, TotalJumpsToDisplay), 0); + } + else + { + TotalJumpsToDisplay = AvailableJumpsToDisplay = abs(m_pClient->m_Snap.m_aCharacters[ClientID].m_ExtendedData.m_Jumps); + } + + // render available and used jumps + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudAirjump); + Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_AirjumpOffset, AvailableJumpsToDisplay); + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudAirjumpEmpty); + Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_AirjumpEmptyOffset + AvailableJumpsToDisplay, TotalJumpsToDisplay - AvailableJumpsToDisplay); + + float x = 5 + 12; + float y = 5 + 12; + + // render weapons + if(pCharacter->m_aWeapons[WEAPON_HAMMER].m_Got) + { + if(pCharacter->m_ActiveWeapon != WEAPON_HAMMER) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + } + x -= 3; + Graphics()->QuadsSetRotation(pi * 7 / 4); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupHammer); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponHammerOffset, x, y); + Graphics()->QuadsSetRotation(0); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + x += 16; + } + if(pCharacter->m_aWeapons[WEAPON_GUN].m_Got) + { + if(pCharacter->m_ActiveWeapon != WEAPON_GUN) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + } + Graphics()->QuadsSetRotation(pi * 7 / 4); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupGun); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponGunOffset, x, y); + Graphics()->QuadsSetRotation(0); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + x += 12; + } + if(pCharacter->m_aWeapons[WEAPON_SHOTGUN].m_Got) + { + if(pCharacter->m_ActiveWeapon != WEAPON_SHOTGUN) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + } + Graphics()->QuadsSetRotation(pi * 7 / 4); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupShotgun); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponShotgunOffset, x, y); + Graphics()->QuadsSetRotation(0); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + x += 12; + } + if(pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got) + { + if(pCharacter->m_ActiveWeapon != WEAPON_GRENADE) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + } + Graphics()->QuadsSetRotation(pi * 7 / 4); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupGrenade); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponGrenadeOffset, x, y); + Graphics()->QuadsSetRotation(0); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + x += 12; + } + if(pCharacter->m_aWeapons[WEAPON_LASER].m_Got) + { + if(pCharacter->m_ActiveWeapon != WEAPON_LASER) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + } + Graphics()->QuadsSetRotation(pi * 7 / 4); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupLaser); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponLaserOffset, x, y); + Graphics()->QuadsSetRotation(0); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + x += 12; + } + if(pCharacter->m_aWeapons[WEAPON_NINJA].m_Got) + { + if(pCharacter->m_ActiveWeapon != WEAPON_NINJA) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + } + Graphics()->QuadsSetRotation(pi * 7 / 4); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupNinja); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponNinjaOffset, x, y); + Graphics()->QuadsSetRotation(0); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + + const int Max = g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000; + float NinjaProgress = clamp(pCharacter->m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000 - Client()->GameTick(g_Config.m_ClDummy), 0, Max) / (float)Max; + if(NinjaProgress > 0.0f && m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo) + { + x += 12; + RenderNinjaBarPos(x, y - 12, 6.f, 24.f, NinjaProgress); + } + } + + // render capabilities + x = 5; + y += 12; + if(TotalJumpsToDisplay > 0) + { + y += 12; + } + bool HasCapabilities = false; + if(pCharacter->m_EndlessJump) + { + HasCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudEndlessJump); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_EndlessJumpOffset, x, y); + x += 12; + } + if(pCharacter->m_EndlessHook) + { + HasCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudEndlessHook); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_EndlessHookOffset, x, y); + x += 12; + } + if(pCharacter->m_Jetpack) + { + HasCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudJetpack); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_JetpackOffset, x, y); + x += 12; + } + if(pCharacter->m_HasTelegunGun && pCharacter->m_aWeapons[WEAPON_GUN].m_Got) + { + HasCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudTeleportGun); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_TeleportGunOffset, x, y); + x += 12; + } + if(pCharacter->m_HasTelegunGrenade && pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got) + { + HasCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudTeleportGrenade); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_TeleportGrenadeOffset, x, y); + x += 12; + } + if(pCharacter->m_HasTelegunLaser && pCharacter->m_aWeapons[WEAPON_LASER].m_Got) + { + HasCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudTeleportLaser); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_TeleportLaserOffset, x, y); + } + + // render prohibited capabilities + x = 5; + if(HasCapabilities) + { + y += 12; + } + bool HasProhibitedCapabilities = false; + if(pCharacter->m_Solo) + { + HasProhibitedCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudSolo); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_SoloOffset, x, y); + x += 12; + } + if(pCharacter->m_NoCollision) + { + HasProhibitedCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNoCollision); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_NoCollisionOffset, x, y); + x += 12; + } + if(pCharacter->m_NoHookHit) + { + HasProhibitedCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNoHookHit); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_NoHookHitOffset, x, y); + x += 12; + } + if(pCharacter->m_NoHammerHit) + { + HasProhibitedCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNoHammerHit); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_NoHammerHitOffset, x, y); + x += 12; + } + if((pCharacter->m_NoShotgunHit && pCharacter->m_aWeapons[WEAPON_SHOTGUN].m_Got)) + { + HasProhibitedCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNoShotgunHit); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_NoShotgunHitOffset, x, y); + x += 12; + } + if((pCharacter->m_NoGrenadeHit && pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got)) + { + HasProhibitedCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNoGrenadeHit); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_NoGrenadeHitOffset, x, y); + x += 12; + } + if((pCharacter->m_NoLaserHit && pCharacter->m_aWeapons[WEAPON_LASER].m_Got)) + { + HasProhibitedCapabilities = true; + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNoLaserHit); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_NoLaserHitOffset, x, y); + } + + // render dummy actions and freeze state + x = 5; + if(HasProhibitedCapabilities) + { + y += 12; + } + if(m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo && m_pClient->m_Snap.m_aCharacters[ClientID].m_ExtendedDisplayInfo.m_IsInPracticeMode) + { + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudPracticeMode); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_PracticeModeOffset, x, y); + x += 12; + } + if(pCharacter->m_DeepFrozen) + { + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudDeepFrozen); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_DeepFrozenOffset, x, y); + x += 12; + } + if(pCharacter->m_LiveFrozen) + { + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudLiveFrozen); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_LiveFrozenOffset, x, y); + } +} + +void CHud::RenderNinjaBarPos(const float x, float y, const float width, const float height, float Progress, const float Alpha) +{ + Progress = clamp(Progress, 0.0f, 1.0f); + + // what percentage of the end pieces is used for the progress indicator and how much is the rest + // half of the ends are used for the progress display + const float RestPct = 0.5f; + const float ProgPct = 0.5f; + + const float EndHeight = width; // to keep the correct scale - the width of the sprite is as long as the height + const float BarWidth = width; + const float WholeBarHeight = height; + const float MiddleBarHeight = WholeBarHeight - (EndHeight * 2.0f); + const float EndProgressHeight = EndHeight * ProgPct; + const float EndRestHeight = EndHeight * RestPct; + const float ProgressBarHeight = WholeBarHeight - (EndProgressHeight * 2.0f); + const float EndProgressProportion = EndProgressHeight / ProgressBarHeight; + const float MiddleProgressProportion = MiddleBarHeight / ProgressBarHeight; + + // we cut 2% of all sides of all sprites so we don't get edge bleeding + const float CutL = 0.02f; + const float CutR = 0.98f; + const float CutT = 0.02f; + const float CutB = 0.98f; + const float Slice = 0.02f; + + // beginning piece + float BeginningPieceProgress = 1; + if(Progress <= 1) + { + if(Progress <= (EndProgressProportion + MiddleProgressProportion)) + { + BeginningPieceProgress = 0; + } + else + { + BeginningPieceProgress = (Progress - EndProgressProportion - MiddleProgressProportion) / EndProgressProportion; + } + } + // empty + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmptyRight); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // Subset: btm_r, top_r, top_m, btm_m | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise + Graphics()->QuadsSetSubsetFree(CutR, CutB, CutR, CutT, ProgPct - (ProgPct - CutL) * (1.0f - BeginningPieceProgress), CutT, ProgPct - (ProgPct - CutL) * (1.0f - BeginningPieceProgress), CutB); + IGraphics::CQuadItem QuadEmptyBeginning(x, y, BarWidth, EndRestHeight + EndProgressHeight * (1.0f - BeginningPieceProgress)); + Graphics()->QuadsDrawTL(&QuadEmptyBeginning, 1); + Graphics()->QuadsEnd(); + // full + if(BeginningPieceProgress > 0.0f) + { + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarFullLeft); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // Subset: btm_m, top_m, top_r, btm_r | it is rotated 90 degrees clockwise + Graphics()->QuadsSetSubsetFree(RestPct + (ProgPct - CutL) * (1.0f - BeginningPieceProgress), CutB, RestPct + (ProgPct - CutL) * (1.0f - BeginningPieceProgress), CutT, CutR, CutT, CutR, CutB); + IGraphics::CQuadItem QuadFullBeginning(x, y + (EndRestHeight + EndProgressHeight * (1.0f - BeginningPieceProgress)), BarWidth, EndProgressHeight * BeginningPieceProgress); + Graphics()->QuadsDrawTL(&QuadFullBeginning, 1); + Graphics()->QuadsEnd(); + } + + // middle piece + y += EndHeight; + + float MiddlePieceProgress = 1; + if(Progress <= EndProgressProportion + MiddleProgressProportion) + { + if(Progress <= EndProgressProportion) + { + MiddlePieceProgress = 0; + } + else + { + MiddlePieceProgress = (Progress - EndProgressProportion) / MiddleProgressProportion; + } + } + + const float FullMiddleBarHeight = MiddleBarHeight * MiddlePieceProgress; + const float EmptyMiddleBarHeight = MiddleBarHeight - FullMiddleBarHeight; + + // empty ninja bar + if(EmptyMiddleBarHeight > 0.0f) + { + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmpty); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // select the middle portion of the sprite so we don't get edge bleeding + if(EmptyMiddleBarHeight <= EndHeight) + { + // prevent pixel puree, select only a small slice + // Subset: btm_r, top_r, top_m, btm_m | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise + Graphics()->QuadsSetSubsetFree(CutR, CutB, CutR, CutT, CutR - Slice, CutT, CutR - Slice, CutB); + } + else + { + // Subset: btm_r, top_r, top_l, btm_l | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise + Graphics()->QuadsSetSubsetFree(CutR, CutB, CutR, CutT, CutL, CutT, CutL, CutB); + } + IGraphics::CQuadItem QuadEmpty(x, y, BarWidth, EmptyMiddleBarHeight); + Graphics()->QuadsDrawTL(&QuadEmpty, 1); + Graphics()->QuadsEnd(); + } + + // full ninja bar + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarFull); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // select the middle portion of the sprite so we don't get edge bleeding + if(FullMiddleBarHeight <= EndHeight) + { + // prevent pixel puree, select only a small slice + // Subset: btm_m, top_m, top_r, btm_r | it is rotated 90 degrees clockwise + Graphics()->QuadsSetSubsetFree(CutR - Slice, CutB, CutR - Slice, CutT, CutR, CutT, CutR, CutB); + } + else + { + // Subset: btm_l, top_l, top_r, btm_r | it is rotated 90 degrees clockwise + Graphics()->QuadsSetSubsetFree(CutL, CutB, CutL, CutT, CutR, CutT, CutR, CutB); + } + IGraphics::CQuadItem QuadFull(x, y + EmptyMiddleBarHeight, BarWidth, FullMiddleBarHeight); + Graphics()->QuadsDrawTL(&QuadFull, 1); + Graphics()->QuadsEnd(); + + // ending piece + y += MiddleBarHeight; + float EndingPieceProgress = 1; + if(Progress <= EndProgressProportion) + { + EndingPieceProgress = Progress / EndProgressProportion; + } + // empty + if(EndingPieceProgress < 1.0f) + { + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmptyRight); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // Subset: btm_l, top_l, top_m, btm_m | it is rotated 90 degrees clockwise + Graphics()->QuadsSetSubsetFree(CutL, CutB, CutL, CutT, ProgPct - (ProgPct - CutL) * EndingPieceProgress, CutT, ProgPct - (ProgPct - CutL) * EndingPieceProgress, CutB); + IGraphics::CQuadItem QuadEmptyEnding(x, y, BarWidth, EndProgressHeight * (1.0f - EndingPieceProgress)); + Graphics()->QuadsDrawTL(&QuadEmptyEnding, 1); + Graphics()->QuadsEnd(); + } + // full + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarFullLeft); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); + // Subset: btm_m, top_m, top_l, btm_l | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise + Graphics()->QuadsSetSubsetFree(RestPct + (ProgPct - CutL) * EndingPieceProgress, CutB, RestPct + (ProgPct - CutL) * EndingPieceProgress, CutT, CutL, CutT, CutL, CutB); + IGraphics::CQuadItem QuadFullEnding(x, y + (EndProgressHeight * (1.0f - EndingPieceProgress)), BarWidth, EndRestHeight + EndProgressHeight * EndingPieceProgress); + Graphics()->QuadsDrawTL(&QuadFullEnding, 1); + Graphics()->QuadsEnd(); + + Graphics()->QuadsSetSubset(0, 0, 1, 1); + Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); +} + +void CHud::RenderDummyActions() +{ + if(!g_Config.m_ClShowhudDummyActions || (m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER) || !Client()->DummyConnected()) + { + return; + } + // render small dummy actions hud + const float BoxHeight = 27.0f; + const float BoxWidth = 14.0f; + + float StartX = m_Width - BoxWidth; + float StartY = 285.0f - BoxHeight - 4; // 4 units distance to the next display; + if(g_Config.m_ClShowhudPlayerPosition || g_Config.m_ClShowhudPlayerSpeed || g_Config.m_ClShowhudPlayerAngle) + { + StartY -= 4; + } + StartY -= GetMovementInformationBoxHeight(); + + if(g_Config.m_ClShowhudScore) + { + StartY -= 56; + } + + Graphics()->TextureClear(); + Graphics()->QuadsBegin(); + Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.4f); + RenderTools()->DrawRoundRectExt(StartX, StartY, BoxWidth, BoxHeight, 5.0f, CUI::CORNER_L); + Graphics()->QuadsEnd(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + + float y = StartY + 1; + float x = StartX + 1; + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + if(g_Config.m_ClDummyHammer) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + } + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudDummyHammer); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_DummyHammerOffset, x, y); + y += 13; + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + if(g_Config.m_ClDummyCopyMoves) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + } + Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudDummyCopy); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_DummyCopyOffset, x, y); +} + +inline int CHud::GetDigitsIndex(int Value, int Max) +{ + if(Value < 0) + { + Value *= -1; + } + int DigitsIndex = (int)log10((Value ? Value : 1)); + if(DigitsIndex > Max) + { + DigitsIndex = Max; + } + if(DigitsIndex < 0) + { + DigitsIndex = 0; + } + return DigitsIndex; +} + +inline float CHud::GetMovementInformationBoxHeight() +{ + float BoxHeight = 3 * MOVEMENT_INFORMATION_LINE_HEIGHT * (g_Config.m_ClShowhudPlayerPosition + g_Config.m_ClShowhudPlayerSpeed) + 2 * MOVEMENT_INFORMATION_LINE_HEIGHT * g_Config.m_ClShowhudPlayerAngle; + if(g_Config.m_ClShowhudPlayerPosition || g_Config.m_ClShowhudPlayerSpeed || g_Config.m_ClShowhudPlayerAngle) + { + BoxHeight += 2; + } + return BoxHeight; +} + +void CHud::RenderMovementInformation(const int ClientID) +{ + // Draw the infomations depending on settings: Position, speed and target angle + // This display is only to present the available information from the last snapshot, not to interpolate or predict + if(!g_Config.m_ClShowhudPlayerPosition && !g_Config.m_ClShowhudPlayerSpeed && !g_Config.m_ClShowhudPlayerAngle) + { + return; + } + const float LineSpacer = 1.0f; // above and below each entry + const float Fontsize = 6.0f; + + float BoxHeight = GetMovementInformationBoxHeight(); + const float BoxWidth = 62.0f; + + float StartX = m_Width - BoxWidth; + float StartY = 285.0f - BoxHeight - 4; // 4 units distance to the next display; + if(g_Config.m_ClShowhudScore) + { + StartY -= 56; + } + + Graphics()->TextureClear(); + Graphics()->QuadsBegin(); + Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.4f); + RenderTools()->DrawRoundRectExt(StartX, StartY, BoxWidth, BoxHeight, 5.0f, CUI::CORNER_L); + Graphics()->QuadsEnd(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + + CNetObj_Character *Character = &m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur; + const float TicksPerSecond = 50.0f; + + // To make the player position relative to blocks we need to divide by the block size + float PosX = Character->m_X / 32.0f; + float PosY = Character->m_Y / 32.0f; + + float VelspeedX = Character->m_VelX / 256.0f * TicksPerSecond; + if(Character->m_VelX >= -1 && Character->m_VelX <= 1) + { + VelspeedX = 0; + } + float VelspeedY = Character->m_VelY / 256.0f * TicksPerSecond; + if(Character->m_VelY >= -128 && Character->m_VelY <= 128) + { + VelspeedY = 0; + } + // We show the speed in Blocks per Second (Bps) and therefore have to divide by the block size + float DisplaySpeedX = VelspeedX / 32; + float DisplaySpeedY = VelspeedY / 32; + if(m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo) + { + // On DDNet servers the actual speed on X axis is displayed, i.e. VelspeedX * Ramp + DisplaySpeedX *= (m_pClient->m_Snap.m_aCharacters[ClientID].m_ExtendedDisplayInfo.m_RampValue / 1000.0f); + } + + float Angle = Character->m_Angle / 256.0f; + if(Angle > pi) + { + Angle -= 2.0f * pi; + } + if(m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo) + { + // On DDNet servers the more accurate angle is displayed, calculated from the target coordinates + CNetObj_DDNetCharacterDisplayInfo *CharacterDisplayInfo = &m_pClient->m_Snap.m_aCharacters[ClientID].m_ExtendedDisplayInfo; + Angle = atan2f(CharacterDisplayInfo->m_TargetY, CharacterDisplayInfo->m_TargetX); + } + float DisplayAngle = Angle * 180.0f / pi; + + char aBuf[128]; + float w; + + float y = StartY + LineSpacer * 2; + float xl = StartX + 2; + float xr = m_Width - 2; + int DigitsIndex = 0; + + static float s_TextWidth0 = TextRender()->TextWidth(0, Fontsize, "0.00", -1, -1.0f); + static float s_TextWidth00 = TextRender()->TextWidth(0, Fontsize, "00.00", -1, -1.0f); + static float s_TextWidth000 = TextRender()->TextWidth(0, Fontsize, "000.00", -1, -1.0f); + static float s_TextWidth0000 = TextRender()->TextWidth(0, Fontsize, "0000.00", -1, -1.0f); + static float s_TextWidth00000 = TextRender()->TextWidth(0, Fontsize, "00000.00", -1, -1.0f); + static float s_TextWidth000000 = TextRender()->TextWidth(0, Fontsize, "000000.00", -1, -1.0f); + static float s_TextWidth[6] = {s_TextWidth0, s_TextWidth00, s_TextWidth000, s_TextWidth0000, s_TextWidth00000, s_TextWidth000000}; + static float s_TextWidthMinus0 = TextRender()->TextWidth(0, Fontsize, "-0.00", -1, -1.0f); + static float s_TextWidthMinus00 = TextRender()->TextWidth(0, Fontsize, "-00.00", -1, -1.0f); + static float s_TextWidthMinus000 = TextRender()->TextWidth(0, Fontsize, "-000.00", -1, -1.0f); + static float s_TextWidthMinus0000 = TextRender()->TextWidth(0, Fontsize, "-0000.00", -1, -1.0f); + static float s_TextWidthMinus00000 = TextRender()->TextWidth(0, Fontsize, "-00000.00", -1, -1.0f); + static float s_TextWidthMinus000000 = TextRender()->TextWidth(0, Fontsize, "-000000.00", -1, -1.0f); + static float s_TextWidthMinus[6] = {s_TextWidthMinus0, s_TextWidthMinus00, s_TextWidthMinus000, s_TextWidthMinus0000, s_TextWidthMinus00000, s_TextWidthMinus000000}; + + if(g_Config.m_ClShowhudPlayerPosition) + { + TextRender()->Text(0, xl, y, Fontsize, Localize("Position:"), -1.0f); + y += MOVEMENT_INFORMATION_LINE_HEIGHT; + + TextRender()->Text(0, xl, y, Fontsize, "X:", -1.0f); + str_format(aBuf, sizeof(aBuf), "%.2f", PosX); + DigitsIndex = GetDigitsIndex(PosX, 5); + w = (PosX < 0) ? s_TextWidthMinus[DigitsIndex] : s_TextWidth[DigitsIndex]; + TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f); + y += MOVEMENT_INFORMATION_LINE_HEIGHT; + + TextRender()->Text(0, xl, y, Fontsize, "Y:", -1.0f); + str_format(aBuf, sizeof(aBuf), "%.2f", PosY); + DigitsIndex = GetDigitsIndex(PosY, 5); + w = (PosY < 0) ? s_TextWidthMinus[DigitsIndex] : s_TextWidth[DigitsIndex]; + TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f); + y += MOVEMENT_INFORMATION_LINE_HEIGHT; + } + + if(g_Config.m_ClShowhudPlayerSpeed) + { + TextRender()->Text(0, xl, y, Fontsize, Localize("Speed:"), -1.0f); + y += MOVEMENT_INFORMATION_LINE_HEIGHT; + + TextRender()->Text(0, xl, y, Fontsize, "X:", -1.0f); + str_format(aBuf, sizeof(aBuf), "%.2f", DisplaySpeedX); + DigitsIndex = GetDigitsIndex(DisplaySpeedX, 5); + w = (DisplaySpeedX < 0) ? s_TextWidthMinus[DigitsIndex] : s_TextWidth[DigitsIndex]; + TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f); + y += MOVEMENT_INFORMATION_LINE_HEIGHT; + + TextRender()->Text(0, xl, y, Fontsize, "Y:", -1.0f); + str_format(aBuf, sizeof(aBuf), "%.2f", DisplaySpeedY); + DigitsIndex = GetDigitsIndex(DisplaySpeedY, 5); + w = (DisplaySpeedY < 0) ? s_TextWidthMinus[DigitsIndex] : s_TextWidth[DigitsIndex]; + TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f); + y += MOVEMENT_INFORMATION_LINE_HEIGHT; + } + + if(g_Config.m_ClShowhudPlayerAngle) + { + TextRender()->Text(0, xl, y, Fontsize, Localize("Angle:"), -1.0f); + y += MOVEMENT_INFORMATION_LINE_HEIGHT; + str_format(aBuf, sizeof(aBuf), "%.2f", DisplayAngle); + DigitsIndex = GetDigitsIndex(DisplayAngle, 5); + w = (DisplayAngle < 0) ? s_TextWidthMinus[DigitsIndex] : s_TextWidth[DigitsIndex]; + TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f); + } } void CHud::RenderSpectatorHud() @@ -842,16 +1540,34 @@ void CHud::OnRender() if(g_Config.m_ClShowhud) #endif { - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)) + if(m_pClient->m_Snap.m_pLocalCharacter && !m_pClient->m_Snap.m_SpecInfo.m_Active && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)) { - if(g_Config.m_ClShowhudHealthAmmo) - RenderHealthAndAmmo(m_pClient->m_Snap.m_pLocalCharacter); + if(g_Config.m_ClShowhudHealthAmmo && (!m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientID].m_HasExtendedData || !g_Config.m_ClDDRaceHud)) + { + RenderAmmoHealthAndArmor(m_pClient->m_Snap.m_pLocalCharacter); + } + if(m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientID].m_HasExtendedData && g_Config.m_ClDDRaceHud) + { + RenderPlayerState(m_pClient->m_Snap.m_LocalClientID); + } + RenderMovementInformation(m_pClient->m_Snap.m_LocalClientID); RenderDDRaceEffects(); } else if(m_pClient->m_Snap.m_SpecInfo.m_Active) { - if(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW && g_Config.m_ClShowhudHealthAmmo) - RenderHealthAndAmmo(&m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID].m_Cur); + int SpectatorID = m_pClient->m_Snap.m_SpecInfo.m_SpectatorID; + if(SpectatorID != SPEC_FREEVIEW && g_Config.m_ClShowhudHealthAmmo && (!m_pClient->m_Snap.m_aCharacters[SpectatorID].m_HasExtendedData || !g_Config.m_ClDDRaceHud)) + { + RenderAmmoHealthAndArmor(&m_pClient->m_Snap.m_aCharacters[SpectatorID].m_Cur); + } + if(SpectatorID != SPEC_FREEVIEW && m_pClient->m_Snap.m_aCharacters[SpectatorID].m_HasExtendedData && g_Config.m_ClDDRaceHud) + { + RenderPlayerState(SpectatorID); + } + if(SpectatorID != SPEC_FREEVIEW) + { + RenderMovementInformation(SpectatorID); + } RenderSpectatorHud(); } @@ -861,6 +1577,7 @@ void CHud::OnRender() RenderSuddenDeath(); if(g_Config.m_ClShowhudScore) RenderScoreHud(); + RenderDummyActions(); RenderWarmupTimer(); RenderTextInfo(); RenderLocalTime((m_Width / 7) * 3); @@ -899,7 +1616,7 @@ void CHud::OnMessage(int MsgType, void *pRawMsg) CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg; // NETMSGTYPE_SV_RACETIME on old race servers - if(MsgType == NETMSGTYPE_SV_RECORDLEGACY && GameClient()->m_GameInfo.m_DDRaceRecordMessage) + if(MsgType == NETMSGTYPE_SV_RECORDLEGACY && m_pClient->m_GameInfo.m_DDRaceRecordMessage) { m_DDRaceTimeReceived = true; @@ -913,7 +1630,7 @@ void CHud::OnMessage(int MsgType, void *pRawMsg) m_CheckpointTick = Client()->GameTick(g_Config.m_ClDummy); } } - else if(MsgType == NETMSGTYPE_SV_RECORD || GameClient()->m_GameInfo.m_RaceRecordMessage) + else if(MsgType == NETMSGTYPE_SV_RECORD || m_pClient->m_GameInfo.m_RaceRecordMessage) { m_ServerRecord = (float)pMsg->m_ServerTimeBest / 100; m_PlayerRecord[g_Config.m_ClDummy] = (float)pMsg->m_PlayerTimeBest / 100; diff --git a/src/game/client/components/hud.h b/src/game/client/components/hud.h index 0d3a3386a..8c1622e14 100644 --- a/src/game/client/components/hud.h +++ b/src/game/client/components/hud.h @@ -49,8 +49,13 @@ class CHud : public CComponent void RenderTeambalanceWarning(); void RenderVoting(); - void PrepareHealthAmoQuads(); - void RenderHealthAndAmmo(const CNetObj_Character *pCharacter); + void PrepareAmmoHealthAndArmorQuads(); + void RenderAmmoHealthAndArmor(const CNetObj_Character *pCharacter); + + void PreparePlayerStateQuads(); + void RenderPlayerState(const int ClientID); + void RenderDummyActions(); + void RenderMovementInformation(const int ClientID); void RenderGameTimer(); void RenderPauseNotification(); @@ -62,6 +67,8 @@ class CHud : public CComponent void MapscreenToGroup(float CenterX, float CenterY, struct CMapItemGroup *PGroup); + static constexpr float MOVEMENT_INFORMATION_LINE_HEIGHT = 8.0f; + public: CHud(); virtual int Sizeof() const override { return sizeof(*this); } @@ -75,6 +82,7 @@ public: // DDRace virtual void OnMessage(int MsgType, void *pRawMsg) override; + void RenderNinjaBarPos(float x, const float y, const float width, const float height, float Progress, float Alpha = 1.0f); private: void RenderRecord(); @@ -87,6 +95,44 @@ private: int m_CheckpointTick; bool m_FinishTime; bool m_DDRaceTimeReceived; + + inline float GetMovementInformationBoxHeight(); + inline int GetDigitsIndex(int Value, int Max); + + // Quad Offsets + int m_AmmoOffset[NUM_WEAPONS]; + int m_HealthOffset; + int m_EmptyHealthOffset; + int m_ArmorOffset; + int m_EmptyArmorOffset; + int m_CursorOffset[NUM_WEAPONS]; + int m_FlagOffset; + int m_AirjumpOffset; + int m_AirjumpEmptyOffset; + int m_WeaponHammerOffset; + int m_WeaponGunOffset; + int m_WeaponShotgunOffset; + int m_WeaponGrenadeOffset; + int m_WeaponLaserOffset; + int m_WeaponNinjaOffset; + int m_EndlessJumpOffset; + int m_EndlessHookOffset; + int m_JetpackOffset; + int m_TeleportGrenadeOffset; + int m_TeleportGunOffset; + int m_TeleportLaserOffset; + int m_SoloOffset; + int m_NoCollisionOffset; + int m_NoHookHitOffset; + int m_NoHammerHitOffset; + int m_NoShotgunHitOffset; + int m_NoGrenadeHitOffset; + int m_NoLaserHitOffset; + int m_DeepFrozenOffset; + int m_LiveFrozenOffset; + int m_DummyHammerOffset; + int m_DummyCopyOffset; + int m_PracticeModeOffset; }; #endif diff --git a/src/game/client/components/items.cpp b/src/game/client/components/items.cpp index ed9c5dcff..485bfee72 100644 --- a/src/game/client/components/items.cpp +++ b/src/game/client/components/items.cpp @@ -128,74 +128,46 @@ void CItems::RenderProjectile(const CProjectileData *pCurrent, int ItemID) { Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteWeaponProjectiles[CurWeapon]); Graphics()->SetColor(1.f, 1.f, 1.f, Alpha); - - int QuadOffset = 2 + 8 + NUM_WEAPONS + CurWeapon; - - Graphics()->RenderQuadContainerAsSprite(m_ItemsQuadContainerIndex, QuadOffset, Pos.x, Pos.y); + Graphics()->RenderQuadContainerAsSprite(m_ItemsQuadContainerIndex, m_ProjectileOffset[CurWeapon], Pos.x, Pos.y); } } void CItems::RenderPickup(const CNetObj_Pickup *pPrev, const CNetObj_Pickup *pCurrent, bool IsPredicted) { - const int c[] = { - SPRITE_PICKUP_HEALTH, - SPRITE_PICKUP_ARMOR, - SPRITE_PICKUP_WEAPON, - SPRITE_PICKUP_NINJA, - SPRITE_PICKUP_ARMOR_SHOTGUN, - SPRITE_PICKUP_ARMOR_GRENADE, - SPRITE_PICKUP_ARMOR_NINJA, - SPRITE_PICKUP_ARMOR_LASER}; - int CurWeapon = clamp(pCurrent->m_Subtype, 0, NUM_WEAPONS - 1); - - if(c[pCurrent->m_Type] == SPRITE_PICKUP_HEALTH) - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupHealth); - else if(c[pCurrent->m_Type] == SPRITE_PICKUP_ARMOR) - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupArmor); - else if(c[pCurrent->m_Type] == SPRITE_PICKUP_ARMOR_SHOTGUN) - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupArmorShotgun); - else if(c[pCurrent->m_Type] == SPRITE_PICKUP_ARMOR_GRENADE) - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupArmorGrenade); - else if(c[pCurrent->m_Type] == SPRITE_PICKUP_ARMOR_LASER) - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupArmorLaser); - else if(c[pCurrent->m_Type] == SPRITE_PICKUP_ARMOR_NINJA) - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupArmorNinja); - else if(c[pCurrent->m_Type] == SPRITE_PICKUP_WEAPON) - { - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupWeapons[CurWeapon]); - } - else if(c[pCurrent->m_Type] == SPRITE_PICKUP_NINJA) - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupNinja); - Graphics()->QuadsSetRotation(0); - Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); - int QuadOffset = 2; - + float Angle = 0.0f; float IntraTick = IsPredicted ? Client()->PredIntraGameTick(g_Config.m_ClDummy) : Client()->IntraGameTick(g_Config.m_ClDummy); vec2 Pos = mix(vec2(pPrev->m_X, pPrev->m_Y), vec2(pCurrent->m_X, pCurrent->m_Y), IntraTick); - float Angle = 0.0f; - if(pCurrent->m_Type == POWERUP_WEAPON) + if(pCurrent->m_Type == POWERUP_HEALTH) { - Angle = 0; //-pi/6;//-0.25f * pi * 2.0f; - QuadOffset += 2 + CurWeapon; + QuadOffset = m_PickupHealthOffset; + Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupHealth); } - else + else if(pCurrent->m_Type == POWERUP_ARMOR) { - QuadOffset += pCurrent->m_Type; - - if(c[pCurrent->m_Type] == SPRITE_PICKUP_NINJA) - { - QuadOffset = 2 + 8 - 1; // ninja is the last weapon - m_pClient->m_Effects.PowerupShine(Pos, vec2(96, 18)); - Pos.x -= 10.0f; - } - else if(c[pCurrent->m_Type] >= SPRITE_PICKUP_ARMOR_SHOTGUN && c[pCurrent->m_Type] <= SPRITE_PICKUP_ARMOR_NINJA) - { - QuadOffset = m_WeaponArmorQuadOffset + (c[pCurrent->m_Type] - SPRITE_PICKUP_ARMOR_SHOTGUN); - } + QuadOffset = m_PickupArmorOffset; + Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupArmor); } - + else if(pCurrent->m_Type == POWERUP_WEAPON) + { + QuadOffset = m_PickupWeaponOffset[CurWeapon]; + Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupWeapons[CurWeapon]); + } + else if(pCurrent->m_Type == POWERUP_NINJA) + { + QuadOffset = m_PickupNinjaOffset; + m_pClient->m_Effects.PowerupShine(Pos, vec2(96, 18)); + Pos.x -= 10.0f; + Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupNinja); + } + else if(pCurrent->m_Type >= POWERUP_ARMOR_SHOTGUN && pCurrent->m_Type <= POWERUP_ARMOR_LASER) + { + QuadOffset = m_PickupWeaponArmorOffset[pCurrent->m_Type - POWERUP_ARMOR_SHOTGUN]; + Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpritePickupWeaponArmor[pCurrent->m_Type - POWERUP_ARMOR_SHOTGUN]); + } + Graphics()->QuadsSetRotation(0); + Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); Graphics()->QuadsSetRotation(Angle); static float s_Time = 0.0f; @@ -230,10 +202,15 @@ void CItems::RenderFlag(const CNetObj_Flag *pPrev, const CNetObj_Flag *pCurrent, Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagBlue); Graphics()->QuadsSetRotation(0); Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); - int QuadOffset = 0; - - if(pCurrent->m_Team != TEAM_RED) - ++QuadOffset; + int QuadOffset; + if(pCurrent->m_Team == TEAM_RED) + { + QuadOffset = m_RedFlagOffset; + } + else + { + QuadOffset = m_BlueFlagOffset; + } Graphics()->QuadsSetRotation(Angle); @@ -317,14 +294,12 @@ void CItems::RenderLaser(const struct CNetObj_Laser *pCurrent, bool IsPredicted) // render head { int CurParticle = (Client()->GameTick(g_Config.m_ClDummy) % 3); - int QuadOffset = 2 + 8 + NUM_WEAPONS * 2 + CurParticle; - Graphics()->TextureSet(GameClient()->m_ParticlesSkin.m_SpriteParticleSplat[CurParticle]); Graphics()->QuadsSetRotation(Client()->GameTick(g_Config.m_ClDummy)); Graphics()->SetColor(OuterColor.r, OuterColor.g, OuterColor.b, 1.0f); - Graphics()->RenderQuadContainerAsSprite(m_ItemsQuadContainerIndex, QuadOffset, Pos.x, Pos.y); + Graphics()->RenderQuadContainerAsSprite(m_ItemsQuadContainerIndex, m_ParticleSplatOffset[CurParticle], Pos.x, Pos.y); Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f); - Graphics()->RenderQuadContainerAsSprite(m_ItemsQuadContainerIndex, QuadOffset, Pos.x, Pos.y, 20.f / 24.f, 20.f / 24.f); + Graphics()->RenderQuadContainerAsSprite(m_ItemsQuadContainerIndex, m_ParticleSplatOffset[CurParticle], Pos.x, Pos.y, 20.f / 24.f, 20.f / 24.f); } } @@ -515,68 +490,46 @@ void CItems::OnInit() m_ItemsQuadContainerIndex = Graphics()->CreateQuadContainer(false); Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, -21.f, -42.f, 42.f, 84.f); + m_RedFlagOffset = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, -21.f, -42.f, 42.f, 84.f); Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, -21.f, -42.f, 42.f, 84.f); + m_BlueFlagOffset = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, -21.f, -42.f, 42.f, 84.f); float ScaleX, ScaleY; RenderTools()->GetSpriteScale(SPRITE_PICKUP_HEALTH, ScaleX, ScaleY); Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); + m_PickupHealthOffset = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); RenderTools()->GetSpriteScale(SPRITE_PICKUP_ARMOR, ScaleX, ScaleY); Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); - RenderTools()->GetSpriteScale(&client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_HAMMER], ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, client_data7::g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_VisualSize * ScaleX, client_data7::g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_VisualSize * ScaleY); - RenderTools()->GetSpriteScale(&client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_GUN], ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, client_data7::g_pData->m_Weapons.m_aId[WEAPON_GUN].m_VisualSize * ScaleX, client_data7::g_pData->m_Weapons.m_aId[WEAPON_GUN].m_VisualSize * ScaleY); - RenderTools()->GetSpriteScale(&client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_SHOTGUN], ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, client_data7::g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_VisualSize * ScaleX, client_data7::g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_VisualSize * ScaleY); - RenderTools()->GetSpriteScale(&client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_GRENADE], ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, client_data7::g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_VisualSize * ScaleX, client_data7::g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_VisualSize * ScaleY); - RenderTools()->GetSpriteScale(&client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_LASER], ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, client_data7::g_pData->m_Weapons.m_aId[WEAPON_LASER].m_VisualSize * ScaleX, client_data7::g_pData->m_Weapons.m_aId[WEAPON_LASER].m_VisualSize * ScaleY); - RenderTools()->GetSpriteScale(SPRITE_PICKUP_NINJA, ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 128.f * ScaleX, 128.f * ScaleY); + m_PickupArmorOffset = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); for(int i = 0; i < NUM_WEAPONS; ++i) { RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[i].m_pSpriteBody, ScaleX, ScaleY); Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleX, g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleY); + m_PickupWeaponOffset[i] = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleX, g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleY); + } + RenderTools()->GetSpriteScale(SPRITE_PICKUP_NINJA, ScaleX, ScaleY); + Graphics()->QuadsSetSubset(0, 0, 1, 1); + m_PickupNinjaOffset = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 128.f * ScaleX, 128.f * ScaleY); + + for(int i = 0; i < 4; i++) + { + RenderTools()->GetSpriteScale(SPRITE_PICKUP_ARMOR_SHOTGUN + i, ScaleX, ScaleY); + Graphics()->QuadsSetSubset(0, 0, 1, 1); + m_PickupWeaponArmorOffset[i] = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); } - for(int i = 0; i < NUM_WEAPONS; ++i) + for(int &ProjectileOffset : m_ProjectileOffset) { Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 32.f); + ProjectileOffset = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 32.f); } - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 24.f); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 24.f); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 24.f); - - RenderTools()->GetSpriteScale(SPRITE_PICKUP_ARMOR_SHOTGUN, ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - m_WeaponArmorQuadOffset = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); - RenderTools()->GetSpriteScale(SPRITE_PICKUP_ARMOR_GRENADE, ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); - RenderTools()->GetSpriteScale(SPRITE_PICKUP_ARMOR_NINJA, ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); - RenderTools()->GetSpriteScale(SPRITE_PICKUP_ARMOR_LASER, ScaleX, ScaleY); - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY); + for(int &ParticleSplatOffset : m_ParticleSplatOffset) + { + Graphics()->QuadsSetSubset(0, 0, 1, 1); + ParticleSplatOffset = RenderTools()->QuadContainerAddSprite(m_ItemsQuadContainerIndex, 24.f); + } Graphics()->QuadContainerUpload(m_ItemsQuadContainerIndex); } diff --git a/src/game/client/components/items.h b/src/game/client/components/items.h index b60238403..5bc4a70e4 100644 --- a/src/game/client/components/items.h +++ b/src/game/client/components/items.h @@ -3,6 +3,7 @@ #ifndef GAME_CLIENT_COMPONENTS_ITEMS_H #define GAME_CLIENT_COMPONENTS_ITEMS_H #include +#include class CProjectileData; @@ -15,14 +16,23 @@ class CItems : public CComponent int m_ItemsQuadContainerIndex; - int m_WeaponArmorQuadOffset = 0; - public: virtual int Sizeof() const override { return sizeof(*this); } virtual void OnRender() override; virtual void OnInit() override; void ReconstructSmokeTrail(const CProjectileData *pCurrent, int DestroyTick); + +private: + int m_BlueFlagOffset; + int m_RedFlagOffset; + int m_PickupHealthOffset; + int m_PickupArmorOffset; + int m_PickupWeaponOffset[NUM_WEAPONS]; + int m_PickupNinjaOffset; + int m_PickupWeaponArmorOffset[4]; + int m_ProjectileOffset[NUM_WEAPONS]; + int m_ParticleSplatOffset[3]; }; #endif diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 3132445cb..5221a0fb1 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -1038,6 +1038,7 @@ void CMenus::OnInit() Console()->Chain("cl_asset_game", ConchainAssetGame, this); Console()->Chain("cl_asset_emoticons", ConchainAssetEmoticons, this); Console()->Chain("cl_asset_particles", ConchainAssetParticles, this); + Console()->Chain("cl_asset_hud", ConchainAssetHud, this); m_TextureBlob = Graphics()->LoadTexture("blob.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index fb969781e..8877e7242 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -246,11 +246,16 @@ public: { }; + struct SCustomHud : public SCustomItem + { + }; + protected: sorted_array m_EntitiesList; sorted_array m_GameList; sorted_array m_EmoticonList; sorted_array m_ParticlesList; + sorted_array m_HudList; bool m_IsInit = false; @@ -260,11 +265,13 @@ protected: static int GameScan(const char *pName, int IsDir, int DirType, void *pUser); static int EmoticonsScan(const char *pName, int IsDir, int DirType, void *pUser); static int ParticlesScan(const char *pName, int IsDir, int DirType, void *pUser); + static int HudScan(const char *pName, int IsDir, int DirType, void *pUser); static void ConchainAssetsEntities(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainAssetGame(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainAssetParticles(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainAssetEmoticons(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainAssetHud(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); void ClearCustomItems(int CurTab); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 2b0f3896d..145b86a95 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2354,8 +2354,14 @@ void CMenus::RenderSettingsHUD(CUIRect MainView) // ***** HUD ***** // DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhud, Localize("Show ingame HUD"), &g_Config.m_ClShowhud, &MainView, LineMargin); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClDDRaceHud, Localize("Use DDRace HUD"), &g_Config.m_ClDDRaceHud, &MainView, LineMargin); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClDDRaceScoreBoard, Localize("Use DDRace Scoreboard"), &g_Config.m_ClDDRaceScoreBoard, &MainView, LineMargin); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudDummyActions, Localize("Show dummy actions"), &g_Config.m_ClShowhudDummyActions, &MainView, LineMargin); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowFreezeBars, Localize("Show freeze bars"), &g_Config.m_ClShowFreezeBars, &MainView, LineMargin); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowIDs, Localize("Show client IDs"), &g_Config.m_ClShowIDs, &MainView, LineMargin); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudPlayerPosition, Localize("Show player position"), &g_Config.m_ClShowhudPlayerPosition, &MainView, LineMargin); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudPlayerSpeed, Localize("Show player speed"), &g_Config.m_ClShowhudPlayerSpeed, &MainView, LineMargin); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudPlayerAngle, Localize("Show player target angle"), &g_Config.m_ClShowhudPlayerAngle, &MainView, LineMargin); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudScore, Localize("Show score"), &g_Config.m_ClShowhudScore, &MainView, LineMargin); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudHealthAmmo, Localize("Show health + ammo"), &g_Config.m_ClShowhudHealthAmmo, &MainView, LineMargin); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowChat, Localize("Show chat"), &g_Config.m_ClShowChat, &MainView, LineMargin); diff --git a/src/game/client/components/menus_settings_assets.cpp b/src/game/client/components/menus_settings_assets.cpp index ef54113fb..e1e406bcf 100644 --- a/src/game/client/components/menus_settings_assets.cpp +++ b/src/game/client/components/menus_settings_assets.cpp @@ -15,6 +15,16 @@ struct SMenuAssetScanUser TMenuAssetScanLoadedFunc m_LoadedFunc; }; +// IDs of the tabs in the Assets menu +enum +{ + ASSETS_TAB_ENTITIES = 0, + ASSETS_TAB_GAME = 1, + ASSETS_TAB_EMOTICONS = 2, + ASSETS_TAB_PARTICLES = 3, + ASSETS_TAB_HUD = 4 +}; + void CMenus::LoadEntities(SCustomEntities *pEntitiesItem, void *pUser) { auto *pRealUser = (SMenuAssetScanUser *)pUser; @@ -208,33 +218,44 @@ int CMenus::ParticlesScan(const char *pName, int IsDir, int DirType, void *pUser return AssetScan(pName, IsDir, DirType, pThis->m_ParticlesList, "particles", pGraphics, pUser); } +int CMenus::HudScan(const char *pName, int IsDir, int DirType, void *pUser) +{ + CMenus *pMenus = (CMenus *)pUser; + IGraphics *pGraphics = pMenus->Graphics(); + return AssetScan(pName, IsDir, DirType, pMenus->m_HudList, "hud", pGraphics, pUser); +} + static sorted_array s_SearchEntitiesList; static sorted_array s_SearchGamesList; static sorted_array s_SearchEmoticonsList; static sorted_array s_SearchParticlesList; +static sorted_array s_SearchHudList; -static bool s_InitCustomList[4] = { +static const int NumberOfAssetsTabs = 5; +static bool s_InitCustomList[NumberOfAssetsTabs] = { true, }; -static int s_CustomListSize[4] = { +static int s_CustomListSize[NumberOfAssetsTabs] = { 0, }; -static char s_aFilterString[4][50]; +static char s_aFilterString[NumberOfAssetsTabs][50]; -static int s_CurCustomTab = 0; +static int s_CurCustomTab = ASSETS_TAB_ENTITIES; static const CMenus::SCustomItem *GetCustomItem(int CurTab, int Index) { - if(CurTab == 0) + if(CurTab == ASSETS_TAB_ENTITIES) return s_SearchEntitiesList[Index]; - else if(CurTab == 1) + else if(CurTab == ASSETS_TAB_GAME) return s_SearchGamesList[Index]; - else if(CurTab == 2) + else if(CurTab == ASSETS_TAB_EMOTICONS) return s_SearchEmoticonsList[Index]; - else if(CurTab == 3) + else if(CurTab == ASSETS_TAB_PARTICLES) return s_SearchParticlesList[Index]; + else if(CurTab == ASSETS_TAB_HUD) + return s_SearchHudList[Index]; return NULL; } @@ -253,7 +274,7 @@ void ClearAssetList(sorted_array &List, IGraphics *pGraphics) void CMenus::ClearCustomItems(int CurTab) { - if(CurTab == 0) + if(CurTab == ASSETS_TAB_ENTITIES) { for(int i = 0; i < m_EntitiesList.size(); ++i) { @@ -269,27 +290,34 @@ void CMenus::ClearCustomItems(int CurTab) // reload current entities m_pClient->m_MapImages.ChangeEntitiesPath(g_Config.m_ClAssetsEntites); } - else if(CurTab == 1) + else if(CurTab == ASSETS_TAB_GAME) { ClearAssetList(m_GameList, Graphics()); // reload current game skin GameClient()->LoadGameSkin(g_Config.m_ClAssetGame); } - else if(CurTab == 2) + else if(CurTab == ASSETS_TAB_EMOTICONS) { ClearAssetList(m_EmoticonList, Graphics()); // reload current emoticons skin GameClient()->LoadEmoticonsSkin(g_Config.m_ClAssetEmoticons); } - else if(CurTab == 3) + else if(CurTab == ASSETS_TAB_PARTICLES) { ClearAssetList(m_ParticlesList, Graphics()); // reload current particles skin GameClient()->LoadParticlesSkin(g_Config.m_ClAssetParticles); } + else if(CurTab == ASSETS_TAB_HUD) + { + ClearAssetList(m_HudList, Graphics()); + + // reload current hud skin + GameClient()->LoadHudSkin(g_Config.m_ClAssetHud); + } s_InitCustomList[CurTab] = true; } @@ -330,24 +358,27 @@ int InitSearchList(sorted_array &SearchList, sorted_array void CMenus::RenderSettingsCustom(CUIRect MainView) { - CUIRect Label, CustomList, QuickSearch, QuickSearchClearButton, DirectoryButton, Page1Tab, Page2Tab, Page3Tab, Page4Tab, ReloadButton; + CUIRect Label, CustomList, QuickSearch, QuickSearchClearButton, DirectoryButton, Page1Tab, Page2Tab, Page3Tab, Page4Tab, Page5Tab, ReloadButton; MainView.HSplitTop(20, &Label, &MainView); float TabsW = Label.w; - Label.VSplitLeft(TabsW / 4, &Page1Tab, &Page2Tab); - Page2Tab.VSplitLeft(TabsW / 4, &Page2Tab, &Page3Tab); - Page3Tab.VSplitLeft(TabsW / 4, &Page3Tab, &Page4Tab); + Label.VSplitLeft(TabsW / NumberOfAssetsTabs, &Page1Tab, &Page2Tab); + Page2Tab.VSplitLeft(TabsW / NumberOfAssetsTabs, &Page2Tab, &Page3Tab); + Page3Tab.VSplitLeft(TabsW / NumberOfAssetsTabs, &Page3Tab, &Page4Tab); + Page4Tab.VSplitLeft(TabsW / NumberOfAssetsTabs, &Page4Tab, &Page5Tab); - static int s_aPageTabs[4] = {}; + static int s_aPageTabs[NumberOfAssetsTabs] = {}; - if(DoButton_MenuTab((void *)&s_aPageTabs[0], Localize("Entities"), s_CurCustomTab == 0, &Page1Tab, 5, NULL, NULL, NULL, NULL, 4)) - s_CurCustomTab = 0; - if(DoButton_MenuTab((void *)&s_aPageTabs[1], Localize("Game"), s_CurCustomTab == 1, &Page2Tab, 0, NULL, NULL, NULL, NULL, 4)) - s_CurCustomTab = 1; - if(DoButton_MenuTab((void *)&s_aPageTabs[2], Localize("Emoticons"), s_CurCustomTab == 2, &Page3Tab, 0, NULL, NULL, NULL, NULL, 4)) - s_CurCustomTab = 2; - if(DoButton_MenuTab((void *)&s_aPageTabs[3], Localize("Particles"), s_CurCustomTab == 3, &Page4Tab, 10, NULL, NULL, NULL, NULL, 4)) - s_CurCustomTab = 3; + if(DoButton_MenuTab((void *)&s_aPageTabs[0], Localize("Entities"), s_CurCustomTab == ASSETS_TAB_ENTITIES, &Page1Tab, 5, NULL, NULL, NULL, NULL, 4)) + s_CurCustomTab = ASSETS_TAB_ENTITIES; + if(DoButton_MenuTab((void *)&s_aPageTabs[1], Localize("Game"), s_CurCustomTab == ASSETS_TAB_GAME, &Page2Tab, 0, NULL, NULL, NULL, NULL, 4)) + s_CurCustomTab = ASSETS_TAB_GAME; + if(DoButton_MenuTab((void *)&s_aPageTabs[2], Localize("Emoticons"), s_CurCustomTab == ASSETS_TAB_EMOTICONS, &Page3Tab, 0, NULL, NULL, NULL, NULL, 4)) + s_CurCustomTab = ASSETS_TAB_EMOTICONS; + if(DoButton_MenuTab((void *)&s_aPageTabs[3], Localize("Particles"), s_CurCustomTab == ASSETS_TAB_PARTICLES, &Page4Tab, 0, NULL, NULL, NULL, NULL, 4)) + s_CurCustomTab = ASSETS_TAB_PARTICLES; + if(DoButton_MenuTab((void *)&s_aPageTabs[4], Localize("HUD"), s_CurCustomTab == ASSETS_TAB_HUD, &Page5Tab, 10, NULL, NULL, NULL, NULL, 4)) + s_CurCustomTab = ASSETS_TAB_HUD; int64_t LoadStartTime = time_get_microseconds(); SMenuAssetScanUser User; @@ -356,7 +387,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) if(time_get_microseconds() - LoadStartTime > 500000) RenderLoading(false, false); }; - if(s_CurCustomTab == 0) + if(s_CurCustomTab == ASSETS_TAB_ENTITIES) { if(m_EntitiesList.size() == 0) { @@ -371,18 +402,22 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) if(m_EntitiesList.size() != s_CustomListSize[s_CurCustomTab]) s_InitCustomList[s_CurCustomTab] = true; } - else if(s_CurCustomTab == 1) + else if(s_CurCustomTab == ASSETS_TAB_GAME) { InitAssetList(m_GameList, "assets/game", "game", GameScan, Graphics(), Storage(), &User); } - else if(s_CurCustomTab == 2) + else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS) { InitAssetList(m_EmoticonList, "assets/emoticons", "emoticons", EmoticonsScan, Graphics(), Storage(), &User); } - else if(s_CurCustomTab == 3) + else if(s_CurCustomTab == ASSETS_TAB_PARTICLES) { InitAssetList(m_ParticlesList, "assets/particles", "particles", ParticlesScan, Graphics(), Storage(), &User); } + else if(s_CurCustomTab == ASSETS_TAB_HUD) + { + InitAssetList(m_HudList, "assets/hud", "hud", HudScan, Graphics(), Storage(), this); + } MainView.HSplitTop(10.0f, 0, &MainView); @@ -392,7 +427,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) if(s_InitCustomList[s_CurCustomTab]) { int ListSize = 0; - if(s_CurCustomTab == 0) + if(s_CurCustomTab == ASSETS_TAB_ENTITIES) { s_SearchEntitiesList.clear(); ListSize = m_EntitiesList.size(); @@ -407,18 +442,22 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) s_SearchEntitiesList.add_unsorted(s); } } - else if(s_CurCustomTab == 1) + else if(s_CurCustomTab == ASSETS_TAB_GAME) { ListSize = InitSearchList(s_SearchGamesList, m_GameList); } - else if(s_CurCustomTab == 2) + else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS) { ListSize = InitSearchList(s_SearchEmoticonsList, m_EmoticonList); } - else if(s_CurCustomTab == 3) + else if(s_CurCustomTab == ASSETS_TAB_PARTICLES) { ListSize = InitSearchList(s_SearchParticlesList, m_ParticlesList); } + else if(s_CurCustomTab == ASSETS_TAB_HUD) + { + ListSize = InitSearchList(s_SearchHudList, m_HudList); + } s_InitCustomList[s_CurCustomTab] = false; s_CustomListSize[s_CurCustomTab] = ListSize; } @@ -430,23 +469,28 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) int SearchListSize = 0; - if(s_CurCustomTab == 0) + if(s_CurCustomTab == ASSETS_TAB_ENTITIES) { SearchListSize = s_SearchEntitiesList.size(); } - else if(s_CurCustomTab == 1) + else if(s_CurCustomTab == ASSETS_TAB_GAME) { SearchListSize = s_SearchGamesList.size(); TextureHeight = 75; } - else if(s_CurCustomTab == 2) + else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS) { SearchListSize = s_SearchEmoticonsList.size(); } - else if(s_CurCustomTab == 3) + else if(s_CurCustomTab == ASSETS_TAB_PARTICLES) { SearchListSize = s_SearchParticlesList.size(); } + else if(s_CurCustomTab == ASSETS_TAB_HUD) + { + SearchListSize = s_SearchHudList.size(); + TextureHeight = 128; + } UiDoListboxStart(&s_InitCustomList[s_CurCustomTab], &CustomList, TextureHeight + 15.0f + 10.0f + Margin, "", "", SearchListSize, CustomList.w / (Margin + TextureWidth), OldSelected, s_ScrollValue, true); for(int i = 0; i < SearchListSize; ++i) @@ -455,26 +499,31 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) if(s == NULL) continue; - if(s_CurCustomTab == 0) + if(s_CurCustomTab == ASSETS_TAB_ENTITIES) { if(str_comp(s->m_aName, g_Config.m_ClAssetsEntites) == 0) OldSelected = i; } - else if(s_CurCustomTab == 1) + else if(s_CurCustomTab == ASSETS_TAB_GAME) { if(str_comp(s->m_aName, g_Config.m_ClAssetGame) == 0) OldSelected = i; } - else if(s_CurCustomTab == 2) + else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS) { if(str_comp(s->m_aName, g_Config.m_ClAssetEmoticons) == 0) OldSelected = i; } - else if(s_CurCustomTab == 3) + else if(s_CurCustomTab == ASSETS_TAB_PARTICLES) { if(str_comp(s->m_aName, g_Config.m_ClAssetParticles) == 0) OldSelected = i; } + else if(s_CurCustomTab == ASSETS_TAB_HUD) + { + if(str_comp(s->m_aName, g_Config.m_ClAssetHud) == 0) + OldSelected = i; + } CListboxItem Item = UiDoListboxNextItem(s, OldSelected == i); CUIRect ItemRect = Item.m_Rect; @@ -504,26 +553,31 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) { if(GetCustomItem(s_CurCustomTab, NewSelected)->m_aName[0] != '\0') { - if(s_CurCustomTab == 0) + if(s_CurCustomTab == ASSETS_TAB_ENTITIES) { str_copy(g_Config.m_ClAssetsEntites, GetCustomItem(s_CurCustomTab, NewSelected)->m_aName, sizeof(g_Config.m_ClAssetsEntites)); m_pClient->m_MapImages.ChangeEntitiesPath(GetCustomItem(s_CurCustomTab, NewSelected)->m_aName); } - else if(s_CurCustomTab == 1) + else if(s_CurCustomTab == ASSETS_TAB_GAME) { str_copy(g_Config.m_ClAssetGame, GetCustomItem(s_CurCustomTab, NewSelected)->m_aName, sizeof(g_Config.m_ClAssetGame)); GameClient()->LoadGameSkin(g_Config.m_ClAssetGame); } - else if(s_CurCustomTab == 2) + else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS) { str_copy(g_Config.m_ClAssetEmoticons, GetCustomItem(s_CurCustomTab, NewSelected)->m_aName, sizeof(g_Config.m_ClAssetEmoticons)); GameClient()->LoadEmoticonsSkin(g_Config.m_ClAssetEmoticons); } - else if(s_CurCustomTab == 3) + else if(s_CurCustomTab == ASSETS_TAB_PARTICLES) { str_copy(g_Config.m_ClAssetParticles, GetCustomItem(s_CurCustomTab, NewSelected)->m_aName, sizeof(g_Config.m_ClAssetParticles)); GameClient()->LoadParticlesSkin(g_Config.m_ClAssetParticles); } + else if(s_CurCustomTab == ASSETS_TAB_HUD) + { + str_copy(g_Config.m_ClAssetHud, GetCustomItem(s_CurCustomTab, NewSelected)->m_aName, sizeof(g_Config.m_ClAssetHud)); + GameClient()->LoadHudSkin(g_Config.m_ClAssetHud); + } } } @@ -567,14 +621,16 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) { char aBuf[IO_MAX_PATH_LENGTH]; char aBufFull[IO_MAX_PATH_LENGTH + 7]; - if(s_CurCustomTab == 0) + if(s_CurCustomTab == ASSETS_TAB_ENTITIES) str_copy(aBufFull, "assets/entities", sizeof(aBufFull)); - else if(s_CurCustomTab == 1) + else if(s_CurCustomTab == ASSETS_TAB_GAME) str_copy(aBufFull, "assets/game", sizeof(aBufFull)); - else if(s_CurCustomTab == 2) + else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS) str_copy(aBufFull, "assets/emoticons", sizeof(aBufFull)); - else if(s_CurCustomTab == 3) + else if(s_CurCustomTab == ASSETS_TAB_PARTICLES) str_copy(aBufFull, "assets/particles", sizeof(aBufFull)); + else if(s_CurCustomTab == ASSETS_TAB_HUD) + str_copy(aBufFull, "assets/hud", sizeof(aBufFull)); Storage()->GetCompletePath(IStorage::TYPE_SAVE, aBufFull, aBuf, sizeof(aBuf)); Storage()->CreateFolder("assets", IStorage::TYPE_SAVE); Storage()->CreateFolder(aBufFull, IStorage::TYPE_SAVE); @@ -654,3 +710,18 @@ void CMenus::ConchainAssetEmoticons(IConsole::IResult *pResult, void *pUserData, pfnCallback(pResult, pCallbackUserData); } + +void CMenus::ConchainAssetHud(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CMenus *pThis = (CMenus *)pUserData; + if(pResult->NumArguments() == 1) + { + const char *pArg = pResult->GetString(0); + if(str_comp(pArg, g_Config.m_ClAssetHud) != 0) + { + pThis->GameClient()->LoadHudSkin(pArg); + } + } + + pfnCallback(pResult, pCallbackUserData); +} diff --git a/src/game/client/components/nameplates.cpp b/src/game/client/components/nameplates.cpp index 978b16180..bb29bcfc3 100644 --- a/src/game/client/components/nameplates.cpp +++ b/src/game/client/components/nameplates.cpp @@ -378,10 +378,8 @@ void CNamePlates::OnInit() { ResetNamePlates(); - // the direction + // Quad for the direction arrows above the player m_DirectionQuadContainerIndex = Graphics()->CreateQuadContainer(false); - - IGraphics::CQuadItem QuadItem(0.f, 0.f, 22.f, 22.f); - Graphics()->QuadContainerAddQuads(m_DirectionQuadContainerIndex, &QuadItem, 1); + RenderTools()->QuadContainerAddSprite(m_DirectionQuadContainerIndex, 0.f, 0.f, 22.f); Graphics()->QuadContainerUpload(m_DirectionQuadContainerIndex); } diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index f35258df7..6c741d4bc 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -76,6 +76,54 @@ inline float AngularApproach(float Src, float Dst, float Amount) return Dst; return n; } + +float CPlayers::GetPlayerTargetAngle( + const CNetObj_Character *pPrevChar, + const CNetObj_Character *pPlayerChar, + int ClientID, + float Intra) +{ + float AngleIntraTick = Intra; + // using unpredicted angle when rendering other players in-game + if(ClientID >= 0) + AngleIntraTick = Client()->IntraGameTick(g_Config.m_ClDummy); + if(ClientID >= 0 && m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo) + { + CNetObj_DDNetCharacterDisplayInfo *CharacterDisplayInfo = &m_pClient->m_Snap.m_aCharacters[ClientID].m_ExtendedDisplayInfo; + if(m_pClient->m_Snap.m_aCharacters[ClientID].m_PrevExtendedDisplayInfo) + { + const CNetObj_DDNetCharacterDisplayInfo *PrevCharacterDisplayInfo = m_pClient->m_Snap.m_aCharacters[ClientID].m_PrevExtendedDisplayInfo; + + float MixX = mix((float)PrevCharacterDisplayInfo->m_TargetX, (float)CharacterDisplayInfo->m_TargetX, AngleIntraTick); + float MixY = mix((float)PrevCharacterDisplayInfo->m_TargetY, (float)CharacterDisplayInfo->m_TargetY, AngleIntraTick); + + return angle(vec2(MixX, MixY)); + } + else + { + return angle(vec2(CharacterDisplayInfo->m_TargetX, CharacterDisplayInfo->m_TargetY)); + } + } + else + { + // If the player moves their weapon through top, then change + // the end angle by 2*Pi, so that the mix function will use the + // short path and not the long one. + if(pPlayerChar->m_Angle > (256.0f * pi) && pPrevChar->m_Angle < 0) + { + return mix((float)pPrevChar->m_Angle, (float)(pPlayerChar->m_Angle - 256.0f * 2 * pi), AngleIntraTick) / 256.0f; + } + else if(pPlayerChar->m_Angle < 0 && pPrevChar->m_Angle > (256.0f * pi)) + { + return mix((float)pPrevChar->m_Angle, (float)(pPlayerChar->m_Angle + 256.0f * 2 * pi), AngleIntraTick) / 256.0f; + } + else + { + return mix((float)pPrevChar->m_Angle, (float)pPlayerChar->m_Angle, AngleIntraTick) / 256.0f; + } + } +} + void CPlayers::RenderHookCollLine( const CNetObj_Character *pPrevChar, const CNetObj_Character *pPlayerChar, @@ -104,19 +152,7 @@ void CPlayers::RenderHookCollLine( } else { - float AngleIntraTick = IntraTick; - // using unpredicted angle when rendering other players in-game - if(ClientID >= 0) - AngleIntraTick = Client()->IntraGameTick(g_Config.m_ClDummy); - // If the player moves their weapon through top, then change - // the end angle by 2*Pi, so that the mix function will use the - // short path and not the long one. - if(Player.m_Angle > (256.0f * pi) && Prev.m_Angle < 0) - Player.m_Angle -= 256.0f * 2 * pi; - else if(Player.m_Angle < 0 && Prev.m_Angle > (256.0f * pi)) - Player.m_Angle += 256.0f * 2 * pi; - - Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, AngleIntraTick) / 256.0f; + Angle = GetPlayerTargetAngle(&Prev, &Player, ClientID, IntraTick); } vec2 Direction = direction(Angle); @@ -365,19 +401,7 @@ void CPlayers::RenderPlayer( } else { - float AngleIntraTick = IntraTick; - // using unpredicted angle when rendering other players in-game - if(ClientID >= 0) - AngleIntraTick = Client()->IntraGameTick(g_Config.m_ClDummy); - // If the player moves their weapon through top, then change - // the end angle by 2*Pi, so that the mix function will use the - // short path and not the long one. - if(Player.m_Angle > (256.0f * pi) && Prev.m_Angle < 0) - Player.m_Angle -= 256.0f * 2 * pi; - else if(Player.m_Angle < 0 && Prev.m_Angle > (256.0f * pi)) - Player.m_Angle += 256.0f * 2 * pi; - - Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, AngleIntraTick) / 256.0f; + Angle = GetPlayerTargetAngle(&Prev, &Player, ClientID, IntraTick); } vec2 Direction = direction(Angle); diff --git a/src/game/client/components/players.h b/src/game/client/components/players.h index e8ce8491f..470448276 100644 --- a/src/game/client/components/players.h +++ b/src/game/client/components/players.h @@ -31,13 +31,17 @@ class CPlayers : public CComponent const CNetObj_Character *pPlayerChar, int ClientID, float Intra = 0.f); + float GetPlayerTargetAngle( + const CNetObj_Character *pPrevChar, + const CNetObj_Character *pPlayerChar, + int ClientID, + float Intra = 0.f); bool IsPlayerInfoAvailable(int ClientID) const; int m_WeaponEmoteQuadContainerIndex; int m_WeaponSpriteMuzzleQuadContainerIndex[NUM_WEAPONS]; public: - vec2 m_CurPredictedPos[MAX_CLIENTS]; virtual int Sizeof() const override { return sizeof(*this); } virtual void OnInit() override; virtual void OnRender() override; diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp index 855d57d9b..16ec086c5 100644 --- a/src/game/client/components/scoreboard.cpp +++ b/src/game/client/components/scoreboard.cpp @@ -693,7 +693,7 @@ void CScoreboard::OnRender() RenderGoals(Width / 2 - w / 2, 150 + 760 + 10, w); RenderSpectators(Width / 2 - w / 2, 150 + 760 + 10 + 50 + 10, w); - RenderRecordingNotification((Width / 7) * 4); + RenderRecordingNotification((Width / 7) * 4 + 20); } bool CScoreboard::Active() diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 1bf6219bc..9d1c7147b 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -45,6 +45,7 @@ #include "components/effects.h" #include "components/emoticon.h" #include "components/flow.h" +#include "components/freezebars.h" #include "components/hud.h" #include "components/items.h" #include "components/killmessages.h" @@ -125,6 +126,7 @@ void CGameClient::OnConsoleInit() m_All.Add(&m_Particles.m_RenderExplosions); m_All.Add(&m_NamePlates); m_All.Add(&m_Particles.m_RenderGeneral); + m_All.Add(&m_FreezeBars); m_All.Add(&m_DamageInd); m_All.Add(&m_Hud); m_All.Add(&m_Spectator); @@ -262,6 +264,7 @@ void CGameClient::OnInit() m_GameSkinLoaded = false; m_ParticlesSkinLoaded = false; m_EmoticonsSkinLoaded = false; + m_HudSkinLoaded = false; // setup load amount// load textures for(int i = 0; i < g_pData->m_NumImages; i++) @@ -272,6 +275,8 @@ void CGameClient::OnInit() LoadEmoticonsSkin(g_Config.m_ClAssetEmoticons); else if(i == IMAGE_PARTICLES) LoadParticlesSkin(g_Config.m_ClAssetParticles); + else if(i == IMAGE_HUD) + LoadHudSkin(g_Config.m_ClAssetHud); else g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); m_Menus.RenderLoading(false); @@ -1095,8 +1100,7 @@ void CGameClient::OnNewSnapshot() mem_zero(&TempCore, sizeof(TempCore)); mem_zero(&TempTeams, sizeof(TempTeams)); TempCore.Init(&TempWorld, Collision(), &TempTeams); - TempCore.Read(pCharacter); - TempCore.m_ActiveWeapon = pCharacter->m_Weapon; + TempCore.ReadCharacter(pCharacter); while(pCharacter->m_Tick < Tick) { @@ -1271,6 +1275,7 @@ void CGameClient::OnNewSnapshot() { 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); + m_aClients[Item.m_ID].m_Predicted.ReadCharacter((const CNetObj_Character *)pData); if(pOld) { m_Snap.m_aCharacters[Item.m_ID].m_Active = true; @@ -1341,6 +1346,20 @@ void CGameClient::OnNewSnapshot() pClient->m_Predicted.ReadDDNet(pCharacterData); } } + else if(Item.m_Type == NETOBJTYPE_DDNETCHARACTERDISPLAYINFO) + { + const CNetObj_DDNetCharacterDisplayInfo *pCharacterDisplayInfo = (const CNetObj_DDNetCharacterDisplayInfo *)pData; + + if(Item.m_ID < MAX_CLIENTS) + { + m_Snap.m_aCharacters[Item.m_ID].m_ExtendedDisplayInfo = *pCharacterDisplayInfo; + m_Snap.m_aCharacters[Item.m_ID].m_PrevExtendedDisplayInfo = (const CNetObj_DDNetCharacterDisplayInfo *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_DDNETCHARACTERDISPLAYINFO, Item.m_ID); + m_Snap.m_aCharacters[Item.m_ID].m_HasExtendedDisplayInfo = true; + + CClientData *pClient = &m_aClients[Item.m_ID]; + pClient->m_Predicted.ReadDDNetDisplayInfo(pCharacterDisplayInfo); + } + } else if(Item.m_Type == NETOBJTYPE_SPECCHAR) { const CNetObj_SpecChar *pSpecCharData = (const CNetObj_SpecChar *)pData; @@ -1705,13 +1724,11 @@ void CGameClient::OnPredict() { if(m_Snap.m_pLocalCharacter) { - m_PredictedChar.Read(m_Snap.m_pLocalCharacter); - m_PredictedChar.m_ActiveWeapon = m_Snap.m_pLocalCharacter->m_Weapon; + m_PredictedChar.ReadCharacter(m_Snap.m_pLocalCharacter); } if(m_Snap.m_pLocalPrevCharacter) { - m_PredictedPrevChar.Read(m_Snap.m_pLocalPrevCharacter); - m_PredictedPrevChar.m_ActiveWeapon = m_Snap.m_pLocalPrevCharacter->m_Weapon; + m_PredictedPrevChar.ReadCharacter(m_Snap.m_pLocalPrevCharacter); } return; } @@ -2418,6 +2435,7 @@ void CGameClient::UpdatePrediction() int GameTeam = (m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) ? m_aClients[i].m_Team : i; m_GameWorld.NetCharAdd(i, &m_Snap.m_aCharacters[i].m_Cur, m_Snap.m_aCharacters[i].m_HasExtendedData ? &m_Snap.m_aCharacters[i].m_ExtendedData : 0, + m_Snap.m_aCharacters[i].m_HasExtendedDisplayInfo ? &m_Snap.m_aCharacters[i].m_ExtendedDisplayInfo : 0, GameTeam, IsLocal); } @@ -2516,7 +2534,7 @@ void CGameClient::DetectStrongHook() float PredictErr[2]; CCharacterCore ToCharCur; - ToCharCur.Read(&m_Snap.m_aCharacters[ToPlayer].m_Cur); + ToCharCur.ReadCharacterCore(&m_Snap.m_aCharacters[ToPlayer].m_Cur); CWorldCore World; World.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; @@ -2526,12 +2544,12 @@ void CGameClient::DetectStrongHook() CCharacterCore ToChar = pFromCharWorld->GetCore(); ToChar.Init(&World, Collision(), &m_Teams); World.m_apCharacters[ToPlayer] = &ToChar; - ToChar.Read(&m_Snap.m_aCharacters[ToPlayer].m_Prev); + ToChar.ReadCharacterCore(&m_Snap.m_aCharacters[ToPlayer].m_Prev); CCharacterCore FromChar = pFromCharWorld->GetCore(); FromChar.Init(&World, Collision(), &m_Teams); World.m_apCharacters[FromPlayer] = &FromChar; - FromChar.Read(&m_Snap.m_aCharacters[FromPlayer].m_Prev); + FromChar.ReadCharacterCore(&m_Snap.m_aCharacters[FromPlayer].m_Prev); for(int Tick = Client()->PrevGameTick(g_Config.m_ClDummy); Tick < Client()->GameTick(g_Config.m_ClDummy); Tick++) { @@ -2731,6 +2749,11 @@ void CGameClient::LoadGameSkin(const char *pPath, bool AsDir) SpritePickupWeapon = IGraphics::CTextureHandle(); } + for(auto &SpritePickupWeaponArmor : m_GameSkin.m_SpritePickupWeaponArmor) + { + SpritePickupWeaponArmor = IGraphics::CTextureHandle(); + } + Graphics()->UnloadTexture(&m_GameSkin.m_SpriteFlagBlue); Graphics()->UnloadTexture(&m_GameSkin.m_SpriteFlagRed); @@ -2852,16 +2875,16 @@ void CGameClient::LoadGameSkin(const char *pPath, bool AsDir) // pickups m_GameSkin.m_SpritePickupHealth = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_HEALTH]); m_GameSkin.m_SpritePickupArmor = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR]); + m_GameSkin.m_SpritePickupHammer = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_HAMMER]); + m_GameSkin.m_SpritePickupGun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_GUN]); + m_GameSkin.m_SpritePickupShotgun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_SHOTGUN]); + m_GameSkin.m_SpritePickupGrenade = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_GRENADE]); + m_GameSkin.m_SpritePickupLaser = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_LASER]); + m_GameSkin.m_SpritePickupNinja = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_NINJA]); m_GameSkin.m_SpritePickupArmorShotgun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_SHOTGUN]); m_GameSkin.m_SpritePickupArmorGrenade = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_GRENADE]); - m_GameSkin.m_SpritePickupArmorLaser = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_LASER]); m_GameSkin.m_SpritePickupArmorNinja = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_NINJA]); - m_GameSkin.m_SpritePickupGrenade = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_GRENADE]); - m_GameSkin.m_SpritePickupShotgun = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_SHOTGUN]); - m_GameSkin.m_SpritePickupLaser = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_LASER]); - m_GameSkin.m_SpritePickupNinja = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_NINJA]); - m_GameSkin.m_SpritePickupGun = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_GUN]); - m_GameSkin.m_SpritePickupHammer = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_PICKUP_HAMMER]); + m_GameSkin.m_SpritePickupArmorLaser = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_LASER]); m_GameSkin.m_SpritePickupWeapons[0] = m_GameSkin.m_SpritePickupHammer; m_GameSkin.m_SpritePickupWeapons[1] = m_GameSkin.m_SpritePickupGun; @@ -2870,6 +2893,11 @@ void CGameClient::LoadGameSkin(const char *pPath, bool AsDir) m_GameSkin.m_SpritePickupWeapons[4] = m_GameSkin.m_SpritePickupLaser; m_GameSkin.m_SpritePickupWeapons[5] = m_GameSkin.m_SpritePickupNinja; + m_GameSkin.m_SpritePickupWeaponArmor[0] = m_GameSkin.m_SpritePickupArmorShotgun; + m_GameSkin.m_SpritePickupWeaponArmor[1] = m_GameSkin.m_SpritePickupArmorGrenade; + m_GameSkin.m_SpritePickupWeaponArmor[2] = m_GameSkin.m_SpritePickupArmorNinja; + m_GameSkin.m_SpritePickupWeaponArmor[3] = m_GameSkin.m_SpritePickupArmorLaser; + // flags m_GameSkin.m_SpriteFlagBlue = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_FLAG_BLUE]); m_GameSkin.m_SpriteFlagRed = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_FLAG_RED]); @@ -3007,6 +3035,101 @@ void CGameClient::LoadParticlesSkin(const char *pPath, bool AsDir) } } +void CGameClient::LoadHudSkin(const char *pPath, bool AsDir) +{ + if(m_HudSkinLoaded) + { + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudAirjump); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudAirjumpEmpty); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudSolo); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoCollision); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudEndlessJump); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudEndlessHook); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudJetpack); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudFreezeBarFullLeft); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudFreezeBarFull); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudFreezeBarEmpty); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudFreezeBarEmptyRight); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNinjaBarFullLeft); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNinjaBarFull); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNinjaBarEmpty); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNinjaBarEmptyRight); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoHookHit); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoHammerHit); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoShotgunHit); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoGrenadeHit); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoLaserHit); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudDeepFrozen); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudLiveFrozen); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudTeleportGrenade); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudTeleportGun); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudTeleportLaser); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudPracticeMode); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudDummyHammer); + Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudDummyCopy); + m_HudSkinLoaded = false; + } + + char aPath[IO_MAX_PATH_LENGTH]; + bool IsDefault = false; + if(str_comp(pPath, "default") == 0) + { + str_format(aPath, sizeof(aPath), "%s", g_pData->m_aImages[IMAGE_HUD].m_pFilename); + IsDefault = true; + } + else + { + if(AsDir) + str_format(aPath, sizeof(aPath), "assets/hud/%s/%s", pPath, g_pData->m_aImages[IMAGE_HUD].m_pFilename); + else + str_format(aPath, sizeof(aPath), "assets/hud/%s.png", pPath); + } + + CImageInfo ImgInfo; + bool PngLoaded = Graphics()->LoadPNG(&ImgInfo, aPath, IStorage::TYPE_ALL); + if(!PngLoaded && !IsDefault) + { + if(AsDir) + LoadHudSkin("default"); + else + LoadHudSkin(pPath, true); + } + else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_HUD_AIRJUMP].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_HUD_AIRJUMP].m_pSet->m_Gridy, true) && Graphics()->IsImageFormatRGBA(aPath, ImgInfo)) + { + m_HudSkin.m_SpriteHudAirjump = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_AIRJUMP]); + m_HudSkin.m_SpriteHudAirjumpEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_AIRJUMP_EMPTY]); + m_HudSkin.m_SpriteHudSolo = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_SOLO]); + m_HudSkin.m_SpriteHudNoCollision = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_COLLISION]); + m_HudSkin.m_SpriteHudEndlessJump = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_ENDLESS_JUMP]); + m_HudSkin.m_SpriteHudEndlessHook = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_ENDLESS_HOOK]); + m_HudSkin.m_SpriteHudJetpack = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_JETPACK]); + m_HudSkin.m_SpriteHudFreezeBarFullLeft = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_FREEZE_BAR_FULL_LEFT]); + m_HudSkin.m_SpriteHudFreezeBarFull = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_FREEZE_BAR_FULL]); + m_HudSkin.m_SpriteHudFreezeBarEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_FREEZE_BAR_EMPTY]); + m_HudSkin.m_SpriteHudFreezeBarEmptyRight = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_FREEZE_BAR_EMPTY_RIGHT]); + m_HudSkin.m_SpriteHudNinjaBarFullLeft = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NINJA_BAR_FULL_LEFT]); + m_HudSkin.m_SpriteHudNinjaBarFull = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NINJA_BAR_FULL]); + m_HudSkin.m_SpriteHudNinjaBarEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NINJA_BAR_EMPTY]); + m_HudSkin.m_SpriteHudNinjaBarEmptyRight = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NINJA_BAR_EMPTY_RIGHT]); + m_HudSkin.m_SpriteHudNoHookHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_HOOK_HIT]); + m_HudSkin.m_SpriteHudNoHammerHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_HAMMER_HIT]); + m_HudSkin.m_SpriteHudNoShotgunHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_SHOTGUN_HIT]); + m_HudSkin.m_SpriteHudNoGrenadeHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_GRENADE_HIT]); + m_HudSkin.m_SpriteHudNoLaserHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_LASER_HIT]); + m_HudSkin.m_SpriteHudDeepFrozen = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_DEEP_FROZEN]); + m_HudSkin.m_SpriteHudLiveFrozen = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_LIVE_FROZEN]); + m_HudSkin.m_SpriteHudTeleportGrenade = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_TELEPORT_GRENADE]); + m_HudSkin.m_SpriteHudTeleportGun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_TELEPORT_GUN]); + m_HudSkin.m_SpriteHudTeleportLaser = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_TELEPORT_LASER]); + m_HudSkin.m_SpriteHudPracticeMode = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_PRACTICE_MODE]); + m_HudSkin.m_SpriteHudDummyHammer = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_DUMMY_HAMMER]); + m_HudSkin.m_SpriteHudDummyCopy = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_DUMMY_COPY]); + + m_HudSkinLoaded = true; + free(ImgInfo.m_pData); + } +} + void CGameClient::RefindSkins() { for(auto &Client : m_aClients) diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 1136a466e..a856927a7 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -34,6 +34,7 @@ #include "components/effects.h" #include "components/emoticon.h" #include "components/flow.h" +#include "components/freezebars.h" #include "components/ghost.h" #include "components/hud.h" #include "components/items.h" @@ -132,6 +133,7 @@ public: CPlayers m_Players; CNamePlates m_NamePlates; + CFreezeBars m_FreezeBars; CItems m_Items; CMapImages m_MapImages; @@ -319,6 +321,10 @@ public: CNetObj_DDNetCharacter m_ExtendedData; bool m_HasExtendedData; + const CNetObj_DDNetCharacterDisplayInfo *m_PrevExtendedDisplayInfo; + CNetObj_DDNetCharacterDisplayInfo m_ExtendedDisplayInfo; + bool m_HasExtendedDisplayInfo; + // interpolated position vec2 m_Position; }; @@ -533,6 +539,7 @@ public: void LoadGameSkin(const char *pPath, bool AsDir = false); void LoadEmoticonsSkin(const char *pPath, bool AsDir = false); void LoadParticlesSkin(const char *pPath, bool AsDir = false); + void LoadHudSkin(const char *pPath, bool AsDir = false); void RefindSkins(); @@ -594,8 +601,8 @@ public: IGraphics::CTextureHandle m_SpritePickupArmor; IGraphics::CTextureHandle m_SpritePickupArmorShotgun; IGraphics::CTextureHandle m_SpritePickupArmorGrenade; - IGraphics::CTextureHandle m_SpritePickupArmorLaser; IGraphics::CTextureHandle m_SpritePickupArmorNinja; + IGraphics::CTextureHandle m_SpritePickupArmorLaser; IGraphics::CTextureHandle m_SpritePickupGrenade; IGraphics::CTextureHandle m_SpritePickupShotgun; IGraphics::CTextureHandle m_SpritePickupLaser; @@ -604,6 +611,7 @@ public: IGraphics::CTextureHandle m_SpritePickupHammer; IGraphics::CTextureHandle m_SpritePickupWeapons[6]; + IGraphics::CTextureHandle m_SpritePickupWeaponArmor[4]; // flags IGraphics::CTextureHandle m_SpriteFlagBlue; @@ -648,6 +656,41 @@ public: SClientEmoticonsSkin m_EmoticonsSkin; bool m_EmoticonsSkinLoaded; + struct SClientHudSkin + { + IGraphics::CTextureHandle m_SpriteHudAirjump; + IGraphics::CTextureHandle m_SpriteHudAirjumpEmpty; + IGraphics::CTextureHandle m_SpriteHudSolo; + IGraphics::CTextureHandle m_SpriteHudNoCollision; + IGraphics::CTextureHandle m_SpriteHudEndlessJump; + IGraphics::CTextureHandle m_SpriteHudEndlessHook; + IGraphics::CTextureHandle m_SpriteHudJetpack; + IGraphics::CTextureHandle m_SpriteHudFreezeBarFullLeft; + IGraphics::CTextureHandle m_SpriteHudFreezeBarFull; + IGraphics::CTextureHandle m_SpriteHudFreezeBarEmpty; + IGraphics::CTextureHandle m_SpriteHudFreezeBarEmptyRight; + IGraphics::CTextureHandle m_SpriteHudNinjaBarFullLeft; + IGraphics::CTextureHandle m_SpriteHudNinjaBarFull; + IGraphics::CTextureHandle m_SpriteHudNinjaBarEmpty; + IGraphics::CTextureHandle m_SpriteHudNinjaBarEmptyRight; + IGraphics::CTextureHandle m_SpriteHudNoHookHit; + IGraphics::CTextureHandle m_SpriteHudNoHammerHit; + IGraphics::CTextureHandle m_SpriteHudNoShotgunHit; + IGraphics::CTextureHandle m_SpriteHudNoGrenadeHit; + IGraphics::CTextureHandle m_SpriteHudNoLaserHit; + IGraphics::CTextureHandle m_SpriteHudDeepFrozen; + IGraphics::CTextureHandle m_SpriteHudLiveFrozen; + IGraphics::CTextureHandle m_SpriteHudTeleportGrenade; + IGraphics::CTextureHandle m_SpriteHudTeleportGun; + IGraphics::CTextureHandle m_SpriteHudTeleportLaser; + IGraphics::CTextureHandle m_SpriteHudPracticeMode; + IGraphics::CTextureHandle m_SpriteHudDummyHammer; + IGraphics::CTextureHandle m_SpriteHudDummyCopy; + }; + + SClientHudSkin m_HudSkin; + bool m_HudSkinLoaded; + const std::vector &SnapEntities() { return m_aSnapEntities; } private: diff --git a/src/game/client/prediction/entities/character.cpp b/src/game/client/prediction/entities/character.cpp index 8f7f98362..b646b7bad 100644 --- a/src/game/client/prediction/entities/character.cpp +++ b/src/game/client/prediction/entities/character.cpp @@ -58,14 +58,14 @@ void CCharacter::HandleJetpack() if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses) WillFire = true; - if(FullAuto && (m_LatestInput.m_Fire & 1) && m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + if(FullAuto && (m_LatestInput.m_Fire & 1) && m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) WillFire = true; if(!WillFire) return; // check for ammo - if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime) + if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime) { return; } @@ -87,8 +87,8 @@ void CCharacter::HandleJetpack() void CCharacter::RemoveNinja() { - m_Ninja.m_CurrentMoveTime = 0; - m_aWeapons[WEAPON_NINJA].m_Got = false; + m_Core.m_Ninja.m_CurrentMoveTime = 0; + m_Core.m_aWeapons[WEAPON_NINJA].m_Got = false; m_Core.m_ActiveWeapon = m_LastWeapon; SetWeapon(m_Core.m_ActiveWeapon); @@ -99,7 +99,7 @@ void CCharacter::HandleNinja() if(m_Core.m_ActiveWeapon != WEAPON_NINJA) return; - if((GameWorld()->GameTick() - m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * GameWorld()->GameTickSpeed() / 1000)) + if((GameWorld()->GameTick() - m_Core.m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * GameWorld()->GameTickSpeed() / 1000)) { // time's up, return RemoveNinja(); @@ -109,18 +109,18 @@ void CCharacter::HandleNinja() // force ninja Weapon SetWeapon(WEAPON_NINJA); - m_Ninja.m_CurrentMoveTime--; + m_Core.m_Ninja.m_CurrentMoveTime--; - if(m_Ninja.m_CurrentMoveTime == 0) + if(m_Core.m_Ninja.m_CurrentMoveTime == 0) { // reset velocity - m_Core.m_Vel = m_Ninja.m_ActivationDir * m_Ninja.m_OldVelAmount; + m_Core.m_Vel = m_Core.m_Ninja.m_ActivationDir * m_Core.m_Ninja.m_OldVelAmount; } - if(m_Ninja.m_CurrentMoveTime > 0) + if(m_Core.m_Ninja.m_CurrentMoveTime > 0) { // Set velocity - m_Core.m_Vel = m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity; + m_Core.m_Vel = m_Core.m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity; vec2 OldPos = m_Pos; Collision()->MoveBox(&m_Core.m_Pos, &m_Core.m_Vel, vec2(m_ProximityRadius, m_ProximityRadius), 0.f); @@ -184,7 +184,7 @@ void CCharacter::HandleNinja() void CCharacter::DoWeaponSwitch() { // make sure we can switch - if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_aWeapons[WEAPON_NINJA].m_Got || !m_aWeapons[m_QueuedWeapon].m_Got) + if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_Core.m_aWeapons[WEAPON_NINJA].m_Got || !m_Core.m_aWeapons[m_QueuedWeapon].m_Got) return; // switch Weapon @@ -202,7 +202,7 @@ void CCharacter::HandleWeaponSwitch() bool Anything = false; for(int i = 0; i < NUM_WEAPONS - 1; ++i) - if(m_aWeapons[i].m_Got) + if(m_Core.m_aWeapons[i].m_Got) Anything = true; if(!Anything) return; @@ -215,7 +215,7 @@ void CCharacter::HandleWeaponSwitch() while(Next) // Next Weapon selection { WantedWeapon = (WantedWeapon + 1) % NUM_WEAPONS; - if(m_aWeapons[WantedWeapon].m_Got) + if(m_Core.m_aWeapons[WantedWeapon].m_Got) Next--; } } @@ -225,7 +225,7 @@ void CCharacter::HandleWeaponSwitch() while(Prev) // Prev Weapon selection { WantedWeapon = (WantedWeapon - 1) < 0 ? NUM_WEAPONS - 1 : WantedWeapon - 1; - if(m_aWeapons[WantedWeapon].m_Got) + if(m_Core.m_aWeapons[WantedWeapon].m_Got) Prev--; } } @@ -235,7 +235,7 @@ void CCharacter::HandleWeaponSwitch() WantedWeapon = m_Input.m_WantedWeapon - 1; // check for insane values - if(WantedWeapon >= 0 && WantedWeapon < NUM_WEAPONS && WantedWeapon != m_Core.m_ActiveWeapon && m_aWeapons[WantedWeapon].m_Got) + if(WantedWeapon >= 0 && WantedWeapon < NUM_WEAPONS && WantedWeapon != m_Core.m_ActiveWeapon && m_Core.m_aWeapons[WantedWeapon].m_Got) m_QueuedWeapon = WantedWeapon; DoWeaponSwitch(); @@ -272,14 +272,14 @@ void CCharacter::FireWeapon() if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses) WillFire = true; - if(FullAuto && (m_LatestInput.m_Fire & 1) && m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + if(FullAuto && (m_LatestInput.m_Fire & 1) && m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) WillFire = true; if(!WillFire) return; // check for ammo - if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime) + if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime) { return; } @@ -445,9 +445,9 @@ void CCharacter::FireWeapon() // reset Hit objects m_NumObjectsHit = 0; - m_Ninja.m_ActivationDir = Direction; - m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * GameWorld()->GameTickSpeed() / 1000; - m_Ninja.m_OldVelAmount = length(m_Core.m_Vel); + m_Core.m_Ninja.m_ActivationDir = Direction; + m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * GameWorld()->GameTickSpeed() / 1000; + m_Core.m_Ninja.m_OldVelAmount = length(m_Core.m_Vel); } break; } @@ -482,10 +482,10 @@ void CCharacter::HandleWeapons() void CCharacter::GiveNinja() { - m_Ninja.m_ActivationTick = GameWorld()->GameTick(); - m_aWeapons[WEAPON_NINJA].m_Got = true; + m_Core.m_Ninja.m_ActivationTick = GameWorld()->GameTick(); + m_Core.m_aWeapons[WEAPON_NINJA].m_Got = true; if(!m_FreezeTime) - m_aWeapons[WEAPON_NINJA].m_Ammo = -1; + m_Core.m_aWeapons[WEAPON_NINJA].m_Ammo = -1; if(m_Core.m_ActiveWeapon != WEAPON_NINJA) m_LastWeapon = m_Core.m_ActiveWeapon; m_Core.m_ActiveWeapon = WEAPON_NINJA; @@ -697,7 +697,9 @@ void CCharacter::HandleTiles(int Index) if(Collision()->GetSwitchType(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()]) + { Freeze(Collision()->GetSwitchDelay(MapIndex)); + } } else if(Collision()->GetSwitchType(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER) { @@ -859,7 +861,7 @@ void CCharacter::HandleTiles(int Index) if(m_Core.m_Vel.y > 0 && m_Core.m_Colliding && m_Core.m_LeftWall) { m_Core.m_LeftWall = false; - m_Core.m_JumpedTotal = m_Core.m_Jumps - 1; + m_Core.m_JumpedTotal = m_Core.m_Jumps >= 2 ? m_Core.m_Jumps - 2 : 0; m_Core.m_Jumped = 1; } } @@ -923,7 +925,7 @@ void CCharacter::DDRaceTick() if(m_FreezeTime > 0) m_FreezeTime--; else - m_Ninja.m_ActivationTick = GameWorld()->GameTick(); + m_Core.m_Ninja.m_ActivationTick = GameWorld()->GameTick(); if(!m_CanMoveInFreeze) { m_Input.m_Direction = 0; @@ -935,6 +937,22 @@ void CCharacter::DDRaceTick() } HandleTuneLayer(); + + // check if the tee is in any type of freeze + int Index = Collision()->GetPureMapIndex(m_Pos); + const int aTiles[] = { + Collision()->GetTileIndex(Index), + Collision()->GetFTileIndex(Index), + Collision()->GetSwitchType(Index)}; + m_Core.m_IsInFreeze = false; + for(const int Tile : aTiles) + { + if(Tile == TILE_FREEZE || Tile == TILE_DFREEZE || Tile == TILE_LFREEZE) + { + m_Core.m_IsInFreeze = true; + break; + } + } } void CCharacter::DDRacePostCoreTick() @@ -950,17 +968,32 @@ void CCharacter::DDRacePostCoreTick() if(m_DeepFreeze && !m_Super) Freeze(); - if(m_Core.m_Jumps == -1 && !m_Super) + if(m_Core.m_Jumps == -1) + { + // The player has only one ground jump, so his feet are always dark m_Core.m_Jumped |= 2; - else if(m_Core.m_Jumps == 0 && !m_Super) - m_Core.m_Jumped = 3; + } + else if(m_Core.m_Jumps == 0) + { + // The player has no jumps at all, so his feet are always dark + m_Core.m_Jumped |= 2; + } else if(m_Core.m_Jumps == 1 && m_Core.m_Jumped > 0) - m_Core.m_Jumped = 3; + { + // If the player has only one jump, each jump is the last one + m_Core.m_Jumped |= 2; + } else if(m_Core.m_JumpedTotal < m_Core.m_Jumps - 1 && m_Core.m_Jumped > 1) + { + // The player has not yet used up all his jumps, so his feet remain light m_Core.m_Jumped = 1; + } if((m_Super || m_SuperJump) && m_Core.m_Jumped > 1) + { + // Super players and players with infinite jumps always have light feet m_Core.m_Jumped = 1; + } int CurrentIndex = Collision()->GetMapIndex(m_Pos); HandleSkippableTiles(CurrentIndex); @@ -982,10 +1015,10 @@ bool CCharacter::Freeze(int Seconds) return false; if((Seconds <= 0 || m_Super || m_FreezeTime == -1 || m_FreezeTime > Seconds * GameWorld()->GameTickSpeed()) && Seconds != -1) return false; - if(m_FreezeTick < GameWorld()->GameTick() - GameWorld()->GameTickSpeed() || Seconds == -1) + if(m_Core.m_FreezeTick < GameWorld()->GameTick() - GameWorld()->GameTickSpeed() || Seconds == -1) { m_FreezeTime = Seconds == -1 ? Seconds : Seconds * GameWorld()->GameTickSpeed(); - m_FreezeTick = GameWorld()->GameTick(); + m_Core.m_FreezeTick = GameWorld()->GameTick(); return true; } return false; @@ -1000,10 +1033,10 @@ bool CCharacter::UnFreeze() { if(m_FreezeTime > 0) { - if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Got) + if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Got) m_Core.m_ActiveWeapon = WEAPON_GUN; m_FreezeTime = 0; - m_FreezeTick = 0; + m_Core.m_FreezeTick = 0; m_FrozenLastTick = true; return true; } @@ -1028,10 +1061,10 @@ void CCharacter::GiveWeapon(int Weapon, bool Remove) } else { - m_aWeapons[Weapon].m_Ammo = -1; + m_Core.m_aWeapons[Weapon].m_Ammo = -1; } - m_aWeapons[Weapon].m_Got = !Remove; + m_Core.m_aWeapons[Weapon].m_Got = !Remove; } void CCharacter::GiveAllWeapons() @@ -1047,7 +1080,7 @@ CTeamsCore *CCharacter::TeamsCore() return m_Core.m_pTeams; } -CCharacter::CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended) : +CCharacter::CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, CNetObj_DDNetCharacterDisplayInfo *pExtendedDisplayInfo) : CEntity(pGameWorld, CGameWorld::ENTTYPE_CHARACTER) { m_ID = ID; @@ -1060,7 +1093,7 @@ CCharacter::CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, m_Core.Reset(); m_Core.Init(&GameWorld()->m_Core, GameWorld()->Collision(), GameWorld()->Teams()); m_Core.m_Id = ID; - mem_zero(&m_Ninja, sizeof(m_Ninja)); + mem_zero(&m_Core.m_Ninja, sizeof(m_Core.m_Ninja)); mem_zero(&m_SavedInput, sizeof(m_SavedInput)); m_LatestInput = m_LatestPrevInput = m_PrevInput = m_Input = m_SavedInput; m_ProximityRadius = ms_PhysSize; @@ -1082,7 +1115,7 @@ CCharacter::CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, m_LatestPrevInput = m_LatestInput = m_PrevInput = m_SavedInput = m_Input; ResetPrediction(); - Read(pChar, pExtended, false); + Read(pChar, pExtended, pExtendedDisplayInfo, false); GameWorld()->InsertEntity(this); } @@ -1100,7 +1133,8 @@ void CCharacter::ResetPrediction() m_Core.m_Collision = true; m_NumInputs = 0; m_FreezeTime = 0; - m_FreezeTick = 0; + m_Core.m_FreezeTick = 0; + m_Core.m_IsInFreeze = false; m_DeepFreeze = false; m_LiveFreeze = false; m_FrozenLastTick = false; @@ -1119,9 +1153,9 @@ void CCharacter::ResetPrediction() m_LastTuneZoneTick = 0; } -void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal) +void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, CNetObj_DDNetCharacterDisplayInfo *pExtendedDisplayInfo, bool IsLocal) { - m_Core.Read((CNetObj_CharacterCore *)pChar); + m_Core.ReadCharacterCore((const CNetObj_CharacterCore *)pChar); m_IsLocal = IsLocal; if(pExtended) @@ -1149,12 +1183,6 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende if(pExtended->m_Flags & CHARACTERFLAG_NO_SHOTGUN_HIT) m_Hit |= DISABLE_HIT_SHOTGUN; - m_aWeapons[WEAPON_HAMMER].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_HAMMER) != 0; - m_aWeapons[WEAPON_GUN].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_GUN) != 0; - m_aWeapons[WEAPON_SHOTGUN].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_SHOTGUN) != 0; - m_aWeapons[WEAPON_GRENADE].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_GRENADE) != 0; - m_aWeapons[WEAPON_LASER].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_LASER) != 0; - const bool Ninja = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_NINJA) != 0; if(Ninja && m_Core.m_ActiveWeapon != WEAPON_NINJA) GiveNinja(); @@ -1185,7 +1213,7 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende if(pChar->m_Weapon != m_Core.m_ActiveWeapon) { if(pChar->m_Weapon == WEAPON_NINJA) - m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo = 0; + m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo = 0; else { if(m_Core.m_ActiveWeapon == WEAPON_NINJA) @@ -1195,12 +1223,12 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende SetNinjaCurrentMoveTime(0); } if(pChar->m_Weapon == m_LastSnapWeapon) - m_aWeapons[m_Core.m_ActiveWeapon].m_Got = false; + m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Got = false; } } // add weapon if(pChar->m_Weapon != WEAPON_NINJA) - m_aWeapons[pChar->m_Weapon].m_Got = true; + m_Core.m_aWeapons[pChar->m_Weapon].m_Got = true; // jetpack if(GameWorld()->m_WorldConfig.m_PredictWeapons && Tuning()->m_JetpackStrength > 0) @@ -1208,8 +1236,8 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende m_LastJetpackStrength = Tuning()->m_JetpackStrength; m_Jetpack = true; m_Core.m_Jetpack = true; - m_aWeapons[WEAPON_GUN].m_Got = true; - m_aWeapons[WEAPON_GUN].m_Ammo = -1; + m_Core.m_aWeapons[WEAPON_GUN].m_Got = true; + m_Core.m_aWeapons[WEAPON_GUN].m_Ammo = -1; m_NinjaJetpack = pChar->m_Weapon == WEAPON_NINJA; } else if(pChar->m_Weapon != WEAPON_NINJA) @@ -1247,7 +1275,7 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende // detect unfreeze (in case the player was frozen in the tile prediction and not correctly unfrozen) if(pChar->m_Emote != EMOTE_PAIN && pChar->m_Emote != EMOTE_NORMAL) m_DeepFreeze = false; - if(pChar->m_Weapon != WEAPON_NINJA || pChar->m_AttackTick > m_FreezeTick || absolute(pChar->m_VelX) == 256 * 10 || !GameWorld()->m_WorldConfig.m_PredictFreeze) + if(pChar->m_Weapon != WEAPON_NINJA || pChar->m_AttackTick > m_Core.m_FreezeTick || absolute(pChar->m_VelX) == 256 * 10 || !GameWorld()->m_WorldConfig.m_PredictFreeze) { m_DeepFreeze = false; UnFreeze(); @@ -1274,7 +1302,7 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende // set the current weapon if(pChar->m_Weapon != WEAPON_NINJA) { - m_aWeapons[pChar->m_Weapon].m_Ammo = (GameWorld()->m_WorldConfig.m_InfiniteAmmo || GameWorld()->m_WorldConfig.m_IsDDRace || pChar->m_Weapon == WEAPON_HAMMER) ? -1 : pChar->m_AmmoCount; + m_Core.m_aWeapons[pChar->m_Weapon].m_Ammo = (GameWorld()->m_WorldConfig.m_InfiniteAmmo || GameWorld()->m_WorldConfig.m_IsDDRace || pChar->m_Weapon == WEAPON_HAMMER) ? -1 : pChar->m_AmmoCount; if(pChar->m_Weapon != m_Core.m_ActiveWeapon) SetActiveWeapon(pChar->m_Weapon); } @@ -1302,6 +1330,22 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende m_ReloadTimer = maximum(0, m_AttackTick + FireDelayTicks - GameWorld()->GameTick()); } } + + if(pExtendedDisplayInfo) + { + if(GameWorld()->m_WorldConfig.m_PredictFreeze) + { + m_Core.m_FreezeTick = pExtendedDisplayInfo->m_FreezeTick; + } + m_Core.m_IsInFreeze = pExtendedDisplayInfo->m_IsInFreeze; + m_Core.m_Ninja.m_ActivationTick = pExtendedDisplayInfo->m_NinjaActivationTick; + m_Core.m_JumpedTotal = pExtendedDisplayInfo->m_JumpedTotal; + if(!IsLocal) + { + m_Input.m_TargetX = pExtendedDisplayInfo->m_TargetX; + m_Input.m_TargetY = pExtendedDisplayInfo->m_TargetY; + } + } } void CCharacter::SetCoreWorld(CGameWorld *pGameWorld) diff --git a/src/game/client/prediction/entities/character.h b/src/game/client/prediction/entities/character.h index 24ed57940..d15f2969a 100644 --- a/src/game/client/prediction/entities/character.h +++ b/src/game/client/prediction/entities/character.h @@ -79,7 +79,6 @@ public: bool m_Jetpack; bool m_NinjaJetpack; int m_FreezeTime; - int m_FreezeTick; bool m_FrozenLastTick; bool m_DeepFreeze; bool m_LiveFreeze; @@ -112,13 +111,13 @@ public: CCharacterCore GetCore() { return m_Core; } void SetCore(CCharacterCore Core) { m_Core = Core; } CCharacterCore *Core() { return &m_Core; } - bool GetWeaponGot(int Type) { return m_aWeapons[Type].m_Got; } - void SetWeaponGot(int Type, bool Value) { m_aWeapons[Type].m_Got = Value; } - int GetWeaponAmmo(int Type) { return m_aWeapons[Type].m_Ammo; } - void SetWeaponAmmo(int Type, int Value) { m_aWeapons[Type].m_Ammo = Value; } - void SetNinjaActivationDir(vec2 ActivationDir) { m_Ninja.m_ActivationDir = ActivationDir; } - void SetNinjaActivationTick(int ActivationTick) { m_Ninja.m_ActivationTick = ActivationTick; } - void SetNinjaCurrentMoveTime(int CurrentMoveTime) { m_Ninja.m_CurrentMoveTime = CurrentMoveTime; } + bool GetWeaponGot(int Type) { return m_Core.m_aWeapons[Type].m_Got; } + void SetWeaponGot(int Type, bool Value) { m_Core.m_aWeapons[Type].m_Got = Value; } + int GetWeaponAmmo(int Type) { return m_Core.m_aWeapons[Type].m_Ammo; } + void SetWeaponAmmo(int Type, int Value) { m_Core.m_aWeapons[Type].m_Ammo = Value; } + void SetNinjaActivationDir(vec2 ActivationDir) { m_Core.m_Ninja.m_ActivationDir = ActivationDir; } + void SetNinjaActivationTick(int ActivationTick) { m_Core.m_Ninja.m_ActivationTick = ActivationTick; } + void SetNinjaCurrentMoveTime(int CurrentMoveTime) { m_Core.m_Ninja.m_CurrentMoveTime = CurrentMoveTime; } int GetCID() { return m_ID; } void SetInput(CNetObj_PlayerInput *pNewInput) { @@ -133,8 +132,8 @@ public: int GetAttackTick() { return m_AttackTick; } int GetStrongWeakID() { return m_StrongWeakID; } - CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended = 0); - void Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal); + CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended = 0, CNetObj_DDNetCharacterDisplayInfo *pExtendedDisplayInfo = 0); + void Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, CNetObj_DDNetCharacterDisplayInfo *pExtendedDisplayInfo, bool IsLocal); void SetCoreWorld(CGameWorld *pGameWorld); int m_LastSnapWeapon; @@ -153,14 +152,6 @@ private: int m_aHitObjects[10]; int m_NumObjectsHit; - struct WeaponStat - { - int m_AmmoRegenStart; - int m_Ammo; - int m_Ammocost; - bool m_Got; - } m_aWeapons[NUM_WEAPONS]; - int m_LastWeapon; int m_QueuedWeapon; @@ -178,15 +169,6 @@ private: int m_NumInputs; - // ninja - struct NinjaStat - { - vec2 m_ActivationDir; - int m_ActivationTick; - int m_CurrentMoveTime; - int m_OldVelAmount; - } m_Ninja; - // the player core for the physics CCharacterCore m_Core; diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp index 3cba60127..ed8521146 100644 --- a/src/game/client/prediction/gameworld.cpp +++ b/src/game/client/prediction/gameworld.cpp @@ -346,16 +346,16 @@ void CGameWorld::NetObjBegin() OnModified(); } -void CGameWorld::NetCharAdd(int ObjID, CNetObj_Character *pCharObj, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal) +void CGameWorld::NetCharAdd(int ObjID, CNetObj_Character *pCharObj, CNetObj_DDNetCharacter *pExtended, CNetObj_DDNetCharacterDisplayInfo *pExtendedDisplayInfo, int GameTeam, bool IsLocal) { CCharacter *pChar; if((pChar = (CCharacter *)GetEntity(ObjID, ENTTYPE_CHARACTER))) { - pChar->Read(pCharObj, pExtended, IsLocal); + pChar->Read(pCharObj, pExtended, pExtendedDisplayInfo, IsLocal); pChar->Keep(); } else - pChar = new CCharacter(this, ObjID, pCharObj, pExtended); + pChar = new CCharacter(this, ObjID, pCharObj, pExtended, pExtendedDisplayInfo); if(pChar) pChar->m_GameTeam = GameTeam; diff --git a/src/game/client/prediction/gameworld.h b/src/game/client/prediction/gameworld.h index dc96b4268..c10ef2d77 100644 --- a/src/game/client/prediction/gameworld.h +++ b/src/game/client/prediction/gameworld.h @@ -80,7 +80,7 @@ public: void OnModified(); void NetObjBegin(); - void NetCharAdd(int ObjID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal); + void NetCharAdd(int ObjID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, CNetObj_DDNetCharacterDisplayInfo *pExtendedDisplayInfo, int GameTeam, bool IsLocal); void NetObjAdd(int ObjID, int ObjType, const void *pObjData, const CNetObj_EntityEx *pDataEx); void NetObjEnd(int LocalID); void CopyWorld(CGameWorld *pFrom); diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index 79e0d0c3f..94c9ad04c 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -102,7 +102,9 @@ void CCharacterCore::Reset() m_HasTelegunGun = false; m_HasTelegunGrenade = false; m_HasTelegunLaser = false; + m_FreezeTick = 0; m_FreezeEnd = 0; + m_IsInFreeze = false; m_DeepFrozen = false; m_LiveFrozen = false; @@ -132,35 +134,51 @@ void CCharacterCore::Tick(bool UseInput) float Accel = Grounded ? m_Tuning.m_GroundControlAccel : m_Tuning.m_AirControlAccel; float Friction = Grounded ? m_Tuning.m_GroundFriction : m_Tuning.m_AirFriction; + // handle jumping + // 1 bit = to keep track if a jump has been made on this input (player is holding space bar) + // 2 bit = to track if all air-jumps have been used up (tee gets dark feet) + if(Grounded) + { + m_Jumped &= ~2; + m_JumpedTotal = 0; + } + // handle input if(UseInput) { m_Direction = m_Input.m_Direction; // setup angle - float tmp_angle = atan2f(m_Input.m_TargetY, m_Input.m_TargetX); - if(tmp_angle < -(pi / 2.0f)) + float TmpAngle = atan2f(m_Input.m_TargetY, m_Input.m_TargetX); + if(TmpAngle < -(pi / 2.0f)) { - m_Angle = (int)((tmp_angle + (2.0f * pi)) * 256.0f); + m_Angle = (int)((TmpAngle + (2.0f * pi)) * 256.0f); } else { - m_Angle = (int)(tmp_angle * 256.0f); + m_Angle = (int)(TmpAngle * 256.0f); } // handle jump if(m_Input.m_Jump) { - if(!(m_Jumped & 1)) + if(!(m_Jumped & 1) && m_Jumps != 0) { if(Grounded) { m_TriggeredEvents |= COREEVENT_GROUND_JUMP; m_Vel.y = -m_Tuning.m_GroundJumpImpulse; - m_Jumped |= 1; - m_JumpedTotal = 1; + if(m_Jumps > 1) + { + m_Jumped |= 1; + } + else + { + m_Jumped |= 3; + } + m_JumpedTotal = 0; } - else if(!(m_Jumped & 2)) + else if(!(m_Jumped & 2) && m_Jumps >= 1) { m_TriggeredEvents |= COREEVENT_AIR_JUMP; m_Vel.y = -m_Tuning.m_AirJumpImpulse; @@ -170,7 +188,9 @@ void CCharacterCore::Tick(bool UseInput) } } else + { m_Jumped &= ~1; + } // handle hook if(m_Input.m_Hook) @@ -201,15 +221,6 @@ void CCharacterCore::Tick(bool UseInput) if(m_Direction == 0) m_Vel.x *= Friction; - // handle jumping - // 1 bit = to keep track if a jump has been made on this input (player is holding space bar) - // 2 bit = to keep track if a air-jump has been made (tee gets dark feet) - if(Grounded) - { - m_Jumped &= ~2; - m_JumpedTotal = 0; - } - // do hook if(m_HookState == HOOK_IDLE) { @@ -520,7 +531,7 @@ void CCharacterCore::Write(CNetObj_CharacterCore *pObjCore) pObjCore->m_Angle = m_Angle; } -void CCharacterCore::Read(const CNetObj_CharacterCore *pObjCore) +void CCharacterCore::ReadCharacterCore(const CNetObj_CharacterCore *pObjCore) { m_Pos.x = pObjCore->m_X; m_Pos.y = pObjCore->m_Y; @@ -538,6 +549,11 @@ void CCharacterCore::Read(const CNetObj_CharacterCore *pObjCore) m_Angle = pObjCore->m_Angle; } +void CCharacterCore::ReadCharacter(const CNetObj_Character *pObjChar) +{ + m_ActiveWeapon = pObjChar->m_Weapon; + ReadCharacterCore((const CNetObj_CharacterCore *)pObjChar); +} void CCharacterCore::ReadDDNet(const CNetObj_DDNetCharacter *pObjDDNet) { // Collision @@ -568,14 +584,31 @@ void CCharacterCore::ReadDDNet(const CNetObj_DDNetCharacter *pObjDDNet) m_HasTelegunGun = pObjDDNet->m_Flags & CHARACTERFLAG_TELEGUN_GUN; m_HasTelegunLaser = pObjDDNet->m_Flags & CHARACTERFLAG_TELEGUN_LASER; + // Weapons + m_aWeapons[WEAPON_HAMMER].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_HAMMER) != 0; + m_aWeapons[WEAPON_GUN].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_GUN) != 0; + m_aWeapons[WEAPON_SHOTGUN].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_SHOTGUN) != 0; + m_aWeapons[WEAPON_GRENADE].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_GRENADE) != 0; + m_aWeapons[WEAPON_LASER].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_LASER) != 0; + m_aWeapons[WEAPON_NINJA].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_NINJA) != 0; + + // Available jumps m_Jumps = pObjDDNet->m_Jumps; } +void CCharacterCore::ReadDDNetDisplayInfo(const CNetObj_DDNetCharacterDisplayInfo *pObjDDNet) +{ + m_JumpedTotal = pObjDDNet->m_JumpedTotal; + m_Ninja.m_ActivationTick = pObjDDNet->m_NinjaActivationTick; + m_FreezeTick = pObjDDNet->m_FreezeTick; + m_IsInFreeze = pObjDDNet->m_IsInFreeze; +} + void CCharacterCore::Quantize() { CNetObj_CharacterCore Core; Write(&Core); - Read(&Core); + ReadCharacterCore(&Core); } // DDRace diff --git a/src/game/gamecore.h b/src/game/gamecore.h index f9de83d1c..e8493b912 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -221,11 +221,29 @@ public: int m_HookTick; int m_HookState; int m_HookedPlayer; + int m_ActiveWeapon; + struct WeaponStat + { + int m_AmmoRegenStart; + int m_Ammo; + int m_Ammocost; + bool m_Got; + } m_aWeapons[NUM_WEAPONS]; + + // ninja + struct + { + vec2 m_ActivationDir; + int m_ActivationTick; + int m_CurrentMoveTime; + int m_OldVelAmount; + } m_Ninja; bool m_NewHook; int m_Jumped; + // m_JumpedTotal counts the jumps performed in the air int m_JumpedTotal; int m_Jumps; @@ -240,7 +258,8 @@ public: void Tick(bool UseInput); void Move(); - void Read(const CNetObj_CharacterCore *pObjCore); + void ReadCharacterCore(const CNetObj_CharacterCore *pObjCore); + void ReadCharacter(const CNetObj_Character *pObjChar); void Write(CNetObj_CharacterCore *pObjCore); void Quantize(); @@ -258,6 +277,7 @@ public: void SetTeamsCore(CTeamsCore *pTeams); void SetTeleOuts(std::map> *pTeleOuts); void ReadDDNet(const CNetObj_DDNetCharacter *pObjDDNet); + void ReadDDNetDisplayInfo(const CNetObj_DDNetCharacterDisplayInfo *pObjDDNet); bool m_Solo; bool m_Jetpack; bool m_NoCollision; @@ -272,7 +292,9 @@ public: bool m_HasTelegunGun; bool m_HasTelegunGrenade; bool m_HasTelegunLaser; + int m_FreezeTick; int m_FreezeEnd; + bool m_IsInFreeze; bool m_DeepFrozen; bool m_LiveFrozen; CTuningParams m_Tuning; diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 59090ce3e..4cc6f7707 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -158,14 +158,14 @@ void CCharacter::HandleJetpack() if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses) WillFire = true; - if(FullAuto && (m_LatestInput.m_Fire & 1) && m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + if(FullAuto && (m_LatestInput.m_Fire & 1) && m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) WillFire = true; if(!WillFire) return; // check for ammo - if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime) + if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime) { return; } @@ -192,14 +192,14 @@ void CCharacter::HandleNinja() if(m_Core.m_ActiveWeapon != WEAPON_NINJA) return; - if((Server()->Tick() - m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000)) + if((Server()->Tick() - m_Core.m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000)) { // time's up, return RemoveNinja(); return; } - int NinjaTime = m_Ninja.m_ActivationTick + (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000) - Server()->Tick(); + int NinjaTime = m_Core.m_Ninja.m_ActivationTick + (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000) - Server()->Tick(); if(NinjaTime % Server()->TickSpeed() == 0 && NinjaTime / Server()->TickSpeed() <= 5) { @@ -211,18 +211,18 @@ void CCharacter::HandleNinja() // force ninja Weapon SetWeapon(WEAPON_NINJA); - m_Ninja.m_CurrentMoveTime--; + m_Core.m_Ninja.m_CurrentMoveTime--; - if(m_Ninja.m_CurrentMoveTime == 0) + if(m_Core.m_Ninja.m_CurrentMoveTime == 0) { // reset velocity - m_Core.m_Vel = m_Ninja.m_ActivationDir * m_Ninja.m_OldVelAmount; + m_Core.m_Vel = m_Core.m_Ninja.m_ActivationDir * m_Core.m_Ninja.m_OldVelAmount; } - if(m_Ninja.m_CurrentMoveTime > 0) + if(m_Core.m_Ninja.m_CurrentMoveTime > 0) { // Set velocity - m_Core.m_Vel = m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity; + m_Core.m_Vel = m_Core.m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity; vec2 OldPos = m_Pos; GameServer()->Collision()->MoveBox(&m_Core.m_Pos, &m_Core.m_Vel, vec2(GetProximityRadius(), GetProximityRadius()), 0.f); @@ -285,7 +285,7 @@ void CCharacter::HandleNinja() void CCharacter::DoWeaponSwitch() { // make sure we can switch - if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_aWeapons[WEAPON_NINJA].m_Got || !m_aWeapons[m_QueuedWeapon].m_Got) + if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_Core.m_aWeapons[WEAPON_NINJA].m_Got || !m_Core.m_aWeapons[m_QueuedWeapon].m_Got) return; // switch Weapon @@ -300,7 +300,7 @@ void CCharacter::HandleWeaponSwitch() bool Anything = false; for(int i = 0; i < NUM_WEAPONS - 1; ++i) - if(m_aWeapons[i].m_Got) + if(m_Core.m_aWeapons[i].m_Got) Anything = true; if(!Anything) return; @@ -313,7 +313,7 @@ void CCharacter::HandleWeaponSwitch() while(Next) // Next Weapon selection { WantedWeapon = (WantedWeapon + 1) % NUM_WEAPONS; - if(m_aWeapons[WantedWeapon].m_Got) + if(m_Core.m_aWeapons[WantedWeapon].m_Got) Next--; } } @@ -323,7 +323,7 @@ void CCharacter::HandleWeaponSwitch() while(Prev) // Prev Weapon selection { WantedWeapon = (WantedWeapon - 1) < 0 ? NUM_WEAPONS - 1 : WantedWeapon - 1; - if(m_aWeapons[WantedWeapon].m_Got) + if(m_Core.m_aWeapons[WantedWeapon].m_Got) Prev--; } } @@ -333,7 +333,7 @@ void CCharacter::HandleWeaponSwitch() WantedWeapon = m_Input.m_WantedWeapon - 1; // check for insane values - if(WantedWeapon >= 0 && WantedWeapon < NUM_WEAPONS && WantedWeapon != m_Core.m_ActiveWeapon && m_aWeapons[WantedWeapon].m_Got) + if(WantedWeapon >= 0 && WantedWeapon < NUM_WEAPONS && WantedWeapon != m_Core.m_ActiveWeapon && m_Core.m_aWeapons[WantedWeapon].m_Got) m_QueuedWeapon = WantedWeapon; DoWeaponSwitch(); @@ -372,7 +372,7 @@ void CCharacter::FireWeapon() if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses) WillFire = true; - if(FullAuto && (m_LatestInput.m_Fire & 1) && m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + if(FullAuto && (m_LatestInput.m_Fire & 1) && m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) WillFire = true; if(!WillFire) @@ -390,7 +390,7 @@ void CCharacter::FireWeapon() } // check for ammo - if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) { /*// 125ms is a magical limit of how fast a human can click m_ReloadTimer = 125 * Server()->TickSpeed() / 1000; @@ -576,9 +576,9 @@ void CCharacter::FireWeapon() // reset Hit objects m_NumObjectsHit = 0; - m_Ninja.m_ActivationDir = Direction; - m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000; - m_Ninja.m_OldVelAmount = length(m_Core.m_Vel); + m_Core.m_Ninja.m_ActivationDir = Direction; + m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000; + m_Core.m_Ninja.m_OldVelAmount = length(m_Core.m_Vel); GameServer()->CreateSound(m_Pos, SOUND_NINJA_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID())); } @@ -587,8 +587,8 @@ void CCharacter::FireWeapon() m_AttackTick = Server()->Tick(); - /*if(m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo > 0) // -1 == unlimited - m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo--;*/ + /*if(m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo > 0) // -1 == unlimited + m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo--;*/ if(!m_ReloadTimer) { @@ -623,21 +623,21 @@ void CCharacter::HandleWeapons() void CCharacter::GiveNinja() { - m_Ninja.m_ActivationTick = Server()->Tick(); - m_aWeapons[WEAPON_NINJA].m_Got = true; - m_aWeapons[WEAPON_NINJA].m_Ammo = -1; + m_Core.m_Ninja.m_ActivationTick = Server()->Tick(); + m_Core.m_aWeapons[WEAPON_NINJA].m_Got = true; + m_Core.m_aWeapons[WEAPON_NINJA].m_Ammo = -1; if(m_Core.m_ActiveWeapon != WEAPON_NINJA) m_LastWeapon = m_Core.m_ActiveWeapon; m_Core.m_ActiveWeapon = WEAPON_NINJA; - if(!m_aWeapons[WEAPON_NINJA].m_Got) + if(!m_Core.m_aWeapons[WEAPON_NINJA].m_Got) GameServer()->CreateSound(m_Pos, SOUND_PICKUP_NINJA, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID())); } void CCharacter::RemoveNinja() { - m_Ninja.m_CurrentMoveTime = 0; - m_aWeapons[WEAPON_NINJA].m_Got = false; + m_Core.m_Ninja.m_CurrentMoveTime = 0; + m_Core.m_aWeapons[WEAPON_NINJA].m_Got = false; m_Core.m_ActiveWeapon = m_LastWeapon; SetWeapon(m_Core.m_ActiveWeapon); @@ -863,12 +863,12 @@ void CCharacter::TickPaused() { ++m_AttackTick; ++m_DamageTakenTick; - ++m_Ninja.m_ActivationTick; + ++m_Core.m_Ninja.m_ActivationTick; ++m_ReckoningTick; if(m_LastAction != -1) ++m_LastAction; - if(m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart > -1) - ++m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart; + if(m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart > -1) + ++m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart; if(m_EmoteStop > -1) ++m_EmoteStop; } @@ -1089,8 +1089,8 @@ void CCharacter::SnapCharacter(int SnappingClient, int ID) { Health = m_Health; Armor = m_Armor; - if(m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo > 0) - AmmoCount = (!m_FreezeTime) ? m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo : 0; + if(m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo > 0) + AmmoCount = (!m_FreezeTime) ? m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo : 0; } if(GetPlayer()->m_Afk || GetPlayer()->IsPaused()) @@ -1152,9 +1152,9 @@ void CCharacter::SnapCharacter(int SnappingClient, int ID) pCharacter->m_AmmoCount = AmmoCount; if(m_FreezeTime > 0 || m_FreezeTime == -1 || m_DeepFreeze) - pCharacter->m_AmmoCount = m_FreezeTick + g_Config.m_SvFreezeDelay * Server()->TickSpeed(); + pCharacter->m_AmmoCount = m_Core.m_FreezeTick + g_Config.m_SvFreezeDelay * Server()->TickSpeed(); else if(Weapon == WEAPON_NINJA) - pCharacter->m_AmmoCount = m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000; + pCharacter->m_AmmoCount = m_Core.m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000; pCharacter->m_Health = Health; pCharacter->m_Armor = Armor; @@ -1228,15 +1228,15 @@ void CCharacter::Snap(int SnappingClient) pDDNetCharacter->m_Flags |= CHARACTERFLAG_TELEGUN_GRENADE; if(m_Core.m_HasTelegunLaser) pDDNetCharacter->m_Flags |= CHARACTERFLAG_TELEGUN_LASER; - if(m_aWeapons[WEAPON_HAMMER].m_Got) + if(m_Core.m_aWeapons[WEAPON_HAMMER].m_Got) pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_HAMMER; - if(m_aWeapons[WEAPON_GUN].m_Got) + if(m_Core.m_aWeapons[WEAPON_GUN].m_Got) pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_GUN; - if(m_aWeapons[WEAPON_SHOTGUN].m_Got) + if(m_Core.m_aWeapons[WEAPON_SHOTGUN].m_Got) pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_SHOTGUN; - if(m_aWeapons[WEAPON_GRENADE].m_Got) + if(m_Core.m_aWeapons[WEAPON_GRENADE].m_Got) pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_GRENADE; - if(m_aWeapons[WEAPON_LASER].m_Got) + if(m_Core.m_aWeapons[WEAPON_LASER].m_Got) pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_LASER; if(m_Core.m_ActiveWeapon == WEAPON_NINJA) pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_NINJA; @@ -1249,6 +1249,18 @@ void CCharacter::Snap(int SnappingClient) pDDNetCharacter->m_Jumps = m_Core.m_Jumps; pDDNetCharacter->m_TeleCheckpoint = m_TeleCheckpoint; pDDNetCharacter->m_StrongWeakID = m_StrongWeakID; + + CNetObj_DDNetCharacterDisplayInfo *pDDNetCharacterDisplayInfo = static_cast(Server()->SnapNewItem(NETOBJTYPE_DDNETCHARACTERDISPLAYINFO, ID, sizeof(CNetObj_DDNetCharacterDisplayInfo))); + if(!pDDNetCharacterDisplayInfo) + return; + pDDNetCharacterDisplayInfo->m_JumpedTotal = m_Core.m_JumpedTotal; + pDDNetCharacterDisplayInfo->m_NinjaActivationTick = m_Core.m_Ninja.m_ActivationTick; + pDDNetCharacterDisplayInfo->m_FreezeTick = m_Core.m_FreezeTick; + pDDNetCharacterDisplayInfo->m_IsInFreeze = m_Core.m_IsInFreeze; + pDDNetCharacterDisplayInfo->m_IsInPracticeMode = Teams()->IsPractice(Team()); + pDDNetCharacterDisplayInfo->m_TargetX = m_Core.m_Input.m_TargetX; + pDDNetCharacterDisplayInfo->m_TargetY = m_Core.m_Input.m_TargetY; + pDDNetCharacterDisplayInfo->m_RampValue = round_to_int(VelocityRamp(length(m_Core.m_Vel) * 50, m_Core.m_Tuning.m_VelrampStart, m_Core.m_Tuning.m_VelrampRange, m_Core.m_Tuning.m_VelrampCurvature) * 1000.0f); } // DDRace @@ -1483,7 +1495,9 @@ void CCharacter::HandleTiles(int Index) // freeze if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Super && !m_DeepFreeze) + { Freeze(); + } else if(((m_TileIndex == TILE_UNFREEZE) || (m_TileFIndex == TILE_UNFREEZE)) && !m_DeepFreeze) UnFreeze(); @@ -1603,7 +1617,7 @@ void CCharacter::HandleTiles(int Index) if(m_Core.m_Vel.y > 0 && m_Core.m_Colliding && m_Core.m_LeftWall) { m_Core.m_LeftWall = false; - m_Core.m_JumpedTotal = m_Core.m_Jumps - 1; + m_Core.m_JumpedTotal = m_Core.m_Jumps >= 2 ? m_Core.m_Jumps - 2 : 0; m_Core.m_Jumped = 1; } } @@ -1704,7 +1718,9 @@ void CCharacter::HandleTiles(int Index) else if(GameServer()->Collision()->GetSwitchType(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER) { if(GameServer()->Collision()->GetSwitchNumber(MapIndex) == 0 || GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()]) + { Freeze(GameServer()->Collision()->GetSwitchDelay(MapIndex)); + } } else if(GameServer()->Collision()->GetSwitchType(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER) { @@ -2080,7 +2096,7 @@ void CCharacter::DDRaceTick() if(m_FreezeTime > 0) m_FreezeTime--; else - m_Ninja.m_ActivationTick = Server()->Tick(); + m_Core.m_Ninja.m_ActivationTick = Server()->Tick(); m_Input.m_Direction = 0; m_Input.m_Jump = 0; m_Input.m_Hook = 0; @@ -2090,29 +2106,28 @@ void CCharacter::DDRaceTick() HandleTuneLayer(); // need this before coretick + // check if the tee is in any type of freeze + int Index = GameServer()->Collision()->GetPureMapIndex(m_Pos); + const int aTiles[] = { + GameServer()->Collision()->GetTileIndex(Index), + GameServer()->Collision()->GetFTileIndex(Index), + GameServer()->Collision()->GetSwitchType(Index)}; + m_Core.m_IsInFreeze = false; + for(const int Tile : aTiles) + { + if(Tile == TILE_FREEZE || Tile == TILE_DFREEZE || Tile == TILE_LFREEZE) + { + m_Core.m_IsInFreeze = true; + break; + } + } + // look for save position for rescue feature if(g_Config.m_SvRescue || ((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || Team() > TEAM_FLOCK) && Team() >= TEAM_FLOCK && Team() < TEAM_SUPER)) { - int Index = GameServer()->Collision()->GetPureMapIndex(m_Pos); - const int aTiles[] = { - GameServer()->Collision()->GetTileIndex(Index), - GameServer()->Collision()->GetFTileIndex(Index), - GameServer()->Collision()->GetSwitchType(Index)}; - if(IsGrounded() && !m_DeepFreeze) + if(!m_Core.m_IsInFreeze && IsGrounded() && !m_DeepFreeze) { - bool IsInFreeze = false; - for(const int Tile : aTiles) - { - if(Tile == TILE_FREEZE || Tile == TILE_DFREEZE || Tile == TILE_LFREEZE) - { - IsInFreeze = true; - break; - } - } - if(!IsInFreeze) - { - SetRescue(); - } + SetRescue(); } } @@ -2131,17 +2146,32 @@ void CCharacter::DDRacePostCoreTick() if(m_DeepFreeze && !m_Super) Freeze(); - if(m_Core.m_Jumps == -1 && !m_Super) + if(m_Core.m_Jumps == -1) + { + // The player has only one ground jump, so his feet are always dark m_Core.m_Jumped |= 2; - else if(m_Core.m_Jumps == 0 && !m_Super) - m_Core.m_Jumped = 3; + } + else if(m_Core.m_Jumps == 0) + { + // The player has no jumps at all, so his feet are always dark + m_Core.m_Jumped |= 2; + } else if(m_Core.m_Jumps == 1 && m_Core.m_Jumped > 0) - m_Core.m_Jumped = 3; + { + // If the player has only one jump, each jump is the last one + m_Core.m_Jumped |= 2; + } else if(m_Core.m_JumpedTotal < m_Core.m_Jumps - 1 && m_Core.m_Jumped > 1) + { + // The player has not yet used up all his jumps, so his feet remain light m_Core.m_Jumped = 1; + } if((m_Super || m_SuperJump) && m_Core.m_Jumped > 1) + { + // Super players and players with infinite jumps always have light feet m_Core.m_Jumped = 1; + } int CurrentIndex = GameServer()->Collision()->GetMapIndex(m_Pos); HandleSkippableTiles(CurrentIndex); @@ -2186,11 +2216,11 @@ bool CCharacter::Freeze(int Seconds) { if((Seconds <= 0 || m_Super || m_FreezeTime == -1 || m_FreezeTime > Seconds * Server()->TickSpeed()) && Seconds != -1) return false; - if(m_FreezeTick < Server()->Tick() - Server()->TickSpeed() || Seconds == -1) + if(m_Core.m_FreezeTick < Server()->Tick() - Server()->TickSpeed() || Seconds == -1) { m_Armor = 0; m_FreezeTime = Seconds == -1 ? Seconds : Seconds * Server()->TickSpeed(); - m_FreezeTick = Server()->Tick(); + m_Core.m_FreezeTick = Server()->Tick(); return true; } return false; @@ -2206,10 +2236,10 @@ bool CCharacter::UnFreeze() if(m_FreezeTime > 0) { m_Armor = 10; - if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Got) + if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Got) m_Core.m_ActiveWeapon = WEAPON_GUN; m_FreezeTime = 0; - m_FreezeTick = 0; + m_Core.m_FreezeTick = 0; m_FrozenLastTick = true; return true; } @@ -2234,10 +2264,10 @@ void CCharacter::GiveWeapon(int Weapon, bool Remove) } else { - m_aWeapons[Weapon].m_Ammo = -1; + m_Core.m_aWeapons[Weapon].m_Ammo = -1; } - m_aWeapons[Weapon].m_Got = !Remove; + m_Core.m_aWeapons[Weapon].m_Got = !Remove; } void CCharacter::GiveAllWeapons() @@ -2252,7 +2282,7 @@ void CCharacter::ResetPickups() { for(int i = WEAPON_SHOTGUN; i < NUM_WEAPONS - 1; i++) { - m_aWeapons[i].m_Got = false; + m_Core.m_aWeapons[i].m_Got = false; if(m_Core.m_ActiveWeapon == i) m_Core.m_ActiveWeapon = WEAPON_GUN; } diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h index 55ab74ec1..95d6613b9 100644 --- a/src/game/server/entities/character.h +++ b/src/game/server/entities/character.h @@ -98,15 +98,6 @@ private: CEntity *m_apHitObjects[10]; int m_NumObjectsHit; - struct WeaponStat - { - int m_AmmoRegenStart; - int m_Ammo; - int m_Ammocost; - bool m_Got; - - } m_aWeapons[NUM_WEAPONS]; - int m_LastWeapon; int m_QueuedWeapon; @@ -139,15 +130,6 @@ private: int m_Health; int m_Armor; - // ninja - struct - { - vec2 m_ActivationDir; - int m_ActivationTick; - int m_CurrentMoveTime; - int m_OldVelAmount; - } m_Ninja; - // the player core for the physics CCharacterCore m_Core; CGameTeams *m_pTeams = nullptr; @@ -203,7 +185,6 @@ public: bool m_NinjaJetpack; int m_TeamBeforeSuper; int m_FreezeTime; - int m_FreezeTick; bool m_FrozenLastTick; bool m_DeepFreeze; bool m_LiveFreeze; @@ -259,14 +240,14 @@ public: CCharacterCore GetCore() { return m_Core; } void SetCore(CCharacterCore Core) { m_Core = Core; } CCharacterCore *Core() { return &m_Core; } - bool GetWeaponGot(int Type) { return m_aWeapons[Type].m_Got; } - void SetWeaponGot(int Type, bool Value) { m_aWeapons[Type].m_Got = Value; } - int GetWeaponAmmo(int Type) { return m_aWeapons[Type].m_Ammo; } - void SetWeaponAmmo(int Type, int Value) { m_aWeapons[Type].m_Ammo = Value; } + bool GetWeaponGot(int Type) { return m_Core.m_aWeapons[Type].m_Got; } + void SetWeaponGot(int Type, bool Value) { m_Core.m_aWeapons[Type].m_Got = Value; } + int GetWeaponAmmo(int Type) { return m_Core.m_aWeapons[Type].m_Ammo; } + void SetWeaponAmmo(int Type, int Value) { m_Core.m_aWeapons[Type].m_Ammo = Value; } bool IsAlive() { return m_Alive; } - void SetNinjaActivationDir(vec2 ActivationDir) { m_Ninja.m_ActivationDir = ActivationDir; } - void SetNinjaActivationTick(int ActivationTick) { m_Ninja.m_ActivationTick = ActivationTick; } - void SetNinjaCurrentMoveTime(int CurrentMoveTime) { m_Ninja.m_CurrentMoveTime = CurrentMoveTime; } + void SetNinjaActivationDir(vec2 ActivationDir) { m_Core.m_Ninja.m_ActivationDir = ActivationDir; } + void SetNinjaActivationTick(int ActivationTick) { m_Core.m_Ninja.m_ActivationTick = ActivationTick; } + void SetNinjaCurrentMoveTime(int CurrentMoveTime) { m_Core.m_Ninja.m_CurrentMoveTime = CurrentMoveTime; } int GetLastAction() const { return m_LastAction; } diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index e18bd5136..f5fe7ec4c 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -26,10 +26,10 @@ void CSaveTee::Save(CCharacter *pChr) for(int i = 0; i < NUM_WEAPONS; i++) { - m_aWeapons[i].m_AmmoRegenStart = pChr->m_aWeapons[i].m_AmmoRegenStart; - m_aWeapons[i].m_Ammo = pChr->m_aWeapons[i].m_Ammo; - m_aWeapons[i].m_Ammocost = pChr->m_aWeapons[i].m_Ammocost; - m_aWeapons[i].m_Got = pChr->m_aWeapons[i].m_Got; + m_aWeapons[i].m_AmmoRegenStart = pChr->m_Core.m_aWeapons[i].m_AmmoRegenStart; + m_aWeapons[i].m_Ammo = pChr->m_Core.m_aWeapons[i].m_Ammo; + m_aWeapons[i].m_Ammocost = pChr->m_Core.m_aWeapons[i].m_Ammocost; + m_aWeapons[i].m_Got = pChr->m_Core.m_aWeapons[i].m_Got; } m_LastWeapon = pChr->m_LastWeapon; @@ -39,7 +39,7 @@ void CSaveTee::Save(CCharacter *pChr) m_Jetpack = pChr->m_Jetpack; m_NinjaJetpack = pChr->m_NinjaJetpack; m_FreezeTime = pChr->m_FreezeTime; - m_FreezeTick = pChr->Server()->Tick() - pChr->m_FreezeTick; + m_FreezeTick = pChr->Server()->Tick() - pChr->m_Core.m_FreezeTick; m_DeepFreeze = pChr->m_DeepFreeze; m_LiveFreeze = pChr->m_LiveFreeze; @@ -120,11 +120,11 @@ void CSaveTee::Load(CCharacter *pChr, int Team, bool IsSwap) for(int i = 0; i < NUM_WEAPONS; i++) { - pChr->m_aWeapons[i].m_AmmoRegenStart = m_aWeapons[i].m_AmmoRegenStart; + pChr->m_Core.m_aWeapons[i].m_AmmoRegenStart = m_aWeapons[i].m_AmmoRegenStart; // m_Ammo not used anymore for tracking freeze following https://github.com/ddnet/ddnet/pull/2086 - pChr->m_aWeapons[i].m_Ammo = -1; - pChr->m_aWeapons[i].m_Ammocost = m_aWeapons[i].m_Ammocost; - pChr->m_aWeapons[i].m_Got = m_aWeapons[i].m_Got; + pChr->m_Core.m_aWeapons[i].m_Ammo = -1; + pChr->m_Core.m_aWeapons[i].m_Ammocost = m_aWeapons[i].m_Ammocost; + pChr->m_Core.m_aWeapons[i].m_Got = m_aWeapons[i].m_Got; } pChr->m_LastWeapon = m_LastWeapon; @@ -134,7 +134,7 @@ void CSaveTee::Load(CCharacter *pChr, int Team, bool IsSwap) pChr->m_Jetpack = m_Jetpack; pChr->m_NinjaJetpack = m_NinjaJetpack; pChr->m_FreezeTime = m_FreezeTime; - pChr->m_FreezeTick = pChr->Server()->Tick() - m_FreezeTick; + pChr->m_Core.m_FreezeTick = pChr->Server()->Tick() - m_FreezeTick; pChr->m_DeepFreeze = m_DeepFreeze; pChr->m_LiveFreeze = m_LiveFreeze; diff --git a/src/game/variables.h b/src/game/variables.h index 265acca12..b24ae7885 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -43,6 +43,10 @@ MACRO_CONFIG_INT(ClShowhud, cl_showhud, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, MACRO_CONFIG_INT(ClShowhudHealthAmmo, cl_showhud_healthammo, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Health + Ammo)") MACRO_CONFIG_INT(ClShowhudScore, cl_showhud_score, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Score)") MACRO_CONFIG_INT(ClShowhudTimer, cl_showhud_timer, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Timer)") +MACRO_CONFIG_INT(ClShowhudDummyActions, cl_showhud_dummy_actions, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Dummy Actions)") +MACRO_CONFIG_INT(ClShowhudPlayerPosition, cl_showhud_player_position, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Player Position)") +MACRO_CONFIG_INT(ClShowhudPlayerSpeed, cl_showhud_player_speed, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Player Speed)") +MACRO_CONFIG_INT(ClShowhudPlayerAngle, cl_showhud_player_angle, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Player Aim Angle)") MACRO_CONFIG_INT(ClShowRecord, cl_showrecord, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show old style DDRace client records") MACRO_CONFIG_INT(ClShowNotifications, cl_shownotifications, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Make the client notify when someone highlights you") MACRO_CONFIG_INT(ClShowEmotes, cl_showemotes, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show tee emotes") @@ -201,5 +205,6 @@ MACRO_CONFIG_INT(DbgDummies, dbg_dummies, 0, 0, 15, CFGFLAG_SERVER, "(Debug buil #endif MACRO_CONFIG_INT(DbgFocus, dbg_focus, 0, 0, 1, CFGFLAG_CLIENT, "") -MACRO_CONFIG_INT(DbgTuning, dbg_tuning, 0, 0, 1, CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(DbgTuning, dbg_tuning, 0, 0, 1, CFGFLAG_CLIENT, "Display information about the tuning parameters that affect the own player") + #endif