From d6c7bcdbdcb0def36b560881d1c78be324861c4d Mon Sep 17 00:00:00 2001 From: Chairn Date: Sun, 19 Nov 2023 17:33:34 +0100 Subject: [PATCH 001/198] Init vec2 members to 0 following #6256 --- src/game/client/components/camera.cpp | 4 ++++ src/game/client/components/controls.cpp | 4 ++++ src/game/client/components/menu_background.cpp | 4 ++++ src/game/client/components/particles.h | 1 + src/game/client/components/spectator.cpp | 1 + src/game/editor/editor.h | 1 + src/game/gamecore.cpp | 1 + src/game/server/entities/dragger.cpp | 1 + src/game/server/entities/gun.cpp | 1 + src/game/server/entities/laser.cpp | 1 + src/game/server/entities/light.cpp | 2 ++ src/game/server/entities/pickup.cpp | 1 + 12 files changed, 22 insertions(+) diff --git a/src/game/client/components/camera.cpp b/src/game/client/components/camera.cpp index 9dc393a9063..4837be0ba3e 100644 --- a/src/game/client/components/camera.cpp +++ b/src/game/client/components/camera.cpp @@ -24,6 +24,10 @@ CCamera::CCamera() m_GotoTeleOffset = 0; m_GotoSwitchLastPos = ivec2(-1, -1); m_GotoTeleLastPos = ivec2(-1, -1); + + mem_zero(m_aLastPos, sizeof(m_aLastPos)); + m_PrevCenter = vec2(0, 0); + m_Center = vec2(0, 0); } float CCamera::ZoomProgress(float CurrentTime) const diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index c1c0ed9ed36..fc4c590bc83 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -20,6 +20,10 @@ CControls::CControls() mem_zero(&m_aLastData, sizeof(m_aLastData)); m_LastDummy = 0; m_OtherFire = 0; + + mem_zero(m_aMousePos, sizeof(m_aMousePos)); + mem_zero(m_aMousePosOnAction, sizeof(m_aMousePosOnAction)); + mem_zero(m_aTargetPos, sizeof(m_aTargetPos)); } void CControls::OnReset() diff --git a/src/game/client/components/menu_background.cpp b/src/game/client/components/menu_background.cpp index b9b0c97c000..7a6ee7b7834 100644 --- a/src/game/client/components/menu_background.cpp +++ b/src/game/client/components/menu_background.cpp @@ -54,6 +54,10 @@ std::array GenerateMenuBackgroundPositions() CMenuBackground::CMenuBackground() : CBackground(CMapLayers::TYPE_FULL_DESIGN, false) { + m_RotationCenter = vec2(0.0f, 0.0f); + m_AnimationStartPos = vec2(0.0f, 0.0f); + m_Camera.m_Center = vec2(0.0f, 0.0f); + m_Camera.m_PrevCenter = vec2(0.0f, 0.0f); // unused in this class m_ChangedPosition = false; ResetPositions(); diff --git a/src/game/client/components/particles.h b/src/game/client/components/particles.h index 7b60aeea24c..eb32993ceb9 100644 --- a/src/game/client/components/particles.h +++ b/src/game/client/components/particles.h @@ -10,6 +10,7 @@ struct CParticle { void SetDefault() { + m_Pos = vec2(0, 0); m_Vel = vec2(0, 0); m_LifeSpan = 0; m_StartSize = 32; diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index 0f5d28d40fe..f01f2543338 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -162,6 +162,7 @@ void CSpectator::ConMultiView(IConsole::IResult *pResult, void *pUserData) CSpectator::CSpectator() { + m_SelectorMouse = vec2(0.0f, 0.0f); OnReset(); m_OldMouseX = m_OldMouseY = 0.0f; } diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 19327666c0f..48123392c0e 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -390,6 +390,7 @@ class CEditor : public IEditor m_QuadKnifeActive = false; m_QuadKnifeCount = 0; + mem_zero(m_aQuadKnifePoints, sizeof(m_aQuadKnifePoints)); m_CheckerTexture.Invalidate(); m_BackgroundTexture.Invalidate(); diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index 5dc1fd00262..1c9c11dd377 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -99,6 +99,7 @@ void CCharacterCore::Reset() m_NewHook = false; m_HookPos = vec2(0, 0); m_HookDir = vec2(0, 0); + m_HookTeleBase = vec2(0, 0); m_HookTick = 0; m_HookState = HOOK_IDLE; SetHookedPlayer(-1); diff --git a/src/game/server/entities/dragger.cpp b/src/game/server/entities/dragger.cpp index a455c302993..e22800187ca 100644 --- a/src/game/server/entities/dragger.cpp +++ b/src/game/server/entities/dragger.cpp @@ -16,6 +16,7 @@ CDragger::CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool IgnoreWalls, int Layer, int Number) : CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER) { + m_Core = vec2(0.0f, 0.0f); m_Pos = Pos; m_Strength = Strength; m_IgnoreWalls = IgnoreWalls; diff --git a/src/game/server/entities/gun.cpp b/src/game/server/entities/gun.cpp index aa77858c3a1..5c03604d638 100644 --- a/src/game/server/entities/gun.cpp +++ b/src/game/server/entities/gun.cpp @@ -16,6 +16,7 @@ CGun::CGun(CGameWorld *pGameWorld, vec2 Pos, bool Freeze, bool Explosive, int Layer, int Number) : CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER) { + m_Core = vec2(0.0f, 0.0f); m_Pos = Pos; m_Freeze = Freeze; m_Explosive = Explosive; diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp index e0fd0e578fb..2907d5b9589 100644 --- a/src/game/server/entities/laser.cpp +++ b/src/game/server/entities/laser.cpp @@ -20,6 +20,7 @@ CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEner m_Dir = Direction; m_Bounces = 0; m_EvalTick = 0; + m_TelePos = vec2(0, 0); m_WasTele = false; m_Type = Type; m_TeleportCancelled = false; diff --git a/src/game/server/entities/light.cpp b/src/game/server/entities/light.cpp index 8a15cdfdbab..42aea3f952f 100644 --- a/src/game/server/entities/light.cpp +++ b/src/game/server/entities/light.cpp @@ -15,6 +15,8 @@ CLight::CLight(CGameWorld *pGameWorld, vec2 Pos, float Rotation, int Length, int Layer, int Number) : CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER) { + m_To = vec2(0.0f, 0.0f); + m_Core = vec2(0.0f, 0.0f); m_Layer = Layer; m_Number = Number; m_Tick = (Server()->TickSpeed() * 0.15f); diff --git a/src/game/server/entities/pickup.cpp b/src/game/server/entities/pickup.cpp index 0e234f5fec1..6b599a08e56 100644 --- a/src/game/server/entities/pickup.cpp +++ b/src/game/server/entities/pickup.cpp @@ -15,6 +15,7 @@ static constexpr int gs_PickupPhysSize = 14; CPickup::CPickup(CGameWorld *pGameWorld, int Type, int SubType, int Layer, int Number) : CEntity(pGameWorld, CGameWorld::ENTTYPE_PICKUP, vec2(0, 0), gs_PickupPhysSize) { + m_Core = vec2(0.0f, 0.0f); m_Type = Type; m_Subtype = SubType; From 5b27062ded7adb9dd81a7abafaea8ac5a9691815 Mon Sep 17 00:00:00 2001 From: Chairn Date: Sun, 19 Nov 2023 17:34:48 +0100 Subject: [PATCH 002/198] Remove unused members --- src/game/client/components/menu_background.h | 1 - src/game/client/prediction/entities/laser.cpp | 11 +---------- src/game/client/prediction/entities/laser.h | 2 -- src/game/gamecore.h | 1 - src/game/server/entities/character.h | 1 - src/game/server/gameworld.cpp | 1 - 6 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/game/client/components/menu_background.h b/src/game/client/components/menu_background.h index 777f922903e..a33f4d36bb5 100644 --- a/src/game/client/components/menu_background.h +++ b/src/game/client/components/menu_background.h @@ -81,7 +81,6 @@ class CMenuBackground : public CBackground CBackgroundEngineMap *CreateBGMap() override; - vec2 m_MenuCenter; vec2 m_RotationCenter; std::array m_aPositions; int m_CurrentPosition; diff --git a/src/game/client/prediction/entities/laser.cpp b/src/game/client/prediction/entities/laser.cpp index 0560643c68b..1f377192a18 100644 --- a/src/game/client/prediction/entities/laser.cpp +++ b/src/game/client/prediction/entities/laser.cpp @@ -20,7 +20,6 @@ CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEner m_Dir = Direction; m_Bounces = 0; m_EvalTick = 0; - m_WasTele = false; m_Type = Type; m_ZeroEnergyBounceInLastTick = false; m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0; @@ -34,7 +33,7 @@ bool CLaser::HitCharacter(vec2 From, vec2 To) vec2 At; CCharacter *pOwnerChar = GameWorld()->GetCharacterByID(m_Owner); CCharacter *pHit; - bool DontHitSelf = (g_Config.m_SvOldLaser || !GameWorld()->m_WorldConfig.m_IsDDRace) || (m_Bounces == 0 && !m_WasTele); + bool DontHitSelf = (g_Config.m_SvOldLaser || !GameWorld()->m_WorldConfig.m_IsDDRace) || (m_Bounces == 0); if(pOwnerChar ? (!pOwnerChar->LaserHitDisabled() && m_Type == WEAPON_LASER) || (!pOwnerChar->ShotgunHitDisabled() && m_Type == WEAPON_SHOTGUN) : g_Config.m_SvHit) pHit = GameWorld()->IntersectCharacter(m_Pos, To, 0.f, At, DontHitSelf ? pOwnerChar : 0, m_Owner); @@ -102,13 +101,6 @@ void CLaser::DoBounce() int Res; int z; - if(m_WasTele) - { - m_PrevPos = m_TelePos; - m_Pos = m_TelePos; - m_TelePos = vec2(0, 0); - } - vec2 To = m_Pos + m_Dir * m_Energy; Res = Collision()->IntersectLineTeleWeapon(m_Pos, To, &Coltile, &To, &z); @@ -151,7 +143,6 @@ void CLaser::DoBounce() m_ZeroEnergyBounceInLastTick = Distance == 0.0f; m_Bounces++; - m_WasTele = false; int BounceNum = GetTuning(m_TuneZone)->m_LaserBounceNum; diff --git a/src/game/client/prediction/entities/laser.h b/src/game/client/prediction/entities/laser.h index 51570cf3fc2..3bf8e273732 100644 --- a/src/game/client/prediction/entities/laser.h +++ b/src/game/client/prediction/entities/laser.h @@ -30,8 +30,6 @@ class CLaser : public CEntity private: vec2 m_From; vec2 m_Dir; - vec2 m_TelePos; - bool m_WasTele; float m_Energy; int m_Bounces; int m_EvalTick; diff --git a/src/game/gamecore.h b/src/game/gamecore.h index b9f6e9b2971..26341a78314 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -284,7 +284,6 @@ class CCharacterCore bool m_Reset; CCollision *Collision() { return m_pCollision; } - vec2 m_LastVel; int m_Colliding; bool m_LeftWall; diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h index 89dc031c4a9..8f984b77015 100644 --- a/src/game/server/entities/character.h +++ b/src/game/server/entities/character.h @@ -203,7 +203,6 @@ class CCharacter : public CEntity int m_MoveRestrictions; - vec2 m_Intersection; int64_t m_LastStartWarning; int64_t m_LastRescue; bool m_LastRefillJumps; diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp index c35359a8fbd..9247cca8838 100644 --- a/src/game/server/gameworld.cpp +++ b/src/game/server/gameworld.cpp @@ -349,7 +349,6 @@ std::vector CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1 float Len = distance(pChr->m_Pos, IntersectPos); if(Len < pChr->m_ProximityRadius + Radius) { - pChr->m_Intersection = IntersectPos; vpCharacters.push_back(pChr); } } From 2dcbaf605eb361ea26a9be18d1f1e2a66bd14fac Mon Sep 17 00:00:00 2001 From: Chairn Date: Sun, 19 Nov 2023 17:35:48 +0100 Subject: [PATCH 003/198] Small cleanup: simplify member type and computation --- src/game/client/components/damageind.cpp | 2 +- src/game/client/ui_scrollregion.cpp | 9 +++++---- src/game/client/ui_scrollregion.h | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/game/client/components/damageind.cpp b/src/game/client/components/damageind.cpp index b9c6e06f3dd..031fc9aa9c3 100644 --- a/src/game/client/components/damageind.cpp +++ b/src/game/client/components/damageind.cpp @@ -40,7 +40,7 @@ void CDamageInd::Create(vec2 Pos, vec2 Dir, float Alpha) { pItem->m_Pos = Pos; pItem->m_StartTime = LocalTime(); - pItem->m_Dir = Dir * -1; + pItem->m_Dir = -Dir; pItem->m_StartAngle = -random_angle(); pItem->m_Color = ColorRGBA(1.0f, 1.0f, 1.0f, Alpha); pItem->m_StartAlpha = Alpha; diff --git a/src/game/client/ui_scrollregion.cpp b/src/game/client/ui_scrollregion.cpp index 37ec86143b7..2d3303d4f0f 100644 --- a/src/game/client/ui_scrollregion.cpp +++ b/src/game/client/ui_scrollregion.cpp @@ -22,6 +22,7 @@ CScrollRegion::CScrollRegion() m_AnimInitScrollY = 0.0f; m_AnimTargetScrollY = 0.0f; + m_SliderGrabPos = 0.0f; m_ContentScrollOff = vec2(0.0f, 0.0f); m_Params = CScrollRegionParams(); } @@ -146,8 +147,8 @@ void CScrollRegion::End() if(UI()->CheckActiveItem(pID) && UI()->MouseButton(0)) { float MouseY = UI()->MouseY(); - m_ScrollY += (MouseY - (Slider.y + m_SliderGrabPos.y)) / MaxSlider * MaxScroll; - m_SliderGrabPos.y = clamp(m_SliderGrabPos.y, 0.0f, SliderHeight); + m_ScrollY += (MouseY - (Slider.y + m_SliderGrabPos)) / MaxSlider * MaxScroll; + m_SliderGrabPos = clamp(m_SliderGrabPos, 0.0f, SliderHeight); m_AnimTargetScrollY = m_ScrollY; m_AnimTime = 0.0f; Grabbed = true; @@ -159,7 +160,7 @@ void CScrollRegion::End() if(!UI()->CheckActiveItem(pID) && UI()->MouseButtonClicked(0)) { UI()->SetActiveItem(pID); - m_SliderGrabPos.y = UI()->MouseY() - Slider.y; + m_SliderGrabPos = UI()->MouseY() - Slider.y; m_AnimTargetScrollY = m_ScrollY; m_AnimTime = 0.0f; m_Params.m_Active = true; @@ -170,7 +171,7 @@ void CScrollRegion::End() m_ScrollY += (UI()->MouseY() - (Slider.y + Slider.h / 2.0f)) / MaxSlider * MaxScroll; UI()->SetHotItem(pID); UI()->SetActiveItem(pID); - m_SliderGrabPos.y = Slider.h / 2.0f; + m_SliderGrabPos = Slider.h / 2.0f; m_AnimTargetScrollY = m_ScrollY; m_AnimTime = 0.0f; m_Params.m_Active = true; diff --git a/src/game/client/ui_scrollregion.h b/src/game/client/ui_scrollregion.h index f196fb61c21..2a601be5341 100644 --- a/src/game/client/ui_scrollregion.h +++ b/src/game/client/ui_scrollregion.h @@ -117,7 +117,7 @@ class CScrollRegion : private CUIElementBase CUIRect m_ClipRect; CUIRect m_RailRect; CUIRect m_LastAddedRect; // saved for ScrollHere() - vec2 m_SliderGrabPos; // where did user grab the slider + float m_SliderGrabPos; // where did user grab the slider vec2 m_ContentScrollOff; CScrollRegionParams m_Params; From 717aad267781d2e019266e57fd65ebe4214b67f8 Mon Sep 17 00:00:00 2001 From: trml Date: Sun, 19 Nov 2023 19:46:54 +0100 Subject: [PATCH 004/198] Make prediction margin take effect immediately --- src/engine/client/client.cpp | 4 ++-- src/engine/client/smooth_time.cpp | 27 ++++++--------------------- src/engine/client/smooth_time.h | 8 ++------ 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 708b39e3db3..0c733943a6b 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -348,7 +348,7 @@ void CClient::SendInput() m_aInputs[i][m_aCurrentInput[i]].m_Tick = m_aPredTick[g_Config.m_ClDummy]; m_aInputs[i][m_aCurrentInput[i]].m_PredictedTime = m_PredictedTime.Get(Now); - m_aInputs[i][m_aCurrentInput[i]].m_PredictionMargin = m_PredictedTime.GetMargin(Now); + m_aInputs[i][m_aCurrentInput[i]].m_PredictionMargin = PredictionMargin() * time_freq() / 1000; m_aInputs[i][m_aCurrentInput[i]].m_Time = Now; // pack it @@ -1710,7 +1710,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) if(m_aInputs[Conn][k].m_Tick == InputPredTick) { Target = m_aInputs[Conn][k].m_PredictedTime + (Now - m_aInputs[Conn][k].m_Time); - Target = Target - (int64_t)((TimeLeft / 1000.0f) * time_freq()) + m_aInputs[Conn][k].m_PredictionMargin; + Target = Target - (int64_t)((TimeLeft / 1000.0f) * time_freq()); break; } } diff --git a/src/engine/client/smooth_time.cpp b/src/engine/client/smooth_time.cpp index 059383b4340..9a9675fe770 100644 --- a/src/engine/client/smooth_time.cpp +++ b/src/engine/client/smooth_time.cpp @@ -12,9 +12,7 @@ void CSmoothTime::Init(int64_t Target) m_Snap = time_get(); m_Current = Target; m_Target = Target; - m_SnapMargin = m_Snap; - m_CurrentMargin = 0; - m_TargetMargin = 0; + m_Margin = 0; m_aAdjustSpeed[ADJUSTDIRECTION_DOWN] = 0.3f; m_aAdjustSpeed[ADJUSTDIRECTION_UP] = 0.3f; } @@ -41,15 +39,15 @@ int64_t CSmoothTime::Get(int64_t Now) const a = 1.0f; int64_t r = c + (int64_t)((t - c) * a); - return r + GetMargin(Now); + return r + m_Margin; } void CSmoothTime::UpdateInt(int64_t Target) { int64_t Now = time_get(); - m_Current = Get(Now) - GetMargin(Now); + m_Current = Get(Now) - m_Margin; m_Snap = Now; - m_Target = Target - GetMargin(Now); + m_Target = Target; } void CSmoothTime::Update(CGraph *pGraph, int64_t Target, int TimeLeft, EAdjustDirection AdjustDirection) @@ -97,20 +95,7 @@ void CSmoothTime::Update(CGraph *pGraph, int64_t Target, int TimeLeft, EAdjustDi UpdateInt(Target); } -int64_t CSmoothTime::GetMargin(int64_t Now) const -{ - int64_t TimePassed = Now - m_SnapMargin; - int64_t Diff = m_TargetMargin - m_CurrentMargin; - - float a = clamp(TimePassed / (float)time_freq(), -1.f, 1.f); - int64_t Lim = maximum((int64_t)(a * absolute(Diff)), 1 + TimePassed / 100); - return m_CurrentMargin + (int64_t)clamp(Diff, -Lim, Lim); -} - -void CSmoothTime::UpdateMargin(int64_t TargetMargin) +void CSmoothTime::UpdateMargin(int64_t Margin) { - int64_t Now = time_get(); - m_CurrentMargin = GetMargin(Now); - m_SnapMargin = Now; - m_TargetMargin = TargetMargin; + m_Margin = Margin; } diff --git a/src/engine/client/smooth_time.h b/src/engine/client/smooth_time.h index 999e729b316..efd41e8c4fe 100644 --- a/src/engine/client/smooth_time.h +++ b/src/engine/client/smooth_time.h @@ -22,10 +22,7 @@ class CSmoothTime int64_t m_Snap; int64_t m_Current; int64_t m_Target; - - int64_t m_SnapMargin; - int64_t m_CurrentMargin; - int64_t m_TargetMargin; + int64_t m_Margin; int m_SpikeCounter; float m_aAdjustSpeed[NUM_ADJUSTDIRECTIONS]; @@ -39,8 +36,7 @@ class CSmoothTime void UpdateInt(int64_t Target); void Update(CGraph *pGraph, int64_t Target, int TimeLeft, EAdjustDirection AdjustDirection); - int64_t GetMargin(int64_t Now) const; - void UpdateMargin(int64_t TargetMargin); + void UpdateMargin(int64_t Margin); }; #endif From 904abae6ac5dd1a24497458cbe99990e9113cada Mon Sep 17 00:00:00 2001 From: dobrykafe <121701317+dobrykafe@users.noreply.github.com> Date: Sun, 19 Nov 2023 22:36:47 +0100 Subject: [PATCH 005/198] replace `sizeof x` with `sizeof(x)` --- src/engine/client/updater.cpp | 10 +++++----- src/game/server/ddracechat.cpp | 4 ++-- src/game/server/ddracecommands.cpp | 24 ++++++++++++------------ src/game/server/gamecontext.cpp | 8 ++++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/engine/client/updater.cpp b/src/engine/client/updater.cpp index 5cd7eda197f..3a6b6e389b8 100644 --- a/src/engine/client/updater.cpp +++ b/src/engine/client/updater.cpp @@ -200,9 +200,9 @@ bool CUpdater::ReplaceClient() str_format(aPath, sizeof(aPath), "update/%s", m_aClientExecTmp); Success &= m_pStorage->RenameBinaryFile(aPath, PLAT_CLIENT_EXEC); #if !defined(CONF_FAMILY_WINDOWS) - m_pStorage->GetBinaryPath(PLAT_CLIENT_EXEC, aPath, sizeof aPath); + m_pStorage->GetBinaryPath(PLAT_CLIENT_EXEC, aPath, sizeof(aPath)); char aBuf[512]; - str_format(aBuf, sizeof aBuf, "chmod +x %s", aPath); + str_format(aBuf, sizeof(aBuf), "chmod +x %s", aPath); if(system(aBuf)) { dbg_msg("updater", "ERROR: failed to set client executable bit"); @@ -224,9 +224,9 @@ bool CUpdater::ReplaceServer() str_format(aPath, sizeof(aPath), "update/%s", m_aServerExecTmp); Success &= m_pStorage->RenameBinaryFile(aPath, PLAT_SERVER_EXEC); #if !defined(CONF_FAMILY_WINDOWS) - m_pStorage->GetBinaryPath(PLAT_SERVER_EXEC, aPath, sizeof aPath); + m_pStorage->GetBinaryPath(PLAT_SERVER_EXEC, aPath, sizeof(aPath)); char aBuf[512]; - str_format(aBuf, sizeof aBuf, "chmod +x %s", aPath); + str_format(aBuf, sizeof(aBuf), "chmod +x %s", aPath); if(system(aBuf)) { dbg_msg("updater", "ERROR: failed to set server executable bit"); @@ -241,7 +241,7 @@ void CUpdater::ParseUpdate() char aPath[IO_MAX_PATH_LENGTH]; void *pBuf; unsigned Length; - if(!m_pStorage->ReadFile(m_pStorage->GetBinaryPath("update/update.json", aPath, sizeof aPath), IStorage::TYPE_ABSOLUTE, &pBuf, &Length)) + if(!m_pStorage->ReadFile(m_pStorage->GetBinaryPath("update/update.json", aPath, sizeof(aPath)), IStorage::TYPE_ABSOLUTE, &pBuf, &Length)) return; json_value *pVersions = json_parse((json_char *)pBuf, Length); diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 2be9d9405dc..acfc32adead 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -1004,10 +1004,10 @@ void CGameContext::ConInviteTeam(IConsole::IResult *pResult, void *pUserData) pSelf->m_apPlayers[pResult->m_ClientID]->m_LastInvited = pSelf->Server()->Tick(); char aBuf[512]; - str_format(aBuf, sizeof aBuf, "'%s' invited you to team %d.", pSelf->Server()->ClientName(pResult->m_ClientID), Team); + str_format(aBuf, sizeof(aBuf), "'%s' invited you to team %d.", pSelf->Server()->ClientName(pResult->m_ClientID), Team); pSelf->SendChatTarget(Target, aBuf); - str_format(aBuf, sizeof aBuf, "'%s' invited '%s' to your team.", pSelf->Server()->ClientName(pResult->m_ClientID), pSelf->Server()->ClientName(Target)); + str_format(aBuf, sizeof(aBuf), "'%s' invited '%s' to your team.", pSelf->Server()->ClientName(pResult->m_ClientID), pSelf->Server()->ClientName(Target)); pSelf->SendChatTeam(Team, aBuf); } else diff --git a/src/game/server/ddracecommands.cpp b/src/game/server/ddracecommands.cpp index cba968a9458..8bf894a37aa 100644 --- a/src/game/server/ddracecommands.cpp +++ b/src/game/server/ddracecommands.cpp @@ -480,10 +480,10 @@ void CGameContext::VoteMute(const NETADDR *pAddr, int Secs, const char *pReason, char aBuf[128]; if(pReason[0]) - str_format(aBuf, sizeof aBuf, "'%s' banned '%s' for %d seconds from voting (%s)", + str_format(aBuf, sizeof(aBuf), "'%s' banned '%s' for %d seconds from voting (%s)", Server()->ClientName(AuthedID), pDisplayName, Secs, pReason); else - str_format(aBuf, sizeof aBuf, "'%s' banned '%s' for %d seconds from voting", + str_format(aBuf, sizeof(aBuf), "'%s' banned '%s' for %d seconds from voting", Server()->ClientName(AuthedID), pDisplayName, Secs); SendChat(-1, CHAT_ALL, aBuf); } @@ -499,7 +499,7 @@ bool CGameContext::VoteUnmute(const NETADDR *pAddr, const char *pDisplayName, in if(pDisplayName) { char aBuf[128]; - str_format(aBuf, sizeof aBuf, "'%s' unbanned '%s' from voting.", + str_format(aBuf, sizeof(aBuf), "'%s' unbanned '%s' from voting.", Server()->ClientName(AuthedID), pDisplayName); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "voteunmute", aBuf); } @@ -555,9 +555,9 @@ void CGameContext::Mute(const NETADDR *pAddr, int Secs, const char *pDisplayName char aBuf[128]; if(pReason[0]) - str_format(aBuf, sizeof aBuf, "'%s' has been muted for %d seconds (%s)", pDisplayName, Secs, pReason); + str_format(aBuf, sizeof(aBuf), "'%s' has been muted for %d seconds (%s)", pDisplayName, Secs, pReason); else - str_format(aBuf, sizeof aBuf, "'%s' has been muted for %d seconds", pDisplayName, Secs); + str_format(aBuf, sizeof(aBuf), "'%s' has been muted for %d seconds", pDisplayName, Secs); SendChat(-1, CHAT_ALL, aBuf); } @@ -598,7 +598,7 @@ void CGameContext::ConVoteUnmute(IConsole::IResult *pResult, void *pUserData) if(Found) { char aBuf[128]; - str_format(aBuf, sizeof aBuf, "'%s' unbanned '%s' from voting.", + str_format(aBuf, sizeof(aBuf), "'%s' unbanned '%s' from voting.", pSelf->Server()->ClientName(pResult->m_ClientID), pSelf->Server()->ClientName(Victim)); pSelf->SendChat(-1, 0, aBuf); } @@ -624,7 +624,7 @@ void CGameContext::ConVoteMutes(IConsole::IResult *pResult, void *pUserData) for(int i = 0; i < pSelf->m_NumVoteMutes; i++) { net_addr_str(&pSelf->m_aVoteMutes[i].m_Addr, aIpBuf, sizeof(aIpBuf), false); - str_format(aBuf, sizeof aBuf, "%d: \"%s\", %d seconds left (%s)", i, + str_format(aBuf, sizeof(aBuf), "%d: \"%s\", %d seconds left (%s)", i, aIpBuf, (pSelf->m_aVoteMutes[i].m_Expire - pSelf->Server()->Tick()) / pSelf->Server()->TickSpeed(), pSelf->m_aVoteMutes[i].m_aReason); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "votemutes", aBuf); } @@ -742,7 +742,7 @@ void CGameContext::ConMutes(IConsole::IResult *pResult, void *pUserData) for(int i = 0; i < pSelf->m_NumMutes; i++) { net_addr_str(&pSelf->m_aMutes[i].m_Addr, aIpBuf, sizeof(aIpBuf), false); - str_format(aBuf, sizeof aBuf, "%d: \"%s\", %d seconds left (%s)", i, aIpBuf, + str_format(aBuf, sizeof(aBuf), "%d: \"%s\", %d seconds left (%s)", i, aIpBuf, (pSelf->m_aMutes[i].m_Expire - pSelf->Server()->Tick()) / pSelf->Server()->TickSpeed(), pSelf->m_aMutes[i].m_aReason); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "mutes", aBuf); } @@ -816,7 +816,7 @@ void CGameContext::ConFreezeHammer(IConsole::IResult *pResult, void *pUserData) return; char aBuf[128]; - str_format(aBuf, sizeof aBuf, "'%s' got freeze hammer!", + str_format(aBuf, sizeof(aBuf), "'%s' got freeze hammer!", pSelf->Server()->ClientName(Victim)); pSelf->SendChat(-1, CHAT_ALL, aBuf); @@ -834,7 +834,7 @@ void CGameContext::ConUnFreezeHammer(IConsole::IResult *pResult, void *pUserData return; char aBuf[128]; - str_format(aBuf, sizeof aBuf, "'%s' lost freeze hammer!", + str_format(aBuf, sizeof(aBuf), "'%s' lost freeze hammer!", pSelf->Server()->ClientName(Victim)); pSelf->SendChat(-1, CHAT_ALL, aBuf); @@ -912,9 +912,9 @@ void CGameContext::ConDumpLog(IConsole::IResult *pResult, void *pUserData) char aBuf[256]; if(pEntry->m_FromServer) - str_format(aBuf, sizeof aBuf, "%s, %d seconds ago", pEntry->m_aDescription, Seconds); + str_format(aBuf, sizeof(aBuf), "%s, %d seconds ago", pEntry->m_aDescription, Seconds); else - str_format(aBuf, sizeof aBuf, "%s, %d seconds ago < addr=<{%s}> name='%s' client=%d", + str_format(aBuf, sizeof(aBuf), "%s, %d seconds ago < addr=<{%s}> name='%s' client=%d", pEntry->m_aDescription, Seconds, pEntry->m_aClientAddrStr, pEntry->m_aClientName, pEntry->m_ClientVersion); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "log", aBuf); } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index bd661e5bf38..8b4b5e5de7c 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -570,7 +570,7 @@ void CGameContext::SendChat(int ChatterClientID, int Team, const char *pText, in Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, i); } - str_format(aBuf, sizeof aBuf, "Chat: %s", aText); + str_format(aBuf, sizeof(aBuf), "Chat: %s", aText); LogEvent(aBuf, ChatterClientID); } else @@ -1558,7 +1558,7 @@ void CGameContext::OnClientEnter(int ClientID) char aBuf[128]; NETADDR Addr; Server()->GetClientAddr(ClientID, &Addr); - str_format(aBuf, sizeof aBuf, "This server has an initial chat delay, you will need to wait %d seconds before talking.", g_Config.m_SvChatInitialDelay); + str_format(aBuf, sizeof(aBuf), "This server has an initial chat delay, you will need to wait %d seconds before talking.", g_Config.m_SvChatInitialDelay); SendChatTarget(ClientID, aBuf); Mute(&Addr, g_Config.m_SvChatInitialDelay, Server()->ClientName(ClientID), "Initial chat delay", true); } @@ -4203,9 +4203,9 @@ bool CGameContext::ProcessSpamProtection(int ClientID, bool RespectChatInitialDe { char aBuf[128]; if(Muted.m_InitialChatDelay) - str_format(aBuf, sizeof aBuf, "This server has an initial chat delay, you will be able to talk in %d seconds.", Expires); + str_format(aBuf, sizeof(aBuf), "This server has an initial chat delay, you will be able to talk in %d seconds.", Expires); else - str_format(aBuf, sizeof aBuf, "You are not permitted to talk for the next %d seconds.", Expires); + str_format(aBuf, sizeof(aBuf), "You are not permitted to talk for the next %d seconds.", Expires); SendChatTarget(ClientID, aBuf); return true; } From 9ede40513de00c5ccbcef5110ab35ae0ba009674 Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Sun, 19 Nov 2023 23:35:00 +0100 Subject: [PATCH 006/198] Revert "Add flags for compatibility with Windows 8 - 11" This reverts commit 744434be833971ead2de5d10e8abcd4056922339. --- other/manifest/client.manifest.in | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/other/manifest/client.manifest.in b/other/manifest/client.manifest.in index 802656614e1..dca4cf522ee 100644 --- a/other/manifest/client.manifest.in +++ b/other/manifest/client.manifest.in @@ -11,16 +11,10 @@ - - - - - - - - - - + + + + From c2d02ad45c901f4658c4fda142a6ddcbc38007c3 Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 20 Nov 2023 12:33:08 +0100 Subject: [PATCH 007/198] Account for zoom when teleporting to cursor. --- src/game/server/ddracechat.cpp | 3 ++- src/game/server/ddracecommands.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 49ebdf3e217..9ae5dd6e6d1 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -1524,7 +1524,8 @@ void CGameContext::ConTele(IConsole::IResult *pResult, void *pUserData) vec2 Pos = pPlayer->m_ViewPos; if(pResult->NumArguments() == 0 && !pPlayer->IsPaused()) { - Pos = Pos + vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); + vec2 ZoomScale = vec2(pPlayer->m_ShowDistance.x / 1400.0f, pPlayer->m_ShowDistance.y / 800.0f); + Pos = Pos + (vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY) * ZoomScale); } else if(pResult->NumArguments() > 0) { diff --git a/src/game/server/ddracecommands.cpp b/src/game/server/ddracecommands.cpp index d1af8431d4b..0e5b3aef8bd 100644 --- a/src/game/server/ddracecommands.cpp +++ b/src/game/server/ddracecommands.cpp @@ -409,8 +409,10 @@ void CGameContext::ConTeleport(IConsole::IResult *pResult, void *pUserData) { vec2 Pos = pSelf->m_apPlayers[TeleTo]->m_ViewPos; if(!pPlayer->IsPaused() && !pResult->NumArguments()) - Pos = Pos + vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); - + { + vec2 ZoomScale = vec2(pPlayer->m_ShowDistance.x / 1400.0f, pPlayer->m_ShowDistance.y / 800.0f); + Pos = Pos + (vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY) * ZoomScale); + } pSelf->Teleport(pChr, Pos); pChr->UnFreeze(); pChr->Core()->m_Vel = vec2(0, 0); From 7b6a126b53a366d694c3df729839f60980caabda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 20 Nov 2023 20:37:41 +0100 Subject: [PATCH 008/198] Also set Codecov target for patch to 0% --- codecov.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codecov.yml b/codecov.yml index 8556451afc1..00bb737403e 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,3 +6,6 @@ coverage: project: default: target: 0% + patch: + default: + target: 0% From 15e4f9a8866834ac6e701b21a3ce913ce8b9bda3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 20 Nov 2023 19:15:04 +0100 Subject: [PATCH 009/198] Remove remaining obsolete `// ignore_convention` comments --- src/engine/client/graphics_threaded.cpp | 4 ++-- src/tools/map_replace_image.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index a43038c4c77..145c671f7ac 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -576,9 +576,9 @@ int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int Sto { pImg->m_pData = pImgBuffer; - if(ImageFormat == IMAGE_FORMAT_RGB) // ignore_convention + if(ImageFormat == IMAGE_FORMAT_RGB) pImg->m_Format = CImageInfo::FORMAT_RGB; - else if(ImageFormat == IMAGE_FORMAT_RGBA) // ignore_convention + else if(ImageFormat == IMAGE_FORMAT_RGBA) pImg->m_Format = CImageInfo::FORMAT_RGBA; else { diff --git a/src/tools/map_replace_image.cpp b/src/tools/map_replace_image.cpp index a1c755e5bf6..c9e655a8c07 100644 --- a/src/tools/map_replace_image.cpp +++ b/src/tools/map_replace_image.cpp @@ -48,9 +48,9 @@ int LoadPNG(CImageInfo *pImg, const char *pFilename) { pImg->m_pData = pImgBuffer; - if(ImageFormat == IMAGE_FORMAT_RGB) // ignore_convention + if(ImageFormat == IMAGE_FORMAT_RGB) pImg->m_Format = CImageInfo::FORMAT_RGB; - else if(ImageFormat == IMAGE_FORMAT_RGBA) // ignore_convention + else if(ImageFormat == IMAGE_FORMAT_RGBA) pImg->m_Format = CImageInfo::FORMAT_RGBA; else { From 0427dfff2ef550727021fbec627db9eba61f5944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 20 Nov 2023 19:18:45 +0100 Subject: [PATCH 010/198] Use `bool` instead of `int` --- src/engine/client/graphics_threaded.cpp | 10 +++++----- src/engine/client/graphics_threaded.h | 2 +- src/engine/graphics.h | 2 +- src/tools/map_replace_image.cpp | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index 145c671f7ac..27314cfa1be 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -551,7 +551,7 @@ bool CGraphics_Threaded::UpdateTextTexture(CTextureHandle TextureID, int x, int return true; } -int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) +bool CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) { char aCompleteFilename[IO_MAX_PATH_LENGTH]; IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename)); @@ -583,7 +583,7 @@ int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int Sto else { free(pImgBuffer); - return 0; + return false; } if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0) @@ -613,16 +613,16 @@ int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int Sto else { dbg_msg("game/png", "image had unsupported image format. filename='%s'", pFilename); - return 0; + return false; } } else { dbg_msg("game/png", "failed to open file. filename='%s'", pFilename); - return 0; + return false; } - return 1; + return true; } void CGraphics_Threaded::FreePNG(CImageInfo *pImg) diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h index 03ca8dba9f2..514680749d9 100644 --- a/src/engine/client/graphics_threaded.h +++ b/src/engine/client/graphics_threaded.h @@ -965,7 +965,7 @@ class CGraphics_Threaded : public IEngineGraphics // simple uncompressed RGBA loaders IGraphics::CTextureHandle LoadTexture(const char *pFilename, int StorageType, int Flags = 0) override; - int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) override; + bool LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) override; void FreePNG(CImageInfo *pImg) override; bool CheckImageDivisibility(const char *pFileName, CImageInfo &Img, int DivX, int DivY, bool AllowResize) override; diff --git a/src/engine/graphics.h b/src/engine/graphics.h index dac4a31076f..67043c5b30f 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -320,7 +320,7 @@ class IGraphics : public IInterface virtual const TTWGraphicsGPUList &GetGPUs() const = 0; - virtual int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) = 0; + virtual bool LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) = 0; virtual void FreePNG(CImageInfo *pImg) = 0; virtual bool CheckImageDivisibility(const char *pFileName, CImageInfo &Img, int DivX, int DivY, bool AllowResize) = 0; diff --git a/src/tools/map_replace_image.cpp b/src/tools/map_replace_image.cpp index c9e655a8c07..b67ce18aede 100644 --- a/src/tools/map_replace_image.cpp +++ b/src/tools/map_replace_image.cpp @@ -23,7 +23,7 @@ int g_NewDataID = -1; int g_NewDataSize = 0; void *g_pNewData = nullptr; -int LoadPNG(CImageInfo *pImg, const char *pFilename) +bool LoadPNG(CImageInfo *pImg, const char *pFilename) { IOHANDLE File = io_open(pFilename, IOFLAG_READ); if(File) @@ -55,16 +55,16 @@ int LoadPNG(CImageInfo *pImg, const char *pFilename) else { free(pImgBuffer); - return 0; + return false; } } } else - return 0; + return false; } else - return 0; - return 1; + return false; + return true; } void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, const char *pImgName, const char *pImgFile, CMapItemImage *pNewImgItem) From 82b75ddfe0a5a3df206ed7527974356a9a58f806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 20 Nov 2023 19:20:30 +0100 Subject: [PATCH 011/198] Improve error log messages for PNG loading --- src/engine/client/graphics_threaded.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index 27314cfa1be..e4c4852ea45 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -2,6 +2,7 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include +#include #include #if defined(CONF_FAMILY_UNIX) @@ -583,6 +584,7 @@ bool CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int St else { free(pImgBuffer); + log_error("game/png", "image had unsupported image format. filename='%s' format='%d'", pFilename, (int)ImageFormat); return false; } @@ -612,13 +614,13 @@ bool CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int St } else { - dbg_msg("game/png", "image had unsupported image format. filename='%s'", pFilename); + log_error("game/png", "failed to load file. filename='%s'", pFilename); return false; } } else { - dbg_msg("game/png", "failed to open file. filename='%s'", pFilename); + log_error("game/png", "failed to open file. filename='%s'", pFilename); return false; } From f0a17435e6c8f3593bc0abd1e8fd38fa492f783d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 20 Nov 2023 19:24:33 +0100 Subject: [PATCH 012/198] Ensure freed image buffer is not propagated The image data is freed when the image format is unsupported, but `CImageInfo::m_pData` would still point to the freed memory, so double-frees were possible. --- src/engine/client/graphics_threaded.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index e4c4852ea45..a293e4a0fc6 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -575,8 +575,6 @@ bool CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int St int PngliteIncompatible; if(::LoadPNG(ImageByteBuffer, pFilename, PngliteIncompatible, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat)) { - pImg->m_pData = pImgBuffer; - if(ImageFormat == IMAGE_FORMAT_RGB) pImg->m_Format = CImageInfo::FORMAT_RGB; else if(ImageFormat == IMAGE_FORMAT_RGBA) @@ -587,6 +585,7 @@ bool CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int St log_error("game/png", "image had unsupported image format. filename='%s' format='%d'", pFilename, (int)ImageFormat); return false; } + pImg->m_pData = pImgBuffer; if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0) { From bcae7da6b4f90afcc2062e8a5f398d82de25b000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 20 Nov 2023 19:42:02 +0100 Subject: [PATCH 013/198] Handle all color channel counts in image loader Greyscale images with alpha channel (i.e. channel count = 2) were incorrectly handled as RGBA images, causing the client to crash when loading such images. Now the images can successfully be loaded with the image loader, but the client still only supports loading RGB and RGBA images like before. --- src/engine/gfx/image_loader.cpp | 21 +++++++++++++++------ src/engine/gfx/image_loader.h | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/engine/gfx/image_loader.cpp b/src/engine/gfx/image_loader.cpp index 76ac23205c6..eefb597cdba 100644 --- a/src/engine/gfx/image_loader.cpp +++ b/src/engine/gfx/image_loader.cpp @@ -59,12 +59,15 @@ static EImageFormat LibPNGGetImageFormat(int ColorChannelCount) { case 1: return IMAGE_FORMAT_R; + case 2: + return IMAGE_FORMAT_RA; case 3: return IMAGE_FORMAT_RGB; case 4: return IMAGE_FORMAT_RGBA; default: - return IMAGE_FORMAT_RGBA; + dbg_assert(false, "ColorChannelCount invalid"); + dbg_break(); } } @@ -276,14 +279,20 @@ static void FlushPNGWrite(png_structp png_ptr) {} static int ImageLoaderHelperFormatToColorChannel(EImageFormat Format) { - if(Format == IMAGE_FORMAT_R) + switch(Format) + { + case IMAGE_FORMAT_R: return 1; - else if(Format == IMAGE_FORMAT_RGB) + case IMAGE_FORMAT_RA: + return 2; + case IMAGE_FORMAT_RGB: return 3; - else if(Format == IMAGE_FORMAT_RGBA) + case IMAGE_FORMAT_RGBA: return 4; - - return 4; + default: + dbg_assert(false, "Format invalid"); + dbg_break(); + } } bool SavePNG(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, int Width, int Height) diff --git a/src/engine/gfx/image_loader.h b/src/engine/gfx/image_loader.h index 72a176e4e04..0da2474a0f4 100644 --- a/src/engine/gfx/image_loader.h +++ b/src/engine/gfx/image_loader.h @@ -8,6 +8,7 @@ enum EImageFormat { IMAGE_FORMAT_R = 0, + IMAGE_FORMAT_RA, IMAGE_FORMAT_RGB, IMAGE_FORMAT_RGBA, }; From 29a32b7accc4f13149d99f70b8e29dd439bdf9ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 20 Nov 2023 22:23:39 +0100 Subject: [PATCH 014/198] Use `const char *` instead of `char const *` consistently Both are equivalent. Seems to already be used consistently for all other types. --- src/engine/server.h | 6 +++--- src/engine/server/server.cpp | 2 +- src/engine/server/server.h | 4 ++-- src/game/editor/editor.h | 2 +- src/test/score.cpp | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/server.h b/src/engine/server.h index e3ae46f3094..f94bf2171b4 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -205,8 +205,8 @@ class IServer : public IInterface virtual void GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, SHA256_DIGEST *pSha256, int *pMapCrc) = 0; virtual bool WouldClientNameChange(int ClientID, const char *pNameRequest) = 0; - virtual void SetClientName(int ClientID, char const *pName) = 0; - virtual void SetClientClan(int ClientID, char const *pClan) = 0; + virtual void SetClientName(int ClientID, const char *pName) = 0; + virtual void SetClientClan(int ClientID, const char *pClan) = 0; virtual void SetClientCountry(int ClientID, int Country) = 0; virtual void SetClientScore(int ClientID, std::optional Score) = 0; virtual void SetClientFlags(int ClientID, int Flags) = 0; @@ -253,7 +253,7 @@ class IServer : public IInterface virtual bool DnsblWhite(int ClientID) = 0; virtual bool DnsblPending(int ClientID) = 0; virtual bool DnsblBlack(int ClientID) = 0; - virtual const char *GetAnnouncementLine(char const *pFileName) = 0; + virtual const char *GetAnnouncementLine(const char *pFileName) = 0; virtual bool ClientPrevIngame(int ClientID) = 0; virtual const char *GetNetErrorString(int ClientID) = 0; virtual void ResetNetErrorString(int ClientID) = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index d6008d6d072..4ba4a5807c6 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -3868,7 +3868,7 @@ void CServer::GetClientAddr(int ClientID, NETADDR *pAddr) const } } -const char *CServer::GetAnnouncementLine(char const *pFileName) +const char *CServer::GetAnnouncementLine(const char *pFileName) { if(str_comp(pFileName, m_aAnnouncementFile) != 0) { diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 3d0e3e873e3..8888345556a 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -288,7 +288,7 @@ class CServer : public IServer bool WouldClientNameChange(int ClientID, const char *pNameRequest) override; void SetClientName(int ClientID, const char *pName) override; - void SetClientClan(int ClientID, char const *pClan) override; + void SetClientClan(int ClientID, const char *pClan) override; void SetClientCountry(int ClientID, int Country) override; void SetClientScore(int ClientID, std::optional Score) override; void SetClientFlags(int ClientID, int Flags) override; @@ -457,7 +457,7 @@ class CServer : public IServer void GetClientAddr(int ClientID, NETADDR *pAddr) const override; int m_aPrevStates[MAX_CLIENTS]; - const char *GetAnnouncementLine(char const *pFileName) override; + const char *GetAnnouncementLine(const char *pFileName) override; int *GetIdMap(int ClientID) override; diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 48123392c0e..9dcd36b7254 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -439,7 +439,7 @@ class CEditor : public IEditor bool (*pfnFunc)(const char *pFilename, int StorageType, void *pUser), void *pUser); struct SStringKeyComparator { - bool operator()(char const *pLhs, char const *pRhs) const + bool operator()(const char *pLhs, const char *pRhs) const { return str_comp(pLhs, pRhs) < 0; } diff --git a/src/test/score.cpp b/src/test/score.cpp index 2e8a0f1393a..fddcefd8b3b 100644 --- a/src/test/score.cpp +++ b/src/test/score.cpp @@ -19,7 +19,7 @@ char *CSaveTeam::GetString() return nullptr; } -int CSaveTeam::FromString(char const *) +int CSaveTeam::FromString(const char *) { // Dummy implementation for testing return 1; From 0d8a0d3b1d158bc24ac5925c39445eafe28c4907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 12 Nov 2023 15:21:47 +0100 Subject: [PATCH 015/198] Dynamically connect/disconnect debug dummies, cleanup Call expected server callback functions to simulate clients dynamically connecting and disconnecting when changing the `dbg_dummies` variable. This makes the debug dummies more useful for debugging. Previously, the debug dummies were considered invalid clients, whereas they are now considered to be ingame, so they should behave mostly like real clients being connected to the server. The debug dummies also have correct client names now, e.g. "Debug dummy 42". The game server code is cleaned up by moving all special handling for debug dummies to the engine server function `CServer::UpdateDebugDummies`. The left/right direction inputs for debug dummies are now properly added to the client input array, so their input handling should be consistent with normal clients, which fixes some inconsistent prediction with debug dummies. --- src/engine/server/server.cpp | 45 ++++++++++++++++++++++++++++++ src/engine/server/server.h | 5 ++++ src/game/server/gamecontext.cpp | 33 ---------------------- src/game/server/gamecontroller.cpp | 7 ----- src/game/server/player.cpp | 23 ++++----------- src/game/variables.h | 4 +-- 6 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 4ba4a5807c6..0773ed7a14f 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -2615,6 +2615,44 @@ int CServer::LoadMap(const char *pMapName) return 1; } +#ifdef CONF_DEBUG +void CServer::UpdateDebugDummies(bool ForceDisconnect) +{ + if(m_PreviousDebugDummies == g_Config.m_DbgDummies && !ForceDisconnect) + return; + + for(int DummyIndex = 0; DummyIndex < maximum(m_PreviousDebugDummies, g_Config.m_DbgDummies); ++DummyIndex) + { + const bool AddDummy = !ForceDisconnect && DummyIndex < g_Config.m_DbgDummies; + const int ClientID = MAX_CLIENTS - DummyIndex - 1; + if(AddDummy && m_aClients[ClientID].m_State == CClient::STATE_EMPTY) + { + NewClientCallback(ClientID, this, false); + GameServer()->OnClientConnected(ClientID, nullptr); + m_aClients[ClientID].m_State = CClient::STATE_INGAME; + str_format(m_aClients[ClientID].m_aName, sizeof(m_aClients[ClientID].m_aName), "Debug dummy %d", DummyIndex + 1); + GameServer()->OnClientEnter(ClientID); + } + else if(!AddDummy && m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { + DelClientCallback(ClientID, "Dropping debug dummy", this); + } + + if(AddDummy && m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { + CNetObj_PlayerInput Input = {0}; + Input.m_Direction = (ClientID & 1) ? -1 : 1; + m_aClients[ClientID].m_aInputs[0].m_GameTick = Tick() + 1; + mem_copy(m_aClients[ClientID].m_aInputs[0].m_aData, &Input, minimum(sizeof(Input), sizeof(m_aClients[ClientID].m_aInputs[0].m_aData))); + m_aClients[ClientID].m_LatestInput = m_aClients[ClientID].m_aInputs[0]; + m_aClients[ClientID].m_CurrentInput = 0; + } + } + + m_PreviousDebugDummies = ForceDisconnect ? 0 : g_Config.m_DbgDummies; +} +#endif + int CServer::Run() { if(m_RunServer == UNINITIALIZED) @@ -2762,6 +2800,9 @@ int CServer::Run() } } +#ifdef CONF_DEBUG + UpdateDebugDummies(true); +#endif GameServer()->OnShutdown(m_pPersistentData); for(int ClientID = 0; ClientID < MAX_CLIENTS; ClientID++) @@ -2851,6 +2892,10 @@ int CServer::Run() { GameServer()->OnPreTickTeehistorian(); +#ifdef CONF_DEBUG + UpdateDebugDummies(false); +#endif + for(int c = 0; c < MAX_CLIENTS; c++) { if(m_aClients[c].m_State != CClient::STATE_INGAME) diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 8888345556a..16d4427216b 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -122,6 +122,11 @@ class CServer : public IServer class CDbConnectionPool *m_pConnectionPool; +#ifdef CONF_DEBUG + int m_PreviousDebugDummies = 0; + void UpdateDebugDummies(bool ForceDisconnect); +#endif + public: class IGameServer *GameServer() { return m_pGameServer; } class CConfig *Config() { return m_pConfig; } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 8b4b5e5de7c..154d12c902c 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -1195,21 +1195,6 @@ void CGameContext::OnTick() m_SqlRandomMapResult = nullptr; } -#ifdef CONF_DEBUG - if(g_Config.m_DbgDummies) - { - for(int i = 0; i < g_Config.m_DbgDummies; i++) - { - if(m_apPlayers[MAX_CLIENTS - i - 1]) - { - CNetObj_PlayerInput Input = {0}; - Input.m_Direction = (i & 1) ? -1 : 1; - m_apPlayers[MAX_CLIENTS - i - 1]->OnPredictedInput(&Input); - } - } - } -#endif - // Record player position at the end of the tick if(m_TeeHistorianActive) { @@ -1615,14 +1600,6 @@ void CGameContext::OnClientConnected(int ClientID, void *pData) m_apPlayers[ClientID]->SetInitialAfk(Afk); NextUniqueClientID += 1; -#ifdef CONF_DEBUG - if(g_Config.m_DbgDummies) - { - if(ClientID >= MAX_CLIENTS - g_Config.m_DbgDummies) - return; - } -#endif - SendMotd(ClientID); SendSettings(ClientID); @@ -3702,16 +3679,6 @@ void CGameContext::OnInit(const void *pPersistentData) Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "git-revision", GIT_SHORTREV_HASH); m_pAntibot->RoundStart(this); - -#ifdef CONF_DEBUG - if(g_Config.m_DbgDummies) - { - for(int i = 0; i < g_Config.m_DbgDummies; i++) - { - OnClientConnected(MAX_CLIENTS - i - 1, 0); - } - } -#endif } void CGameContext::CreateAllEntities(bool Initial) diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 10badd46668..93638f9cf5f 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -53,13 +53,6 @@ void IGameController::DoActivityCheck() for(int i = 0; i < MAX_CLIENTS; ++i) { -#ifdef CONF_DEBUG - if(g_Config.m_DbgDummies) - { - if(i >= MAX_CLIENTS - g_Config.m_DbgDummies) - break; - } -#endif if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS && Server()->GetAuthedState(i) == AUTHED_NO) { if(Server()->Tick() > GameServer()->m_apPlayers[i]->m_LastActionTick + g_Config.m_SvInactiveKickTime * Server()->TickSpeed() * 60) diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 25c72aa0e60..4007ff3af5e 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -169,14 +169,7 @@ void CPlayer::Tick() m_ScoreFinishResult = nullptr; } - bool ClientIngame = Server()->ClientIngame(m_ClientID); -#ifdef CONF_DEBUG - if(g_Config.m_DbgDummies && m_ClientID >= MAX_CLIENTS - g_Config.m_DbgDummies) - { - ClientIngame = true; - } -#endif - if(!ClientIngame) + if(!Server()->ClientIngame(m_ClientID)) return; if(m_ChatScore > 0) @@ -302,11 +295,8 @@ void CPlayer::PostTick() void CPlayer::PostPostTick() { -#ifdef CONF_DEBUG - if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS - g_Config.m_DbgDummies) -#endif - if(!Server()->ClientIngame(m_ClientID)) - return; + if(!Server()->ClientIngame(m_ClientID)) + return; if(!GameServer()->m_World.m_Paused && !m_pCharacter && m_Spawning && m_WeakHookSpawn) TryRespawn(); @@ -314,11 +304,8 @@ void CPlayer::PostPostTick() void CPlayer::Snap(int SnappingClient) { -#ifdef CONF_DEBUG - if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS - g_Config.m_DbgDummies) -#endif - if(!Server()->ClientIngame(m_ClientID)) - return; + if(!Server()->ClientIngame(m_ClientID)) + return; int id = m_ClientID; if(!Server()->Translate(id, SnappingClient)) diff --git a/src/game/variables.h b/src/game/variables.h index 93320fbad8c..30bd7b8ea6b 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -221,8 +221,8 @@ MACRO_CONFIG_INT(ClVideoX264Crf, cl_video_crf, 18, 0, 51, CFGFLAG_CLIENT | CFGFL MACRO_CONFIG_INT(ClVideoX264Preset, cl_video_preset, 5, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Set preset when encode video with libx264, default is 5 (medium), 0 is ultrafast, 9 is placebo (the slowest, not recommend)") // debug -#ifdef CONF_DEBUG // this one can crash the server if not used correctly -MACRO_CONFIG_INT(DbgDummies, dbg_dummies, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "(Debug build only)") +#ifdef CONF_DEBUG +MACRO_CONFIG_INT(DbgDummies, dbg_dummies, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Add debug dummies to server (Debug build only)") #endif MACRO_CONFIG_INT(DbgTuning, dbg_tuning, 0, 0, 2, CFGFLAG_CLIENT, "Display information about the tuning parameters that affect the own player (0 = off, 1 = show changed, 2 = show all)") From 51e3c37c031fcb148fa458d66220d4702ccf88ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 21 Nov 2023 21:49:29 +0100 Subject: [PATCH 016/198] Use `mem_copy` instead of `for`-loop in `CPacker::AddRaw` Using `memcpy` is faster, assuming the compiler doesn't already transform the loop to `memcpy` automatically. --- src/engine/shared/packer.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp index 2e64bbc3cbd..b0177644def 100644 --- a/src/engine/shared/packer.cpp +++ b/src/engine/shared/packer.cpp @@ -72,12 +72,8 @@ void CPacker::AddRaw(const void *pData, int Size) return; } - const unsigned char *pSrc = (const unsigned char *)pData; - while(Size) - { - *m_pCurrent++ = *pSrc++; - Size--; - } + mem_copy(m_pCurrent, pData, Size); + m_pCurrent += Size; } void CUnpacker::Reset(const void *pData, int Size) From ab4e737a57c2ae3ab67c1e5a7af53f19b0d733c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 21 Nov 2023 21:57:06 +0100 Subject: [PATCH 017/198] Fix misaligned `int` loads in `CUnpacker::GetUncompressedInt` Use `mem_copy` instead of dereferencing potentially misaligned address as `int`. --- src/engine/shared/packer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp index b0177644def..2b183113b16 100644 --- a/src/engine/shared/packer.cpp +++ b/src/engine/shared/packer.cpp @@ -130,7 +130,8 @@ int CUnpacker::GetUncompressedInt() return 0; } - int i = *(int *)m_pCurrent; + int i; + mem_copy(&i, m_pCurrent, sizeof(int)); m_pCurrent += sizeof(int); return i; } From ef8a87a3597ea8a2d18b68b74eb6376e5e9a23cd Mon Sep 17 00:00:00 2001 From: furo Date: Wed, 22 Nov 2023 00:54:54 +0100 Subject: [PATCH 018/198] Add `demo_extract_chat` to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 78b7d035ff2..fc39ec3d7a7 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ libsteam_api.a config_retrieve config_store crapnet +demo_extract_chat dilate dummy_map fake_server From b9f786c14f8c296b170606c41ca5acf72eac8271 Mon Sep 17 00:00:00 2001 From: Learath Date: Wed, 22 Nov 2023 18:34:39 +0100 Subject: [PATCH 019/198] Revert the mess-chain #7247. Apply #7171 Co-authored-by: Valentin Bashkirov --- src/game/gamecore.cpp | 5 +---- src/game/server/entities/character.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index 1c9c11dd377..409fa0780c0 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -79,10 +79,7 @@ void CCharacterCore::Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore m_Id = -1; // fail safe, if core's tuning didn't get updated at all, just fallback to world tuning. - if(m_pWorld) - m_Tuning = m_pWorld->m_aTuning[g_Config.m_ClDummy]; - - Reset(); + m_Tuning = m_pWorld->m_aTuning[g_Config.m_ClDummy]; } void CCharacterCore::SetCoreWorld(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams) diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 6506570fe79..3c4e702d464 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -83,8 +83,7 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos) m_ReckoningTick = 0; m_SendCore = CCharacterCore(); - m_ReckoningCore = m_Core; - m_ReckoningCore.SetCoreWorld(nullptr, Collision(), nullptr); + m_ReckoningCore = CCharacterCore(); GameServer()->m_World.InsertEntity(this); m_Alive = true; @@ -790,6 +789,9 @@ void CCharacter::TickDeferred() { // advance the dummy { + CWorldCore TempWorld; + m_ReckoningCore.Init(&TempWorld, Collision(), &Teams()->m_Core, m_pTeleOuts); + m_ReckoningCore.m_Id = m_pPlayer->GetCID(); m_ReckoningCore.Tick(false); m_ReckoningCore.Move(); m_ReckoningCore.Quantize(); @@ -878,8 +880,6 @@ void CCharacter::TickDeferred() m_ReckoningTick = Server()->Tick(); m_SendCore = m_Core; m_ReckoningCore = m_Core; - m_ReckoningCore.SetCoreWorld(nullptr, Collision(), nullptr); - m_ReckoningCore.m_Tuning = CTuningParams(); m_Core.m_Reset = false; } } From 48bfecc0d3cd47c15d51b3f189d528feda93aad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 22 Nov 2023 17:57:42 +0100 Subject: [PATCH 020/198] Ensure debug dummies are not included in server info Also fix normal clients being disconnected/controlled like debug dummies. Closes #7523. --- src/engine/server/server.cpp | 21 +++++++++++++-------- src/engine/server/server.h | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 0773ed7a14f..b26c911c1dd 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -573,6 +573,7 @@ int CServer::Init() Client.m_Traffic = 0; Client.m_TrafficSince = 0; Client.m_ShowIps = false; + Client.m_DebugDummy = false; Client.m_AuthKey = -1; Client.m_Latency = 0; Client.m_Sixup = false; @@ -1083,6 +1084,7 @@ int CServer::NewClientNoAuthCallback(int ClientID, void *pUser) pThis->m_aClients[ClientID].m_AuthTries = 0; pThis->m_aClients[ClientID].m_pRconCmdToSend = 0; pThis->m_aClients[ClientID].m_ShowIps = false; + pThis->m_aClients[ClientID].m_DebugDummy = false; pThis->m_aClients[ClientID].m_DDNetVersion = VERSION_NONE; pThis->m_aClients[ClientID].m_GotDDNetVersionPacket = false; pThis->m_aClients[ClientID].m_DDNetVersionSettled = false; @@ -1114,6 +1116,7 @@ int CServer::NewClientCallback(int ClientID, void *pUser, bool Sixup) pThis->m_aClients[ClientID].m_Traffic = 0; pThis->m_aClients[ClientID].m_TrafficSince = 0; pThis->m_aClients[ClientID].m_ShowIps = false; + pThis->m_aClients[ClientID].m_DebugDummy = false; pThis->m_aClients[ClientID].m_DDNetVersion = VERSION_NONE; pThis->m_aClients[ClientID].m_GotDDNetVersionPacket = false; pThis->m_aClients[ClientID].m_DDNetVersionSettled = false; @@ -1200,6 +1203,7 @@ int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser) pThis->m_aClients[ClientID].m_Traffic = 0; pThis->m_aClients[ClientID].m_TrafficSince = 0; pThis->m_aClients[ClientID].m_ShowIps = false; + pThis->m_aClients[ClientID].m_DebugDummy = false; pThis->m_aPrevStates[ClientID] = CClient::STATE_EMPTY; pThis->m_aClients[ClientID].m_Snapshots.PurgeAll(); pThis->m_aClients[ClientID].m_Sixup = false; @@ -1933,7 +1937,7 @@ void CServer::CacheServerInfo(CCache *pCache, int Type, bool SendClients) int PlayerCount = 0, ClientCount = 0; for(int i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if(m_aClients[i].IncludedInServerInfo()) { if(GameServer()->IsClientPlayer(i)) PlayerCount++; @@ -2065,7 +2069,7 @@ void CServer::CacheServerInfo(CCache *pCache, int Type, bool SendClients) for(int i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if(m_aClients[i].IncludedInServerInfo()) { if(Remaining == 0) { @@ -2147,7 +2151,7 @@ void CServer::CacheServerInfoSixup(CCache *pCache, bool SendClients) int PlayerCount = 0, ClientCount = 0; for(int i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if(m_aClients[i].IncludedInServerInfo()) { if(GameServer()->IsClientPlayer(i)) PlayerCount++; @@ -2183,7 +2187,7 @@ void CServer::CacheServerInfoSixup(CCache *pCache, bool SendClients) { for(int i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if(m_aClients[i].IncludedInServerInfo()) { Packer.AddString(ClientName(i), MAX_NAME_LENGTH); // client name Packer.AddString(ClientClan(i), MAX_CLAN_LENGTH); // client clan @@ -2286,7 +2290,7 @@ void CServer::UpdateRegisterServerInfo() int PlayerCount = 0, ClientCount = 0; for(int i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if(m_aClients[i].IncludedInServerInfo()) { if(GameServer()->IsClientPlayer(i)) PlayerCount++; @@ -2334,7 +2338,7 @@ void CServer::UpdateRegisterServerInfo() bool FirstPlayer = true; for(int i = 0; i < MAX_CLIENTS; i++) { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) + if(m_aClients[i].IncludedInServerInfo()) { char aCName[32]; char aCClan[32]; @@ -2628,17 +2632,18 @@ void CServer::UpdateDebugDummies(bool ForceDisconnect) if(AddDummy && m_aClients[ClientID].m_State == CClient::STATE_EMPTY) { NewClientCallback(ClientID, this, false); + m_aClients[ClientID].m_DebugDummy = true; GameServer()->OnClientConnected(ClientID, nullptr); m_aClients[ClientID].m_State = CClient::STATE_INGAME; str_format(m_aClients[ClientID].m_aName, sizeof(m_aClients[ClientID].m_aName), "Debug dummy %d", DummyIndex + 1); GameServer()->OnClientEnter(ClientID); } - else if(!AddDummy && m_aClients[ClientID].m_State == CClient::STATE_INGAME) + else if(!AddDummy && m_aClients[ClientID].m_DebugDummy) { DelClientCallback(ClientID, "Dropping debug dummy", this); } - if(AddDummy && m_aClients[ClientID].m_State == CClient::STATE_INGAME) + if(AddDummy && m_aClients[ClientID].m_DebugDummy) { CNetObj_PlayerInput Input = {0}; Input.m_Direction = (ClientID & 1) ? -1 : 1; diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 16d4427216b..3dd469b4956 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -197,6 +197,7 @@ class CServer : public IServer int m_NextMapChunk; int m_Flags; bool m_ShowIps; + bool m_DebugDummy; const IConsole::CCommandInfo *m_pRconCmdToSend; @@ -220,6 +221,11 @@ class CServer : public IServer std::shared_ptr m_pDnsblLookup; bool m_Sixup; + + bool IncludedInServerInfo() const + { + return m_State != STATE_EMPTY && !m_DebugDummy; + } }; CClient m_aClients[MAX_CLIENTS]; From d331bcb496b8ab8427590e10424e5384b9c34a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 22 Nov 2023 23:12:29 +0100 Subject: [PATCH 021/198] Fix color game settings not being reset correctly Handling color settings the same as int settings in `CConsole::ResetGameSettings` is not correct, as this causes the color variable data to be cast to int variable data. There are currently no color game settings affected by this. --- src/engine/shared/console.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 9e2b9629ebf..593b816c8ba 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -1289,7 +1289,18 @@ void CConsole::ResetGameSettings() } \ } -#define MACRO_CONFIG_COL(Name, ScriptName, Def, Save, Desc) MACRO_CONFIG_INT(Name, ScriptName, Def, 0, 0, Save, Desc) +#define MACRO_CONFIG_COL(Name, ScriptName, Def, Flags, Desc) \ + { \ + if(((Flags)&CFGFLAG_GAME) == CFGFLAG_GAME) \ + { \ + CCommand *pCommand = FindCommand(#ScriptName, CFGFLAG_GAME); \ + void *pUserData = pCommand->m_pUserData; \ + FCommandCallback pfnCallback = pCommand->m_pfnCallback; \ + TraverseChain(&pfnCallback, &pUserData); \ + CColVariableData *pData = (CColVariableData *)pUserData; \ + *pData->m_pVariable = pData->m_OldValue; \ + } \ + } #define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Flags, Desc) \ { \ From 61c201c37cc775f0dc6abc2bfaab2d56a03ed525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 22 Nov 2023 23:12:47 +0100 Subject: [PATCH 022/198] Fix string game settings not being reset correctly Due to swapped `str_copy` arguments, the old value for string game variables was being overridden by the current value instead of the other way around in `CConsole::ResetGameSettings`. There are currently no string game settings affected by this. --- src/engine/shared/console.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 593b816c8ba..39b2bae3548 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -1311,7 +1311,7 @@ void CConsole::ResetGameSettings() FCommandCallback pfnCallback = pCommand->m_pfnCallback; \ TraverseChain(&pfnCallback, &pUserData); \ CStrVariableData *pData = (CStrVariableData *)pUserData; \ - str_copy(pData->m_pOldValue, pData->m_pStr, pData->m_MaxSize); \ + str_copy(pData->m_pStr, pData->m_pOldValue, pData->m_MaxSize); \ } \ } From ad41069c20eb40c5578b56a66bcf2090a7252205 Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 23 Nov 2023 07:52:09 +0100 Subject: [PATCH 023/198] Count deaths in statboard from `KILLMSGTEAM` --- src/game/client/components/statboard.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/game/client/components/statboard.cpp b/src/game/client/components/statboard.cpp index 30136742b4c..71b35a1b42a 100644 --- a/src/game/client/components/statboard.cpp +++ b/src/game/client/components/statboard.cpp @@ -73,6 +73,20 @@ void CStatboard::OnMessage(int MsgType, void *pRawMsg) else pStats[pMsg->m_Victim].m_Suicides++; } + else if(MsgType == NETMSGTYPE_SV_KILLMSGTEAM) + { + CNetMsg_Sv_KillMsgTeam *pMsg = (CNetMsg_Sv_KillMsgTeam *)pRawMsg; + CGameClient::CClientStats *pStats = m_pClient->m_aStats; + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_pClient->m_Teams.Team(i) == pMsg->m_Team) + { + pStats[i].m_Deaths++; + pStats[i].m_Suicides++; + } + } + } else if(MsgType == NETMSGTYPE_SV_CHAT) { CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; From 04c5c6a5d618754994c66b2a608fc14cf05fb148 Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 23 Nov 2023 11:57:35 +0100 Subject: [PATCH 024/198] More fixes to `KILLMSGTEAM` --- src/game/client/components/ghost.cpp | 14 ++++++++++++++ src/game/client/components/race_demo.cpp | 9 +++++++++ src/game/client/gameclient.cpp | 24 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/game/client/components/ghost.cpp b/src/game/client/components/ghost.cpp index f8b71ee802c..d15fa52a22b 100644 --- a/src/game/client/components/ghost.cpp +++ b/src/game/client/components/ghost.cpp @@ -628,6 +628,20 @@ void CGhost::OnMessage(int MsgType, void *pRawMsg) m_LastDeathTick = Client()->GameTick(g_Config.m_ClDummy); } } + else if(MsgType == NETMSGTYPE_SV_KILLMSGTEAM) + { + CNetMsg_Sv_KillMsgTeam *pMsg = (CNetMsg_Sv_KillMsgTeam *)pRawMsg; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_pClient->m_Teams.Team(i) == pMsg->m_Team && i == m_pClient->m_Snap.m_LocalClientID) + { + if(m_Recording) + StopRecord(); + StopRender(); + m_LastDeathTick = Client()->GameTick(g_Config.m_ClDummy); + } + } + } else if(MsgType == NETMSGTYPE_SV_CHAT) { CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; diff --git a/src/game/client/components/race_demo.cpp b/src/game/client/components/race_demo.cpp index 43ce4c78656..4f57bc5b45a 100644 --- a/src/game/client/components/race_demo.cpp +++ b/src/game/client/components/race_demo.cpp @@ -135,6 +135,15 @@ void CRaceDemo::OnMessage(int MsgType, void *pRawMsg) if(pMsg->m_Victim == m_pClient->m_Snap.m_LocalClientID && Client()->RaceRecord_IsRecording()) StopRecord(m_Time); } + else if(MsgType == NETMSGTYPE_SV_KILLMSGTEAM) + { + CNetMsg_Sv_KillMsgTeam *pMsg = (CNetMsg_Sv_KillMsgTeam *)pRawMsg; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_pClient->m_Teams.Team(i) == pMsg->m_Team && i == m_pClient->m_Snap.m_LocalClientID && Client()->RaceRecord_IsRecording()) + StopRecord(m_Time); + } + } else if(MsgType == NETMSGTYPE_SV_CHAT) { CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index c9a0db2adcb..397e3d12219 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -934,6 +934,30 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm } } } + else if(MsgId == NETMSGTYPE_SV_KILLMSGTEAM) + { + CNetMsg_Sv_KillMsgTeam *pMsg = (CNetMsg_Sv_KillMsgTeam *)pRawMsg; + + // reset prediction + std::vector> vStrongWeakSorted; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_Teams.Team(i) == pMsg->m_Team) + { + if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i)) + { + pChar->ResetPrediction(); + vStrongWeakSorted.emplace_back(i, pMsg->m_First == i ? MAX_CLIENTS : pChar ? pChar->GetStrongWeakID() : 0); + } + m_GameWorld.ReleaseHooked(i); + } + } + std::stable_sort(vStrongWeakSorted.begin(), vStrongWeakSorted.end(), [](auto &Left, auto &Right) { return Left.second > Right.second; }); + for(auto ID : vStrongWeakSorted) + { + m_CharOrder.GiveWeak(ID.first); + } + } } void CGameClient::OnStateChange(int NewState, int OldState) From cfced5553485c62f758a59354f2c8d3e0b6283d6 Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Thu, 23 Nov 2023 13:20:28 +0100 Subject: [PATCH 025/198] Fix macOS CI Seen in https://github.com/ddnet/ddnet/actions/runs/6969106161/job/18964960431 Error: No such keg: /usr/local/Cellar/python@3.10 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b07e2936751..be265b06430 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,7 +92,7 @@ jobs: brew update || true brew install pkg-config sdl2 ffmpeg ninja molten-vk vulkan-headers glslang spirv-tools || true # --overwrite for: Target /usr/local/bin/2to3 already exists. - brew link --overwrite python@3.10 + brew link --overwrite python@3.11 brew upgrade freetype pip3 install dmgbuild echo /Library/Frameworks/Python.framework/Versions/3.11/bin >> $GITHUB_PATH From b9fd612ef575aa015c1e0fd1bc1af542cf9731f4 Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 23 Nov 2023 14:42:30 +0100 Subject: [PATCH 026/198] Replace `50` with `SERVER_TICK_SPEED` or `TickSpeed()` --- src/engine/client/client.cpp | 38 ++++++++++----------- src/engine/server/server.cpp | 8 ++--- src/game/client/components/items.cpp | 14 ++++---- src/game/client/components/killmessages.cpp | 2 +- src/game/client/components/menus_demo.cpp | 14 ++++---- src/game/client/components/players.cpp | 10 +++--- src/game/client/gameclient.cpp | 6 ++-- src/game/gamecore.h | 1 - src/game/server/entities/character.cpp | 2 +- src/game/server/teehistorian.cpp | 1 - src/game/tuning.h | 4 +-- 11 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index c9dbcbbf490..26a7b3c56e7 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -304,7 +304,7 @@ void CClient::Rcon(const char *pCmd) bool CClient::ConnectionProblems() const { - return m_aNetClient[g_Config.m_ClDummy].GotProblems(MaxLatencyTicks() * time_freq() / SERVER_TICK_SPEED) != 0; + return m_aNetClient[g_Config.m_ClDummy].GotProblems(MaxLatencyTicks() * time_freq() / GameTickSpeed()) != 0; } void CClient::DirectInput(int *pInput, int Size) @@ -743,7 +743,7 @@ int CClient::GetCurrentRaceTime() { if(GameClient()->GetLastRaceTick() < 0) return 0; - return (GameTick(g_Config.m_ClDummy) - GameClient()->GetLastRaceTick()) / 50; + return (GameTick(g_Config.m_ClDummy) - GameClient()->GetLastRaceTick()) / GameTickSpeed(); } void CClient::GetServerInfo(CServerInfo *pServerInfo) const @@ -1904,11 +1904,11 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) // start at 200ms and work from there if(!Dummy) { - m_PredictedTime.Init(GameTick * time_freq() / 50); + m_PredictedTime.Init(GameTick * time_freq() / GameTickSpeed()); m_PredictedTime.SetAdjustSpeed(CSmoothTime::ADJUSTDIRECTION_UP, 1000.0f); m_PredictedTime.UpdateMargin(PredictionMargin() * time_freq() / 1000); } - m_aGameTime[Conn].Init((GameTick - 1) * time_freq() / 50); + m_aGameTime[Conn].Init((GameTick - 1) * time_freq() / GameTickSpeed()); m_aapSnapshots[Conn][SNAP_PREV] = m_aSnapshotStorage[Conn].m_pFirst; m_aapSnapshots[Conn][SNAP_CURRENT] = m_aSnapshotStorage[Conn].m_pLast; if(!Dummy) @@ -1930,12 +1930,12 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) if(m_aReceivedSnapshots[Conn] > 2) { int64_t Now = m_aGameTime[Conn].Get(time_get()); - int64_t TickStart = GameTick * time_freq() / 50; + int64_t TickStart = GameTick * time_freq() / GameTickSpeed(); int64_t TimeLeft = (TickStart - Now) * 1000 / time_freq(); - m_aGameTime[Conn].Update(&m_GametimeMarginGraph, (GameTick - 1) * time_freq() / 50, TimeLeft, CSmoothTime::ADJUSTDIRECTION_DOWN); + m_aGameTime[Conn].Update(&m_GametimeMarginGraph, (GameTick - 1) * time_freq() / GameTickSpeed(), TimeLeft, CSmoothTime::ADJUSTDIRECTION_DOWN); } - if(m_aReceivedSnapshots[Conn] > 50 && !m_aCodeRunAfterJoin[Conn]) + if(m_aReceivedSnapshots[Conn] > GameTickSpeed() && !m_aCodeRunAfterJoin[Conn]) { if(m_ServerCapabilities.m_ChatTimeoutCode) { @@ -2466,7 +2466,7 @@ void CClient::Update() while(true) { CSnapshotStorage::CHolder *pCur = m_aapSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]; - int64_t TickStart = (pCur->m_Tick) * time_freq() / 50; + int64_t TickStart = (pCur->m_Tick) * time_freq() / GameTickSpeed(); if(TickStart < Now) { @@ -2505,7 +2505,7 @@ void CClient::Update() while(true) { CSnapshotStorage::CHolder *pCur = m_aapSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; - int64_t TickStart = (pCur->m_Tick) * time_freq() / 50; + int64_t TickStart = (pCur->m_Tick) * time_freq() / GameTickSpeed(); if(TickStart < Now) { @@ -2534,23 +2534,23 @@ void CClient::Update() if(m_aapSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aapSnapshots[g_Config.m_ClDummy][SNAP_PREV]) { - int64_t CurTickStart = m_aapSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick * time_freq() / SERVER_TICK_SPEED; - int64_t PrevTickStart = m_aapSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick * time_freq() / SERVER_TICK_SPEED; - int PrevPredTick = (int)(PredNow * SERVER_TICK_SPEED / time_freq()); + int64_t CurTickStart = m_aapSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick * time_freq() / GameTickSpeed(); + int64_t PrevTickStart = m_aapSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick * time_freq() / GameTickSpeed(); + int PrevPredTick = (int)(PredNow * GameTickSpeed() / time_freq()); int NewPredTick = PrevPredTick + 1; m_aGameIntraTick[g_Config.m_ClDummy] = (Now - PrevTickStart) / (float)(CurTickStart - PrevTickStart); m_aGameTickTime[g_Config.m_ClDummy] = (Now - PrevTickStart) / (float)time_freq(); - m_aGameIntraTickSincePrev[g_Config.m_ClDummy] = (Now - PrevTickStart) / (float)(time_freq() / SERVER_TICK_SPEED); + m_aGameIntraTickSincePrev[g_Config.m_ClDummy] = (Now - PrevTickStart) / (float)(time_freq() / GameTickSpeed()); - int64_t CurPredTickStart = NewPredTick * time_freq() / SERVER_TICK_SPEED; - int64_t PrevPredTickStart = PrevPredTick * time_freq() / SERVER_TICK_SPEED; + int64_t CurPredTickStart = NewPredTick * time_freq() / GameTickSpeed(); + int64_t PrevPredTickStart = PrevPredTick * time_freq() / GameTickSpeed(); m_aPredIntraTick[g_Config.m_ClDummy] = (PredNow - PrevPredTickStart) / (float)(CurPredTickStart - PrevPredTickStart); if(absolute(NewPredTick - m_aapSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick) > MaxLatencyTicks()) { m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!"); - m_PredictedTime.Init(CurTickStart + 2 * time_freq() / SERVER_TICK_SPEED); + m_PredictedTime.Init(CurTickStart + 2 * time_freq() / GameTickSpeed()); } if(NewPredTick > m_aPredTick[g_Config.m_ClDummy]) @@ -4665,8 +4665,8 @@ void CClient::GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float Mix int64_t PredTime = m_PredictedTime.Get(time_get()); int64_t SmoothTime = clamp(GameTime + (int64_t)(MixAmount * (PredTime - GameTime)), GameTime, PredTime); - *pSmoothTick = (int)(SmoothTime * 50 / time_freq()) + 1; - *pSmoothIntraTick = (SmoothTime - (*pSmoothTick - 1) * time_freq() / 50) / (float)(time_freq() / 50); + *pSmoothTick = (int)(SmoothTime * GameTickSpeed() / time_freq()) + 1; + *pSmoothIntraTick = (SmoothTime - (*pSmoothTick - 1) * time_freq() / GameTickSpeed()) / (float)(time_freq() / GameTickSpeed()); } void CClient::AddWarning(const SWarning &Warning) @@ -4693,7 +4693,7 @@ SWarning *CClient::GetCurWarning() int CClient::MaxLatencyTicks() const { - return SERVER_TICK_SPEED + (PredictionMargin() * SERVER_TICK_SPEED) / 1000; + return GameTickSpeed() + (PredictionMargin() * GameTickSpeed()) / 1000; } int CClient::PredictionMargin() const diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index b26c911c1dd..f142996fec5 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -317,7 +317,7 @@ CServer::CServer() m_aDemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta, true); m_aDemoRecorder[MAX_CLIENTS] = CDemoRecorder(&m_SnapshotDelta, false); - m_TickSpeed = SERVER_TICK_SPEED; + m_TickSpeed = TickSpeed(); m_pGameServer = 0; @@ -558,7 +558,7 @@ void CServer::RedirectClient(int ClientID, int Port, bool Verbose) int64_t CServer::TickStartTime(int Tick) { - return m_GameStartTime + (time_freq() * Tick) / SERVER_TICK_SPEED; + return m_GameStartTime + (time_freq() * Tick) / TickSpeed(); } int CServer::Init() @@ -943,7 +943,7 @@ void CServer::DoSnapshot() continue; // this client is trying to recover, don't spam snapshots - if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick() % 50) != 0) + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick() % TickSpeed()) != 0) continue; // this client is trying to recover, don't spam snapshots @@ -970,7 +970,7 @@ void CServer::DoSnapshot() // remove old snapshots // keep 3 seconds worth of snapshots - m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentGameTick - SERVER_TICK_SPEED * 3); + m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentGameTick - TickSpeed() * 3); // save the snapshot m_aClients[i].m_Snapshots.Add(m_CurrentGameTick, time_get(), SnapshotSize, pData, 0, nullptr); diff --git a/src/game/client/components/items.cpp b/src/game/client/components/items.cpp index 41043f35458..d3f8b21dd3a 100644 --- a/src/game/client/components/items.cpp +++ b/src/game/client/components/items.cpp @@ -60,16 +60,16 @@ void CItems::RenderProjectile(const CProjectileData *pCurrent, int ItemID) float Ct; if(m_pClient->Predict() && m_pClient->AntiPingGrenade() && LocalPlayerInGame && !IsOtherTeam) - Ct = ((float)(Client()->PredGameTick(g_Config.m_ClDummy) - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy)) / (float)SERVER_TICK_SPEED; + Ct = ((float)(Client()->PredGameTick(g_Config.m_ClDummy) - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy)) / (float)Client()->GameTickSpeed(); else - Ct = (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)SERVER_TICK_SPEED + s_LastGameTickTime; + Ct = (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)Client()->GameTickSpeed() + s_LastGameTickTime; if(Ct < 0) { if(Ct > -s_LastGameTickTime / 2) { // Fixup the timing which might be screwed during demo playback because // s_LastGameTickTime depends on the system timer, while the other part - // (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)SERVER_TICK_SPEED + // (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)Client()->GameTickSpeed() // is virtually constant (for projectiles fired on the current game tick): // (x - (x+2)) / 50 = -0.04 // @@ -309,7 +309,7 @@ void CItems::RenderLaser(const CLaserData *pCurrent, bool IsPredicted) Ticks = (float)(Client()->PredGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy); else Ticks = (float)(Client()->GameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->IntraGameTick(g_Config.m_ClDummy); - float Ms = (Ticks / 50.0f) * 1000.0f; + float Ms = (Ticks / Client()->GameTickSpeed()) * 1000.0f; float a = Ms / m_pClient->GetTuning(TuneZone)->m_LaserBounceDelay; a = clamp(a, 0.0f, 1.0f); float Ia = 1 - a; @@ -626,11 +626,11 @@ void CItems::ReconstructSmokeTrail(const CProjectileData *pCurrent, int DestroyT Speed = pTuning->m_GunSpeed; } - float Pt = ((float)(Client()->PredGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy)) / (float)SERVER_TICK_SPEED; + float Pt = ((float)(Client()->PredGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy)) / (float)Client()->GameTickSpeed(); if(Pt < 0) return; // projectile haven't been shot yet - float Gt = (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)SERVER_TICK_SPEED + Client()->GameTickTime(g_Config.m_ClDummy); + float Gt = (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)Client()->GameTickSpeed() + Client()->GameTickTime(g_Config.m_ClDummy); float Alpha = 1.f; if(pCurrent->m_ExtraInfo && pCurrent->m_Owner >= 0 && m_pClient->IsOtherTeam(pCurrent->m_Owner)) @@ -640,7 +640,7 @@ void CItems::ReconstructSmokeTrail(const CProjectileData *pCurrent, int DestroyT float T = Pt; if(DestroyTick >= 0) - T = minimum(Pt, ((float)(DestroyTick - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy)) / (float)SERVER_TICK_SPEED); + T = minimum(Pt, ((float)(DestroyTick - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy)) / (float)Client()->GameTickSpeed()); float MinTrailSpan = 0.4f * ((pCurrent->m_Type == WEAPON_GRENADE) ? 0.5f : 0.25f); float Step = maximum(Client()->FrameTimeAvg(), (pCurrent->m_Type == WEAPON_GRENADE) ? 0.02f : 0.01f); diff --git a/src/game/client/components/killmessages.cpp b/src/game/client/components/killmessages.cpp index 82ec93a5312..ac62949c938 100644 --- a/src/game/client/components/killmessages.cpp +++ b/src/game/client/components/killmessages.cpp @@ -287,7 +287,7 @@ void CKillMessages::OnRender() for(int i = 1; i <= MAX_KILLMSGS; i++) { int r = (m_KillmsgCurrent + i) % MAX_KILLMSGS; - if(Client()->GameTick(g_Config.m_ClDummy) > m_aKillmsgs[r].m_Tick + 50 * 10) + if(Client()->GameTick(g_Config.m_ClDummy) > m_aKillmsgs[r].m_Tick + Client()->GameTickSpeed() * 10) continue; float x = StartX; diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 578280fa9bf..3e69044e9b4 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -155,7 +155,7 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) static int s_SkipDurationIndex = 1; static const int s_aSkipDurationsSeconds[] = {1, 5, 10, 30, 60, 5 * 60, 10 * 60}; - const int DemoLengthSeconds = TotalTicks / SERVER_TICK_SPEED; + const int DemoLengthSeconds = TotalTicks / Client()->GameTickSpeed(); int NumDurationLabels = 0; for(size_t i = 0; i < std::size(s_aSkipDurationsSeconds); ++i) { @@ -445,9 +445,9 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) // draw time char aCurrentTime[32]; - str_time((int64_t)CurrentTick / SERVER_TICK_SPEED * 100, TIME_HOURS, aCurrentTime, sizeof(aCurrentTime)); + str_time((int64_t)CurrentTick / Client()->GameTickSpeed() * 100, TIME_HOURS, aCurrentTime, sizeof(aCurrentTime)); char aTotalTime[32]; - str_time((int64_t)TotalTicks / SERVER_TICK_SPEED * 100, TIME_HOURS, aTotalTime, sizeof(aTotalTime)); + str_time((int64_t)TotalTicks / Client()->GameTickSpeed() * 100, TIME_HOURS, aTotalTime, sizeof(aTotalTime)); str_format(aBuffer, sizeof(aBuffer), "%s / %s", aCurrentTime, aTotalTime); UI()->DoLabel(&SeekBar, aBuffer, SeekBar.h * 0.70f, TEXTALIGN_MC); @@ -491,7 +491,7 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) { const int HoveredTick = (int)(clamp((UI()->MouseX() - SeekBar.x - Rounding) / (float)(SeekBar.w - 2 * Rounding), 0.0f, 1.0f) * TotalTicks); static char s_aHoveredTime[32]; - str_time((int64_t)HoveredTick / SERVER_TICK_SPEED * 100, TIME_HOURS, s_aHoveredTime, sizeof(s_aHoveredTime)); + str_time((int64_t)HoveredTick / Client()->GameTickSpeed() * 100, TIME_HOURS, s_aHoveredTime, sizeof(s_aHoveredTime)); GameClient()->m_Tooltips.DoToolTip(pId, &SeekBar, s_aHoveredTime); } } @@ -779,11 +779,11 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView) const int64_t RealSliceBegin = g_Config.m_ClDemoSliceBegin == -1 ? 0 : (g_Config.m_ClDemoSliceBegin - pInfo->m_FirstTick); const int64_t RealSliceEnd = (g_Config.m_ClDemoSliceEnd == -1 ? pInfo->m_LastTick : g_Config.m_ClDemoSliceEnd) - pInfo->m_FirstTick; char aSliceBegin[32]; - str_time(RealSliceBegin / SERVER_TICK_SPEED * 100, TIME_HOURS, aSliceBegin, sizeof(aSliceBegin)); + str_time(RealSliceBegin / Client()->GameTickSpeed() * 100, TIME_HOURS, aSliceBegin, sizeof(aSliceBegin)); char aSliceEnd[32]; - str_time(RealSliceEnd / SERVER_TICK_SPEED * 100, TIME_HOURS, aSliceEnd, sizeof(aSliceEnd)); + str_time(RealSliceEnd / Client()->GameTickSpeed() * 100, TIME_HOURS, aSliceEnd, sizeof(aSliceEnd)); char aSliceLength[32]; - str_time((RealSliceEnd - RealSliceBegin) / SERVER_TICK_SPEED * 100, TIME_HOURS, aSliceLength, sizeof(aSliceLength)); + str_time((RealSliceEnd - RealSliceBegin) / Client()->GameTickSpeed() * 100, TIME_HOURS, aSliceLength, sizeof(aSliceLength)); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "%s: %s – %s", Localize("Cut interval"), aSliceBegin, aSliceEnd); UI()->DoLabel(&SliceInterval, aBuf, 18.0f, TEXTALIGN_ML); diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 6fa6511677c..87cd222a047 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -377,15 +377,15 @@ void CPlayers::RenderPlayer( } bool PredictLocalWeapons = false; - float AttackTime = (Client()->PrevGameTick(g_Config.m_ClDummy) - Player.m_AttackTick) / (float)SERVER_TICK_SPEED + Client()->GameTickTime(g_Config.m_ClDummy); - float LastAttackTime = (Client()->PrevGameTick(g_Config.m_ClDummy) - Player.m_AttackTick) / (float)SERVER_TICK_SPEED + s_LastGameTickTime; + float AttackTime = (Client()->PrevGameTick(g_Config.m_ClDummy) - Player.m_AttackTick) / (float)Client()->GameTickSpeed() + Client()->GameTickTime(g_Config.m_ClDummy); + float LastAttackTime = (Client()->PrevGameTick(g_Config.m_ClDummy) - Player.m_AttackTick) / (float)Client()->GameTickSpeed() + s_LastGameTickTime; if(ClientID >= 0 && m_pClient->m_aClients[ClientID].m_IsPredictedLocal && m_pClient->AntiPingGunfire()) { PredictLocalWeapons = true; - AttackTime = (Client()->PredIntraGameTick(g_Config.m_ClDummy) + (Client()->PredGameTick(g_Config.m_ClDummy) - 1 - Player.m_AttackTick)) / (float)SERVER_TICK_SPEED; - LastAttackTime = (s_LastPredIntraTick + (Client()->PredGameTick(g_Config.m_ClDummy) - 1 - Player.m_AttackTick)) / (float)SERVER_TICK_SPEED; + AttackTime = (Client()->PredIntraGameTick(g_Config.m_ClDummy) + (Client()->PredGameTick(g_Config.m_ClDummy) - 1 - Player.m_AttackTick)) / (float)Client()->GameTickSpeed(); + LastAttackTime = (s_LastPredIntraTick + (Client()->PredGameTick(g_Config.m_ClDummy) - 1 - Player.m_AttackTick)) / (float)Client()->GameTickSpeed(); } - float AttackTicksPassed = AttackTime * (float)SERVER_TICK_SPEED; + float AttackTicksPassed = AttackTime * (float)Client()->GameTickSpeed(); float Angle; if(Local && (!m_pClient->m_Snap.m_SpecInfo.m_Active || m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) && Client()->State() != IClient::STATE_DEMOPLAYBACK) diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index c9a0db2adcb..b2e66bafd97 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -355,7 +355,7 @@ void CGameClient::OnInit() str_format(aBuf, sizeof(aBuf), "initialisation finished after %.2fms", ((End - Start) * 1000) / (float)time_freq()); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "gameclient", aBuf); - m_GameWorld.m_GameTickSpeed = SERVER_TICK_SPEED; + m_GameWorld.m_GameTickSpeed = Client()->GameTickSpeed(); m_GameWorld.m_pCollision = Collision(); m_GameWorld.m_pTuningList = m_aTuningList; @@ -2557,7 +2557,7 @@ void CGameClient::UpdatePrediction() } // advance the gameworld to the current gametick - if(pLocalChar && absolute(m_GameWorld.GameTick() - Client()->GameTick(g_Config.m_ClDummy)) < SERVER_TICK_SPEED) + if(pLocalChar && absolute(m_GameWorld.GameTick() - Client()->GameTick(g_Config.m_ClDummy)) < Client()->GameTickSpeed()) { for(int Tick = m_GameWorld.GameTick() + 1; Tick <= Client()->GameTick(g_Config.m_ClDummy); Tick++) { @@ -2688,7 +2688,7 @@ void CGameClient::DetectStrongHook() int ToPlayer = m_Snap.m_aCharacters[FromPlayer].m_Prev.m_HookedPlayer; if(ToPlayer < 0 || ToPlayer >= MAX_CLIENTS || !m_Snap.m_aCharacters[ToPlayer].m_Active || ToPlayer != m_Snap.m_aCharacters[FromPlayer].m_Cur.m_HookedPlayer) continue; - if(absolute(minimum(m_aLastUpdateTick[ToPlayer], m_aLastUpdateTick[FromPlayer]) - Client()->GameTick(g_Config.m_ClDummy)) < SERVER_TICK_SPEED / 4) + if(absolute(minimum(m_aLastUpdateTick[ToPlayer], m_aLastUpdateTick[FromPlayer]) - Client()->GameTick(g_Config.m_ClDummy)) < Client()->GameTickSpeed() / 4) continue; if(m_Snap.m_aCharacters[FromPlayer].m_Prev.m_Direction != m_Snap.m_aCharacters[FromPlayer].m_Cur.m_Direction || m_Snap.m_aCharacters[ToPlayer].m_Prev.m_Direction != m_Snap.m_aCharacters[ToPlayer].m_Cur.m_Direction) continue; diff --git a/src/game/gamecore.h b/src/game/gamecore.h index 26341a78314..8c81c00bda9 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -45,7 +45,6 @@ class CTuningParams public: CTuningParams() { - const float TicksPerSecond = 50.0f; #define MACRO_TUNING_PARAM(Name, ScriptName, Value, Description) m_##Name.Set((int)((Value)*100.0f)); #include "tuning.h" #undef MACRO_TUNING_PARAM diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 3c4e702d464..7f177835cc5 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -1060,7 +1060,7 @@ void CCharacter::SnapCharacter(int SnappingClient, int ID) if(Emote == EMOTE_NORMAL) { - if(250 - ((Server()->Tick() - m_LastAction) % (250)) < 5) + if(5 * Server()->TickSpeed() - ((Server()->Tick() - m_LastAction) % (5 * Server()->TickSpeed())) < 5) Emote = EMOTE_BLINK; } diff --git a/src/game/server/teehistorian.cpp b/src/game/server/teehistorian.cpp index d0bc838db14..083ccd65d99 100644 --- a/src/game/server/teehistorian.cpp +++ b/src/game/server/teehistorian.cpp @@ -181,7 +181,6 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo) First = true; - const float TicksPerSecond = 50.0f; #define MACRO_TUNING_PARAM(Name, ScriptName, Value, Description) \ if(pGameInfo->m_pTuning->m_##Name.Get() != (int)((Value)*100)) \ { \ diff --git a/src/game/tuning.h b/src/game/tuning.h index 887f43f9790..ac6340012e1 100644 --- a/src/game/tuning.h +++ b/src/game/tuning.h @@ -5,11 +5,11 @@ // physics tuning MACRO_TUNING_PARAM(GroundControlSpeed, ground_control_speed, 10.0f, "Max speed the tee can get on ground") -MACRO_TUNING_PARAM(GroundControlAccel, ground_control_accel, 100.0f / TicksPerSecond, "Acceleration speed on the ground") +MACRO_TUNING_PARAM(GroundControlAccel, ground_control_accel, 100.0f / SERVER_TICK_SPEED, "Acceleration speed on the ground") MACRO_TUNING_PARAM(GroundFriction, ground_friction, 0.5f, "Friction on the ground") MACRO_TUNING_PARAM(GroundJumpImpulse, ground_jump_impulse, 13.2f, "Impulse when jumping on ground") MACRO_TUNING_PARAM(AirJumpImpulse, air_jump_impulse, 12.0f, "Impulse when jumping in air") -MACRO_TUNING_PARAM(AirControlSpeed, air_control_speed, 250.0f / TicksPerSecond, "Max speed the tee can get in the air") +MACRO_TUNING_PARAM(AirControlSpeed, air_control_speed, 250.0f / SERVER_TICK_SPEED, "Max speed the tee can get in the air") MACRO_TUNING_PARAM(AirControlAccel, air_control_accel, 1.5f, "Acceleration speed in air") MACRO_TUNING_PARAM(AirFriction, air_friction, 0.95f, "Friction in the air") MACRO_TUNING_PARAM(HookLength, hook_length, 380.0f, "Length of the hook") From 9627e7b3a853293a5c0bc8b9646dfd80e087dfe9 Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 23 Nov 2023 15:33:30 +0100 Subject: [PATCH 027/198] Simplify TickSpeed getters --- src/engine/client.h | 4 +--- src/engine/client/client.cpp | 2 -- src/engine/server.h | 3 +-- src/engine/server/server.cpp | 2 -- src/game/client/gameclient.cpp | 1 - src/game/client/prediction/gameworld.cpp | 1 - src/game/client/prediction/gameworld.h | 3 +-- 7 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/engine/client.h b/src/engine/client.h index 5028a167f70..cab64464545 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -87,8 +87,6 @@ class IClient : public IInterface float m_GlobalTime; float m_RenderFrameTime; - int m_GameTickSpeed; - float m_FrameTimeAvg; TMapLoadingCallbackFunc m_MapLoadingCBFunc; @@ -141,7 +139,7 @@ class IClient : public IInterface inline float PredIntraGameTick(int Conn) const { return m_aPredIntraTick[Conn]; } inline float IntraGameTickSincePrev(int Conn) const { return m_aGameIntraTickSincePrev[Conn]; } inline float GameTickTime(int Conn) const { return m_aGameTickTime[Conn]; } - inline int GameTickSpeed() const { return m_GameTickSpeed; } + inline int GameTickSpeed() const { return SERVER_TICK_SPEED; } // other time access inline float RenderFrameTime() const { return m_RenderFrameTime; } diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 26a7b3c56e7..e16d3097bda 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -83,8 +83,6 @@ CClient::CClient() : m_RenderFrameTime = 0.0001f; m_LastRenderTime = time_get(); - m_GameTickSpeed = SERVER_TICK_SPEED; - m_SnapCrcErrors = 0; m_AutoScreenshotRecycle = false; m_AutoStatScreenshotRecycle = false; diff --git a/src/engine/server.h b/src/engine/server.h index f94bf2171b4..55c5cc7a48f 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -29,7 +29,6 @@ class IServer : public IInterface MACRO_INTERFACE("server", 0) protected: int m_CurrentGameTick; - int m_TickSpeed; public: /* @@ -46,7 +45,7 @@ class IServer : public IInterface }; int Tick() const { return m_CurrentGameTick; } - int TickSpeed() const { return m_TickSpeed; } + int TickSpeed() const { return SERVER_TICK_SPEED; } virtual int Port() const = 0; virtual int MaxClients() const = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index f142996fec5..3b2f9674323 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -317,8 +317,6 @@ CServer::CServer() m_aDemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta, true); m_aDemoRecorder[MAX_CLIENTS] = CDemoRecorder(&m_SnapshotDelta, false); - m_TickSpeed = TickSpeed(); - m_pGameServer = 0; m_CurrentGameTick = MIN_TICK; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index b2e66bafd97..c635fbc3059 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -355,7 +355,6 @@ void CGameClient::OnInit() str_format(aBuf, sizeof(aBuf), "initialisation finished after %.2fms", ((End - Start) * 1000) / (float)time_freq()); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "gameclient", aBuf); - m_GameWorld.m_GameTickSpeed = Client()->GameTickSpeed(); m_GameWorld.m_pCollision = Collision(); m_GameWorld.m_pTuningList = m_aTuningList; diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp index eddc4e761e0..58f9eeb6979 100644 --- a/src/game/client/prediction/gameworld.cpp +++ b/src/game/client/prediction/gameworld.cpp @@ -593,7 +593,6 @@ void CGameWorld::CopyWorld(CGameWorld *pFrom) pFrom->m_pChild = this; m_GameTick = pFrom->m_GameTick; - m_GameTickSpeed = pFrom->m_GameTickSpeed; m_pCollision = pFrom->m_pCollision; m_WorldConfig = pFrom->m_WorldConfig; for(int i = 0; i < 2; i++) diff --git a/src/game/client/prediction/gameworld.h b/src/game/client/prediction/gameworld.h index 32d00dd78a7..fbb63ad9b05 100644 --- a/src/game/client/prediction/gameworld.h +++ b/src/game/client/prediction/gameworld.h @@ -51,12 +51,11 @@ class CGameWorld std::vector IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis = nullptr); int m_GameTick; - int m_GameTickSpeed; CCollision *m_pCollision; // getter for server variables int GameTick() { return m_GameTick; } - int GameTickSpeed() { return m_GameTickSpeed; } + int GameTickSpeed() { return SERVER_TICK_SPEED; } CCollision *Collision() { return m_pCollision; } CTeamsCore *Teams() { return &m_Teams; } std::vector &Switchers() { return m_Core.m_vSwitchers; } From 36a029c62210e631839eb300caf61bf4484f3378 Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 23 Nov 2023 15:41:49 +0100 Subject: [PATCH 028/198] Set correct race precision value for 0.7 --- src/game/server/gamecontroller.cpp | 2 +- src/game/server/player.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 93638f9cf5f..270dc6a0edf 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -641,7 +641,7 @@ void IGameController::Snap(int SnappingClient) return; pRaceData->m_BestTime = round_to_int(m_CurrentRecord * 1000); - pRaceData->m_Precision = 0; + pRaceData->m_Precision = 2; pRaceData->m_RaceFlags = protocol7::RACEFLAG_HIDE_KILLMSG | protocol7::RACEFLAG_KEEP_WANTED_WEAPON; } diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 4007ff3af5e..de3ae840eb5 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -379,7 +379,7 @@ void CPlayer::Snap(int SnappingClient) pPlayerInfo->m_PlayerFlags |= protocol7::PLAYERFLAG_ADMIN; // Times are in milliseconds for 0.7 - pPlayerInfo->m_Score = Score == -9999 ? -1 : -Score * 1000; + pPlayerInfo->m_Score = m_Score.has_value() ? GameServer()->Score()->PlayerData(id)->m_BestTime * 1000 : -1; pPlayerInfo->m_Latency = Latency; } From 00f2c8a89f9f9287dc9b2c9ed3b5258c91bc68c9 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 23 Nov 2023 20:13:23 +1300 Subject: [PATCH 029/198] Show previous commands in the console's scrollback buffer After you issue a command in the client console or rcon, the command will first be printed to the console prefixed with a '>' symbol. Cherry-pick of https://github.com/teeworlds/teeworlds/pull/3185 --- src/game/client/components/console.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 6b02a044221..3e067ed3072 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -254,6 +254,10 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) { char *pEntry = m_History.Allocate(m_Input.GetLength() + 1); str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1); + // print out the user's commands before they get run + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString()); + m_pGameConsole->PrintLine(m_Type, aBuf); } } ExecuteLine(m_Input.GetString()); From 7ca10e2645e6e235406ea288e9e35f548104b0c5 Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 23 Nov 2023 23:38:59 +0100 Subject: [PATCH 030/198] Quote names with spaces when auto completing. --- src/game/client/components/chat.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index a2aa1baa05c..75d4b1815dc 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -393,6 +393,19 @@ bool CChat::OnInput(const IInput::CEvent &Event) // add part before the name str_truncate(aBuf, sizeof(aBuf), m_Input.GetString(), m_PlaceholderOffset); + // quote the name + char aQuoted[128]; + if(m_Input.GetString()[0] == '/' && (str_find(pCompletionString, " ") || str_find(pCompletionString, "\""))) + { + // escape the name + str_copy(aQuoted, "\""); + char *pDst = aQuoted + str_length(aQuoted); + str_escape(&pDst, pCompletionString, aQuoted + sizeof(aQuoted)); + str_append(aQuoted, "\""); + + pCompletionString = aQuoted; + } + // add the name str_append(aBuf, pCompletionString); From daa06092522310594e750f9a3dab4fd96e917ae2 Mon Sep 17 00:00:00 2001 From: furo Date: Fri, 24 Nov 2023 16:55:36 +0100 Subject: [PATCH 031/198] Hide "Allow unused" button when not using "DDNet" entities. --- src/game/editor/popups.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 3cddb43215c..0dd22b9b0ea 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -280,15 +280,18 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect Selector.VSplitMid(&No, &Yes); pEditor->UI()->DoLabel(&Label, "Allow unused", 10.0f, TEXTALIGN_ML); - static int s_ButtonNo = 0; - static int s_ButtonYes = 0; - if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !pEditor->m_AllowPlaceUnusedTiles, &No, 0, "[ctrl+u] Disallow placing unused tiles")) - { - pEditor->m_AllowPlaceUnusedTiles = false; - } - if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", pEditor->m_AllowPlaceUnusedTiles, &Yes, 0, "[ctrl+u] Allow placing unused tiles")) + if(pEditor->m_AllowPlaceUnusedTiles != -1) { - pEditor->m_AllowPlaceUnusedTiles = true; + static int s_ButtonNo = 0; + static int s_ButtonYes = 0; + if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !pEditor->m_AllowPlaceUnusedTiles, &No, 0, "[ctrl+u] Disallow placing unused tiles")) + { + pEditor->m_AllowPlaceUnusedTiles = false; + } + if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", pEditor->m_AllowPlaceUnusedTiles, &Yes, 0, "[ctrl+u] Allow placing unused tiles")) + { + pEditor->m_AllowPlaceUnusedTiles = true; + } } } From 8eab64f3f1e5a20788fb6bd20e09cbaf7e39e435 Mon Sep 17 00:00:00 2001 From: By <130899529+By622@users.noreply.github.com> Date: Sat, 25 Nov 2023 16:26:38 +0800 Subject: [PATCH 032/198] Update traditional_chinese.txt --- data/languages/traditional_chinese.txt | 77 +++++++++++++------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/data/languages/traditional_chinese.txt b/data/languages/traditional_chinese.txt index 563e5238f36..a0b79857d3c 100644 --- a/data/languages/traditional_chinese.txt +++ b/data/languages/traditional_chinese.txt @@ -24,6 +24,7 @@ # 2023-05-25 cheeser0613 # 2023-07-20 By # 2023-08-11 By +# 2023-11-25 By ##### /authors ##### ##### translated strings ##### @@ -698,7 +699,7 @@ Check now == 檢查更新 Loading DDNet Client -== 載入 DDNet 客戶端中... +== 正在載入 DDNet 客戶端 Browser == 瀏覽 @@ -1349,7 +1350,7 @@ Connected == 連接成功 Loading map file from storage -== 正在從硬盤中加載地圖檔案 +== 正在從硬盤中載入地圖檔案 Why are you slowmo replaying to read this? == 所以你爲什麽要慢速回放來看這個? @@ -1376,16 +1377,16 @@ Requesting to join the game == 正在請求加入游戲 Loading menu images -== 正在加載主菜單圖片 +== 正在載入主菜單圖片 Loading demo files -== 正在加載回放檔案 +== 正在載入回放檔案 Loading ghost files -== 正在加載影子檔案 +== 正在載入影子檔案 Loading skin files -== 正在加載外觀檔案 +== 正在載入外觀檔案 Appearance == 游戲界面 @@ -1439,13 +1440,13 @@ A Tee == 其他玩家 Loading assets -== 正在加載材質 +== 正在載入材質 Loading race demo files -== 正在加載回放檔案 +== 正在載入回放檔案 Loading sound files -== 正在加載聲音檔案 +== 正在載入聲音檔案 Play the current demo == 播放回放 @@ -1641,22 +1642,22 @@ No server selected == 未選擇伺服器 Mark the beginning of a cut (right click to reset) -== 標記裁剪起點 (右鍵重置) +== 標記裁切起點 (右鍵重置) Mark the end of a cut (right click to reset) -== 標記裁剪終點 (右鍵重置) +== 標記裁切終點 (右鍵重置) Close the demo player == 關閉回放 Export demo cut -== 另存裁剪部分 +== 另存裁切部分 Cut interval -== 裁剪區間 +== 裁切區間 Cut length -== 裁剪長度 +== 裁切長度 Axis == 軸 @@ -1704,76 +1705,76 @@ Moved ingame == 遊戲內移動 Error playing demo -== +== 回放播放錯誤 Some map images could not be loaded. Check the local console for details. -== +== 未能載入某些地圖圖片。檢查本機控制台以取得詳情。 Some map sounds could not be loaded. Check the local console for details. -== +== 未能載入某些地圖音效。檢查本機控制台以取得詳情。 Loading menu themes -== +== 正在載入主選單主題 Render complete -== +== 渲染完畢 Videos directory -== +== 影片目錄 Video was saved to '%s' -== +== 影片保存至 "%s" Go back the specified duration -== +== 倒轉指定時長 [Demo player duration] %d min. -== +== %d 分 [Demo player duration] %d sec. -== +== %d 秒 Change the skip duration -== +== 變更指定時長 Go forward the specified duration -== +== 快轉指定時長 Render cut to video -== +== 渲染裁切部分 No demo selected -== +== 未選擇回放 Created -== +== 建立日期 Netversion -== +== Netversion [Demo details] map not included -== +== 未包含地圖 Ghosts directory -== +== 影子目錄 Activate all -== +== 全部啟用 Deactivate all -== +== 全部停用 Enable ghost -== +== 啟用影子 Only save improvements -== +== 只保存更佳記錄 Regular background color -== +== 原圖層背景顏色 Entities background color -== +== 實體層背景顏色 From 91a875bede884733fe2b70cdc9bda05c77906d38 Mon Sep 17 00:00:00 2001 From: By <130899529+By622@users.noreply.github.com> Date: Sat, 25 Nov 2023 16:28:39 +0800 Subject: [PATCH 033/198] Update simplified_chinese.txt --- data/languages/simplified_chinese.txt | 49 ++++++++++++++------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/data/languages/simplified_chinese.txt b/data/languages/simplified_chinese.txt index a3f39d28608..e8b5742d6f5 100644 --- a/data/languages/simplified_chinese.txt +++ b/data/languages/simplified_chinese.txt @@ -35,6 +35,7 @@ # 2023-05-21 RemakePower # 2023-07-20 By # 2023-08-11 By +# 2023-11-25 By ##### /authors ##### ##### translated strings ##### @@ -1715,76 +1716,76 @@ Moved ingame == 游戏内移动 Error playing demo -== +== 回放播放错误 Some map images could not be loaded. Check the local console for details. -== +== 未能加载某些地图图片。检查本地控制台以获取详情。 Some map sounds could not be loaded. Check the local console for details. -== +== 未能加载某些地图音效。检查本地控制台以获取详情。 Loading menu themes -== +== 正在加载主菜单主题 Render complete -== +== 渲染完毕 Videos directory -== +== 视频目录 Video was saved to '%s' -== +== 视频保存至 "%s" Go back the specified duration -== +== 快退指定时长 [Demo player duration] %d min. -== +== %d 分 [Demo player duration] %d sec. -== +== %d 秒 Change the skip duration -== +== 变更指定时长 Go forward the specified duration -== +== 快进指定时长 Render cut to video -== +== 渲染裁剪部分 No demo selected -== +== 未选择回放 Created -== +== 创建日期 Netversion -== +== Netversion [Demo details] map not included -== +== 未包含地图 Ghosts directory -== +== 影子目录 Activate all -== +== 全部启用 Deactivate all -== +== 全部停用 Enable ghost -== +== 启用影子 Only save improvements -== +== 只保存更佳记录 Regular background color -== +== 原图层背景颜色 Entities background color -== +== 实体层背景颜色 From c216567431f9681e438e6d5cce2f0bb99bdd3e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 25 Nov 2023 11:24:31 +0100 Subject: [PATCH 034/198] Add `IConsole::FlagMask` getter function So other interfaces/components can check which console commands are active. In particular this will be used to refactor the config manager. The respective setter function already exists. --- src/engine/console.h | 2 ++ src/engine/shared/console.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/engine/console.h b/src/engine/console.h index c9faed80d2f..81987034c74 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -127,6 +127,8 @@ class IConsole : public IInterface // DDRace bool m_Cheated; + + virtual int FlagMask() const = 0; virtual void SetFlagMask(int FlagMask) = 0; }; diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index d29b8a775e4..7a9168601b3 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -227,6 +227,8 @@ class CConsole : public IConsole // DDRace static void ConUserCommandStatus(IConsole::IResult *pResult, void *pUser); + + int FlagMask() const override { return m_FlagMask; } void SetFlagMask(int FlagMask) override { m_FlagMask = FlagMask; } }; From e410dd85f80d92c47fce96dadbb829f89f6f95e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 25 Nov 2023 11:27:45 +0100 Subject: [PATCH 035/198] Replace `IConsole::m_Cheated` variable with `Cheated` getter Interfaces should not have member variables, so the variable is moved to `CConsole`. Only a getter `IConsole::Cheated` is added because the cheated state of the console is never reset. --- src/engine/console.h | 2 +- src/engine/shared/console.h | 4 ++++ src/game/server/score.cpp | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/engine/console.h b/src/engine/console.h index 81987034c74..bac6cf7a837 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -126,7 +126,7 @@ class IConsole : public IInterface // DDRace - bool m_Cheated; + virtual bool Cheated() const = 0; virtual int FlagMask() const = 0; virtual void SetFlagMask(int FlagMask) = 0; diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index 7a9168601b3..614dd0e0cb9 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -191,6 +191,8 @@ class CConsole : public IConsole void AddCommandSorted(CCommand *pCommand); CCommand *FindCommand(const char *pName, int FlagMask); + bool m_Cheated; + public: IConfigManager *ConfigManager() { return m_pConfigManager; } CConfig *Config() { return m_pConfig; } @@ -228,6 +230,8 @@ class CConsole : public IConsole static void ConUserCommandStatus(IConsole::IResult *pResult, void *pUser); + bool Cheated() const override { return m_Cheated; } + int FlagMask() const override { return m_FlagMask; } void SetFlagMask(int FlagMask) override { m_FlagMask = FlagMask; } }; diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index b70fe89026d..610ebd1531a 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -143,7 +143,7 @@ void CScore::MapInfo(int ClientID, const char *pMapName) void CScore::SaveScore(int ClientID, float Time, const char *pTimestamp, const float aTimeCp[NUM_CHECKPOINTS], bool NotEligible) { CConsole *pCon = (CConsole *)GameServer()->Console(); - if(pCon->m_Cheated || NotEligible) + if(pCon->Cheated() || NotEligible) return; CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; @@ -166,7 +166,7 @@ void CScore::SaveScore(int ClientID, float Time, const char *pTimestamp, const f void CScore::SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp) { CConsole *pCon = (CConsole *)GameServer()->Console(); - if(pCon->m_Cheated) + if(pCon->Cheated()) return; for(unsigned int i = 0; i < Size; i++) { From 42b952bc6c7b1a895674500ecb0e8296133c08e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 25 Nov 2023 14:25:09 +0100 Subject: [PATCH 036/198] Move config variables to `config_variables.h`, remove `variables.h` The contents of `variables.h` are moved to `config_variables.h` instead of being included with the preprocessor. The file `variables.h` is removed, so all config variables can be found in a single file instead of being spread over two files without any clear structure. The original declaration order of config variables is preserved. The unnecessary header guard `GAME_VARIABLES_H` from `variables.h` is removed, as the comment `// This file can be included several times.` already serves the same purpose of silencing the header guard check. A comment is added at the end of `config_variables.h` to mark the location where modders should ideally declare all of their own config variables to avoid merge conflicts with us in the future. Closes #7472. --- CMakeLists.txt | 1 - scripts/check_config_variables.py | 2 +- src/engine/shared/config_variables.h | 228 +++++++++++++++++++++++++- src/game/variables.h | 230 --------------------------- 4 files changed, 227 insertions(+), 234 deletions(-) delete mode 100644 src/game/variables.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d6ace897bc..fbd9982cb96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2012,7 +2012,6 @@ set_src(GAME_SHARED GLOB src/game teamscore.cpp teamscore.h tuning.h - variables.h version.h voting.h ) diff --git a/scripts/check_config_variables.py b/scripts/check_config_variables.py index c4f81f3ceb3..603f1494e8d 100755 --- a/scripts/check_config_variables.py +++ b/scripts/check_config_variables.py @@ -46,7 +46,7 @@ def find_config_variables(config_variables): return variables_not_found def main(): - lines = read_all_lines('src/game/variables.h') + read_all_lines('src/engine/shared/config_variables.h') + lines = read_all_lines('src/engine/shared/config_variables.h') config_variables = parse_config_variables(lines) config_variables_not_found = find_config_variables(config_variables) for variable_code in config_variables_not_found: diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index f516991f65b..c8dabdf9004 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -3,8 +3,228 @@ // This file can be included several times. -// TODO: remove this -#include "././game/variables.h" +#ifndef MACRO_CONFIG_INT +#error "The config macros must be defined" +#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Save, Desc) ; +#define MACRO_CONFIG_COL(Name, ScriptName, Def, Save, Desc) ; +#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Save, Desc) ; +#endif + +// client +MACRO_CONFIG_INT(ClPredict, cl_predict, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict client movements") +MACRO_CONFIG_INT(ClPredictDummy, cl_predict_dummy, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict dummy movements") +MACRO_CONFIG_INT(ClAntiPingLimit, cl_antiping_limit, 0, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Antiping limit (0 to disable)") +MACRO_CONFIG_INT(ClAntiPing, cl_antiping, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable antiping, i. e. more aggressive prediction.") +MACRO_CONFIG_INT(ClAntiPingPlayers, cl_antiping_players, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict other player's movement more aggressively (only enabled if cl_antiping is set to 1)") +MACRO_CONFIG_INT(ClAntiPingGrenade, cl_antiping_grenade, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict grenades (only enabled if cl_antiping is set to 1)") +MACRO_CONFIG_INT(ClAntiPingWeapons, cl_antiping_weapons, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict weapon projectiles (only enabled if cl_antiping is set to 1)") +MACRO_CONFIG_INT(ClAntiPingSmooth, cl_antiping_smooth, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Make the prediction of other player's movement smoother") +MACRO_CONFIG_INT(ClAntiPingGunfire, cl_antiping_gunfire, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict gunfire and show predicted weapon physics (with cl_antiping_grenade 1 and cl_antiping_weapons 1)") +MACRO_CONFIG_INT(ClPredictionMargin, cl_prediction_margin, 10, 1, 300, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Prediction margin in ms (adds latency, can reduce lag from ping jumps)") +MACRO_CONFIG_INT(ClSubTickAiming, cl_sub_tick_aiming, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Send aiming data at sub-tick accuracy") + +MACRO_CONFIG_INT(ClNameplates, cl_nameplates, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show name plates") +MACRO_CONFIG_INT(ClAfkEmote, cl_afk_emote, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show zzz emote next to afk players") +MACRO_CONFIG_INT(ClNameplatesAlways, cl_nameplates_always, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Always show name plates disregarding of distance") +MACRO_CONFIG_INT(ClNameplatesTeamcolors, cl_nameplates_teamcolors, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Use team colors for name plates") +MACRO_CONFIG_INT(ClNameplatesSize, cl_nameplates_size, 50, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Size of the name plates from 0 to 100%") +MACRO_CONFIG_INT(ClNameplatesClan, cl_nameplates_clan, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show clan in name plates") +MACRO_CONFIG_INT(ClNameplatesClanSize, cl_nameplates_clan_size, 30, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Size of the clan plates from 0 to 100%") +MACRO_CONFIG_INT(ClNameplatesIDs, cl_nameplates_ids, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show IDs in name plates") +MACRO_CONFIG_INT(ClNameplatesOwn, cl_nameplates_own, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show own name plate (useful for demo recording)") +MACRO_CONFIG_INT(ClNameplatesFriendMark, cl_nameplates_friendmark, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show friend mark (♥) in name plates") +MACRO_CONFIG_INT(ClNameplatesStrong, cl_nameplates_strong, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show strong/weak in name plates (0 - off, 1 - icons, 2 - icons + numbers)") +MACRO_CONFIG_INT(ClTextEntities, cl_text_entities, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Render textual entity data") +MACRO_CONFIG_INT(ClTextEntitiesSize, cl_text_entities_size, 100, 1, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Size of textual entity data from 1 to 100%") +MACRO_CONFIG_INT(ClStreamerMode, cl_streamer_mode, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Censor sensitive information such as /save password") + +MACRO_CONFIG_COL(ClAuthedPlayerColor, cl_authed_player_color, 5898211, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Color of name of authenticated player in scoreboard") +MACRO_CONFIG_COL(ClSameClanColor, cl_same_clan_color, 5898211, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Clan color of players with the same clan as you in scoreboard.") + +MACRO_CONFIG_INT(ClEnablePingColor, cl_enable_ping_color, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Whether ping is colored in scoreboard.") +MACRO_CONFIG_INT(ClAutoswitchWeapons, cl_autoswitch_weapons, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Auto switch weapon on pickup") +MACRO_CONFIG_INT(ClAutoswitchWeaponsOutOfAmmo, cl_autoswitch_weapons_out_of_ammo, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Auto switch weapon when out of ammo") + +MACRO_CONFIG_INT(ClShowhud, cl_showhud, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD") +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(ClShowhudDDRace, cl_showhud_ddrace, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show ingame HUD (DDRace HUD)") +MACRO_CONFIG_INT(ClShowhudJumpsIndicator, cl_showhud_jumps_indicator, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show ingame HUD (Jumps you have and have used)") +MACRO_CONFIG_INT(ClShowFreezeBars, cl_show_freeze_bars, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Whether to show a freeze bar under frozen players to indicate the thaw time") +MACRO_CONFIG_INT(ClFreezeBarsAlphaInsideFreeze, cl_freezebars_alpha_inside_freeze, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Opacity of freeze bars inside freeze (0 invisible, 100 fully visible)") +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") +MACRO_CONFIG_INT(ClShowChat, cl_showchat, 1, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show chat (2 to always show large chat area)") +MACRO_CONFIG_INT(ClShowChatFriends, cl_show_chat_friends, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show only chat messages from friends") +MACRO_CONFIG_INT(ClShowChatSystem, cl_show_chat_system, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show chat messages from the server") +MACRO_CONFIG_INT(ClShowKillMessages, cl_showkillmessages, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show kill messages") +MACRO_CONFIG_INT(ClShowVotesAfterVoting, cl_show_votes_after_voting, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show votes window after voting") +MACRO_CONFIG_INT(ClShowLocalTimeAlways, cl_show_local_time_always, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Always show local time") +MACRO_CONFIG_INT(ClShowfps, cl_showfps, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame FPS counter") +MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame prediction time in milliseconds") +MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes") +MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last") + +MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator") +MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded") + +MACRO_CONFIG_INT(ClWarningTeambalance, cl_warning_teambalance, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Warn about team balance") + +MACRO_CONFIG_INT(ClMouseDeadzone, cl_mouse_deadzone, 0, 0, 3000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Deadzone for the camera to follow the cursor") +MACRO_CONFIG_INT(ClMouseFollowfactor, cl_mouse_followfactor, 0, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Factor for the camera to follow the cursor") +MACRO_CONFIG_INT(ClMouseMaxDistance, cl_mouse_max_distance, 400, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Maximum cursor distance") +MACRO_CONFIG_INT(ClMouseMinDistance, cl_mouse_min_distance, 0, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Minimum cursor distance") + +MACRO_CONFIG_INT(ClDyncam, cl_dyncam, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Enable dyncam") +MACRO_CONFIG_INT(ClDyncamMaxDistance, cl_dyncam_max_distance, 1000, 0, 2000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Maximum dynamic camera cursor distance") +MACRO_CONFIG_INT(ClDyncamMinDistance, cl_dyncam_min_distance, 0, 0, 2000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Minimum dynamic camera cursor distance") +MACRO_CONFIG_INT(ClDyncamMousesens, cl_dyncam_mousesens, 0, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Mouse sens used when dyncam is toggled on") +MACRO_CONFIG_INT(ClDyncamDeadzone, cl_dyncam_deadzone, 300, 1, 1300, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Deadzone for the dynamic camera to follow the cursor") +MACRO_CONFIG_INT(ClDyncamFollowFactor, cl_dyncam_follow_factor, 60, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Factor for the dynamic camera to follow the cursor") + +MACRO_CONFIG_INT(ClDyncamSmoothness, cl_dyncam_smoothness, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Transition amount of the camera movement, 0=instant, 100=slow and smooth") +MACRO_CONFIG_INT(ClDyncamStabilizing, cl_dyncam_stabilizing, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Amount of camera slowdown during fast cursor movement. High value can cause delay in camera movement") + +MACRO_CONFIG_INT(ClMultiViewSensitivity, cl_multiview_sensitivity, 100, 0, 200, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Set how fast the camera will move to the desired location (higher = faster)") +MACRO_CONFIG_INT(ClMultiViewZoomSmoothness, cl_multiview_zoom_smoothness, 1300, 50, 5000, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Set the smoothness of the multi-view zoom (in ms, higher = slower)") + +MACRO_CONFIG_INT(EdAutosaveInterval, ed_autosave_interval, 10, 0, 240, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interval in minutes at which a copy of the current editor map is automatically saved to the 'auto' folder (0 for off)") +MACRO_CONFIG_INT(EdAutosaveMax, ed_autosave_max, 10, 0, 1000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Maximum number of autosaves that are kept per map name (0 = no limit)") +MACRO_CONFIG_INT(EdSmoothZoomTime, ed_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth zoom animation in the editor in ms (0 for off)") +MACRO_CONFIG_INT(EdLimitMaxZoomLevel, ed_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming in the editor should be limited or not (0 = no limit)") +MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target") +MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show pressed keys") + +MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show welcome message indicating the first launch of the client") +MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long to show the server message of the day") + +// http map download +MACRO_CONFIG_STR(ClMapDownloadUrl, cl_map_download_url, 100, "https://maps.ddnet.org", CFGFLAG_CLIENT | CFGFLAG_SAVE, "URL used to download maps (can start with http:// or https://)") +MACRO_CONFIG_INT(ClMapDownloadConnectTimeoutMs, cl_map_download_connect_timeout_ms, 2000, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: timeout for the connect phase in milliseconds (0 to disable)") +MACRO_CONFIG_INT(ClMapDownloadLowSpeedLimit, cl_map_download_low_speed_limit, 4000, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: Set low speed limit in bytes per second (0 to disable)") +MACRO_CONFIG_INT(ClMapDownloadLowSpeedTime, cl_map_download_low_speed_time, 3, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: Set low speed limit time period (0 to disable)") + +MACRO_CONFIG_STR(ClLanguagefile, cl_languagefile, 255, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "What language file to use") +MACRO_CONFIG_STR(ClSkinDownloadUrl, cl_skin_download_url, 100, "https://skins.ddnet.org/skin/", CFGFLAG_CLIENT | CFGFLAG_SAVE, "URL used to download skins") +MACRO_CONFIG_STR(ClSkinCommunityDownloadUrl, cl_skin_community_download_url, 100, "https://skins.ddnet.org/skin/community/", CFGFLAG_CLIENT | CFGFLAG_SAVE, "URL used to download community skins") +MACRO_CONFIG_INT(ClVanillaSkinsOnly, cl_vanilla_skins_only, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Only show skins available in Vanilla Teeworlds") +MACRO_CONFIG_INT(ClDownloadSkins, cl_download_skins, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Download skins from cl_skin_download_url on-the-fly") +MACRO_CONFIG_INT(ClDownloadCommunitySkins, cl_download_community_skins, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Allow to download skins created by the community. Uses cl_skin_community_download_url instead of cl_skin_download_url for the download") +MACRO_CONFIG_INT(ClAutoStatboardScreenshot, cl_auto_statboard_screenshot, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Automatically take game over statboard screenshot") +MACRO_CONFIG_INT(ClAutoStatboardScreenshotMax, cl_auto_statboard_screenshot_max, 10, 0, 1000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Maximum number of automatically created statboard screenshots (0 = no limit)") + +MACRO_CONFIG_INT(ClDefaultZoom, cl_default_zoom, 10, 0, 20, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Default zoom level") +MACRO_CONFIG_INT(ClSmoothZoomTime, cl_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth zoom animation ingame in ms (0 for off)") +MACRO_CONFIG_INT(ClLimitMaxZoomLevel, cl_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming ingame should be limited or not (0 = no limit)") + +MACRO_CONFIG_INT(ClPlayerUseCustomColor, player_use_custom_color, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors") +MACRO_CONFIG_COL(ClPlayerColorBody, player_color_body, 65408, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player body color") +MACRO_CONFIG_COL(ClPlayerColorFeet, player_color_feet, 65408, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player feet color") +MACRO_CONFIG_STR(ClPlayerSkin, player_skin, 24, "default", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin") +MACRO_CONFIG_INT(ClPlayerDefaultEyes, player_default_eyes, 0, 0, 5, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Player eyes when joining server. 0 = normal, 1 = pain, 2 = happy, 3 = surprise, 4 = angry, 5 = blink") +MACRO_CONFIG_STR(ClSkinPrefix, cl_skin_prefix, 12, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Replace the skins by skins with this prefix (e.g. kitty, santa)") +MACRO_CONFIG_INT(ClFatSkins, cl_fat_skins, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable fat skins") + +MACRO_CONFIG_INT(UiPage, ui_page, 6, 6, 10, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface page") +MACRO_CONFIG_INT(UiSettingsPage, ui_settings_page, 0, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface settings page") +MACRO_CONFIG_INT(UiToolboxPage, ui_toolbox_page, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Toolbox page") +MACRO_CONFIG_STR(UiServerAddress, ui_server_address, 1024, "localhost:8303", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Interface server address") +MACRO_CONFIG_INT(UiMousesens, ui_mousesens, 200, 1, 100000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Mouse sensitivity for menus/editor") +MACRO_CONFIG_INT(UiControllerSens, ui_controller_sens, 100, 1, 100000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Controller sensitivity for menus/editor") +MACRO_CONFIG_INT(UiSmoothScrollTime, ui_smooth_scroll_time, 500, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth scrolling animation in menus/editor in ms (0 for off)") + +MACRO_CONFIG_COL(UiColor, ui_color, 0xE4A046AF, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLALPHA, "Interface color") // 160 70 175 228 hasalpha + +MACRO_CONFIG_INT(UiColorizePing, ui_colorize_ping, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight ping") +MACRO_CONFIG_INT(UiColorizeGametype, ui_colorize_gametype, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight gametype") + +MACRO_CONFIG_INT(UiCloseWindowAfterChangingSetting, ui_close_window_after_changing_setting, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Close window after changing setting") +MACRO_CONFIG_INT(UiUnreadNews, ui_unread_news, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Whether there is unread news") + +MACRO_CONFIG_INT(GfxNoclip, gfx_noclip, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Disable clipping") + +// dummy +MACRO_CONFIG_STR(ClDummyName, dummy_name, 16, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Name of the dummy") +MACRO_CONFIG_STR(ClDummyClan, dummy_clan, 12, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Clan of the dummy") +MACRO_CONFIG_INT(ClDummyCountry, dummy_country, -1, -1, 1000, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Country of the Dummy") +MACRO_CONFIG_INT(ClDummyUseCustomColor, dummy_use_custom_color, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors") +MACRO_CONFIG_COL(ClDummyColorBody, dummy_color_body, 65408, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy body color") +MACRO_CONFIG_COL(ClDummyColorFeet, dummy_color_feet, 65408, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy feet color") +MACRO_CONFIG_STR(ClDummySkin, dummy_skin, 24, "default", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin") +MACRO_CONFIG_INT(ClDummyDefaultEyes, dummy_default_eyes, 0, 0, 5, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Dummy eyes when joining server. 0 = normal, 1 = pain, 2 = happy, 3 = surprise, 4 = angry, 5 = blink") +MACRO_CONFIG_INT(ClDummy, cl_dummy, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "0 - player / 1 - dummy") +MACRO_CONFIG_INT(ClDummyHammer, cl_dummy_hammer, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy is hammering for a hammerfly") +MACRO_CONFIG_INT(ClDummyResetOnSwitch, cl_dummy_resetonswitch, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Whether dummy or player should stop pressing keys when you switch. 0 = off, 1 = dummy, 2 = player") +MACRO_CONFIG_INT(ClDummyRestoreWeapon, cl_dummy_restore_weapon, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Whether dummy should switch to last weapon after hammerfly") +MACRO_CONFIG_INT(ClDummyCopyMoves, cl_dummy_copy_moves, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy should copy your moves") + +// more controllable dummy command +MACRO_CONFIG_INT(ClDummyControl, cl_dummy_control, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether can you control dummy at the same time (cl_dummy_jump, cl_dummy_fire, cl_dummy_hook)") +MACRO_CONFIG_INT(ClDummyJump, cl_dummy_jump, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy is jumping (requires cl_dummy_control 1)") +MACRO_CONFIG_INT(ClDummyFire, cl_dummy_fire, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy is firing (requires cl_dummy_control 1)") +MACRO_CONFIG_INT(ClDummyHook, cl_dummy_hook, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy is hooking (requires cl_dummy_control 1)") + +// start menu +MACRO_CONFIG_INT(ClShowStartMenuImages, cl_show_start_menu_images, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show start menu images") +MACRO_CONFIG_INT(ClSkipStartMenu, cl_skip_start_menu, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Skip the start menu") + +// server +MACRO_CONFIG_INT(SvWarmup, sv_warmup, 0, 0, 0, CFGFLAG_SERVER, "Number of seconds to do warmup before round starts") +MACRO_CONFIG_STR(SvMotd, sv_motd, 900, "", CFGFLAG_SERVER, "Message of the day to display for the clients") +MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "ddnet", CFGFLAG_SAVE | CFGFLAG_SERVER, "Game type (ddnet, mod)") +MACRO_CONFIG_INT(SvTournamentMode, sv_tournament_mode, 0, 0, 1, CFGFLAG_SERVER, "Tournament mode. When enabled, players joins the server as spectator") +MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection") + +MACRO_CONFIG_INT(SvSpectatorSlots, sv_spectator_slots, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Number of slots to reserve for spectators") +MACRO_CONFIG_INT(SvInactiveKickTime, sv_inactivekick_time, 0, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before taking care of inactive players") +MACRO_CONFIG_INT(SvInactiveKick, sv_inactivekick, 0, 0, 2, CFGFLAG_SERVER, "How to deal with inactive players (0=move to spectator, 1=move to free spectator slot/kick, 2=kick)") + +MACRO_CONFIG_INT(SvStrictSpectateMode, sv_strict_spectate_mode, 0, 0, 1, CFGFLAG_SERVER, "Restricts information in spectator mode") +MACRO_CONFIG_INT(SvVoteSpectate, sv_vote_spectate, 1, 0, 1, CFGFLAG_SERVER, "Allow voting to move players to spectators") +MACRO_CONFIG_INT(SvVoteSpectateRejoindelay, sv_vote_spectate_rejoindelay, 3, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before a player can rejoin after being moved to spectators by vote") +MACRO_CONFIG_INT(SvVoteKick, sv_vote_kick, 1, 0, 1, CFGFLAG_SERVER, "Allow voting to kick players") +MACRO_CONFIG_INT(SvVoteKickMin, sv_vote_kick_min, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Minimum number of players required to start a kick vote") +MACRO_CONFIG_INT(SvVoteKickBantime, sv_vote_kick_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time in seconds to ban a player if kicked by vote. 0 makes it just use kick") +MACRO_CONFIG_INT(SvJoinVoteDelay, sv_join_vote_delay, 300, 0, 1000, CFGFLAG_SERVER, "Add a delay before recently joined players can call any vote or participate in a kick/spec vote (in seconds)") +MACRO_CONFIG_INT(SvOldTeleportWeapons, sv_old_teleport_weapons, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Teleporting of all weapons (deprecated, use special entities instead)") +MACRO_CONFIG_INT(SvOldTeleportHook, sv_old_teleport_hook, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Hook through teleporter (deprecated, use special entities instead)") +MACRO_CONFIG_INT(SvTeleportHoldHook, sv_teleport_hold_hook, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Hold hook when teleported") +MACRO_CONFIG_INT(SvTeleportLoseWeapons, sv_teleport_lose_weapons, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Lose weapons when teleported (useful for some race maps)") +MACRO_CONFIG_INT(SvDeepfly, sv_deepfly, 1, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Allow fire non auto weapons when deep") +MACRO_CONFIG_INT(SvDestroyBulletsOnDeath, sv_destroy_bullets_on_death, 1, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Destroy bullets when their owner dies") +MACRO_CONFIG_INT(SvDestroyLasersOnDeath, sv_destroy_lasers_on_death, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Destroy lasers when their owner dies") + +MACRO_CONFIG_INT(SvMapUpdateRate, sv_mapupdaterate, 5, 1, 100, CFGFLAG_SERVER, "64 player id <-> vanilla id players map update rate") + +MACRO_CONFIG_STR(SvServerType, sv_server_type, 64, "none", CFGFLAG_SERVER, "Type of the server (novice, moderate, ...)") + +MACRO_CONFIG_INT(SvSendVotesPerTick, sv_send_votes_per_tick, 5, 1, 15, CFGFLAG_SERVER, "Number of vote options being send per tick") + +MACRO_CONFIG_INT(SvRescue, sv_rescue, 0, 0, 1, CFGFLAG_SERVER, "Allow /rescue command so players can teleport themselves out of freeze (setting only works in initial config)") +MACRO_CONFIG_INT(SvRescueDelay, sv_rescue_delay, 1, 0, 1000, CFGFLAG_SERVER, "Number of seconds between two rescues") +MACRO_CONFIG_INT(SvPractice, sv_practice, 1, 0, 1, CFGFLAG_SERVER, "Enable practice mode for teams. Means you can use /rescue, but in turn your rank doesn't count.") + +MACRO_CONFIG_INT(ClVideoPauseWithDemo, cl_video_pausewithdemo, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Pause video rendering when demo playing pause") +MACRO_CONFIG_INT(ClVideoShowhud, cl_video_showhud, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD when rendering video") +MACRO_CONFIG_INT(ClVideoShowChat, cl_video_showchat, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show chat when rendering video") +MACRO_CONFIG_INT(ClVideoSndEnable, cl_video_sound_enable, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Use sound when rendering video") +MACRO_CONFIG_INT(ClVideoShowHookCollOther, cl_video_show_hook_coll_other, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show other players' hook collision lines when rendering video") +MACRO_CONFIG_INT(ClVideoShowDirection, cl_video_show_direction, 0, 0, 3, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show players' key presses when rendering video (1 = other players', 2 = also your own, 3 = only your own)") +MACRO_CONFIG_INT(ClVideoX264Crf, cl_video_crf, 18, 0, 51, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Set crf when encode video with libx264 (0 for highest quality, 51 for lowest)") +MACRO_CONFIG_INT(ClVideoX264Preset, cl_video_preset, 5, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Set preset when encode video with libx264, default is 5 (medium), 0 is ultrafast, 9 is placebo (the slowest, not recommend)") + +// debug +#ifdef CONF_DEBUG +MACRO_CONFIG_INT(DbgDummies, dbg_dummies, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Add debug dummies to server (Debug build only)") +#endif + +MACRO_CONFIG_INT(DbgTuning, dbg_tuning, 0, 0, 2, CFGFLAG_CLIENT, "Display information about the tuning parameters that affect the own player (0 = off, 1 = show changed, 2 = show all)") MACRO_CONFIG_STR(PlayerName, player_name, 16, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Name of the player") MACRO_CONFIG_STR(PlayerClan, player_clan, 12, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Clan of the player") @@ -452,3 +672,7 @@ MACRO_CONFIG_INT(GfxRenderThreadCount, gfx_render_thread_count, 3, 0, 0, CFGFLAG MACRO_CONFIG_INT(GfxDriverIsBlocked, gfx_driver_is_blocked, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "If 1, the current driver is in a blocked error state.") MACRO_CONFIG_INT(ClVideoRecorderFPS, cl_video_recorder_fps, 60, 1, 1000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "At which FPS the videorecorder should record demos.") + +/* + * Add config variables for mods below this comment to avoid merge conflicts. + */ diff --git a/src/game/variables.h b/src/game/variables.h deleted file mode 100644 index 30bd7b8ea6b..00000000000 --- a/src/game/variables.h +++ /dev/null @@ -1,230 +0,0 @@ - -/* If you are missing that file, acquire a complete release at teeworlds.com. */ -#ifndef GAME_VARIABLES_H -#define GAME_VARIABLES_H -#undef GAME_VARIABLES_H // this file will be included several times - -#ifndef MACRO_CONFIG_INT -#error "The config macros must be defined" -#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Save, Desc) ; -#define MACRO_CONFIG_COL(Name, ScriptName, Def, Save, Desc) ; -#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Save, Desc) ; -#endif - -// client -MACRO_CONFIG_INT(ClPredict, cl_predict, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict client movements") -MACRO_CONFIG_INT(ClPredictDummy, cl_predict_dummy, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict dummy movements") -MACRO_CONFIG_INT(ClAntiPingLimit, cl_antiping_limit, 0, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Antiping limit (0 to disable)") -MACRO_CONFIG_INT(ClAntiPing, cl_antiping, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable antiping, i. e. more aggressive prediction.") -MACRO_CONFIG_INT(ClAntiPingPlayers, cl_antiping_players, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict other player's movement more aggressively (only enabled if cl_antiping is set to 1)") -MACRO_CONFIG_INT(ClAntiPingGrenade, cl_antiping_grenade, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict grenades (only enabled if cl_antiping is set to 1)") -MACRO_CONFIG_INT(ClAntiPingWeapons, cl_antiping_weapons, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict weapon projectiles (only enabled if cl_antiping is set to 1)") -MACRO_CONFIG_INT(ClAntiPingSmooth, cl_antiping_smooth, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Make the prediction of other player's movement smoother") -MACRO_CONFIG_INT(ClAntiPingGunfire, cl_antiping_gunfire, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict gunfire and show predicted weapon physics (with cl_antiping_grenade 1 and cl_antiping_weapons 1)") -MACRO_CONFIG_INT(ClPredictionMargin, cl_prediction_margin, 10, 1, 300, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Prediction margin in ms (adds latency, can reduce lag from ping jumps)") -MACRO_CONFIG_INT(ClSubTickAiming, cl_sub_tick_aiming, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Send aiming data at sub-tick accuracy") - -MACRO_CONFIG_INT(ClNameplates, cl_nameplates, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show name plates") -MACRO_CONFIG_INT(ClAfkEmote, cl_afk_emote, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show zzz emote next to afk players") -MACRO_CONFIG_INT(ClNameplatesAlways, cl_nameplates_always, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Always show name plates disregarding of distance") -MACRO_CONFIG_INT(ClNameplatesTeamcolors, cl_nameplates_teamcolors, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Use team colors for name plates") -MACRO_CONFIG_INT(ClNameplatesSize, cl_nameplates_size, 50, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Size of the name plates from 0 to 100%") -MACRO_CONFIG_INT(ClNameplatesClan, cl_nameplates_clan, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show clan in name plates") -MACRO_CONFIG_INT(ClNameplatesClanSize, cl_nameplates_clan_size, 30, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Size of the clan plates from 0 to 100%") -MACRO_CONFIG_INT(ClNameplatesIDs, cl_nameplates_ids, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show IDs in name plates") -MACRO_CONFIG_INT(ClNameplatesOwn, cl_nameplates_own, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show own name plate (useful for demo recording)") -MACRO_CONFIG_INT(ClNameplatesFriendMark, cl_nameplates_friendmark, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show friend mark (♥) in name plates") -MACRO_CONFIG_INT(ClNameplatesStrong, cl_nameplates_strong, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show strong/weak in name plates (0 - off, 1 - icons, 2 - icons + numbers)") -MACRO_CONFIG_INT(ClTextEntities, cl_text_entities, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Render textual entity data") -MACRO_CONFIG_INT(ClTextEntitiesSize, cl_text_entities_size, 100, 1, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Size of textual entity data from 1 to 100%") -MACRO_CONFIG_INT(ClStreamerMode, cl_streamer_mode, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Censor sensitive information such as /save password") - -MACRO_CONFIG_COL(ClAuthedPlayerColor, cl_authed_player_color, 5898211, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Color of name of authenticated player in scoreboard") -MACRO_CONFIG_COL(ClSameClanColor, cl_same_clan_color, 5898211, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Clan color of players with the same clan as you in scoreboard.") - -MACRO_CONFIG_INT(ClEnablePingColor, cl_enable_ping_color, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Whether ping is colored in scoreboard.") -MACRO_CONFIG_INT(ClAutoswitchWeapons, cl_autoswitch_weapons, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Auto switch weapon on pickup") -MACRO_CONFIG_INT(ClAutoswitchWeaponsOutOfAmmo, cl_autoswitch_weapons_out_of_ammo, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Auto switch weapon when out of ammo") - -MACRO_CONFIG_INT(ClShowhud, cl_showhud, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD") -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(ClShowhudDDRace, cl_showhud_ddrace, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show ingame HUD (DDRace HUD)") -MACRO_CONFIG_INT(ClShowhudJumpsIndicator, cl_showhud_jumps_indicator, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show ingame HUD (Jumps you have and have used)") -MACRO_CONFIG_INT(ClShowFreezeBars, cl_show_freeze_bars, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Whether to show a freeze bar under frozen players to indicate the thaw time") -MACRO_CONFIG_INT(ClFreezeBarsAlphaInsideFreeze, cl_freezebars_alpha_inside_freeze, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Opacity of freeze bars inside freeze (0 invisible, 100 fully visible)") -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") -MACRO_CONFIG_INT(ClShowChat, cl_showchat, 1, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show chat (2 to always show large chat area)") -MACRO_CONFIG_INT(ClShowChatFriends, cl_show_chat_friends, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show only chat messages from friends") -MACRO_CONFIG_INT(ClShowChatSystem, cl_show_chat_system, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show chat messages from the server") -MACRO_CONFIG_INT(ClShowKillMessages, cl_showkillmessages, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show kill messages") -MACRO_CONFIG_INT(ClShowVotesAfterVoting, cl_show_votes_after_voting, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show votes window after voting") -MACRO_CONFIG_INT(ClShowLocalTimeAlways, cl_show_local_time_always, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Always show local time") -MACRO_CONFIG_INT(ClShowfps, cl_showfps, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame FPS counter") -MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame prediction time in milliseconds") -MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes") -MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last") - -MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator") -MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded") - -MACRO_CONFIG_INT(ClWarningTeambalance, cl_warning_teambalance, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Warn about team balance") - -MACRO_CONFIG_INT(ClMouseDeadzone, cl_mouse_deadzone, 0, 0, 3000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Deadzone for the camera to follow the cursor") -MACRO_CONFIG_INT(ClMouseFollowfactor, cl_mouse_followfactor, 0, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Factor for the camera to follow the cursor") -MACRO_CONFIG_INT(ClMouseMaxDistance, cl_mouse_max_distance, 400, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Maximum cursor distance") -MACRO_CONFIG_INT(ClMouseMinDistance, cl_mouse_min_distance, 0, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Minimum cursor distance") - -MACRO_CONFIG_INT(ClDyncam, cl_dyncam, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Enable dyncam") -MACRO_CONFIG_INT(ClDyncamMaxDistance, cl_dyncam_max_distance, 1000, 0, 2000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Maximum dynamic camera cursor distance") -MACRO_CONFIG_INT(ClDyncamMinDistance, cl_dyncam_min_distance, 0, 0, 2000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Minimum dynamic camera cursor distance") -MACRO_CONFIG_INT(ClDyncamMousesens, cl_dyncam_mousesens, 0, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Mouse sens used when dyncam is toggled on") -MACRO_CONFIG_INT(ClDyncamDeadzone, cl_dyncam_deadzone, 300, 1, 1300, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Deadzone for the dynamic camera to follow the cursor") -MACRO_CONFIG_INT(ClDyncamFollowFactor, cl_dyncam_follow_factor, 60, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Factor for the dynamic camera to follow the cursor") - -MACRO_CONFIG_INT(ClDyncamSmoothness, cl_dyncam_smoothness, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Transition amount of the camera movement, 0=instant, 100=slow and smooth") -MACRO_CONFIG_INT(ClDyncamStabilizing, cl_dyncam_stabilizing, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Amount of camera slowdown during fast cursor movement. High value can cause delay in camera movement") - -MACRO_CONFIG_INT(ClMultiViewSensitivity, cl_multiview_sensitivity, 100, 0, 200, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Set how fast the camera will move to the desired location (higher = faster)") -MACRO_CONFIG_INT(ClMultiViewZoomSmoothness, cl_multiview_zoom_smoothness, 1300, 50, 5000, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Set the smoothness of the multi-view zoom (in ms, higher = slower)") - -MACRO_CONFIG_INT(EdAutosaveInterval, ed_autosave_interval, 10, 0, 240, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interval in minutes at which a copy of the current editor map is automatically saved to the 'auto' folder (0 for off)") -MACRO_CONFIG_INT(EdAutosaveMax, ed_autosave_max, 10, 0, 1000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Maximum number of autosaves that are kept per map name (0 = no limit)") -MACRO_CONFIG_INT(EdSmoothZoomTime, ed_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth zoom animation in the editor in ms (0 for off)") -MACRO_CONFIG_INT(EdLimitMaxZoomLevel, ed_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming in the editor should be limited or not (0 = no limit)") -MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target") -MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show pressed keys") - -MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show welcome message indicating the first launch of the client") -MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long to show the server message of the day") - -// http map download -MACRO_CONFIG_STR(ClMapDownloadUrl, cl_map_download_url, 100, "https://maps.ddnet.org", CFGFLAG_CLIENT | CFGFLAG_SAVE, "URL used to download maps (can start with http:// or https://)") -MACRO_CONFIG_INT(ClMapDownloadConnectTimeoutMs, cl_map_download_connect_timeout_ms, 2000, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: timeout for the connect phase in milliseconds (0 to disable)") -MACRO_CONFIG_INT(ClMapDownloadLowSpeedLimit, cl_map_download_low_speed_limit, 4000, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: Set low speed limit in bytes per second (0 to disable)") -MACRO_CONFIG_INT(ClMapDownloadLowSpeedTime, cl_map_download_low_speed_time, 3, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: Set low speed limit time period (0 to disable)") - -MACRO_CONFIG_STR(ClLanguagefile, cl_languagefile, 255, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "What language file to use") -MACRO_CONFIG_STR(ClSkinDownloadUrl, cl_skin_download_url, 100, "https://skins.ddnet.org/skin/", CFGFLAG_CLIENT | CFGFLAG_SAVE, "URL used to download skins") -MACRO_CONFIG_STR(ClSkinCommunityDownloadUrl, cl_skin_community_download_url, 100, "https://skins.ddnet.org/skin/community/", CFGFLAG_CLIENT | CFGFLAG_SAVE, "URL used to download community skins") -MACRO_CONFIG_INT(ClVanillaSkinsOnly, cl_vanilla_skins_only, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Only show skins available in Vanilla Teeworlds") -MACRO_CONFIG_INT(ClDownloadSkins, cl_download_skins, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Download skins from cl_skin_download_url on-the-fly") -MACRO_CONFIG_INT(ClDownloadCommunitySkins, cl_download_community_skins, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Allow to download skins created by the community. Uses cl_skin_community_download_url instead of cl_skin_download_url for the download") -MACRO_CONFIG_INT(ClAutoStatboardScreenshot, cl_auto_statboard_screenshot, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Automatically take game over statboard screenshot") -MACRO_CONFIG_INT(ClAutoStatboardScreenshotMax, cl_auto_statboard_screenshot_max, 10, 0, 1000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Maximum number of automatically created statboard screenshots (0 = no limit)") - -MACRO_CONFIG_INT(ClDefaultZoom, cl_default_zoom, 10, 0, 20, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Default zoom level") -MACRO_CONFIG_INT(ClSmoothZoomTime, cl_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth zoom animation ingame in ms (0 for off)") -MACRO_CONFIG_INT(ClLimitMaxZoomLevel, cl_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming ingame should be limited or not (0 = no limit)") - -MACRO_CONFIG_INT(ClPlayerUseCustomColor, player_use_custom_color, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors") -MACRO_CONFIG_COL(ClPlayerColorBody, player_color_body, 65408, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player body color") -MACRO_CONFIG_COL(ClPlayerColorFeet, player_color_feet, 65408, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player feet color") -MACRO_CONFIG_STR(ClPlayerSkin, player_skin, 24, "default", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin") -MACRO_CONFIG_INT(ClPlayerDefaultEyes, player_default_eyes, 0, 0, 5, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Player eyes when joining server. 0 = normal, 1 = pain, 2 = happy, 3 = surprise, 4 = angry, 5 = blink") -MACRO_CONFIG_STR(ClSkinPrefix, cl_skin_prefix, 12, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Replace the skins by skins with this prefix (e.g. kitty, santa)") -MACRO_CONFIG_INT(ClFatSkins, cl_fat_skins, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable fat skins") - -MACRO_CONFIG_INT(UiPage, ui_page, 6, 6, 10, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface page") -MACRO_CONFIG_INT(UiSettingsPage, ui_settings_page, 0, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface settings page") -MACRO_CONFIG_INT(UiToolboxPage, ui_toolbox_page, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Toolbox page") -MACRO_CONFIG_STR(UiServerAddress, ui_server_address, 1024, "localhost:8303", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Interface server address") -MACRO_CONFIG_INT(UiMousesens, ui_mousesens, 200, 1, 100000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Mouse sensitivity for menus/editor") -MACRO_CONFIG_INT(UiControllerSens, ui_controller_sens, 100, 1, 100000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Controller sensitivity for menus/editor") -MACRO_CONFIG_INT(UiSmoothScrollTime, ui_smooth_scroll_time, 500, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth scrolling animation in menus/editor in ms (0 for off)") - -MACRO_CONFIG_COL(UiColor, ui_color, 0xE4A046AF, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLALPHA, "Interface color") // 160 70 175 228 hasalpha - -MACRO_CONFIG_INT(UiColorizePing, ui_colorize_ping, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight ping") -MACRO_CONFIG_INT(UiColorizeGametype, ui_colorize_gametype, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight gametype") - -MACRO_CONFIG_INT(UiCloseWindowAfterChangingSetting, ui_close_window_after_changing_setting, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Close window after changing setting") -MACRO_CONFIG_INT(UiUnreadNews, ui_unread_news, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Whether there is unread news") - -MACRO_CONFIG_INT(GfxNoclip, gfx_noclip, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Disable clipping") - -// dummy -MACRO_CONFIG_STR(ClDummyName, dummy_name, 16, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Name of the dummy") -MACRO_CONFIG_STR(ClDummyClan, dummy_clan, 12, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Clan of the dummy") -MACRO_CONFIG_INT(ClDummyCountry, dummy_country, -1, -1, 1000, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Country of the Dummy") -MACRO_CONFIG_INT(ClDummyUseCustomColor, dummy_use_custom_color, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors") -MACRO_CONFIG_COL(ClDummyColorBody, dummy_color_body, 65408, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy body color") -MACRO_CONFIG_COL(ClDummyColorFeet, dummy_color_feet, 65408, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy feet color") -MACRO_CONFIG_STR(ClDummySkin, dummy_skin, 24, "default", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin") -MACRO_CONFIG_INT(ClDummyDefaultEyes, dummy_default_eyes, 0, 0, 5, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Dummy eyes when joining server. 0 = normal, 1 = pain, 2 = happy, 3 = surprise, 4 = angry, 5 = blink") -MACRO_CONFIG_INT(ClDummy, cl_dummy, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "0 - player / 1 - dummy") -MACRO_CONFIG_INT(ClDummyHammer, cl_dummy_hammer, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy is hammering for a hammerfly") -MACRO_CONFIG_INT(ClDummyResetOnSwitch, cl_dummy_resetonswitch, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Whether dummy or player should stop pressing keys when you switch. 0 = off, 1 = dummy, 2 = player") -MACRO_CONFIG_INT(ClDummyRestoreWeapon, cl_dummy_restore_weapon, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Whether dummy should switch to last weapon after hammerfly") -MACRO_CONFIG_INT(ClDummyCopyMoves, cl_dummy_copy_moves, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy should copy your moves") - -// more controllable dummy command -MACRO_CONFIG_INT(ClDummyControl, cl_dummy_control, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether can you control dummy at the same time (cl_dummy_jump, cl_dummy_fire, cl_dummy_hook)") -MACRO_CONFIG_INT(ClDummyJump, cl_dummy_jump, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy is jumping (requires cl_dummy_control 1)") -MACRO_CONFIG_INT(ClDummyFire, cl_dummy_fire, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy is firing (requires cl_dummy_control 1)") -MACRO_CONFIG_INT(ClDummyHook, cl_dummy_hook, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Whether dummy is hooking (requires cl_dummy_control 1)") - -// start menu -MACRO_CONFIG_INT(ClShowStartMenuImages, cl_show_start_menu_images, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show start menu images") -MACRO_CONFIG_INT(ClSkipStartMenu, cl_skip_start_menu, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Skip the start menu") - -// server -MACRO_CONFIG_INT(SvWarmup, sv_warmup, 0, 0, 0, CFGFLAG_SERVER, "Number of seconds to do warmup before round starts") -MACRO_CONFIG_STR(SvMotd, sv_motd, 900, "", CFGFLAG_SERVER, "Message of the day to display for the clients") -MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "ddnet", CFGFLAG_SAVE | CFGFLAG_SERVER, "Game type (ddnet, mod)") -MACRO_CONFIG_INT(SvTournamentMode, sv_tournament_mode, 0, 0, 1, CFGFLAG_SERVER, "Tournament mode. When enabled, players joins the server as spectator") -MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection") - -MACRO_CONFIG_INT(SvSpectatorSlots, sv_spectator_slots, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Number of slots to reserve for spectators") -MACRO_CONFIG_INT(SvInactiveKickTime, sv_inactivekick_time, 0, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before taking care of inactive players") -MACRO_CONFIG_INT(SvInactiveKick, sv_inactivekick, 0, 0, 2, CFGFLAG_SERVER, "How to deal with inactive players (0=move to spectator, 1=move to free spectator slot/kick, 2=kick)") - -MACRO_CONFIG_INT(SvStrictSpectateMode, sv_strict_spectate_mode, 0, 0, 1, CFGFLAG_SERVER, "Restricts information in spectator mode") -MACRO_CONFIG_INT(SvVoteSpectate, sv_vote_spectate, 1, 0, 1, CFGFLAG_SERVER, "Allow voting to move players to spectators") -MACRO_CONFIG_INT(SvVoteSpectateRejoindelay, sv_vote_spectate_rejoindelay, 3, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before a player can rejoin after being moved to spectators by vote") -MACRO_CONFIG_INT(SvVoteKick, sv_vote_kick, 1, 0, 1, CFGFLAG_SERVER, "Allow voting to kick players") -MACRO_CONFIG_INT(SvVoteKickMin, sv_vote_kick_min, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Minimum number of players required to start a kick vote") -MACRO_CONFIG_INT(SvVoteKickBantime, sv_vote_kick_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time in seconds to ban a player if kicked by vote. 0 makes it just use kick") -MACRO_CONFIG_INT(SvJoinVoteDelay, sv_join_vote_delay, 300, 0, 1000, CFGFLAG_SERVER, "Add a delay before recently joined players can call any vote or participate in a kick/spec vote (in seconds)") -MACRO_CONFIG_INT(SvOldTeleportWeapons, sv_old_teleport_weapons, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Teleporting of all weapons (deprecated, use special entities instead)") -MACRO_CONFIG_INT(SvOldTeleportHook, sv_old_teleport_hook, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Hook through teleporter (deprecated, use special entities instead)") -MACRO_CONFIG_INT(SvTeleportHoldHook, sv_teleport_hold_hook, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Hold hook when teleported") -MACRO_CONFIG_INT(SvTeleportLoseWeapons, sv_teleport_lose_weapons, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Lose weapons when teleported (useful for some race maps)") -MACRO_CONFIG_INT(SvDeepfly, sv_deepfly, 1, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Allow fire non auto weapons when deep") -MACRO_CONFIG_INT(SvDestroyBulletsOnDeath, sv_destroy_bullets_on_death, 1, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Destroy bullets when their owner dies") -MACRO_CONFIG_INT(SvDestroyLasersOnDeath, sv_destroy_lasers_on_death, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Destroy lasers when their owner dies") - -MACRO_CONFIG_INT(SvMapUpdateRate, sv_mapupdaterate, 5, 1, 100, CFGFLAG_SERVER, "64 player id <-> vanilla id players map update rate") - -MACRO_CONFIG_STR(SvServerType, sv_server_type, 64, "none", CFGFLAG_SERVER, "Type of the server (novice, moderate, ...)") - -MACRO_CONFIG_INT(SvSendVotesPerTick, sv_send_votes_per_tick, 5, 1, 15, CFGFLAG_SERVER, "Number of vote options being send per tick") - -MACRO_CONFIG_INT(SvRescue, sv_rescue, 0, 0, 1, CFGFLAG_SERVER, "Allow /rescue command so players can teleport themselves out of freeze (setting only works in initial config)") -MACRO_CONFIG_INT(SvRescueDelay, sv_rescue_delay, 1, 0, 1000, CFGFLAG_SERVER, "Number of seconds between two rescues") -MACRO_CONFIG_INT(SvPractice, sv_practice, 1, 0, 1, CFGFLAG_SERVER, "Enable practice mode for teams. Means you can use /rescue, but in turn your rank doesn't count.") - -MACRO_CONFIG_INT(ClVideoPauseWithDemo, cl_video_pausewithdemo, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Pause video rendering when demo playing pause") -MACRO_CONFIG_INT(ClVideoShowhud, cl_video_showhud, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD when rendering video") -MACRO_CONFIG_INT(ClVideoShowChat, cl_video_showchat, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show chat when rendering video") -MACRO_CONFIG_INT(ClVideoSndEnable, cl_video_sound_enable, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Use sound when rendering video") -MACRO_CONFIG_INT(ClVideoShowHookCollOther, cl_video_show_hook_coll_other, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show other players' hook collision lines when rendering video") -MACRO_CONFIG_INT(ClVideoShowDirection, cl_video_show_direction, 0, 0, 3, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show players' key presses when rendering video (1 = other players', 2 = also your own, 3 = only your own)") -MACRO_CONFIG_INT(ClVideoX264Crf, cl_video_crf, 18, 0, 51, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Set crf when encode video with libx264 (0 for highest quality, 51 for lowest)") -MACRO_CONFIG_INT(ClVideoX264Preset, cl_video_preset, 5, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Set preset when encode video with libx264, default is 5 (medium), 0 is ultrafast, 9 is placebo (the slowest, not recommend)") - -// debug -#ifdef CONF_DEBUG -MACRO_CONFIG_INT(DbgDummies, dbg_dummies, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Add debug dummies to server (Debug build only)") -#endif - -MACRO_CONFIG_INT(DbgTuning, dbg_tuning, 0, 0, 2, CFGFLAG_CLIENT, "Display information about the tuning parameters that affect the own player (0 = off, 1 = show changed, 2 = show all)") - -#endif From 8156052cc10b9aecf39ce7b194ff10891520a414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 25 Nov 2023 17:09:14 +0100 Subject: [PATCH 037/198] Refactor name bans, move code to separate class `CNameBans` Unify all code for name bans in new class `CNameBans` in the existing `name_ban.cpp/h` files. The previously global function `IsNameBanned` is now the member function `CNameBans::IsBanned`. The existing name ban tests are extended for the `CNameBans` class. Move `CNameBan` constructor definition to source file to avoid including `system.h` in the header file. Use `bool` instead of `int` for `m_IsSubstring`. Reorder `CNameBan` constructor arguments and remove unnecessary default value. --- src/engine/server/name_ban.cpp | 114 +++++++++++++++++++++++++++++++-- src/engine/server/name_ban.h | 31 +++++---- src/engine/server/server.cpp | 64 +----------------- src/engine/server/server.h | 6 +- src/test/name_ban.cpp | 82 +++++++++++++++++++----- 5 files changed, 198 insertions(+), 99 deletions(-) diff --git a/src/engine/server/name_ban.cpp b/src/engine/server/name_ban.cpp index f53b8d06618..b08922161f5 100644 --- a/src/engine/server/name_ban.cpp +++ b/src/engine/server/name_ban.cpp @@ -1,6 +1,92 @@ #include "name_ban.h" -CNameBan *IsNameBanned(const char *pName, std::vector &vNameBans) +#include + +#include + +CNameBan::CNameBan(const char *pName, const char *pReason, int Distance, bool IsSubstring) : + m_Distance(Distance), m_IsSubstring(IsSubstring) +{ + str_copy(m_aName, pName); + str_copy(m_aReason, pReason); + m_SkeletonLength = str_utf8_to_skeleton(m_aName, m_aSkeleton, std::size(m_aSkeleton)); +} + +void CNameBans::InitConsole(IConsole *pConsole) +{ + m_pConsole = pConsole; + + m_pConsole->Register("name_ban", "s[name] ?i[distance] ?i[is_substring] ?r[reason]", CFGFLAG_SERVER, ConNameBan, this, "Ban a certain nickname"); + m_pConsole->Register("name_unban", "s[name]", CFGFLAG_SERVER, ConNameUnban, this, "Unban a certain nickname"); + m_pConsole->Register("name_bans", "", CFGFLAG_SERVER, ConNameBans, this, "List all name bans"); +} + +void CNameBans::Ban(const char *pName, const char *pReason, const int Distance, const bool IsSubstring) +{ + for(auto &Ban : m_vNameBans) + { + if(str_comp(Ban.m_aName, pName) == 0) + { + if(m_pConsole) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "changed name='%s' distance=%d old_distance=%d is_substring=%d old_is_substring=%d reason='%s' old_reason='%s'", pName, Distance, Ban.m_Distance, IsSubstring, Ban.m_IsSubstring, pReason, Ban.m_aReason); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); + } + str_copy(Ban.m_aReason, pReason); + Ban.m_Distance = Distance; + Ban.m_IsSubstring = IsSubstring; + return; + } + } + + m_vNameBans.emplace_back(pName, pReason, Distance, IsSubstring); + if(m_pConsole) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "added name='%s' distance=%d is_substring=%d reason='%s'", pName, Distance, IsSubstring, pReason); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); + } +} + +void CNameBans::Unban(const char *pName) +{ + auto ToRemove = std::remove_if(m_vNameBans.begin(), m_vNameBans.end(), [pName](const CNameBan &Ban) { return str_comp(Ban.m_aName, pName) == 0; }); + if(ToRemove == m_vNameBans.end()) + { + if(m_pConsole) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "name ban '%s' not found", pName); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); + } + } + else + { + if(m_pConsole) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "removed name='%s' distance=%d is_substring=%d reason='%s'", (*ToRemove).m_aName, (*ToRemove).m_Distance, (*ToRemove).m_IsSubstring, (*ToRemove).m_aReason); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); + } + m_vNameBans.erase(ToRemove, m_vNameBans.end()); + } +} + +void CNameBans::Dump() const +{ + if(!m_pConsole) + return; + + char aBuf[256]; + for(const auto &Ban : m_vNameBans) + { + str_format(aBuf, sizeof(aBuf), "name='%s' distance=%d is_substring=%d reason='%s'", Ban.m_aName, Ban.m_Distance, Ban.m_IsSubstring, Ban.m_aReason); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); + } +} + +const CNameBan *CNameBans::IsBanned(const char *pName) const { char aTrimmed[MAX_NAME_LENGTH]; str_copy(aTrimmed, str_utf8_skip_whitespaces(pName)); @@ -10,12 +96,32 @@ CNameBan *IsNameBanned(const char *pName, std::vector &vNameBans) int SkeletonLength = str_utf8_to_skeleton(aTrimmed, aSkeleton, std::size(aSkeleton)); int aBuffer[MAX_NAME_SKELETON_LENGTH * 2 + 2]; - CNameBan *pResult = nullptr; - for(CNameBan &Ban : vNameBans) + const CNameBan *pResult = nullptr; + for(const CNameBan &Ban : m_vNameBans) { int Distance = str_utf32_dist_buffer(aSkeleton, SkeletonLength, Ban.m_aSkeleton, Ban.m_SkeletonLength, aBuffer, std::size(aBuffer)); - if(Distance <= Ban.m_Distance || (Ban.m_IsSubstring == 1 && str_utf8_find_nocase(pName, Ban.m_aName))) + if(Distance <= Ban.m_Distance || (Ban.m_IsSubstring && str_utf8_find_nocase(pName, Ban.m_aName))) pResult = &Ban; } return pResult; } + +void CNameBans::ConNameBan(IConsole::IResult *pResult, void *pUser) +{ + const char *pName = pResult->GetString(0); + const char *pReason = pResult->NumArguments() > 3 ? pResult->GetString(3) : ""; + const int Distance = pResult->NumArguments() > 1 ? pResult->GetInteger(1) : str_length(pName) / 3; + const bool IsSubstring = pResult->NumArguments() > 2 ? pResult->GetInteger(2) != 0 : false; + static_cast(pUser)->Ban(pName, pReason, Distance, IsSubstring); +} + +void CNameBans::ConNameUnban(IConsole::IResult *pResult, void *pUser) +{ + const char *pName = pResult->GetString(0); + static_cast(pUser)->Unban(pName); +} + +void CNameBans::ConNameBans(IConsole::IResult *pResult, void *pUser) +{ + static_cast(pUser)->Dump(); +} diff --git a/src/engine/server/name_ban.h b/src/engine/server/name_ban.h index 1d2ca20d404..ff58630a205 100644 --- a/src/engine/server/name_ban.h +++ b/src/engine/server/name_ban.h @@ -1,7 +1,7 @@ #ifndef ENGINE_SERVER_NAME_BAN_H #define ENGINE_SERVER_NAME_BAN_H -#include +#include #include #include @@ -15,22 +15,31 @@ enum class CNameBan { public: - CNameBan() {} - CNameBan(const char *pName, int Distance, int IsSubstring, const char *pReason = "") : - m_Distance(Distance), m_IsSubstring(IsSubstring) - { - str_copy(m_aName, pName); - m_SkeletonLength = str_utf8_to_skeleton(m_aName, m_aSkeleton, std::size(m_aSkeleton)); - str_copy(m_aReason, pReason); - } + CNameBan(const char *pName, const char *pReason, int Distance, bool IsSubstring); + char m_aName[MAX_NAME_LENGTH]; char m_aReason[MAX_NAMEBAN_REASON_LENGTH]; int m_aSkeleton[MAX_NAME_SKELETON_LENGTH]; int m_SkeletonLength; int m_Distance; - int m_IsSubstring; + bool m_IsSubstring; }; -CNameBan *IsNameBanned(const char *pName, std::vector &vNameBans); +class CNameBans +{ + IConsole *m_pConsole = nullptr; + std::vector m_vNameBans; + + static void ConNameBan(IConsole::IResult *pResult, void *pUser); + static void ConNameUnban(IConsole::IResult *pResult, void *pUser); + static void ConNameBans(IConsole::IResult *pResult, void *pUser); + +public: + void InitConsole(IConsole *pConsole); + void Ban(const char *pName, const char *pReason, const int Distance, const bool IsSubstring); + void Unban(const char *pName); + void Dump() const; + const CNameBan *IsBanned(const char *pName) const; +}; #endif // ENGINE_SERVER_NAME_BAN_H diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 3b2f9674323..06daf419525 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -403,7 +403,7 @@ bool CServer::SetClientNameImpl(int ClientID, const char *pNameRequest, bool Set if(m_aClients[ClientID].m_State < CClient::STATE_READY) return false; - CNameBan *pBanned = IsNameBanned(pNameRequest, m_vNameBans); + const CNameBan *pBanned = m_NameBans.IsBanned(pNameRequest); if(pBanned) { if(m_aClients[ClientID].m_State == CClient::STATE_READY && Set) @@ -3352,63 +3352,6 @@ void CServer::ConAuthList(IConsole::IResult *pResult, void *pUser) pManager->ListKeys(ListKeysCallback, pThis); } -void CServer::ConNameBan(IConsole::IResult *pResult, void *pUser) -{ - CServer *pThis = (CServer *)pUser; - char aBuf[256]; - const char *pName = pResult->GetString(0); - const char *pReason = pResult->NumArguments() > 3 ? pResult->GetString(3) : ""; - int Distance = pResult->NumArguments() > 1 ? pResult->GetInteger(1) : str_length(pName) / 3; - int IsSubstring = pResult->NumArguments() > 2 ? pResult->GetInteger(2) : 0; - - for(auto &Ban : pThis->m_vNameBans) - { - if(str_comp(Ban.m_aName, pName) == 0) - { - str_format(aBuf, sizeof(aBuf), "changed name='%s' distance=%d old_distance=%d is_substring=%d old_is_substring=%d reason='%s' old_reason='%s'", pName, Distance, Ban.m_Distance, IsSubstring, Ban.m_IsSubstring, pReason, Ban.m_aReason); - pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); - Ban.m_Distance = Distance; - Ban.m_IsSubstring = IsSubstring; - str_copy(Ban.m_aReason, pReason); - return; - } - } - - pThis->m_vNameBans.emplace_back(pName, Distance, IsSubstring, pReason); - str_format(aBuf, sizeof(aBuf), "added name='%s' distance=%d is_substring=%d reason='%s'", pName, Distance, IsSubstring, pReason); - pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); -} - -void CServer::ConNameUnban(IConsole::IResult *pResult, void *pUser) -{ - CServer *pThis = (CServer *)pUser; - const char *pName = pResult->GetString(0); - - for(size_t i = 0; i < pThis->m_vNameBans.size(); i++) - { - CNameBan *pBan = &pThis->m_vNameBans[i]; - if(str_comp(pBan->m_aName, pName) == 0) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "removed name='%s' distance=%d is_substring=%d reason='%s'", pBan->m_aName, pBan->m_Distance, pBan->m_IsSubstring, pBan->m_aReason); - pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); - pThis->m_vNameBans.erase(pThis->m_vNameBans.begin() + i); - } - } -} - -void CServer::ConNameBans(IConsole::IResult *pResult, void *pUser) -{ - CServer *pThis = (CServer *)pUser; - - for(auto &Ban : pThis->m_vNameBans) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "name='%s' distance=%d is_substring=%d reason='%s'", Ban.m_aName, Ban.m_Distance, Ban.m_IsSubstring, Ban.m_aReason); - pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf); - } -} - void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser) { CServer *pThis = static_cast(pUser); @@ -3852,10 +3795,6 @@ void CServer::RegisterCommands() Console()->Register("auth_remove", "s[ident]", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, ConAuthRemove, this, "Remove a rcon key"); Console()->Register("auth_list", "", CFGFLAG_SERVER, ConAuthList, this, "List all rcon keys"); - Console()->Register("name_ban", "s[name] ?i[distance] ?i[is_substring] ?r[reason]", CFGFLAG_SERVER, ConNameBan, this, "Ban a certain nickname"); - Console()->Register("name_unban", "s[name]", CFGFLAG_SERVER, ConNameUnban, this, "Unban a certain nickname"); - Console()->Register("name_bans", "", CFGFLAG_SERVER, ConNameBans, this, "List all name bans"); - RustVersionRegister(*Console()); Console()->Chain("sv_name", ConchainSpecialInfoupdate, this); @@ -3880,6 +3819,7 @@ void CServer::RegisterCommands() // register console commands in sub parts m_ServerBan.InitServerBan(Console(), Storage(), this); + m_NameBans.InitConsole(Console()); m_pGameServer->OnConsoleInit(); } diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 3dd469b4956..e36770edb62 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -282,7 +282,7 @@ class CServer : public IServer char m_aErrorShutdownReason[128]; - std::vector m_vNameBans; + CNameBans m_NameBans; size_t m_AnnouncementLastLine; std::vector m_vAnnouncements; @@ -429,10 +429,6 @@ class CServer : public IServer static void ConAuthRemove(IConsole::IResult *pResult, void *pUser); static void ConAuthList(IConsole::IResult *pResult, void *pUser); - static void ConNameBan(IConsole::IResult *pResult, void *pUser); - static void ConNameUnban(IConsole::IResult *pResult, void *pUser); - static void ConNameBans(IConsole::IResult *pResult, void *pUser); - // console commands for sqlmasters static void ConAddSqlServer(IConsole::IResult *pResult, void *pUserData); static void ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData); diff --git a/src/test/name_ban.cpp b/src/test/name_ban.cpp index 2eb765973d0..18bd2a657e4 100644 --- a/src/test/name_ban.cpp +++ b/src/test/name_ban.cpp @@ -4,29 +4,77 @@ TEST(NameBan, Empty) { - std::vector vBans; - EXPECT_FALSE(IsNameBanned("", vBans)); - EXPECT_FALSE(IsNameBanned("abc", vBans)); + CNameBans Bans; + EXPECT_FALSE(Bans.IsBanned("")); + EXPECT_FALSE(Bans.IsBanned("abc")); +} + +TEST(NameBan, BanInfo) +{ + CNameBans Bans; + + Bans.Ban("abc", "old reason", 1, false); + { + const CNameBan *pOld = Bans.IsBanned("abc"); + ASSERT_TRUE(pOld); + EXPECT_STREQ(pOld->m_aName, "abc"); + EXPECT_STREQ(pOld->m_aReason, "old reason"); + EXPECT_EQ(pOld->m_Distance, 1); + EXPECT_EQ(pOld->m_IsSubstring, false); + } + + // Update existing name ban + Bans.Ban("abc", "new reason", 2, true); + { + const CNameBan *pNew = Bans.IsBanned("abc"); + ASSERT_TRUE(pNew); + EXPECT_STREQ(pNew->m_aName, "abc"); + EXPECT_STREQ(pNew->m_aReason, "new reason"); + EXPECT_EQ(pNew->m_Distance, 2); + EXPECT_EQ(pNew->m_IsSubstring, true); + } } TEST(NameBan, Equality) { - std::vector vBans; - vBans.emplace_back("abc", 0, 0); - EXPECT_TRUE(IsNameBanned("abc", vBans)); - EXPECT_TRUE(IsNameBanned(" abc", vBans)); - EXPECT_TRUE(IsNameBanned("abc ", vBans)); - EXPECT_TRUE(IsNameBanned("abc foo", vBans)); // Maximum name length. - EXPECT_TRUE(IsNameBanned("äbc", vBans)); // Confusables - EXPECT_FALSE(IsNameBanned("def", vBans)); - EXPECT_FALSE(IsNameBanned("abcdef", vBans)); + CNameBans Bans; + Bans.Ban("abc", "", 0, false); + EXPECT_TRUE(Bans.IsBanned("abc")); + EXPECT_TRUE(Bans.IsBanned(" abc")); + EXPECT_TRUE(Bans.IsBanned("abc ")); + EXPECT_TRUE(Bans.IsBanned("abc foo")); // Maximum name length. + EXPECT_TRUE(Bans.IsBanned("äbc")); // Confusables + EXPECT_FALSE(Bans.IsBanned("def")); + EXPECT_FALSE(Bans.IsBanned("abcdef")); } TEST(NameBan, Substring) { - std::vector vBans; - vBans.emplace_back("xyz", 0, 1); - EXPECT_TRUE(IsNameBanned("abcxyz", vBans)); - EXPECT_TRUE(IsNameBanned("abcxyzdef", vBans)); - EXPECT_FALSE(IsNameBanned("abcdef", vBans)); + CNameBans Bans; + Bans.Ban("xyz", "", 0, true); + EXPECT_TRUE(Bans.IsBanned("abcxyz")); + EXPECT_TRUE(Bans.IsBanned("abcxyzdef")); + EXPECT_FALSE(Bans.IsBanned("abcdef")); +} + +TEST(NameBan, Unban) +{ + CNameBans Bans; + Bans.Ban("abc", "", 0, false); + Bans.Ban("xyz", "", 0, false); + EXPECT_TRUE(Bans.IsBanned("abc")); + EXPECT_TRUE(Bans.IsBanned("xyz")); + Bans.Unban("abc"); + EXPECT_FALSE(Bans.IsBanned("abc")); + EXPECT_TRUE(Bans.IsBanned("xyz")); + Bans.Unban("xyz"); + EXPECT_FALSE(Bans.IsBanned("abc")); + EXPECT_FALSE(Bans.IsBanned("xyz")); +} + +TEST(NameBan, UnbanNotFound) +{ + // Try to remove a name ban that does not exist + CNameBans Bans; + Bans.Unban("abc"); } From 794e6b750b495148729b92ae93b035d8fde66c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 25 Nov 2023 18:10:24 +0100 Subject: [PATCH 038/198] Also apply name bans to clans, trim space from clans Apply the name bans system also to player clans, meaning players joining with banned clan names are kicked and changing the clan to a banned name while ingame has no effect. Additionally, trim UTF-8 whitespace from beginning and end of clan. This was already done for player names but not for clans. Closes #7516. --- src/engine/server.h | 1 + src/engine/server/server.cpp | 51 ++++++++++++++++++++++++++++++--- src/engine/server/server.h | 2 ++ src/game/server/gamecontext.cpp | 11 +++++-- 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/engine/server.h b/src/engine/server.h index 55c5cc7a48f..850b4f11ae1 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -204,6 +204,7 @@ class IServer : public IInterface virtual void GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, SHA256_DIGEST *pSha256, int *pMapCrc) = 0; virtual bool WouldClientNameChange(int ClientID, const char *pNameRequest) = 0; + virtual bool WouldClientClanChange(int ClientID, const char *pClanRequest) = 0; virtual void SetClientName(int ClientID, const char *pName) = 0; virtual void SetClientClan(int ClientID, const char *pClan) = 0; virtual void SetClientCountry(int ClientID, int Country) = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 06daf419525..359363a69ec 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -452,11 +452,57 @@ bool CServer::SetClientNameImpl(int ClientID, const char *pNameRequest, bool Set return Changed; } +bool CServer::SetClientClanImpl(int ClientID, const char *pClanRequest, bool Set) +{ + dbg_assert(0 <= ClientID && ClientID < MAX_CLIENTS, "invalid client id"); + if(m_aClients[ClientID].m_State < CClient::STATE_READY) + return false; + + const CNameBan *pBanned = m_NameBans.IsBanned(pClanRequest); + if(pBanned) + { + if(m_aClients[ClientID].m_State == CClient::STATE_READY && Set) + { + char aBuf[256]; + if(pBanned->m_aReason[0]) + { + str_format(aBuf, sizeof(aBuf), "Kicked (your clan is banned: %s)", pBanned->m_aReason); + } + else + { + str_copy(aBuf, "Kicked (your clan is banned)"); + } + Kick(ClientID, aBuf); + } + return false; + } + + // trim the clan + char aTrimmedClan[MAX_CLAN_LENGTH]; + str_copy(aTrimmedClan, str_utf8_skip_whitespaces(pClanRequest)); + str_utf8_trim_right(aTrimmedClan); + + bool Changed = str_comp(m_aClients[ClientID].m_aClan, aTrimmedClan) != 0; + + if(Set) + { + // set the client clan + str_copy(m_aClients[ClientID].m_aClan, aTrimmedClan); + } + + return Changed; +} + bool CServer::WouldClientNameChange(int ClientID, const char *pNameRequest) { return SetClientNameImpl(ClientID, pNameRequest, false); } +bool CServer::WouldClientClanChange(int ClientID, const char *pClanRequest) +{ + return SetClientClanImpl(ClientID, pClanRequest, false); +} + void CServer::SetClientName(int ClientID, const char *pName) { SetClientNameImpl(ClientID, pName, true); @@ -464,10 +510,7 @@ void CServer::SetClientName(int ClientID, const char *pName) void CServer::SetClientClan(int ClientID, const char *pClan) { - if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY || !pClan) - return; - - str_copy(m_aClients[ClientID].m_aClan, pClan); + SetClientClanImpl(ClientID, pClan, true); } void CServer::SetClientCountry(int ClientID, int Country) diff --git a/src/engine/server/server.h b/src/engine/server/server.h index e36770edb62..4c9e55922a9 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -296,8 +296,10 @@ class CServer : public IServer bool IsClientNameAvailable(int ClientID, const char *pNameRequest); bool SetClientNameImpl(int ClientID, const char *pNameRequest, bool Set); + bool SetClientClanImpl(int ClientID, const char *pClanRequest, bool Set); bool WouldClientNameChange(int ClientID, const char *pNameRequest) override; + bool WouldClientClanChange(int ClientID, const char *pClanRequest) override; void SetClientName(int ClientID, const char *pName) override; void SetClientClan(int ClientID, const char *pClan) override; void SetClientCountry(int ClientID, int Country) override; diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 154d12c902c..2f7cf788b60 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -2508,9 +2508,11 @@ void CGameContext::OnChangeInfoNetMessage(const CNetMsg_Cl_ChangeInfo *pMsg, int LogEvent("Name change", ClientID); } - if(str_comp(Server()->ClientClan(ClientID), pMsg->m_pClan)) + if(Server()->WouldClientClanChange(ClientID, pMsg->m_pClan)) + { SixupNeedsUpdate = true; - Server()->SetClientClan(ClientID, pMsg->m_pClan); + Server()->SetClientClan(ClientID, pMsg->m_pClan); + } if(Server()->ClientCountry(ClientID) != pMsg->m_Country) SixupNeedsUpdate = true; @@ -2717,6 +2719,11 @@ void CGameContext::OnStartInfoNetMessage(const CNetMsg_Cl_StartInfo *pMsg, int C return; } Server()->SetClientClan(ClientID, pMsg->m_pClan); + // trying to set client clan can delete the player object, check if it still exists + if(!m_apPlayers[ClientID]) + { + return; + } Server()->SetClientCountry(ClientID, pMsg->m_Country); str_copy(pPlayer->m_TeeInfos.m_aSkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_aSkinName)); pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor; From fee9b986fb031b72b3ba3b0a34c7e74ae5c15ef5 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Fri, 24 Nov 2023 23:47:15 +0100 Subject: [PATCH 039/198] Added possibility to edit chat size and width. Refactored chat preview code. --- src/engine/shared/config_variables.h | 2 + src/game/client/components/chat.cpp | 82 +++-- src/game/client/components/chat.h | 23 +- src/game/client/components/menus_settings.cpp | 341 ++++++++++++------ 4 files changed, 308 insertions(+), 140 deletions(-) diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index c8dabdf9004..9966b5365d1 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -621,6 +621,8 @@ MACRO_CONFIG_COL(ClHookCollColorTeeColl, cl_hook_coll_color_tee_coll, 2817919, C MACRO_CONFIG_INT(ClChatTeamColors, cl_chat_teamcolors, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show names in chat in team colors") MACRO_CONFIG_INT(ClChatReset, cl_chat_reset, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Reset chat when pressing escape") MACRO_CONFIG_INT(ClChatOld, cl_chat_old, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Old chat style: No tee, no background") +MACRO_CONFIG_INT(ClChatFontSize, cl_chat_size, 60, 10, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Chat font size") +MACRO_CONFIG_INT(ClChatWidth, cl_chat_width, 200, 140, 400, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Chat width") MACRO_CONFIG_INT(ClShowDirection, cl_show_direction, 1, 0, 3, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show key presses (1 = other players', 2 = also your own, 3 = only your own") MACRO_CONFIG_INT(ClOldGunPosition, cl_old_gun_position, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Tees hold gun a bit higher like in TW 0.6.1 and older") diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 75d4b1815dc..9aec741f99d 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -179,6 +179,22 @@ void CChat::ConchainChatOld(IConsole::IResult *pResult, void *pUserData, IConsol ((CChat *)pUserData)->RebuildChat(); } +void CChat::ConchainChatFontSize(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + CChat *pChat = (CChat *)pUserData; + pChat->EnsureCoherentWidth(); + pChat->RebuildChat(); +} + +void CChat::ConchainChatWidth(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + CChat *pChat = (CChat *)pUserData; + pChat->EnsureCoherentFontSize(); + pChat->RebuildChat(); +} + void CChat::Echo(const char *pString) { AddLine(CLIENT_MSG, 0, pString); @@ -197,6 +213,8 @@ void CChat::OnInit() { Reset(); Console()->Chain("cl_chat_old", ConchainChatOld, this); + Console()->Chain("cl_chat_size", ConchainChatFontSize, this); + Console()->Chain("cl_chat_width", ConchainChatWidth, this); } bool CChat::OnInput(const IInput::CEvent &Event) @@ -884,11 +902,10 @@ void CChat::RefindSkins() } } -void CChat::OnPrepareLines() +void CChat::OnPrepareLines(float y) { float x = 5.0f; - float y = 300.0f - 28.0f; - float FontSize = FONT_SIZE; + float FontSize = this->FontSize(); float ScreenRatio = Graphics()->ScreenAspect(); @@ -898,9 +915,10 @@ void CChat::OnPrepareLines() m_PrevScoreBoardShowed = IsScoreBoardOpen; m_PrevShowChat = ShowLargeArea; - float RealMsgPaddingX = MESSAGE_PADDING_X; - float RealMsgPaddingY = MESSAGE_PADDING_Y; - float RealMsgPaddingTee = MESSAGE_TEE_SIZE + MESSAGE_TEE_PADDING_RIGHT; + const int TeeSize = MessageTeeSize(); + float RealMsgPaddingX = MessagePaddingX(); + float RealMsgPaddingY = MessagePaddingY(); + float RealMsgPaddingTee = TeeSize + MESSAGE_TEE_PADDING_RIGHT; if(g_Config.m_ClChatOld) { @@ -910,9 +928,9 @@ void CChat::OnPrepareLines() } int64_t Now = time(); - float LineWidth = (IsScoreBoardOpen ? 85.0f : 200.0f) - (RealMsgPaddingX * 1.5f) - RealMsgPaddingTee; + float LineWidth = (IsScoreBoardOpen ? maximum(85.f, (FontSize * 85.0f / 6.f)) : g_Config.m_ClChatWidth) - (RealMsgPaddingX * 1.5f) - RealMsgPaddingTee; - float HeightLimit = IsScoreBoardOpen ? 180.0f : m_PrevShowChat ? 50.0f : 200.0f; + float HeightLimit = IsScoreBoardOpen ? 180.0f : (m_PrevShowChat ? 50.0f : 200.0f); float Begin = x; float TextBegin = Begin + RealMsgPaddingX / 2.0f; CTextCursor Cursor; @@ -1095,7 +1113,7 @@ void CChat::OnPrepareLines() { float Height = m_aLines[r].m_aYOffset[OffsetType]; Graphics()->SetColor(1, 1, 1, 1); - m_aLines[r].m_QuadContainerIndex = Graphics()->CreateRectQuadContainer(Begin, y, OriginalWidth + AppendCursor.m_LongestLineWidth + RealMsgPaddingX * 1.5f, Height, MESSAGE_ROUNDING, IGraphics::CORNER_ALL); + m_aLines[r].m_QuadContainerIndex = Graphics()->CreateRectQuadContainer(Begin, y, OriginalWidth + AppendCursor.m_LongestLineWidth + RealMsgPaddingX * 1.5f, Height, MessageRounding(), IGraphics::CORNER_ALL); } TextRender()->SetRenderFlags(CurRenderFlags); @@ -1128,12 +1146,13 @@ void CChat::OnRender() Graphics()->MapScreen(0.0f, 0.0f, Width, Height); float x = 5.0f; - float y = 300.0f - 20.0f; + float y = 300.0f - 20.0f * FontSize() / 6.f; + float ScaledFontSize = FontSize() * (8 / 6.f); if(m_Mode != MODE_NONE) { // render chat input CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, x, y, 8.0f, TEXTFLAG_RENDER); + TextRender()->SetCursor(&Cursor, x, y, ScaledFontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = Width - 190.0f; if(m_Mode == MODE_ALL) @@ -1180,19 +1199,19 @@ void CChat::OnRender() #endif return; - y -= 8.0f; + y -= ScaledFontSize; - OnPrepareLines(); + OnPrepareLines(y); float ScreenRatio = Graphics()->ScreenAspect(); bool IsScoreBoardOpen = m_pClient->m_Scoreboard.Active() && (ScreenRatio > 1.7f); // only assume scoreboard when screen ratio is widescreen(something around 16:9) int64_t Now = time(); - float HeightLimit = IsScoreBoardOpen ? 180.0f : m_PrevShowChat ? 50.0f : 200.0f; + float HeightLimit = IsScoreBoardOpen ? 180.0f : (m_PrevShowChat ? 50.0f : 200.0f); int OffsetType = IsScoreBoardOpen ? 1 : 0; - float RealMsgPaddingX = MESSAGE_PADDING_X; - float RealMsgPaddingY = MESSAGE_PADDING_Y; + float RealMsgPaddingX = MessagePaddingX(); + float RealMsgPaddingY = MessagePaddingY(); if(g_Config.m_ClChatOld) { @@ -1229,6 +1248,7 @@ void CChat::OnRender() { if(!g_Config.m_ClChatOld && m_aLines[r].m_HasRenderTee) { + const int TeeSize = MessageTeeSize(); CTeeRenderInfo RenderInfo; RenderInfo.m_CustomColoredSkin = m_aLines[r].m_CustomColoredSkin; if(m_aLines[r].m_CustomColoredSkin) @@ -1239,16 +1259,16 @@ void CChat::OnRender() RenderInfo.m_ColorBody = m_aLines[r].m_ColorBody; RenderInfo.m_ColorFeet = m_aLines[r].m_ColorFeet; - RenderInfo.m_Size = MESSAGE_TEE_SIZE; + RenderInfo.m_Size = TeeSize; - float RowHeight = FONT_SIZE + RealMsgPaddingY; - float OffsetTeeY = MESSAGE_TEE_SIZE / 2.0f; - float FullHeightMinusTee = RowHeight - MESSAGE_TEE_SIZE; + float RowHeight = FontSize() + RealMsgPaddingY; + float OffsetTeeY = TeeSize / 2.0f; + float FullHeightMinusTee = RowHeight - TeeSize; const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &RenderInfo, OffsetToMid); - vec2 TeeRenderPos(x + (RealMsgPaddingX + MESSAGE_TEE_SIZE) / 2.0f, y + OffsetTeeY + FullHeightMinusTee / 2.0f + OffsetToMid.y); + vec2 TeeRenderPos(x + (RealMsgPaddingX + TeeSize) / 2.0f, y + OffsetTeeY + FullHeightMinusTee / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &RenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), TeeRenderPos, Blend); } @@ -1299,3 +1319,23 @@ void CChat::SayChat(const char *pLine) mem_copy(pEntry->m_aText, pLine, str_length(pLine)); } } + +void CChat::EnsureCoherentFontSize() const +{ + // Adjust font size based on width + if(g_Config.m_ClChatWidth / (float)g_Config.m_ClChatFontSize >= CHAT_FONTSIZE_WIDTH_RATIO) + return; + + // We want to keep a ration between font size and font width so that we don't have a weird rendering + g_Config.m_ClChatFontSize = g_Config.m_ClChatWidth / CHAT_FONTSIZE_WIDTH_RATIO; +} + +void CChat::EnsureCoherentWidth() const +{ + // Adjust width based on font size + if(g_Config.m_ClChatWidth / (float)g_Config.m_ClChatFontSize >= CHAT_FONTSIZE_WIDTH_RATIO) + return; + + // We want to keep a ration between font size and font width so that we don't have a weird rendering + g_Config.m_ClChatWidth = CHAT_FONTSIZE_WIDTH_RATIO * g_Config.m_ClChatFontSize; +} diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h index 55d95b5de32..62ee924390e 100644 --- a/src/game/client/components/chat.h +++ b/src/game/client/components/chat.h @@ -15,13 +15,13 @@ class CChat : public CComponent { - static constexpr float CHAT_WIDTH = 200.0f; static constexpr float CHAT_HEIGHT_FULL = 200.0f; static constexpr float CHAT_HEIGHT_MIN = 50.0f; + static constexpr float CHAT_FONTSIZE_WIDTH_RATIO = 2.5f; enum { - MAX_LINES = 25, + MAX_LINES = 64, MAX_LINE_LENGTH = 256 }; @@ -133,6 +133,8 @@ class CChat : public CComponent static void ConEcho(IConsole::IResult *pResult, void *pUserData); static void ConchainChatOld(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainChatFontSize(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainChatWidth(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); bool LineShouldHighlight(const char *pLine, const char *pName); void StoreSave(const char *pText); @@ -141,13 +143,7 @@ class CChat : public CComponent CChat(); int Sizeof() const override { return sizeof(*this); } - static constexpr float MESSAGE_PADDING_X = 5.0f; - static constexpr float MESSAGE_TEE_SIZE = 7.0f; static constexpr float MESSAGE_TEE_PADDING_RIGHT = 0.5f; - static constexpr float FONT_SIZE = 6.0f; - static constexpr float MESSAGE_PADDING_Y = 1.0f; - static constexpr float MESSAGE_ROUNDING = 3.0f; - static_assert(FONT_SIZE + MESSAGE_PADDING_Y >= MESSAGE_ROUNDING * 2.0f, "Corners for background chat are too huge for this combination of font size and message padding."); bool IsActive() const { return m_Mode != MODE_NONE; } void AddLine(int ClientID, int Team, const char *pLine); @@ -163,7 +159,7 @@ class CChat : public CComponent void OnStateChange(int NewState, int OldState) override; void OnRender() override; void RefindSkins(); - void OnPrepareLines(); + void OnPrepareLines(float y); void Reset(); void OnRelease() override; void OnMessage(int MsgType, void *pRawMsg) override; @@ -171,5 +167,14 @@ class CChat : public CComponent void OnInit() override; void RebuildChat(); + + void EnsureCoherentFontSize() const; + void EnsureCoherentWidth() const; + + float FontSize() const { return g_Config.m_ClChatFontSize / 10.0f; } + float MessagePaddingX() const { return FontSize() * (5 / 6.f); } + float MessagePaddingY() const { return FontSize() * (1 / 6.f); } + float MessageTeeSize() const { return FontSize() * (7 / 6.f); } + float MessageRounding() const { return FontSize() * (1 / 2.f); } }; #endif diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index a0716b8664d..5eb9c73640e 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2556,14 +2556,18 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) } else if(s_CurTab == APPEARANCE_TAB_CHAT) { - MainView.VSplitMid(&LeftView, &RightView); + CChat &Chat = GameClient()->m_Chat; + CUIRect TopView, PreviewView; + MainView.h += 20.f; // Increase height a little + MainView.HSplitTop(MainView.h - 260, &TopView, &PreviewView); + TopView.VSplitMid(&LeftView, &RightView); // ***** Chat ***** // LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView); UI()->DoLabel(&Label, Localize("Chat"), HeadlineFontSize, TEXTALIGN_ML); // General chat settings - LeftView.HSplitTop(SectionTotalMargin + 3 * LineSize, &Section, &LeftView); + LeftView.HSplitTop(SectionTotalMargin + 7 * LineSize, &Section, &LeftView); Section.Margin(SectionMargin, &Section); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClChatTeamColors, Localize("Show names in chat in team colors"), &g_Config.m_ClChatTeamColors, &Section, LineSize); @@ -2572,13 +2576,30 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) if(DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClChatOld, Localize("Use old chat style"), &g_Config.m_ClChatOld, &Section, LineSize)) GameClient()->m_Chat.RebuildChat(); + Section.HSplitTop(2 * LineSize, &Button, &Section); + int PrevFontSize = g_Config.m_ClChatFontSize; + UI()->DoScrollbarOption(&g_Config.m_ClChatFontSize, &g_Config.m_ClChatFontSize, &Button, Localize("Chat font size"), 10, 100, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_MULTILINE); + if(PrevFontSize != g_Config.m_ClChatFontSize) + { + Chat.EnsureCoherentWidth(); + Chat.RebuildChat(); + } + + Section.HSplitTop(2 * LineSize, &Button, &Section); + int PrevWidth = g_Config.m_ClChatWidth; + UI()->DoScrollbarOption(&g_Config.m_ClChatWidth, &g_Config.m_ClChatWidth, &Button, Localize("Chat width"), 120, 400, &CUI::ms_LinearScrollbarScale, CUI::SCROLLBAR_OPTION_MULTILINE); + if(PrevWidth != g_Config.m_ClChatWidth) + { + Chat.EnsureCoherentFontSize(); + Chat.RebuildChat(); + } + // ***** Messages ***** // - LeftView.HSplitTop(MarginToNextSection, 0x0, &LeftView); - LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView); + RightView.HSplitTop(HeadlineAndVMargin, &Label, &RightView); UI()->DoLabel(&Label, Localize("Messages"), HeadlineFontSize, TEXTALIGN_ML); // Message Colors and extra settings - LeftView.HSplitTop(SectionTotalMargin + 6 * ColorPickerLineSize, &Section, &LeftView); + RightView.HSplitTop(SectionTotalMargin + 6 * ColorPickerLineSize, &Section, &RightView); Section.Margin(SectionMargin, &Section); int i = 0; @@ -2594,11 +2615,11 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) DoLine_ColorPicker(&s_aResetIDs[i++], ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, aBuf, &g_Config.m_ClMessageClientColor, ColorRGBA(0.5f, 0.78f, 1.0f)); // ***** Chat Preview ***** // - RightView.HSplitTop(HeadlineAndVMargin, &Label, &RightView); + PreviewView.HSplitTop(HeadlineAndVMargin, &Label, &PreviewView); UI()->DoLabel(&Label, Localize("Preview"), HeadlineFontSize, TEXTALIGN_ML); // Use the rest of the view for preview - Section = RightView; + Section = PreviewView; Section.Margin(SectionMargin, &Section); Section.Draw(ColorRGBA(1, 1, 1, 0.1f), IGraphics::CORNER_ALL, 8.0f); @@ -2613,29 +2634,200 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) ColorRGBA ClientColor = color_cast(ColorHSLA(g_Config.m_ClMessageClientColor)); ColorRGBA DefaultNameColor(0.8f, 0.8f, 0.8f, 1.0f); - constexpr float RealFontSize = CChat::FONT_SIZE * 2; - const float RealMsgPaddingX = (!g_Config.m_ClChatOld ? CChat::MESSAGE_PADDING_X : 0) * 2; - const float RealMsgPaddingY = (!g_Config.m_ClChatOld ? CChat::MESSAGE_PADDING_Y : 0) * 2; - const float RealMsgPaddingTee = (!g_Config.m_ClChatOld ? CChat::MESSAGE_TEE_SIZE + CChat::MESSAGE_TEE_PADDING_RIGHT : 0) * 2; + const float RealFontSize = Chat.FontSize() * 2; + const float RealMsgPaddingX = (!g_Config.m_ClChatOld ? Chat.MessagePaddingX() : 0) * 2; + const float RealMsgPaddingY = (!g_Config.m_ClChatOld ? Chat.MessagePaddingY() : 0) * 2; + const float RealMsgPaddingTee = (!g_Config.m_ClChatOld ? Chat.MessageTeeSize() + CChat::MESSAGE_TEE_PADDING_RIGHT : 0) * 2; const float RealOffsetY = RealFontSize + RealMsgPaddingY; const float X = 5.0f + RealMsgPaddingX / 2.0f + Section.x; float Y = Section.y; + float LineWidth = g_Config.m_ClChatWidth * 2 - (RealMsgPaddingX * 1.5f) - RealMsgPaddingTee; CTextCursor Cursor; TextRender()->SetCursor(&Cursor, X, Y, RealFontSize, TEXTFLAG_RENDER); + Cursor.m_LineWidth = LineWidth; str_copy(aBuf, Client()->PlayerName()); const CAnimState *pIdleState = CAnimState::GetIdle(); - constexpr int PreviewTeeCount = 4; - constexpr float RealTeeSize = CChat::MESSAGE_TEE_SIZE * 2; - constexpr float RealTeeSizeHalved = CChat::MESSAGE_TEE_SIZE; + const float RealTeeSize = Chat.MessageTeeSize() * 2; + const float RealTeeSizeHalved = Chat.MessageTeeSize(); constexpr float TWSkinUnreliableOffset = -0.25f; - constexpr float OffsetTeeY = RealTeeSizeHalved; + const float OffsetTeeY = RealTeeSizeHalved; const float FullHeightMinusTee = RealOffsetY - RealTeeSize; - CTeeRenderInfo aRenderInfo[PreviewTeeCount]; + struct SPreviewLine + { + int m_ClientID; + bool m_Team; + char m_aName[64]; + char m_aText[256]; + bool m_Friend; + bool m_Player; + bool m_Client; + bool m_Highlighted; + int m_TimesRepeated; + + CTeeRenderInfo m_RenderInfo; + }; + + static std::vector s_vLines; + + const auto *pDefaultSkin = GameClient()->m_Skins.Find("default"); + enum ELineFlag + { + FLAG_TEAM = 1 << 0, + FLAG_FRIEND = 1 << 1, + FLAG_HIGHLIGHT = 1 << 2, + FLAG_CLIENT = 1 << 3 + }; + enum + { + PREVIEW_SYS, + PREVIEW_HIGHLIGHT, + PREVIEW_TEAM, + PREVIEW_FRIEND, + PREVIEW_SPAMMER, + PREVIEW_CLIENT + }; + auto &&AddPreviewLine = [](int Index, int ClientID, const char *pName, const char *pText, int Flag, int Repeats) { + s_vLines.emplace_back(); + SPreviewLine *pLine = &s_vLines[s_vLines.size() - 1]; + pLine->m_ClientID = ClientID; + pLine->m_Team = Flag & FLAG_TEAM; + pLine->m_Friend = Flag & FLAG_FRIEND; + pLine->m_Player = ClientID >= 0; + pLine->m_Highlighted = Flag & FLAG_HIGHLIGHT; + pLine->m_Client = Flag & FLAG_CLIENT; + pLine->m_TimesRepeated = Repeats; + str_copy(pLine->m_aName, pName); + str_copy(pLine->m_aText, pText); + }; + auto &&SetLineSkin = [RealTeeSize, &pDefaultSkin](int Index, const CSkin *pSkin) { + if(Index >= (int)s_vLines.size()) + return; + s_vLines[Index].m_RenderInfo.m_Size = RealTeeSize; + s_vLines[Index].m_RenderInfo.m_CustomColoredSkin = false; + if(pSkin != nullptr) + s_vLines[Index].m_RenderInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin; + else if(pDefaultSkin != nullptr) + s_vLines[Index].m_RenderInfo.m_OriginalRenderSkin = pDefaultSkin->m_OriginalSkin; + }; + + auto &&RenderPreview = [&](int LineIndex, int x, int y, bool Render = true) { + if(LineIndex >= (int)s_vLines.size()) + return vec2(0, 0); + CTextCursor LocalCursor; + TextRender()->SetCursor(&LocalCursor, x, y, RealFontSize, Render ? TEXTFLAG_RENDER : 0); + LocalCursor.m_LineWidth = LineWidth; + const auto &Line = s_vLines[LineIndex]; + + char aName[64 + 12] = ""; + + if(g_Config.m_ClShowIDs && Line.m_ClientID >= 0 && Line.m_aName[0] != '\0') + { + if(Line.m_ClientID < 10) + str_format(aName, sizeof(aName), " %d: ", Line.m_ClientID); + else + str_format(aName, sizeof(aName), "%d: ", Line.m_ClientID); + } + + str_append(aName, Line.m_aName); + + char aCount[12]; + if(Line.m_ClientID < 0) + str_format(aCount, sizeof(aCount), "[%d] ", Line.m_TimesRepeated + 1); + else + str_format(aCount, sizeof(aCount), " [%d]", Line.m_TimesRepeated + 1); + + if(Line.m_Player) + { + LocalCursor.m_X += RealMsgPaddingTee; + + if(Line.m_Friend && g_Config.m_ClMessageFriend) + { + if(Render) + TextRender()->TextColor(FriendColor); + TextRender()->TextEx(&LocalCursor, "♥ ", -1); + } + } + + ColorRGBA NameColor; + if(Line.m_Team) + NameColor = CalculateNameColor(color_cast(TeamColor)); + else if(Line.m_Player) + NameColor = DefaultNameColor; + else if(Line.m_Client) + NameColor = ClientColor; + else + NameColor = SystemColor; + + if(Render) + TextRender()->TextColor(NameColor); + + TextRender()->TextEx(&LocalCursor, aName, -1); + + if(Line.m_TimesRepeated > 0) + { + if(Render) + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.3f); + TextRender()->TextEx(&LocalCursor, aCount, -1); + } + + if(Line.m_ClientID >= 0 && Line.m_aName[0] != '\0') + { + if(Render) + TextRender()->TextColor(NameColor); + TextRender()->TextEx(&LocalCursor, ": ", -1); + } + + CTextCursor AppendCursor = LocalCursor; + AppendCursor.m_LongestLineWidth = 0.0f; + if(!g_Config.m_ClChatOld) + { + AppendCursor.m_StartX = LocalCursor.m_X; + AppendCursor.m_LineWidth -= LocalCursor.m_LongestLineWidth; + } + + if(Render) + { + if(Line.m_Highlighted) + TextRender()->TextColor(HighlightedColor); + else if(Line.m_Team) + TextRender()->TextColor(TeamColor); + else if(Line.m_Player) + TextRender()->TextColor(NormalColor); + } + + TextRender()->TextEx(&AppendCursor, Line.m_aText, -1); + if(Render) + TextRender()->TextColor(TextRender()->DefaultTextColor()); + + return vec2{LocalCursor.m_LongestLineWidth + AppendCursor.m_LongestLineWidth, AppendCursor.Height() + RealMsgPaddingY}; + }; + + // Init lines + if(s_vLines.empty()) + { + char aLineBuilder[128]; + + str_format(aLineBuilder, sizeof(aLineBuilder), "'%s' entered and joined the game", aBuf); + AddPreviewLine(PREVIEW_SYS, -1, "*** ", aLineBuilder, 0, 0); + + str_format(aLineBuilder, sizeof(aLineBuilder), "Hey, how are you %s?", aBuf); + AddPreviewLine(PREVIEW_HIGHLIGHT, 7, "Random Tee", aLineBuilder, FLAG_HIGHLIGHT, 0); + + AddPreviewLine(PREVIEW_TEAM, 11, "Your Teammate", "Let's speedrun this!", FLAG_TEAM, 0); + AddPreviewLine(PREVIEW_FRIEND, 8, "Friend", "Hello there", FLAG_FRIEND, 0); + AddPreviewLine(PREVIEW_SPAMMER, 9, "Spammer", "Hey fools, I'm spamming here!", 0, 5); + AddPreviewLine(PREVIEW_CLIENT, -1, "— ", "Echo command executed", FLAG_CLIENT, 0); + } + + SetLineSkin(1, GameClient()->m_Skins.FindOrNullptr("pinky")); + SetLineSkin(2, pDefaultSkin); + SetLineSkin(3, GameClient()->m_Skins.FindOrNullptr("cammostripes")); + SetLineSkin(4, GameClient()->m_Skins.FindOrNullptr("beast")); // Backgrounds first if(!g_Config.m_ClChatOld) @@ -2644,142 +2836,71 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) Graphics()->QuadsBegin(); Graphics()->SetColor(0, 0, 0, 0.12f); - char aLineBuilder[128]; - float Width; float TempY = Y; - constexpr float RealBackgroundRounding = CChat::MESSAGE_ROUNDING * 2.0f; + const float RealBackgroundRounding = Chat.MessageRounding() * 2.0f; + + auto &&RenderMessageBackground = [&](int LineIndex) { + auto Size = RenderPreview(LineIndex, 0, 0, false); + Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Size.x + RealMsgPaddingX * 1.5f, Size.y, RealBackgroundRounding, IGraphics::CORNER_ALL); + return Size.y; + }; if(g_Config.m_ClShowChatSystem) { - str_format(aLineBuilder, sizeof(aLineBuilder), "*** '%s' entered and joined the game", aBuf); - Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1); - Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL); - TempY += RealOffsetY; + TempY += RenderMessageBackground(PREVIEW_SYS); } if(!g_Config.m_ClShowChatFriends) { - str_format(aLineBuilder, sizeof(aLineBuilder), "%sRandom Tee: Hey, how are you %s?", g_Config.m_ClShowIDs ? " 7: " : "", aBuf); - Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1); - Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX + RealMsgPaddingTee, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL); - TempY += RealOffsetY; - - str_format(aLineBuilder, sizeof(aLineBuilder), "%sYour Teammate: Let's speedrun this!", g_Config.m_ClShowIDs ? "11: " : ""); - Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1); - Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX + RealMsgPaddingTee, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL); - TempY += RealOffsetY; + TempY += RenderMessageBackground(PREVIEW_HIGHLIGHT); + TempY += RenderMessageBackground(PREVIEW_TEAM); } - str_format(aLineBuilder, sizeof(aLineBuilder), "%s%sFriend: Hello there", g_Config.m_ClMessageFriend ? "♥ " : "", g_Config.m_ClShowIDs ? " 8: " : ""); - Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1); - Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX + RealMsgPaddingTee, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL); - TempY += RealOffsetY; + TempY += RenderMessageBackground(PREVIEW_FRIEND); if(!g_Config.m_ClShowChatFriends) { - str_format(aLineBuilder, sizeof(aLineBuilder), "%sSpammer [6]: Hey fools, I'm spamming here!", g_Config.m_ClShowIDs ? " 9: " : ""); - Width = TextRender()->TextWidth(RealFontSize, aLineBuilder, -1, -1); - Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX + RealMsgPaddingTee, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL); - TempY += RealOffsetY; + TempY += RenderMessageBackground(PREVIEW_SPAMMER); } - Width = TextRender()->TextWidth(RealFontSize, "— Echo command executed", -1, -1); - Graphics()->DrawRectExt(X - RealMsgPaddingX / 2.0f, TempY - RealMsgPaddingY / 2.0f, Width + RealMsgPaddingX, RealFontSize + RealMsgPaddingY, RealBackgroundRounding, IGraphics::CORNER_ALL); + TempY += RenderMessageBackground(PREVIEW_CLIENT); Graphics()->QuadsEnd(); - - // Load skins - - const auto *pDefaultSkin = GameClient()->m_Skins.Find("default"); - - for(auto &Info : aRenderInfo) - { - Info.m_Size = RealTeeSize; - Info.m_CustomColoredSkin = false; - } - - const CSkin *pSkin = nullptr; - int pos = 0; - - aRenderInfo[pos++].m_OriginalRenderSkin = pDefaultSkin->m_OriginalSkin; - aRenderInfo[pos++].m_OriginalRenderSkin = (pSkin = GameClient()->m_Skins.FindOrNullptr("pinky")) != nullptr ? pSkin->m_OriginalSkin : aRenderInfo[0].m_OriginalRenderSkin; - aRenderInfo[pos++].m_OriginalRenderSkin = (pSkin = GameClient()->m_Skins.FindOrNullptr("cammostripes")) != nullptr ? pSkin->m_OriginalSkin : aRenderInfo[0].m_OriginalRenderSkin; - aRenderInfo[pos++].m_OriginalRenderSkin = (pSkin = GameClient()->m_Skins.FindOrNullptr("beast")) != nullptr ? pSkin->m_OriginalSkin : aRenderInfo[0].m_OriginalRenderSkin; } // System if(g_Config.m_ClShowChatSystem) { - TextRender()->TextColor(SystemColor); - TextRender()->TextEx(&Cursor, "*** '", -1); - TextRender()->TextEx(&Cursor, aBuf, -1); - TextRender()->TextEx(&Cursor, "' entered and joined the game", -1); - TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY); + Y += RenderPreview(PREVIEW_SYS, X, Y).y; } if(!g_Config.m_ClShowChatFriends) { // Highlighted - TextRender()->MoveCursor(&Cursor, RealMsgPaddingTee, 0); - TextRender()->TextColor(DefaultNameColor); - if(g_Config.m_ClShowIDs) - TextRender()->TextEx(&Cursor, " 7: ", -1); - TextRender()->TextEx(&Cursor, "Random Tee: ", -1); - TextRender()->TextColor(HighlightedColor); - TextRender()->TextEx(&Cursor, "Hey, how are you ", -1); - TextRender()->TextEx(&Cursor, aBuf, -1); - TextRender()->TextEx(&Cursor, "?", -1); if(!g_Config.m_ClChatOld) - RenderTools()->RenderTee(pIdleState, &aRenderInfo[1], EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset)); - TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY); + RenderTools()->RenderTee(pIdleState, &s_vLines[PREVIEW_HIGHLIGHT].m_RenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset)); + Y += RenderPreview(PREVIEW_HIGHLIGHT, X, Y).y; // Team - TextRender()->MoveCursor(&Cursor, RealMsgPaddingTee, 0); - TextRender()->TextColor(TeamColor); - if(g_Config.m_ClShowIDs) - TextRender()->TextEx(&Cursor, "11: ", -1); - TextRender()->TextEx(&Cursor, "Your Teammate: Let's speedrun this!", -1); if(!g_Config.m_ClChatOld) - RenderTools()->RenderTee(pIdleState, &aRenderInfo[0], EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset)); - TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY); + RenderTools()->RenderTee(pIdleState, &s_vLines[PREVIEW_TEAM].m_RenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset)); + Y += RenderPreview(PREVIEW_TEAM, X, Y).y; } // Friend - TextRender()->MoveCursor(&Cursor, RealMsgPaddingTee, 0); - if(g_Config.m_ClMessageFriend) - { - TextRender()->TextColor(FriendColor); - TextRender()->TextEx(&Cursor, "♥ ", -1); - } - TextRender()->TextColor(DefaultNameColor); - if(g_Config.m_ClShowIDs) - TextRender()->TextEx(&Cursor, " 8: ", -1); - TextRender()->TextEx(&Cursor, "Friend: ", -1); - TextRender()->TextColor(NormalColor); - TextRender()->TextEx(&Cursor, "Hello there", -1); if(!g_Config.m_ClChatOld) - RenderTools()->RenderTee(pIdleState, &aRenderInfo[2], EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset)); - TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY); + RenderTools()->RenderTee(pIdleState, &s_vLines[PREVIEW_FRIEND].m_RenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset)); + Y += RenderPreview(PREVIEW_FRIEND, X, Y).y; // Normal if(!g_Config.m_ClShowChatFriends) { - TextRender()->MoveCursor(&Cursor, RealMsgPaddingTee, 0); - TextRender()->TextColor(DefaultNameColor); - if(g_Config.m_ClShowIDs) - TextRender()->TextEx(&Cursor, " 9: ", -1); - TextRender()->TextEx(&Cursor, "Spammer ", -1); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.3f); - TextRender()->TextEx(&Cursor, "[6]", -1); - TextRender()->TextColor(NormalColor); - TextRender()->TextEx(&Cursor, ": Hey fools, I'm spamming here!", -1); if(!g_Config.m_ClChatOld) - RenderTools()->RenderTee(pIdleState, &aRenderInfo[3], EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset)); - TextRender()->SetCursorPosition(&Cursor, X, Y += RealOffsetY); + RenderTools()->RenderTee(pIdleState, &s_vLines[PREVIEW_SPAMMER].m_RenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset)); + Y += RenderPreview(PREVIEW_SPAMMER, X, Y).y; } // Client - TextRender()->TextColor(ClientColor); - TextRender()->TextEx(&Cursor, "— Echo command executed", -1); + RenderPreview(PREVIEW_CLIENT, X, Y); TextRender()->SetCursorPosition(&Cursor, X, Y); TextRender()->TextColor(TextRender()->DefaultTextColor()); From 41d2c80f21296f8c82063fbe0d43714ce70ffda2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 22 Nov 2023 23:51:06 +0100 Subject: [PATCH 040/198] Add templated `CHeap::Allocate` function Add a templated `CHeap::Allocate` function to simplify memory allocation of objects using `CHeap`. The function uses perfect forwarding to construct objects with the specified arguments. --- src/engine/client/serverbrowser.cpp | 2 +- src/engine/shared/console.h | 2 +- src/engine/shared/memheap.h | 10 ++++++++++ src/game/client/components/voting.cpp | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index 2b649ef5f5d..236f9d6c30d 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -657,7 +657,7 @@ void CServerBrowser::SetLatency(NETADDR Addr, int Latency) CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR *pAddrs, int NumAddrs) { // create new pEntry - CServerEntry *pEntry = (CServerEntry *)m_ServerlistHeap.Allocate(sizeof(CServerEntry)); + CServerEntry *pEntry = m_ServerlistHeap.Allocate(); mem_zero(pEntry, sizeof(CServerEntry)); // set the info diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index 614dd0e0cb9..d16770c4665 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -172,7 +172,7 @@ class CConsole : public IConsole void AddEntry() { - CQueueEntry *pEntry = static_cast(m_Queue.Allocate(sizeof(CQueueEntry))); + CQueueEntry *pEntry = m_Queue.Allocate(); pEntry->m_pNext = 0; if(!m_pFirst) m_pFirst = pEntry; diff --git a/src/engine/shared/memheap.h b/src/engine/shared/memheap.h index 2d44dab463b..acabfa62336 100644 --- a/src/engine/shared/memheap.h +++ b/src/engine/shared/memheap.h @@ -4,6 +4,9 @@ #define ENGINE_SHARED_MEMHEAP_H #include +#include +#include + class CHeap { struct CChunk @@ -32,5 +35,12 @@ class CHeap void Reset(); void *Allocate(unsigned Size, unsigned Alignment = alignof(std::max_align_t)); const char *StoreString(const char *pSrc); + + template + T *Allocate(TArgs &&... Args) + { + return new(Allocate(sizeof(T), alignof(T))) T(std::forward(Args)...); + } }; + #endif diff --git a/src/game/client/components/voting.cpp b/src/game/client/components/voting.cpp index 21242a9d527..aaab39deddb 100644 --- a/src/game/client/components/voting.cpp +++ b/src/game/client/components/voting.cpp @@ -163,7 +163,7 @@ void CVoting::AddOption(const char *pDescription) m_pRecycleLast = 0; } else - pOption = (CVoteOptionClient *)m_Heap.Allocate(sizeof(CVoteOptionClient)); + pOption = m_Heap.Allocate(); pOption->m_pNext = 0; pOption->m_pPrev = m_pLast; From ec6d7b48bf101e36f6a3138ad6aac6c98be1f3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 22 Nov 2023 23:51:06 +0100 Subject: [PATCH 041/198] Use `nullptr` instead of `0x0` --- src/engine/shared/memheap.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/shared/memheap.cpp b/src/engine/shared/memheap.cpp index a18a8790368..b8c7f174222 100644 --- a/src/engine/shared/memheap.cpp +++ b/src/engine/shared/memheap.cpp @@ -22,7 +22,7 @@ void CHeap::NewChunk() pChunk->m_pMemory = (char *)(pChunk + 1); pChunk->m_pCurrent = pChunk->m_pMemory; pChunk->m_pEnd = pChunk->m_pMemory + CHUNK_SIZE; - pChunk->m_pNext = (CChunk *)0x0; + pChunk->m_pNext = nullptr; pChunk->m_pNext = m_pCurrent; m_pCurrent = pChunk; @@ -38,7 +38,7 @@ void *CHeap::AllocateFromChunk(unsigned int Size, unsigned Alignment) // check if we need can fit the allocation if(m_pCurrent->m_pCurrent + Offset + Size > m_pCurrent->m_pEnd) - return (void *)0x0; + return nullptr; // get memory and move the pointer forward pMem = m_pCurrent->m_pCurrent + Offset; @@ -49,7 +49,7 @@ void *CHeap::AllocateFromChunk(unsigned int Size, unsigned Alignment) // creates a heap CHeap::CHeap() { - m_pCurrent = 0x0; + m_pCurrent = nullptr; Reset(); } @@ -77,7 +77,7 @@ void CHeap::Clear() pChunk = pNext; } - m_pCurrent = 0x0; + m_pCurrent = nullptr; } // From 7a142df58057774183328fc005fa2406796b4ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 26 Nov 2023 13:29:02 +0100 Subject: [PATCH 042/198] Move variable declarations closer to usages --- src/engine/shared/memheap.cpp | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/engine/shared/memheap.cpp b/src/engine/shared/memheap.cpp index b8c7f174222..32e110c7cea 100644 --- a/src/engine/shared/memheap.cpp +++ b/src/engine/shared/memheap.cpp @@ -8,17 +8,14 @@ // allocates a new chunk to be used void CHeap::NewChunk() { - CChunk *pChunk; - char *pMem; - // allocate memory - pMem = (char *)malloc(sizeof(CChunk) + CHUNK_SIZE); + char *pMem = (char *)malloc(sizeof(CChunk) + CHUNK_SIZE); if(!pMem) return; // the chunk structure is located in the beginning of the chunk // init it and return the chunk - pChunk = (CChunk *)pMem; + CChunk *pChunk = (CChunk *)pMem; pChunk->m_pMemory = (char *)(pChunk + 1); pChunk->m_pCurrent = pChunk->m_pMemory; pChunk->m_pEnd = pChunk->m_pMemory + CHUNK_SIZE; @@ -31,7 +28,6 @@ void CHeap::NewChunk() //**************** void *CHeap::AllocateFromChunk(unsigned int Size, unsigned Alignment) { - char *pMem; size_t Offset = reinterpret_cast(m_pCurrent->m_pCurrent) % Alignment; if(Offset) Offset = Alignment - Offset; @@ -41,7 +37,7 @@ void *CHeap::AllocateFromChunk(unsigned int Size, unsigned Alignment) return nullptr; // get memory and move the pointer forward - pMem = m_pCurrent->m_pCurrent + Offset; + char *pMem = m_pCurrent->m_pCurrent + Offset; m_pCurrent->m_pCurrent += Offset + Size; return pMem; } @@ -67,26 +63,19 @@ void CHeap::Reset() // destroys the heap void CHeap::Clear() { - CChunk *pChunk = m_pCurrent; - CChunk *pNext; - - while(pChunk) + while(m_pCurrent) { - pNext = pChunk->m_pNext; - free(pChunk); - pChunk = pNext; + CChunk *pNext = m_pCurrent->m_pNext; + free(m_pCurrent); + m_pCurrent = pNext; } - - m_pCurrent = nullptr; } // void *CHeap::Allocate(unsigned Size, unsigned Alignment) { - char *pMem; - // try to allocate from current chunk - pMem = (char *)AllocateFromChunk(Size, Alignment); + char *pMem = (char *)AllocateFromChunk(Size, Alignment); if(!pMem) { // allocate new chunk and add it to the heap From 591c08cf34cfd5ab0f190e06683523217eb996ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 26 Nov 2023 13:32:47 +0100 Subject: [PATCH 043/198] Use `static_cast`s instead of C style, remove unnecessary casts --- src/engine/shared/memheap.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/engine/shared/memheap.cpp b/src/engine/shared/memheap.cpp index 32e110c7cea..311a6ddb432 100644 --- a/src/engine/shared/memheap.cpp +++ b/src/engine/shared/memheap.cpp @@ -8,15 +8,12 @@ // allocates a new chunk to be used void CHeap::NewChunk() { - // allocate memory - char *pMem = (char *)malloc(sizeof(CChunk) + CHUNK_SIZE); - if(!pMem) - return; - // the chunk structure is located in the beginning of the chunk // init it and return the chunk - CChunk *pChunk = (CChunk *)pMem; - pChunk->m_pMemory = (char *)(pChunk + 1); + CChunk *pChunk = static_cast(malloc(sizeof(CChunk) + CHUNK_SIZE)); + if(!pChunk) + return; + pChunk->m_pMemory = static_cast(static_cast(pChunk + 1)); pChunk->m_pCurrent = pChunk->m_pMemory; pChunk->m_pEnd = pChunk->m_pMemory + CHUNK_SIZE; pChunk->m_pNext = nullptr; @@ -75,14 +72,14 @@ void CHeap::Clear() void *CHeap::Allocate(unsigned Size, unsigned Alignment) { // try to allocate from current chunk - char *pMem = (char *)AllocateFromChunk(Size, Alignment); + void *pMem = AllocateFromChunk(Size, Alignment); if(!pMem) { // allocate new chunk and add it to the heap NewChunk(); // try to allocate again - pMem = (char *)AllocateFromChunk(Size, Alignment); + pMem = AllocateFromChunk(Size, Alignment); } return pMem; From cb53c45f8cc36e6cc0873f5b284417a0382296f7 Mon Sep 17 00:00:00 2001 From: furo Date: Sun, 19 Nov 2023 20:17:51 +0100 Subject: [PATCH 044/198] Rename `killmessages` to `infomessages` --- .../{killmessages.cpp => infomessages.cpp} | 268 ++++++++++-------- .../{killmessages.h => infomessages.h} | 29 +- src/game/client/components/menus_demo.cpp | 2 +- src/game/client/gameclient.cpp | 6 +- src/game/client/gameclient.h | 4 +- 5 files changed, 166 insertions(+), 143 deletions(-) rename src/game/client/components/{killmessages.cpp => infomessages.cpp} (62%) rename src/game/client/components/{killmessages.h => infomessages.h} (72%) diff --git a/src/game/client/components/killmessages.cpp b/src/game/client/components/infomessages.cpp similarity index 62% rename from src/game/client/components/killmessages.cpp rename to src/game/client/components/infomessages.cpp index ac62949c938..fbf87b83778 100644 --- a/src/game/client/components/killmessages.cpp +++ b/src/game/client/components/infomessages.cpp @@ -8,35 +8,35 @@ #include #include -#include "killmessages.h" +#include "infomessages.h" #include #include #include #include -void CKillMessages::OnWindowResize() +void CInfoMessages::OnWindowResize() { - for(auto &Killmsg : m_aKillmsgs) + for(auto &InfoMsg : m_aInfoMsgs) { - TextRender()->DeleteTextContainer(Killmsg.m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(Killmsg.m_KillerTextContainerIndex); + TextRender()->DeleteTextContainer(InfoMsg.m_VictimTextContainerIndex); + TextRender()->DeleteTextContainer(InfoMsg.m_KillerTextContainerIndex); } } -void CKillMessages::OnReset() +void CInfoMessages::OnReset() { - m_KillmsgCurrent = 0; + m_InfoMsgCurrent = 0; - for(auto &Killmsg : m_aKillmsgs) + for(auto &InfoMsg : m_aInfoMsgs) { - Killmsg.m_Tick = -100000; + InfoMsg.m_Tick = -100000; - TextRender()->DeleteTextContainer(Killmsg.m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(Killmsg.m_KillerTextContainerIndex); + TextRender()->DeleteTextContainer(InfoMsg.m_VictimTextContainerIndex); + TextRender()->DeleteTextContainer(InfoMsg.m_KillerTextContainerIndex); } } -void CKillMessages::OnInit() +void CInfoMessages::OnInit() { Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); m_SpriteQuadContainerIndex = Graphics()->CreateQuadContainer(false); @@ -61,48 +61,57 @@ void CKillMessages::OnInit() Graphics()->QuadContainerUpload(m_SpriteQuadContainerIndex); } -void CKillMessages::CreateKillmessageNamesIfNotCreated(CKillMsg &Kill) +void CInfoMessages::AddInfoMsg(int Type, CInfoMsg NewMsg) +{ + NewMsg.m_Type = Type; + NewMsg.m_Tick = Client()->GameTick(g_Config.m_ClDummy); + + m_InfoMsgCurrent = (m_InfoMsgCurrent+1)%MAX_INFOMSGS; + m_aInfoMsgs[m_InfoMsgCurrent] = NewMsg; +} + +void CInfoMessages::CreateKillmessageNamesIfNotCreated(CInfoMsg *pInfoMsg) { const float FontSize = 36.0f; - if(!Kill.m_VictimTextContainerIndex.Valid() && Kill.m_aVictimName[0] != 0) + if(!pInfoMsg->m_VictimTextContainerIndex.Valid() && pInfoMsg->m_aVictimName[0] != 0) { - Kill.m_VictimTextWidth = TextRender()->TextWidth(FontSize, Kill.m_aVictimName, -1, -1.0f); + pInfoMsg->m_VictimTextWidth = TextRender()->TextWidth(FontSize, pInfoMsg->m_aVictimName, -1, -1.0f); CTextCursor Cursor; TextRender()->SetCursor(&Cursor, 0, 0, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; unsigned Color = g_Config.m_ClKillMessageNormalColor; - if(Kill.m_aVictimIds[0] == m_pClient->m_Snap.m_LocalClientID) + if(pInfoMsg->m_aVictimIds[0] == m_pClient->m_Snap.m_LocalClientID) { Color = g_Config.m_ClKillMessageHighlightColor; } TextRender()->TextColor(color_cast(ColorHSLA(Color))); - TextRender()->CreateTextContainer(Kill.m_VictimTextContainerIndex, &Cursor, Kill.m_aVictimName); + TextRender()->CreateTextContainer(pInfoMsg->m_VictimTextContainerIndex, &Cursor, pInfoMsg->m_aVictimName); } - if(!Kill.m_KillerTextContainerIndex.Valid() && Kill.m_aKillerName[0] != 0) + if(!pInfoMsg->m_KillerTextContainerIndex.Valid() && pInfoMsg->m_aKillerName[0] != 0) { - Kill.m_KillerTextWidth = TextRender()->TextWidth(FontSize, Kill.m_aKillerName, -1, -1.0f); + pInfoMsg->m_KillerTextWidth = TextRender()->TextWidth(FontSize, pInfoMsg->m_aKillerName, -1, -1.0f); CTextCursor Cursor; TextRender()->SetCursor(&Cursor, 0, 0, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; unsigned Color = g_Config.m_ClKillMessageNormalColor; - if(Kill.m_KillerID == m_pClient->m_Snap.m_LocalClientID) + if(pInfoMsg->m_KillerID == m_pClient->m_Snap.m_LocalClientID) { Color = g_Config.m_ClKillMessageHighlightColor; } TextRender()->TextColor(color_cast(ColorHSLA(Color))); - TextRender()->CreateTextContainer(Kill.m_KillerTextContainerIndex, &Cursor, Kill.m_aKillerName); + TextRender()->CreateTextContainer(pInfoMsg->m_KillerTextContainerIndex, &Cursor, pInfoMsg->m_aKillerName); } TextRender()->TextColor(TextRender()->DefaultTextColor()); } -void CKillMessages::OnMessage(int MsgType, void *pRawMsg) +void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) { if(m_pClient->m_SuppressEvents) return; @@ -111,7 +120,8 @@ void CKillMessages::OnMessage(int MsgType, void *pRawMsg) { CNetMsg_Sv_KillMsgTeam *pMsg = (CNetMsg_Sv_KillMsgTeam *)pRawMsg; - CKillMsg Kill{}; + CInfoMsg Kill{}; + Kill.m_Type = INFOMSG_KILL; std::vector> vStrongWeakSorted; for(int i = 0; i < MAX_CLIENTS; i++) @@ -167,7 +177,7 @@ void CKillMessages::OnMessage(int MsgType, void *pRawMsg) Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); Graphics()->MapScreen(0, 0, Width * 1.5f, Height * 1.5f); - CreateKillmessageNamesIfNotCreated(Kill); + CreateKillmessageNamesIfNotCreated(&Kill); bool KillMsgValid = true; for(int i = 0; i < Kill.m_TeamSize; i++) @@ -178,12 +188,12 @@ void CKillMessages::OnMessage(int MsgType, void *pRawMsg) if(KillMsgValid) { // add the message - m_KillmsgCurrent = (m_KillmsgCurrent + 1) % MAX_KILLMSGS; + m_InfoMsgCurrent = (m_InfoMsgCurrent + 1) % MAX_INFOMSGS; - TextRender()->DeleteTextContainer(m_aKillmsgs[m_KillmsgCurrent].m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(m_aKillmsgs[m_KillmsgCurrent].m_KillerTextContainerIndex); + TextRender()->DeleteTextContainer(m_aInfoMsgs[m_InfoMsgCurrent].m_VictimTextContainerIndex); + TextRender()->DeleteTextContainer(m_aInfoMsgs[m_InfoMsgCurrent].m_KillerTextContainerIndex); - m_aKillmsgs[m_KillmsgCurrent] = Kill; + m_aInfoMsgs[m_InfoMsgCurrent] = Kill; } else { @@ -198,7 +208,8 @@ void CKillMessages::OnMessage(int MsgType, void *pRawMsg) { CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; - CKillMsg Kill{}; + CInfoMsg Kill{}; + Kill.m_Type = INFOMSG_KILL; Kill.m_TeamSize = 1; Kill.m_aVictimIds[0] = pMsg->m_Victim; @@ -246,19 +257,19 @@ void CKillMessages::OnMessage(int MsgType, void *pRawMsg) Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); Graphics()->MapScreen(0, 0, Width * 1.5f, Height * 1.5f); - CreateKillmessageNamesIfNotCreated(Kill); + CreateKillmessageNamesIfNotCreated(&Kill); bool KillMsgValid = (Kill.m_aVictimRenderInfo[0].m_CustomColoredSkin && Kill.m_aVictimRenderInfo[0].m_ColorableRenderSkin.m_Body.IsValid()) || (!Kill.m_aVictimRenderInfo[0].m_CustomColoredSkin && Kill.m_aVictimRenderInfo[0].m_OriginalRenderSkin.m_Body.IsValid()); KillMsgValid &= Kill.m_KillerID == -1 || ((Kill.m_KillerRenderInfo.m_CustomColoredSkin && Kill.m_KillerRenderInfo.m_ColorableRenderSkin.m_Body.IsValid()) || (!Kill.m_KillerRenderInfo.m_CustomColoredSkin && Kill.m_KillerRenderInfo.m_OriginalRenderSkin.m_Body.IsValid())); if(KillMsgValid) { // add the message - m_KillmsgCurrent = (m_KillmsgCurrent + 1) % MAX_KILLMSGS; + m_InfoMsgCurrent = (m_InfoMsgCurrent + 1) % MAX_INFOMSGS; - TextRender()->DeleteTextContainer(m_aKillmsgs[m_KillmsgCurrent].m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(m_aKillmsgs[m_KillmsgCurrent].m_KillerTextContainerIndex); + TextRender()->DeleteTextContainer(m_aInfoMsgs[m_InfoMsgCurrent].m_VictimTextContainerIndex); + TextRender()->DeleteTextContainer(m_aInfoMsgs[m_InfoMsgCurrent].m_KillerTextContainerIndex); - m_aKillmsgs[m_KillmsgCurrent] = Kill; + m_aInfoMsgs[m_InfoMsgCurrent] = Kill; } else { @@ -270,152 +281,157 @@ void CKillMessages::OnMessage(int MsgType, void *pRawMsg) } } -void CKillMessages::OnRender() +void CInfoMessages::RenderKillMsg(CInfoMsg *pInfoMsg, float x, float y) { - if(!g_Config.m_ClShowKillMessages) - return; + // render victim name + x -= pInfoMsg->m_VictimTextWidth; + ColorRGBA TextColor; + if(g_Config.m_ClChatTeamColors && pInfoMsg->m_VictimDDTeam) + TextColor = m_pClient->GetDDTeamColor(pInfoMsg->m_VictimDDTeam, 0.75f); + else + TextColor = TextRender()->DefaultTextColor(); - float Height = 400 * 3.0f; - float Width = Height * Graphics()->ScreenAspect(); + CreateKillmessageNamesIfNotCreated(pInfoMsg); - Graphics()->MapScreen(0, 0, Width * 1.5f, Height * 1.5f); - Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); + if(pInfoMsg->m_VictimTextContainerIndex.Valid()) + TextRender()->RenderTextContainer(pInfoMsg->m_VictimTextContainerIndex, TextColor, TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); - float StartX = Width * 1.5f - 10.0f; - float y = 30.0f + 100.0f * ((g_Config.m_ClShowfps ? 1 : 0) + (g_Config.m_ClShowpred && Client()->State() != IClient::STATE_DEMOPLAYBACK)); + // render victim tee + x -= 24.0f; - for(int i = 1; i <= MAX_KILLMSGS; i++) + if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) { - int r = (m_KillmsgCurrent + i) % MAX_KILLMSGS; - if(Client()->GameTick(g_Config.m_ClDummy) > m_aKillmsgs[r].m_Tick + Client()->GameTickSpeed() * 10) - continue; + if(pInfoMsg->m_ModeSpecial & 1) + { + int QuadOffset = 0; + if(pInfoMsg->m_aVictimIds[0] == pInfoMsg->m_FlagCarrierBlue) + ++QuadOffset; - float x = StartX; + if(QuadOffset == 0) + Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagRed); + else + Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagBlue); - // render victim name - x -= m_aKillmsgs[r].m_VictimTextWidth; - ColorRGBA TextColor; - if(g_Config.m_ClChatTeamColors && m_aKillmsgs[r].m_VictimDDTeam) - TextColor = m_pClient->GetDDTeamColor(m_aKillmsgs[r].m_VictimDDTeam, 0.75f); - else - TextColor = TextRender()->DefaultTextColor(); + Graphics()->RenderQuadContainerAsSprite(m_SpriteQuadContainerIndex, QuadOffset, x, y - 16); + } + } + + for(int j = (pInfoMsg->m_TeamSize - 1); j >= 0; j--) + { + if(pInfoMsg->m_aVictimIds[j] < 0) + continue; - CreateKillmessageNamesIfNotCreated(m_aKillmsgs[r]); + const CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[j], OffsetToMid); + const vec2 TeeRenderPos = vec2(x, y + 46.0f / 2.0f + OffsetToMid.y); + RenderTools()->RenderTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[j], EMOTE_PAIN, vec2(-1, 0), TeeRenderPos); - if(m_aKillmsgs[r].m_VictimTextContainerIndex.Valid()) - TextRender()->RenderTextContainer(m_aKillmsgs[r].m_VictimTextContainerIndex, TextColor, TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); + x -= 44.0f; + } - // render victim tee - x -= 24.0f; + // render weapon + x -= 32.0f; + if(pInfoMsg->m_Weapon >= 0) + { + Graphics()->TextureSet(GameClient()->m_GameSkin.m_aSpriteWeapons[pInfoMsg->m_Weapon]); + Graphics()->RenderQuadContainerAsSprite(m_SpriteQuadContainerIndex, 4 + pInfoMsg->m_Weapon, x, y + 28); + } + x -= 52.0f; + if(pInfoMsg->m_aVictimIds[0] != pInfoMsg->m_KillerID) + { if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) { - if(m_aKillmsgs[r].m_ModeSpecial & 1) + if(pInfoMsg->m_ModeSpecial & 2) { - int QuadOffset = 0; - if(m_aKillmsgs[r].m_aVictimIds[0] == m_aKillmsgs[r].m_FlagCarrierBlue) + int QuadOffset = 2; + if(pInfoMsg->m_KillerID == pInfoMsg->m_FlagCarrierBlue) ++QuadOffset; - if(QuadOffset == 0) + if(QuadOffset == 2) Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagRed); else Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagBlue); - Graphics()->RenderQuadContainerAsSprite(m_SpriteQuadContainerIndex, QuadOffset, x, y - 16); + Graphics()->RenderQuadContainerAsSprite(m_SpriteQuadContainerIndex, QuadOffset, x - 56, y - 16); } } - for(int j = (m_aKillmsgs[r].m_TeamSize - 1); j >= 0; j--) - { - if(m_aKillmsgs[r].m_aVictimIds[j] < 0) - continue; + // render killer tee + x -= 24.0f; + if(pInfoMsg->m_KillerID >= 0) + { const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &m_aKillmsgs[r].m_aVictimRenderInfo[j], OffsetToMid); + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_KillerRenderInfo, OffsetToMid); const vec2 TeeRenderPos = vec2(x, y + 46.0f / 2.0f + OffsetToMid.y); - RenderTools()->RenderTee(pIdleState, &m_aKillmsgs[r].m_aVictimRenderInfo[j], EMOTE_PAIN, vec2(-1, 0), TeeRenderPos); - - x -= 44.0f; + RenderTools()->RenderTee(pIdleState, &pInfoMsg->m_KillerRenderInfo, EMOTE_ANGRY, vec2(1, 0), TeeRenderPos); } - // render weapon x -= 32.0f; - if(m_aKillmsgs[r].m_Weapon >= 0) - { - Graphics()->TextureSet(GameClient()->m_GameSkin.m_aSpriteWeapons[m_aKillmsgs[r].m_Weapon]); - Graphics()->RenderQuadContainerAsSprite(m_SpriteQuadContainerIndex, 4 + m_aKillmsgs[r].m_Weapon, x, y + 28); - } - x -= 52.0f; - if(m_aKillmsgs[r].m_aVictimIds[0] != m_aKillmsgs[r].m_KillerID) - { - if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) - { - if(m_aKillmsgs[r].m_ModeSpecial & 2) - { - int QuadOffset = 2; - if(m_aKillmsgs[r].m_KillerID == m_aKillmsgs[r].m_FlagCarrierBlue) - ++QuadOffset; - - if(QuadOffset == 2) - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagRed); - else - Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagBlue); - - Graphics()->RenderQuadContainerAsSprite(m_SpriteQuadContainerIndex, QuadOffset, x - 56, y - 16); - } - } + // render killer name + x -= pInfoMsg->m_KillerTextWidth; - // render killer tee - x -= 24.0f; + if(pInfoMsg->m_KillerTextContainerIndex.Valid()) + TextRender()->RenderTextContainer(pInfoMsg->m_KillerTextContainerIndex, TextColor, TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); + } +} - if(m_aKillmsgs[r].m_KillerID >= 0) - { - const CAnimState *pIdleState = CAnimState::GetIdle(); - vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &m_aKillmsgs[r].m_KillerRenderInfo, OffsetToMid); - const vec2 TeeRenderPos = vec2(x, y + 46.0f / 2.0f + OffsetToMid.y); - RenderTools()->RenderTee(pIdleState, &m_aKillmsgs[r].m_KillerRenderInfo, EMOTE_ANGRY, vec2(1, 0), TeeRenderPos); - } - x -= 32.0f; +void CInfoMessages::OnRender() +{ + if(!g_Config.m_ClShowKillMessages) + return; - // render killer name - x -= m_aKillmsgs[r].m_KillerTextWidth; + float Height = 400 * 3.0f; + float Width = Height * Graphics()->ScreenAspect(); - if(m_aKillmsgs[r].m_KillerTextContainerIndex.Valid()) - TextRender()->RenderTextContainer(m_aKillmsgs[r].m_KillerTextContainerIndex, TextColor, TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); - } + Graphics()->MapScreen(0, 0, Width * 1.5f, Height * 1.5f); + Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); + + float StartX = Width * 1.5f - 10.0f; + float y = 30.0f + 100.0f * ((g_Config.m_ClShowfps ? 1 : 0) + (g_Config.m_ClShowpred && Client()->State() != IClient::STATE_DEMOPLAYBACK)); + + for(int i = 1; i <= MAX_INFOMSGS; i++) + { + int r = (m_KillmsgCurrent + i) % MAX_KILLMSGS; + if(Client()->GameTick(g_Config.m_ClDummy) > m_aKillmsgs[r].m_Tick + Client()->GameTickSpeed() * 10) + continue; + + if(pInfoMsg->m_Type == INFOMSG_KILL) + RenderKillMsg(pInfoMsg, StartX, y); y += 46.0f; } } -void CKillMessages::RefindSkins() +void CInfoMessages::RefindSkins() { - for(auto &KillMsg : m_aKillmsgs) + for(auto &InfoMsg : m_aInfoMsgs) { - KillMsg.m_KillerRenderInfo.Reset(); - if(KillMsg.m_KillerID >= 0) + InfoMsg.m_KillerRenderInfo.Reset(); + if(InfoMsg.m_KillerID >= 0) { - const CGameClient::CClientData &Client = GameClient()->m_aClients[KillMsg.m_KillerID]; + const CGameClient::CClientData &Client = GameClient()->m_aClients[InfoMsg.m_KillerID]; if(Client.m_Active && Client.m_aSkinName[0] != '\0') - KillMsg.m_KillerRenderInfo = Client.m_RenderInfo; + InfoMsg.m_KillerRenderInfo = Client.m_RenderInfo; else - KillMsg.m_KillerID = -1; + InfoMsg.m_KillerID = -1; } for(int i = 0; i < MAX_KILLMSG_TEAM_MEMBERS; i++) { - KillMsg.m_aVictimRenderInfo[i].Reset(); - if(KillMsg.m_aVictimIds[i] >= 0) + InfoMsg.m_aVictimRenderInfo[i].Reset(); + if(InfoMsg.m_aVictimIds[i] >= 0) { - const CGameClient::CClientData &Client = GameClient()->m_aClients[KillMsg.m_aVictimIds[i]]; + const CGameClient::CClientData &Client = GameClient()->m_aClients[InfoMsg.m_aVictimIds[i]]; if(Client.m_Active && Client.m_aSkinName[0] != '\0') - KillMsg.m_aVictimRenderInfo[i] = Client.m_RenderInfo; + InfoMsg.m_aVictimRenderInfo[i] = Client.m_RenderInfo; else - KillMsg.m_aVictimIds[i] = -1; + InfoMsg.m_aVictimIds[i] = -1; } } } diff --git a/src/game/client/components/killmessages.h b/src/game/client/components/infomessages.h similarity index 72% rename from src/game/client/components/killmessages.h rename to src/game/client/components/infomessages.h index 44be229d2b9..02a8ae11da9 100644 --- a/src/game/client/components/killmessages.h +++ b/src/game/client/components/infomessages.h @@ -1,24 +1,27 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ -#ifndef GAME_CLIENT_COMPONENTS_KILLMESSAGES_H -#define GAME_CLIENT_COMPONENTS_KILLMESSAGES_H +#ifndef GAME_CLIENT_COMPONENTS_INFOMESSAGES_H +#define GAME_CLIENT_COMPONENTS_INFOMESSAGES_H #include #include -class CKillMessages : public CComponent +class CInfoMessages : public CComponent { int m_SpriteQuadContainerIndex; enum { - MAX_KILLMSGS = 5, + MAX_INFOMSGS = 5, MAX_KILLMSG_TEAM_MEMBERS = 4, + + INFOMSG_KILL = 0, }; public: - // kill messages - struct CKillMsg + // info messages + struct CInfoMsg { - int m_Weapon; + int m_Type; + int m_Tick; int m_aVictimIds[MAX_KILLMSG_TEAM_MEMBERS]; int m_VictimDDTeam; @@ -32,18 +35,22 @@ class CKillMessages : public CComponent float m_KillerTextWidth; CTeeRenderInfo m_KillerRenderInfo; + // kill msg + int m_Weapon; int m_ModeSpecial; // for CTF, if the guy is carrying a flag for example - int m_Tick; int m_FlagCarrierBlue; int m_TeamSize; }; private: - void CreateKillmessageNamesIfNotCreated(CKillMsg &Kill); + void AddInfoMsg(int Type, CInfoMsg NewMsg); + void RenderKillMsg(CInfoMsg *pInfoMsg, float x, float y); + + void CreateKillmessageNamesIfNotCreated(CInfoMsg *pInfoMsg); public: - CKillMsg m_aKillmsgs[MAX_KILLMSGS]; - int m_KillmsgCurrent; + CInfoMsg m_aInfoMsgs[MAX_INFOMSGS]; + int m_InfoMsgCurrent; virtual int Sizeof() const override { return sizeof(*this); } virtual void OnWindowResize() override; diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 3e69044e9b4..506af175edf 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -78,7 +78,7 @@ void CMenus::HandleDemoSeeking(float PositionToSeek, float TimeToSeek) if((PositionToSeek >= 0.0f && PositionToSeek <= 1.0f) || TimeToSeek != 0.0f) { m_pClient->m_Chat.Reset(); - m_pClient->m_KillMessages.OnReset(); + m_pClient->m_InfoMessages.OnReset(); m_pClient->m_Particles.OnReset(); m_pClient->m_Sounds.OnReset(); m_pClient->m_Scoreboard.OnReset(); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 5cd59e236e2..26104ace039 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -52,7 +52,7 @@ #include "components/ghost.h" #include "components/hud.h" #include "components/items.h" -#include "components/killmessages.h" +#include "components/infomessages.h" #include "components/mapimages.h" #include "components/maplayers.h" #include "components/mapsounds.h" @@ -135,7 +135,7 @@ void CGameClient::OnConsoleInit() &m_Hud, &m_Spectator, &m_Emoticon, - &m_KillMessages, + &m_InfoMessages, &m_Chat, &m_Broadcast, &m_DebugHud, @@ -3389,7 +3389,7 @@ void CGameClient::RefindSkins() } m_Ghost.RefindSkins(); m_Chat.RefindSkins(); - m_KillMessages.RefindSkins(); + m_InfoMessages.RefindSkins(); } static bool UnknownMapSettingCallback(const char *pCommand, void *pUser) diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 71156a5b8c4..5787db7488b 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -35,7 +35,7 @@ #include "components/ghost.h" #include "components/hud.h" #include "components/items.h" -#include "components/killmessages.h" +#include "components/infomessages.h" #include "components/mapimages.h" #include "components/maplayers.h" #include "components/mapsounds.h" @@ -111,7 +111,7 @@ class CGameClient : public IGameClient { public: // all components - CKillMessages m_KillMessages; + CInfoMessages m_InfoMessages; CCamera m_Camera; CChat m_Chat; CMotd m_Motd; From 2cb948a57baea34a39f7b7d08f6e5d8faa7f06fe Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 20 Nov 2023 12:23:56 +0100 Subject: [PATCH 045/198] Add finish info messages. --- CMakeLists.txt | 5 +- data/race_flag.png | Bin 0 -> 1999 bytes datasrc/content.py | 1 + datasrc/network.py | 8 + src/base/system.cpp | 8 +- src/base/system.h | 1 + src/game/client/components/infomessages.cpp | 204 ++++++++++++++++---- src/game/client/components/infomessages.h | 26 ++- src/game/client/gameclient.cpp | 2 +- src/game/client/gameclient.h | 2 +- src/game/server/teams.cpp | 14 +- src/test/str.cpp | 17 +- 12 files changed, 232 insertions(+), 56 deletions(-) create mode 100644 data/race_flag.png diff --git a/CMakeLists.txt b/CMakeLists.txt index fbd9982cb96..8b8ae171148 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1543,6 +1543,7 @@ set(EXPECTED_DATA menuimages/play_game.png menuimages/settings.png particles.png + race_flag.png shader/pipeline.frag shader/pipeline.vert shader/prim.frag @@ -2194,10 +2195,10 @@ if(CLIENT) components/ghost.h components/hud.cpp components/hud.h + components/infomessages.cpp + components/infomessages.h components/items.cpp components/items.h - components/killmessages.cpp - components/killmessages.h components/mapimages.cpp components/mapimages.h components/maplayers.cpp diff --git a/data/race_flag.png b/data/race_flag.png new file mode 100644 index 0000000000000000000000000000000000000000..c9ceba5b8d5111903fdbd00653ae35fb182834c3 GIT binary patch literal 1999 zcmV;=2Qc`FP)6!pPBt1?@H@xE$hPBx^dbByKYmtuG*@U z23Njl+JFc(4JAx&ij4mZx*((jcsgYNL}*t^SZAG zz%1}F@L!bVxmo)Mv4Z|=2{tn#z zKm6qY1E^pr$f(z8z$d`Ms=um8k!Z7a&~dL?bfb>C;U6O%tq#_r8$_x-kxT9~gY~xyN~IR}6r6Zp<>7)tQ`} zWNvN_upXpPC^$yBT#kniA2K;P=_n-@ixG`R0qYI-_V$v?%;@13P=Gw_k-{8qhI`* z(((d@+&ty}dq|-jzQ$e|-f3X4c@tgXwA^-UoB0`XL;W}ID7z>SIbc>7PU5stB^9 zA9RI)f8Y5h*WR{bvRu_U{l*>6oH^r5fL{oB5zs9Kh$g{%0FiLRO}VB6uxr;YUt7%rBcaqW|}N5Ejia{ zn#P3-7XXWlcs%Z$bM*onO2D@PqHYCL8w!}1m|$vZs-muVO7`>A7Y_kqS>Dgy6#^Sez|71He|YUxQb~nyMB!$(h#Bo?e0=!(~PQd8ssHcO`(NQj5yx8*m*w`4?uU~I^?@c2P(HBHf?_D8Yb4KyJ*~ zs;a@xojcodAQB>*VJ`43Kz2VofU27-hP+Z4$~UN0trzjtp+5HQ+t-$ZF0BU$DL{mP5Wp)x{Ta`XHFm&X_*MH3 zL=<4pAQ4o6NC*T0Lct)WVNfaMDHj%*p1$w9C1#^b3nD@a5ZM=a1Nd=&GQuyPf5NH! z?H_OR;fHsL#bTU3eR@Sg?CI$t5U|D!ilQ(xGebU~ckl5z`v3qVBO@F+a)d-8;Vh0S zib5n3VfXIcsH&0M`BE5hND|3L00HN{udP10h6(L}3K?2-xt= min) + return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ".%02" PRId64, centisecs / min, + (centisecs % min) / sec, centisecs % sec); + [[fallthrough]]; + case TIME_SECS_CENTISECS: + return str_format(buffer, buffer_size, "%02" PRId64 ".%02" PRId64, (centisecs % min) / sec, centisecs % sec); } return -1; diff --git a/src/base/system.h b/src/base/system.h index b27d853fb50..b10cf5660db 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1754,6 +1754,7 @@ enum TIME_MINS, TIME_HOURS_CENTISECS, TIME_MINS_CENTISECS, + TIME_SECS_CENTISECS, }; /* diff --git a/src/game/client/components/infomessages.cpp b/src/game/client/components/infomessages.cpp index fbf87b83778..1b1b6025ab6 100644 --- a/src/game/client/components/infomessages.cpp +++ b/src/game/client/components/infomessages.cpp @@ -18,24 +18,28 @@ void CInfoMessages::OnWindowResize() { for(auto &InfoMsg : m_aInfoMsgs) { - TextRender()->DeleteTextContainer(InfoMsg.m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(InfoMsg.m_KillerTextContainerIndex); + DeleteTextContainers(&InfoMsg); } } void CInfoMessages::OnReset() { m_InfoMsgCurrent = 0; - for(auto &InfoMsg : m_aInfoMsgs) { InfoMsg.m_Tick = -100000; - - TextRender()->DeleteTextContainer(InfoMsg.m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(InfoMsg.m_KillerTextContainerIndex); + DeleteTextContainers(&InfoMsg); } } +void CInfoMessages::DeleteTextContainers(CInfoMsg *pInfoMsg) +{ + TextRender()->DeleteTextContainer(pInfoMsg->m_VictimTextContainerIndex); + TextRender()->DeleteTextContainer(pInfoMsg->m_KillerTextContainerIndex); + TextRender()->DeleteTextContainer(pInfoMsg->m_DiffTextContainerIndex); + TextRender()->DeleteTextContainer(pInfoMsg->m_TimeTextContainerIndex); +} + void CInfoMessages::OnInit() { Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); @@ -61,21 +65,22 @@ void CInfoMessages::OnInit() Graphics()->QuadContainerUpload(m_SpriteQuadContainerIndex); } -void CInfoMessages::AddInfoMsg(int Type, CInfoMsg NewMsg) +void CInfoMessages::AddInfoMsg(EType Type, CInfoMsg NewMsg) { NewMsg.m_Type = Type; NewMsg.m_Tick = Client()->GameTick(g_Config.m_ClDummy); - m_InfoMsgCurrent = (m_InfoMsgCurrent+1)%MAX_INFOMSGS; + m_InfoMsgCurrent = (m_InfoMsgCurrent + 1) % MAX_INFOMSGS; + DeleteTextContainers(&m_aInfoMsgs[m_InfoMsgCurrent]); m_aInfoMsgs[m_InfoMsgCurrent] = NewMsg; } -void CInfoMessages::CreateKillmessageNamesIfNotCreated(CInfoMsg *pInfoMsg) +void CInfoMessages::CreateNamesIfNotCreated(CInfoMsg *pInfoMsg) { const float FontSize = 36.0f; if(!pInfoMsg->m_VictimTextContainerIndex.Valid() && pInfoMsg->m_aVictimName[0] != 0) { - pInfoMsg->m_VictimTextWidth = TextRender()->TextWidth(FontSize, pInfoMsg->m_aVictimName, -1, -1.0f); + pInfoMsg->m_VictimTextWidth = TextRender()->TextWidth(FontSize, pInfoMsg->m_aVictimName); CTextCursor Cursor; TextRender()->SetCursor(&Cursor, 0, 0, FontSize, TEXTFLAG_RENDER); @@ -93,7 +98,7 @@ void CInfoMessages::CreateKillmessageNamesIfNotCreated(CInfoMsg *pInfoMsg) if(!pInfoMsg->m_KillerTextContainerIndex.Valid() && pInfoMsg->m_aKillerName[0] != 0) { - pInfoMsg->m_KillerTextWidth = TextRender()->TextWidth(FontSize, pInfoMsg->m_aKillerName, -1, -1.0f); + pInfoMsg->m_KillerTextWidth = TextRender()->TextWidth(FontSize, pInfoMsg->m_aKillerName); CTextCursor Cursor; TextRender()->SetCursor(&Cursor, 0, 0, FontSize, TEXTFLAG_RENDER); @@ -111,6 +116,39 @@ void CInfoMessages::CreateKillmessageNamesIfNotCreated(CInfoMsg *pInfoMsg) TextRender()->TextColor(TextRender()->DefaultTextColor()); } +void CInfoMessages::CreateFinishTextContainersIfNotCreated(CInfoMsg *pInfoMsg) +{ + const float FontSize = 36.0f; + if(!pInfoMsg->m_DiffTextContainerIndex.Valid() && pInfoMsg->m_aDiffText[0] != 0) + { + pInfoMsg->m_DiffTextWidth = TextRender()->TextWidth(FontSize, pInfoMsg->m_aDiffText); + CTextCursor Cursor; + TextRender()->SetCursor(&Cursor, 0, 0, FontSize, TEXTFLAG_RENDER); + Cursor.m_LineWidth = -1; + + if(pInfoMsg->m_Diff > 0) + TextRender()->TextColor(1.0f, 0.5f, 0.5f, 1); // red + else if(pInfoMsg->m_Diff < 0) + TextRender()->TextColor(0.5f, 1.0f, 0.5f, 1); // green + else + TextRender()->TextColor(TextRender()->DefaultTextColor()); + + TextRender()->CreateTextContainer(pInfoMsg->m_DiffTextContainerIndex, &Cursor, pInfoMsg->m_aDiffText); + } + if(!pInfoMsg->m_TimeTextContainerIndex.Valid() && pInfoMsg->m_aTimeText[0] != 0) + { + pInfoMsg->m_TimeTextWidth = TextRender()->TextWidth(FontSize, pInfoMsg->m_aTimeText); + CTextCursor Cursor; + TextRender()->SetCursor(&Cursor, 0, 0, FontSize, TEXTFLAG_RENDER); + Cursor.m_LineWidth = -1; + + TextRender()->TextColor(TextRender()->DefaultTextColor()); + + TextRender()->CreateTextContainer(pInfoMsg->m_TimeTextContainerIndex, &Cursor, pInfoMsg->m_aTimeText); + } + TextRender()->TextColor(TextRender()->DefaultTextColor()); +} + void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) { if(m_pClient->m_SuppressEvents) @@ -121,7 +159,6 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) CNetMsg_Sv_KillMsgTeam *pMsg = (CNetMsg_Sv_KillMsgTeam *)pRawMsg; CInfoMsg Kill{}; - Kill.m_Type = INFOMSG_KILL; std::vector> vStrongWeakSorted; for(int i = 0; i < MAX_CLIENTS; i++) @@ -166,7 +203,6 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) Kill.m_Weapon = -1; Kill.m_ModeSpecial = 0; - Kill.m_Tick = Client()->GameTick(g_Config.m_ClDummy); Kill.m_VictimTextWidth = Kill.m_KillerTextWidth = 0.f; @@ -177,7 +213,7 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); Graphics()->MapScreen(0, 0, Width * 1.5f, Height * 1.5f); - CreateKillmessageNamesIfNotCreated(&Kill); + CreateNamesIfNotCreated(&Kill); bool KillMsgValid = true; for(int i = 0; i < Kill.m_TeamSize; i++) @@ -187,18 +223,11 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) if(KillMsgValid) { - // add the message - m_InfoMsgCurrent = (m_InfoMsgCurrent + 1) % MAX_INFOMSGS; - - TextRender()->DeleteTextContainer(m_aInfoMsgs[m_InfoMsgCurrent].m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(m_aInfoMsgs[m_InfoMsgCurrent].m_KillerTextContainerIndex); - - m_aInfoMsgs[m_InfoMsgCurrent] = Kill; + AddInfoMsg(EType::TYPE_KILL, Kill); } else { - TextRender()->DeleteTextContainer(Kill.m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(Kill.m_KillerTextContainerIndex); + DeleteTextContainers(&Kill); } Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); @@ -209,7 +238,6 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; CInfoMsg Kill{}; - Kill.m_Type = INFOMSG_KILL; Kill.m_TeamSize = 1; Kill.m_aVictimIds[0] = pMsg->m_Victim; @@ -244,7 +272,6 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) Kill.m_Weapon = pMsg->m_Weapon; Kill.m_ModeSpecial = pMsg->m_ModeSpecial; - Kill.m_Tick = Client()->GameTick(g_Config.m_ClDummy); Kill.m_FlagCarrierBlue = m_pClient->m_Snap.m_pGameDataObj ? m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue : -1; @@ -257,26 +284,73 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); Graphics()->MapScreen(0, 0, Width * 1.5f, Height * 1.5f); - CreateKillmessageNamesIfNotCreated(&Kill); + CreateNamesIfNotCreated(&Kill); bool KillMsgValid = (Kill.m_aVictimRenderInfo[0].m_CustomColoredSkin && Kill.m_aVictimRenderInfo[0].m_ColorableRenderSkin.m_Body.IsValid()) || (!Kill.m_aVictimRenderInfo[0].m_CustomColoredSkin && Kill.m_aVictimRenderInfo[0].m_OriginalRenderSkin.m_Body.IsValid()); KillMsgValid &= Kill.m_KillerID == -1 || ((Kill.m_KillerRenderInfo.m_CustomColoredSkin && Kill.m_KillerRenderInfo.m_ColorableRenderSkin.m_Body.IsValid()) || (!Kill.m_KillerRenderInfo.m_CustomColoredSkin && Kill.m_KillerRenderInfo.m_OriginalRenderSkin.m_Body.IsValid())); if(KillMsgValid) { - // add the message - m_InfoMsgCurrent = (m_InfoMsgCurrent + 1) % MAX_INFOMSGS; + AddInfoMsg(EType::TYPE_KILL, Kill); + } + else + { + DeleteTextContainers(&Kill); + } - TextRender()->DeleteTextContainer(m_aInfoMsgs[m_InfoMsgCurrent].m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(m_aInfoMsgs[m_InfoMsgCurrent].m_KillerTextContainerIndex); + Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); + } + + if(MsgType == NETMSGTYPE_SV_RACEFINISH) + { + CNetMsg_Sv_RaceFinish *pMsg = (CNetMsg_Sv_RaceFinish *)pRawMsg; + + char aBuf[256]; + + CInfoMsg Finish; + Finish.m_TeamSize = 1; + Finish.m_aVictimIds[0] = pMsg->m_ClientID; + Finish.m_VictimDDTeam = m_pClient->m_Teams.Team(Finish.m_aVictimIds[0]); + str_copy(Finish.m_aVictimName, m_pClient->m_aClients[Finish.m_aVictimIds[0]].m_aName); + Finish.m_aVictimRenderInfo[0] = m_pClient->m_aClients[pMsg->m_ClientID].m_RenderInfo; - m_aInfoMsgs[m_InfoMsgCurrent] = Kill; + Finish.m_aKillerName[0] = '\0'; + + Finish.m_Diff = pMsg->m_Diff; + Finish.m_RecordPersonal = (pMsg->m_RecordPersonal || pMsg->m_RecordServer); + + // diff time text + if(Finish.m_Diff) + { + if(Finish.m_Diff < 0) + { + str_time_float(-Finish.m_Diff / 1000.0f, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)); + str_format(Finish.m_aDiffText, sizeof(Finish.m_aDiffText), "(-%s)", aBuf); + } + else + { + str_time_float(Finish.m_Diff / 1000.0f, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)); + str_format(Finish.m_aDiffText, sizeof(Finish.m_aDiffText), "(+%s)", aBuf); + } } else { - TextRender()->DeleteTextContainer(Kill.m_VictimTextContainerIndex); - TextRender()->DeleteTextContainer(Kill.m_KillerTextContainerIndex); + Finish.m_aDiffText[0] = '\0'; } + // finish time text + str_time_float(pMsg->m_Time / 1000.0f, TIME_HOURS_CENTISECS, Finish.m_aTimeText, sizeof(Finish.m_aTimeText)); + + float ScreenX0, ScreenY0, ScreenX1, ScreenY1; + float Height = 400 * 3.0f; + float Width = Height * Graphics()->ScreenAspect(); + Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); + Graphics()->MapScreen(0, 0, Width * 1.5f, Height * 1.5f); + + CreateNamesIfNotCreated(&Finish); + CreateFinishTextContainersIfNotCreated(&Finish); + + AddInfoMsg(EType::TYPE_FINISH, Finish); + Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } } @@ -291,7 +365,7 @@ void CInfoMessages::RenderKillMsg(CInfoMsg *pInfoMsg, float x, float y) else TextColor = TextRender()->DefaultTextColor(); - CreateKillmessageNamesIfNotCreated(pInfoMsg); + CreateNamesIfNotCreated(pInfoMsg); if(pInfoMsg->m_VictimTextContainerIndex.Valid()) TextRender()->RenderTextContainer(pInfoMsg->m_VictimTextContainerIndex, TextColor, TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); @@ -380,12 +454,60 @@ void CInfoMessages::RenderKillMsg(CInfoMsg *pInfoMsg, float x, float y) } } +void CInfoMessages::RenderFinishMsg(CInfoMsg *pInfoMsg, float x, float y) +{ + // render time diff + CreateFinishTextContainersIfNotCreated(pInfoMsg); + + if(pInfoMsg->m_Diff) + { + x -= pInfoMsg->m_DiffTextWidth; + + if(pInfoMsg->m_DiffTextContainerIndex.Valid()) + TextRender()->RenderTextContainer(pInfoMsg->m_DiffTextContainerIndex, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); + } + + // render time + x -= pInfoMsg->m_TimeTextWidth; + + if(pInfoMsg->m_TimeTextContainerIndex.Valid()) + TextRender()->RenderTextContainer(pInfoMsg->m_TimeTextContainerIndex, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); + + // render flag + x -= 52.0f; + + Graphics()->TextureSet(g_pData->m_aImages[IMAGE_RACEFLAG].m_Id); + Graphics()->QuadsBegin(); + IGraphics::CQuadItem QuadItem(x, y, 52, 52); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + + // render victim name + ColorRGBA TextColor; + x -= pInfoMsg->m_VictimTextWidth; + if(g_Config.m_ClChatTeamColors && pInfoMsg->m_VictimDDTeam) + TextColor = m_pClient->GetDDTeamColor(pInfoMsg->m_VictimDDTeam, 0.75f); + else + TextColor = TextRender()->DefaultTextColor(); + + CreateNamesIfNotCreated(pInfoMsg); + + if(pInfoMsg->m_VictimTextContainerIndex.Valid()) + TextRender()->RenderTextContainer(pInfoMsg->m_VictimTextContainerIndex, TextColor, TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); + + // render victim tee + x -= 24.0f; + + const CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[0], OffsetToMid); + const vec2 TeeRenderPos = vec2(x, y + 46.0f / 2.0f + OffsetToMid.y); + const int Emote = pInfoMsg->m_RecordPersonal ? EMOTE_HAPPY : EMOTE_NORMAL; + RenderTools()->RenderTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[0], Emote, vec2(-1, 0), TeeRenderPos); +} void CInfoMessages::OnRender() { - if(!g_Config.m_ClShowKillMessages) - return; - float Height = 400 * 3.0f; float Width = Height * Graphics()->ScreenAspect(); @@ -397,12 +519,14 @@ void CInfoMessages::OnRender() for(int i = 1; i <= MAX_INFOMSGS; i++) { - int r = (m_KillmsgCurrent + i) % MAX_KILLMSGS; - if(Client()->GameTick(g_Config.m_ClDummy) > m_aKillmsgs[r].m_Tick + Client()->GameTickSpeed() * 10) + CInfoMsg *pInfoMsg = &m_aInfoMsgs[(m_InfoMsgCurrent + i) % MAX_INFOMSGS]; + if(Client()->GameTick(g_Config.m_ClDummy) > pInfoMsg->m_Tick + Client()->GameTickSpeed() * 10) continue; - if(pInfoMsg->m_Type == INFOMSG_KILL) + if(pInfoMsg->m_Type == EType::TYPE_KILL && g_Config.m_ClShowKillMessages) RenderKillMsg(pInfoMsg, StartX, y); + else if(pInfoMsg->m_Type == EType::TYPE_FINISH) + RenderFinishMsg(pInfoMsg, StartX, y); y += 46.0f; } diff --git a/src/game/client/components/infomessages.h b/src/game/client/components/infomessages.h index 02a8ae11da9..680c46c6512 100644 --- a/src/game/client/components/infomessages.h +++ b/src/game/client/components/infomessages.h @@ -12,15 +12,19 @@ class CInfoMessages : public CComponent { MAX_INFOMSGS = 5, MAX_KILLMSG_TEAM_MEMBERS = 4, + }; - INFOMSG_KILL = 0, + enum EType + { + TYPE_KILL, + TYPE_FINISH, }; public: // info messages struct CInfoMsg { - int m_Type; + EType m_Type; int m_Tick; int m_aVictimIds[MAX_KILLMSG_TEAM_MEMBERS]; @@ -40,13 +44,27 @@ class CInfoMessages : public CComponent int m_ModeSpecial; // for CTF, if the guy is carrying a flag for example int m_FlagCarrierBlue; int m_TeamSize; + + // finish msg + int m_Diff; + char m_aTimeText[32]; + char m_aDiffText[32]; + STextContainerIndex m_TimeTextContainerIndex; + STextContainerIndex m_DiffTextContainerIndex; + float m_TimeTextWidth; + float m_DiffTextWidth; + bool m_RecordPersonal; }; private: - void AddInfoMsg(int Type, CInfoMsg NewMsg); + void AddInfoMsg(EType Type, CInfoMsg NewMsg); void RenderKillMsg(CInfoMsg *pInfoMsg, float x, float y); + void RenderFinishMsg(CInfoMsg *pInfoMsg, float x, float y); + + void CreateNamesIfNotCreated(CInfoMsg *pInfoMsg); + void CreateFinishTextContainersIfNotCreated(CInfoMsg *pInfoMsg); - void CreateKillmessageNamesIfNotCreated(CInfoMsg *pInfoMsg); + void DeleteTextContainers(CInfoMsg *pInfoMsg); public: CInfoMsg m_aInfoMsgs[MAX_INFOMSGS]; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 26104ace039..19c92967749 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -51,8 +51,8 @@ #include "components/freezebars.h" #include "components/ghost.h" #include "components/hud.h" -#include "components/items.h" #include "components/infomessages.h" +#include "components/items.h" #include "components/mapimages.h" #include "components/maplayers.h" #include "components/mapsounds.h" diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 5787db7488b..30856f74dc5 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -34,8 +34,8 @@ #include "components/freezebars.h" #include "components/ghost.h" #include "components/hud.h" -#include "components/items.h" #include "components/infomessages.h" +#include "components/items.h" #include "components/mapimages.h" #include "components/maplayers.h" #include "components/mapsounds.h" diff --git a/src/game/server/teams.cpp b/src/game/server/teams.cpp index 1cff6a384ac..aa0cd418acb 100644 --- a/src/game/server/teams.cpp +++ b/src/game/server/teams.cpp @@ -770,6 +770,18 @@ void CGameTeams::OnFinish(CPlayer *Player, float Time, const char *pTimestamp) Server()->SendPackMsg(&MsgLegacy, MSGFLAG_VITAL, ClientID); } } + + CNetMsg_Sv_RaceFinish RaceFinishMsg; + RaceFinishMsg.m_ClientID = ClientID; + RaceFinishMsg.m_Time = Time * 1000; + RaceFinishMsg.m_Diff = 0; + if(pData->m_BestTime) + { + RaceFinishMsg.m_Diff = Diff * 1000 * (Time < pData->m_BestTime ? -1 : 1); + } + RaceFinishMsg.m_RecordPersonal = (Time < pData->m_BestTime || !pData->m_BestTime); + RaceFinishMsg.m_RecordServer = Time < GameServer()->m_pController->m_CurrentRecord; + Server()->SendPackMsg(&RaceFinishMsg, MSGFLAG_VITAL | MSGFLAG_NORECORD, -1); } else { @@ -781,7 +793,7 @@ void CGameTeams::OnFinish(CPlayer *Player, float Time, const char *pTimestamp) { Msg.m_Diff = Diff * 1000 * (Time < pData->m_BestTime ? -1 : 1); } - Msg.m_RecordPersonal = Time < pData->m_BestTime; + Msg.m_RecordPersonal = (Time < pData->m_BestTime || !pData->m_BestTime); Msg.m_RecordServer = Time < GameServer()->m_pController->m_CurrentRecord; Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, -1); } diff --git a/src/test/str.cpp b/src/test/str.cpp index fcd75b705c5..bb450214942 100644 --- a/src/test/str.cpp +++ b/src/test/str.cpp @@ -757,11 +757,11 @@ TEST(Str, Time) EXPECT_EQ(str_time(123456, TIME_DAYS, aBuf, 0), -1); EXPECT_STREQ(aBuf, "foobar"); - EXPECT_EQ(str_time(123456, TIME_MINS_CENTISECS + 1, aBuf, sizeof(aBuf)), -1); + EXPECT_EQ(str_time(123456, TIME_SECS_CENTISECS + 1, aBuf, sizeof(aBuf)), -1); EXPECT_STREQ(aBuf, ""); - EXPECT_EQ(str_time(-123456, TIME_MINS_CENTISECS, aBuf, sizeof(aBuf)), 8); - EXPECT_STREQ(aBuf, "00:00.00"); + EXPECT_EQ(str_time(-123456, TIME_MINS_CENTISECS, aBuf, sizeof(aBuf)), 5); + EXPECT_STREQ(aBuf, "00.00"); EXPECT_EQ(str_time(INT64_MAX, TIME_DAYS, aBuf, sizeof(aBuf)), 23); EXPECT_STREQ(aBuf, "1067519911673d 00:09:18"); @@ -800,6 +800,13 @@ TEST(Str, Time) EXPECT_STREQ(aBuf, "205:45.67"); EXPECT_EQ(str_time(12345678, TIME_MINS_CENTISECS, aBuf, sizeof(aBuf)), 10); EXPECT_STREQ(aBuf, "2057:36.78"); + + EXPECT_EQ(str_time(123456, TIME_SECS_CENTISECS, aBuf, sizeof(aBuf)), 5); + EXPECT_STREQ(aBuf, "34.56"); + EXPECT_EQ(str_time(1234567, TIME_SECS_CENTISECS, aBuf, sizeof(aBuf)), 5); + EXPECT_STREQ(aBuf, "45.67"); + EXPECT_EQ(str_time(12345678, TIME_SECS_CENTISECS, aBuf, sizeof(aBuf)), 5); + EXPECT_STREQ(aBuf, "36.78"); } TEST(Str, TimeFloat) @@ -808,8 +815,8 @@ TEST(Str, TimeFloat) EXPECT_EQ(str_time_float(123456.78, TIME_DAYS, aBuf, sizeof(aBuf)), 11); EXPECT_STREQ(aBuf, "1d 10:17:36"); - EXPECT_EQ(str_time_float(12.16, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)), 8); - EXPECT_STREQ(aBuf, "00:12.16"); + EXPECT_EQ(str_time_float(12.16, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)), 5); + EXPECT_STREQ(aBuf, "12.16"); EXPECT_EQ(str_time_float(22.995, TIME_MINS, aBuf, sizeof(aBuf)), 5); EXPECT_STREQ(aBuf, "00:22"); From eff6361c38aa9ad10fa3467f52e911ff65adfc24 Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 20 Nov 2023 20:16:07 +0100 Subject: [PATCH 046/198] Add settings for info messages --- src/engine/shared/config_variables.h | 1 + src/game/client/components/infomessages.cpp | 16 ++++++---- src/game/client/components/menus_settings.cpp | 29 +++++++++++++------ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 9966b5365d1..536ed952dfc 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -64,6 +64,7 @@ MACRO_CONFIG_INT(ClShowChat, cl_showchat, 1, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE MACRO_CONFIG_INT(ClShowChatFriends, cl_show_chat_friends, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show only chat messages from friends") MACRO_CONFIG_INT(ClShowChatSystem, cl_show_chat_system, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show chat messages from the server") MACRO_CONFIG_INT(ClShowKillMessages, cl_showkillmessages, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show kill messages") +MACRO_CONFIG_INT(ClShowFinishMessages, cl_show_finish_messages, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show finish messages") MACRO_CONFIG_INT(ClShowVotesAfterVoting, cl_show_votes_after_voting, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show votes window after voting") MACRO_CONFIG_INT(ClShowLocalTimeAlways, cl_show_local_time_always, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Always show local time") MACRO_CONFIG_INT(ClShowfps, cl_showfps, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame FPS counter") diff --git a/src/game/client/components/infomessages.cpp b/src/game/client/components/infomessages.cpp index 1b1b6025ab6..d461d670513 100644 --- a/src/game/client/components/infomessages.cpp +++ b/src/game/client/components/infomessages.cpp @@ -154,7 +154,7 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) if(m_pClient->m_SuppressEvents) return; - if(MsgType == NETMSGTYPE_SV_KILLMSGTEAM) + if(MsgType == NETMSGTYPE_SV_KILLMSGTEAM && g_Config.m_ClShowKillMessages) { CNetMsg_Sv_KillMsgTeam *pMsg = (CNetMsg_Sv_KillMsgTeam *)pRawMsg; @@ -233,7 +233,7 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } - if(MsgType == NETMSGTYPE_SV_KILLMSG) + if(MsgType == NETMSGTYPE_SV_KILLMSG && g_Config.m_ClShowKillMessages) { CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; @@ -300,7 +300,7 @@ void CInfoMessages::OnMessage(int MsgType, void *pRawMsg) Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } - if(MsgType == NETMSGTYPE_SV_RACEFINISH) + if(MsgType == NETMSGTYPE_SV_RACEFINISH && g_Config.m_ClShowFinishMessages) { CNetMsg_Sv_RaceFinish *pMsg = (CNetMsg_Sv_RaceFinish *)pRawMsg; @@ -524,11 +524,15 @@ void CInfoMessages::OnRender() continue; if(pInfoMsg->m_Type == EType::TYPE_KILL && g_Config.m_ClShowKillMessages) + { RenderKillMsg(pInfoMsg, StartX, y); - else if(pInfoMsg->m_Type == EType::TYPE_FINISH) + y += 46.0f; + } + else if(pInfoMsg->m_Type == EType::TYPE_FINISH && g_Config.m_ClShowFinishMessages) + { RenderFinishMsg(pInfoMsg, StartX, y); - - y += 46.0f; + y += 46.0f; + } } } diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 5eb9c73640e..8037feec542 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2429,7 +2429,7 @@ enum APPEARANCE_TAB_CHAT = 1, APPEARANCE_TAB_NAME_PLATE = 2, APPEARANCE_TAB_HOOK_COLLISION = 3, - APPEARANCE_TAB_KILL_MESSAGES = 4, + APPEARANCE_TAB_INFO_MESSAGES = 4, APPEARANCE_TAB_LASER = 5, NUMBER_OF_APPEARANCE_TABS = 6, }; @@ -2459,8 +2459,8 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) s_CurTab = APPEARANCE_TAB_NAME_PLATE; if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_HOOK_COLLISION], Localize("Hook Collisions"), s_CurTab == APPEARANCE_TAB_HOOK_COLLISION, &Page4Tab, 0, NULL, NULL, NULL, NULL, 4)) s_CurTab = APPEARANCE_TAB_HOOK_COLLISION; - if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_KILL_MESSAGES], Localize("Kill Messages"), s_CurTab == APPEARANCE_TAB_KILL_MESSAGES, &Page5Tab, 0, NULL, NULL, NULL, NULL, 4)) - s_CurTab = APPEARANCE_TAB_KILL_MESSAGES; + if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_INFO_MESSAGES], Localize("Info Messages"), s_CurTab == APPEARANCE_TAB_INFO_MESSAGES, &Page5Tab, 0, NULL, NULL, NULL, NULL, 4)) + s_CurTab = APPEARANCE_TAB_INFO_MESSAGES; if(DoButton_MenuTab(&s_aPageTabs[APPEARANCE_TAB_LASER], Localize("Laser"), s_CurTab == APPEARANCE_TAB_LASER, &Page6Tab, IGraphics::CORNER_R, NULL, NULL, NULL, NULL, 4)) s_CurTab = APPEARANCE_TAB_LASER; @@ -3005,20 +3005,31 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) DoLine_ColorPicker(&s_HookCollHookableCollResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Something hookable"), &g_Config.m_ClHookCollColorHookableColl, ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f, 1.0f), false); DoLine_ColorPicker(&s_HookCollTeeCollResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("A Tee"), &g_Config.m_ClHookCollColorTeeColl, ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f), false); } - else if(s_CurTab == APPEARANCE_TAB_KILL_MESSAGES) + else if(s_CurTab == APPEARANCE_TAB_INFO_MESSAGES) { MainView.VSplitMid(&LeftView, &RightView); - // ***** Kill Messages ***** // + // ***** Info Messages ***** // LeftView.HSplitTop(HeadlineAndVMargin, &Label, &LeftView); - UI()->DoLabel(&Label, Localize("Kill Messages"), HeadlineFontSize, TEXTALIGN_ML); + UI()->DoLabel(&Label, Localize("Info Messages"), HeadlineFontSize, TEXTALIGN_ML); - // General kill messages settings - LeftView.HSplitTop(SectionTotalMargin + 2 * ColorPickerLineSize, &Section, &LeftView); + // General info messages settings + LeftView.HSplitTop(SectionTotalMargin + 2 * LineSize + 2 * ColorPickerLineSize, &Section, &LeftView); Section.Margin(SectionMargin, &Section); - static CButtonContainer s_KillMessageNormalColorID, s_KillMessageHighlightColorID; + Section.HSplitTop(LineSize, &Button, &Section); + if(DoButton_CheckBox(&g_Config.m_ClShowKillMessages, Localize("Show kill messages"), g_Config.m_ClShowKillMessages, &Button)) + { + g_Config.m_ClShowKillMessages ^= 1; + } + Section.HSplitTop(LineSize, &Button, &Section); + if(DoButton_CheckBox(&g_Config.m_ClShowFinishMessages, Localize("Show finish messages"), g_Config.m_ClShowFinishMessages, &Button)) + { + g_Config.m_ClShowFinishMessages ^= 1; + } + + static CButtonContainer s_KillMessageNormalColorID, s_KillMessageHighlightColorID; DoLine_ColorPicker(&s_KillMessageNormalColorID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Normal Color"), &g_Config.m_ClKillMessageNormalColor, ColorRGBA(1.0f, 1.0f, 1.0f), false); DoLine_ColorPicker(&s_KillMessageHighlightColorID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Highlight Color"), &g_Config.m_ClKillMessageHighlightColor, ColorRGBA(1.0f, 1.0f, 1.0f), false); } From e7f4be1023da4340cba476362f017d534080d924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 26 Nov 2023 17:42:19 +0100 Subject: [PATCH 047/198] Remove unnecessary `rustup default 1.48.0` in non-fancy Linux CI The old default version causes linking errors for the tests in the non-fancy Linux CI, i.e. with Ubuntu 20.04. --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be265b06430..8b225701877 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,7 +56,6 @@ jobs: - name: Prepare Linux (non-fancy) if: ${{ contains(matrix.os, 'ubuntu') && !matrix.fancy }} run: | - rustup default 1.48.0 sudo rm -rf /var/lib/mysql/ /var/run/mysqld sudo mkdir /var/lib/mysql/ /var/run/mysqld/ sudo chown mysql:mysql /var/lib/mysql/ /var/run/mysqld/ From fd6c4ecbea52a0d32d0fcf1bf2b3fc0e1d24851a Mon Sep 17 00:00:00 2001 From: furo Date: Sun, 26 Nov 2023 18:06:50 +0100 Subject: [PATCH 048/198] Fix alignment of buttons in vote menu. --- src/game/client/components/menus_ingame.cpp | 254 ++++++++++---------- 1 file changed, 126 insertions(+), 128 deletions(-) diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index f7da169bd55..106d6511007 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -631,21 +631,19 @@ void CMenus::RenderServerControl(CUIRect MainView) MainView.HSplitBottom(90.0f, &MainView, &RconExtension); // tab bar - { - TabBar.VSplitLeft(TabBar.w / 3, &Button, &TabBar); - static CButtonContainer s_Button0; - if(DoButton_MenuTab(&s_Button0, Localize("Change settings"), s_ControlPage == 0, &Button, 0)) - s_ControlPage = 0; - - TabBar.VSplitMid(&Button, &TabBar); - static CButtonContainer s_Button1; - if(DoButton_MenuTab(&s_Button1, Localize("Kick player"), s_ControlPage == 1, &Button, 0)) - s_ControlPage = 1; - - static CButtonContainer s_Button2; - if(DoButton_MenuTab(&s_Button2, Localize("Move player to spectators"), s_ControlPage == 2, &TabBar, 0)) - s_ControlPage = 2; - } + TabBar.VSplitLeft(TabBar.w / 3, &Button, &TabBar); + static CButtonContainer s_Button0; + if(DoButton_MenuTab(&s_Button0, Localize("Change settings"), s_ControlPage == 0, &Button, 0)) + s_ControlPage = 0; + + TabBar.VSplitMid(&Button, &TabBar); + static CButtonContainer s_Button1; + if(DoButton_MenuTab(&s_Button1, Localize("Kick player"), s_ControlPage == 1, &Button, 0)) + s_ControlPage = 1; + + static CButtonContainer s_Button2; + if(DoButton_MenuTab(&s_Button2, Localize("Move player to spectators"), s_ControlPage == 2, &TabBar, 0)) + s_ControlPage = 2; // render page MainView.HSplitBottom(ms_ButtonHeight + 5 * 2, &MainView, &Bottom); @@ -660,50 +658,105 @@ void CMenus::RenderServerControl(CUIRect MainView) Call = RenderServerControlKick(MainView, true); // vote menu + CUIRect QuickSearch; + + // render quick search + Bottom.VSplitLeft(5.0f, 0, &Bottom); + Bottom.VSplitLeft(250.0f, &QuickSearch, &Bottom); + QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); + + UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML); + float wSearch = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f); + TextRender()->SetRenderFlags(0); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); + QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); + + if(m_ControlPageOpening || (Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())) { - CUIRect QuickSearch; + UI()->SetActiveItem(&m_FilterInput); + m_ControlPageOpening = false; + m_FilterInput.SelectAll(); + } + m_FilterInput.SetEmptyText(Localize("Search")); + UI()->DoClearableEditBox(&m_FilterInput, &QuickSearch, 14.0f); + + // call vote + Bottom.VSplitRight(10.0f, &Bottom, 0); + Bottom.VSplitRight(120.0f, &Bottom, &Button); + Button.HSplitTop(5.0f, 0, &Button); - // render quick search + static CButtonContainer s_CallVoteButton; + if(DoButton_Menu(&s_CallVoteButton, Localize("Call vote"), 0, &Button) || Call) + { + if(s_ControlPage == 0) { - Bottom.VSplitLeft(240.0f, &QuickSearch, &Bottom); - QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); - TextRender()->SetFontPreset(EFontPreset::ICON_FONT); - TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); - - UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML); - float wSearch = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f); - TextRender()->SetRenderFlags(0); - TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); - QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); - QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); - - if(m_ControlPageOpening || (Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())) + m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_CallvoteReasonInput.GetString()); + if(g_Config.m_UiCloseWindowAfterChangingSetting) + SetActive(false); + } + else if(s_ControlPage == 1) + { + if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && + m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) { - UI()->SetActiveItem(&m_FilterInput); - m_ControlPageOpening = false; - m_FilterInput.SelectAll(); + m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString()); + SetActive(false); } - m_FilterInput.SetEmptyText(Localize("Search")); - UI()->DoClearableEditBox(&m_FilterInput, &QuickSearch, 14.0f); } + else if(s_ControlPage == 2) + { + if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && + m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) + { + m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString()); + SetActive(false); + } + } + m_CallvoteReasonInput.Clear(); + } - Bottom.VSplitRight(120.0f, &Bottom, &Button); + // render kick reason + CUIRect Reason; + Bottom.VSplitRight(20.0f, &Bottom, 0); + Bottom.VSplitRight(200.0f, &Bottom, &Reason); + Reason.HSplitTop(5.0f, 0, &Reason); + const char *pLabel = Localize("Reason:"); + UI()->DoLabel(&Reason, pLabel, 14.0f, TEXTALIGN_ML); + float w = TextRender()->TextWidth(14.0f, pLabel, -1, -1.0f); + Reason.VSplitLeft(w + 10.0f, 0, &Reason); + if(Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()) + { + UI()->SetActiveItem(&m_CallvoteReasonInput); + m_CallvoteReasonInput.SelectAll(); + } + UI()->DoEditBox(&m_CallvoteReasonInput, &Reason, 14.0f); - static CButtonContainer s_CallVoteButton; - if(DoButton_Menu(&s_CallVoteButton, Localize("Call vote"), 0, &Button) || Call) + // extended features (only available when authed in rcon) + if(Client()->RconAuthed()) + { + // background + RconExtension.HSplitTop(10.0f, 0, &RconExtension); + RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension); + RconExtension.HSplitTop(5.0f, 0, &RconExtension); + + // force vote + Bottom.VSplitLeft(5.0f, 0, &Bottom); + Bottom.VSplitLeft(120.0f, &Button, &Bottom); + + static CButtonContainer s_ForceVoteButton; + if(DoButton_Menu(&s_ForceVoteButton, Localize("Force vote"), 0, &Button)) { if(s_ControlPage == 0) - { - m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_CallvoteReasonInput.GetString()); - if(g_Config.m_UiCloseWindowAfterChangingSetting) - SetActive(false); - } + m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_CallvoteReasonInput.GetString(), true); else if(s_ControlPage == 1) { if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) { - m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString()); + m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString(), true); SetActive(false); } } @@ -712,102 +765,47 @@ void CMenus::RenderServerControl(CUIRect MainView) if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) { - m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString()); + m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString(), true); SetActive(false); } } m_CallvoteReasonInput.Clear(); } - // render kick reason - CUIRect Reason; - Bottom.VSplitRight(40.0f, &Bottom, 0); - Bottom.VSplitRight(160.0f, &Bottom, &Reason); - Reason.HSplitTop(5.0f, 0, &Reason); - const char *pLabel = Localize("Reason:"); - UI()->DoLabel(&Reason, pLabel, 14.0f, TEXTALIGN_ML); - float w = TextRender()->TextWidth(14.0f, pLabel, -1, -1.0f); - Reason.VSplitLeft(w + 10.0f, 0, &Reason); - if(Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()) + if(s_ControlPage == 0) { - UI()->SetActiveItem(&m_CallvoteReasonInput); - m_CallvoteReasonInput.SelectAll(); - } - UI()->DoEditBox(&m_CallvoteReasonInput, &Reason, 14.0f); + // remove vote + Bottom.VSplitRight(10.0f, &Bottom, 0); + Bottom.VSplitRight(120.0f, 0, &Button); + static CButtonContainer s_RemoveVoteButton; + if(DoButton_Menu(&s_RemoveVoteButton, Localize("Remove"), 0, &Button)) + m_pClient->m_Voting.RemovevoteOption(m_CallvoteSelectedOption); + + // add vote + RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension); + Bottom.VSplitLeft(5.0f, 0, &Bottom); + Bottom.VSplitLeft(250.0f, &Button, &Bottom); + UI()->DoLabel(&Button, Localize("Vote description:"), 14.0f, TEXTALIGN_ML); - // extended features (only available when authed in rcon) - if(Client()->RconAuthed()) - { - // background - RconExtension.Margin(10.0f, &RconExtension); + Bottom.VSplitLeft(20.0f, 0, &Button); + UI()->DoLabel(&Button, Localize("Vote command:"), 14.0f, TEXTALIGN_ML); + + static CLineInputBuffered s_VoteDescriptionInput; + static CLineInputBuffered s_VoteCommandInput; RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension); - RconExtension.HSplitTop(5.0f, 0, &RconExtension); + Bottom.VSplitRight(10.0f, &Bottom, 0); + Bottom.VSplitRight(120.0f, &Bottom, &Button); + static CButtonContainer s_AddVoteButton; + if(DoButton_Menu(&s_AddVoteButton, Localize("Add"), 0, &Button)) + if(!s_VoteDescriptionInput.IsEmpty() && !s_VoteCommandInput.IsEmpty()) + m_pClient->m_Voting.AddvoteOption(s_VoteDescriptionInput.GetString(), s_VoteCommandInput.GetString()); - // force vote Bottom.VSplitLeft(5.0f, 0, &Bottom); - Bottom.VSplitLeft(120.0f, &Button, &Bottom); + Bottom.VSplitLeft(250.0f, &Button, &Bottom); + UI()->DoEditBox(&s_VoteDescriptionInput, &Button, 14.0f); - static CButtonContainer s_ForceVoteButton; - if(DoButton_Menu(&s_ForceVoteButton, Localize("Force vote"), 0, &Button)) - { - if(s_ControlPage == 0) - m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_CallvoteReasonInput.GetString(), true); - else if(s_ControlPage == 1) - { - if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && - m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) - { - m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString(), true); - SetActive(false); - } - } - else if(s_ControlPage == 2) - { - if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && - m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) - { - m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString(), true); - SetActive(false); - } - } - m_CallvoteReasonInput.Clear(); - } - - if(s_ControlPage == 0) - { - // remove vote - Bottom.VSplitRight(10.0f, &Bottom, 0); - Bottom.VSplitRight(120.0f, 0, &Button); - static CButtonContainer s_RemoveVoteButton; - if(DoButton_Menu(&s_RemoveVoteButton, Localize("Remove"), 0, &Button)) - m_pClient->m_Voting.RemovevoteOption(m_CallvoteSelectedOption); - - // add vote - RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension); - Bottom.VSplitLeft(5.0f, 0, &Bottom); - Bottom.VSplitLeft(250.0f, &Button, &Bottom); - UI()->DoLabel(&Button, Localize("Vote description:"), 14.0f, TEXTALIGN_ML); - - Bottom.VSplitLeft(20.0f, 0, &Button); - UI()->DoLabel(&Button, Localize("Vote command:"), 14.0f, TEXTALIGN_ML); - - static CLineInputBuffered<64> s_VoteDescriptionInput; - static CLineInputBuffered<512> s_VoteCommandInput; - RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension); - Bottom.VSplitRight(10.0f, &Bottom, 0); - Bottom.VSplitRight(120.0f, &Bottom, &Button); - static CButtonContainer s_AddVoteButton; - if(DoButton_Menu(&s_AddVoteButton, Localize("Add"), 0, &Button)) - if(!s_VoteDescriptionInput.IsEmpty() && !s_VoteCommandInput.IsEmpty()) - m_pClient->m_Voting.AddvoteOption(s_VoteDescriptionInput.GetString(), s_VoteCommandInput.GetString()); - - Bottom.VSplitLeft(5.0f, 0, &Bottom); - Bottom.VSplitLeft(250.0f, &Button, &Bottom); - UI()->DoEditBox(&s_VoteDescriptionInput, &Button, 14.0f); - - Bottom.VMargin(20.0f, &Button); - UI()->DoEditBox(&s_VoteCommandInput, &Button, 14.0f); - } + Bottom.VMargin(20.0f, &Button); + UI()->DoEditBox(&s_VoteCommandInput, &Button, 14.0f); } } } From a55a69620e70e5aee5a58d930962c50d7d7a277c Mon Sep 17 00:00:00 2001 From: Valentin Bashkirov Date: Mon, 27 Nov 2023 14:14:57 +0100 Subject: [PATCH 049/198] updated russian.txt --- data/languages/russian.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/languages/russian.txt b/data/languages/russian.txt index 1270cd746b0..aaa922d3d53 100644 --- a/data/languages/russian.txt +++ b/data/languages/russian.txt @@ -804,7 +804,7 @@ Pause == Пауза Kill -== Суицид +== Респаун Zoom in == Приблизить @@ -933,7 +933,7 @@ Deaths == Смерти Suicides -== Суицидов +== Респауны Ratio == Соотношение From 5aa9003180a0f29f109e9aa5339aa52d3dce56b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 28 Nov 2023 21:46:03 +0100 Subject: [PATCH 050/198] Remove unused `ver` parameter of `MACRO_INTERFACE` --- src/engine/antibot.h | 4 ++-- src/engine/client.h | 4 ++-- src/engine/config.h | 2 +- src/engine/console.h | 2 +- src/engine/demo.h | 6 +++--- src/engine/discord.h | 2 +- src/engine/editor.h | 2 +- src/engine/engine.h | 2 +- src/engine/favorites.h | 2 +- src/engine/friends.h | 2 +- src/engine/ghost.h | 4 ++-- src/engine/graphics.h | 4 ++-- src/engine/input.h | 4 ++-- src/engine/kernel.h | 2 +- src/engine/map.h | 4 ++-- src/engine/server.h | 4 ++-- src/engine/serverbrowser.h | 2 +- src/engine/sound.h | 4 ++-- src/engine/steam.h | 2 +- src/engine/storage.h | 2 +- src/engine/textrender.h | 4 ++-- src/engine/updater.h | 2 +- src/game/client/components/background.h | 2 +- src/game/client/components/menu_background.h | 2 +- 24 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/engine/antibot.h b/src/engine/antibot.h index bf8e208c4a9..0b432bb78bf 100644 --- a/src/engine/antibot.h +++ b/src/engine/antibot.h @@ -5,7 +5,7 @@ class IAntibot : public IInterface { - MACRO_INTERFACE("antibot", 0) + MACRO_INTERFACE("antibot") public: virtual void RoundStart(class IGameServer *pGameServer) = 0; virtual void RoundEnd() = 0; @@ -29,7 +29,7 @@ class IAntibot : public IInterface class IEngineAntibot : public IAntibot { - MACRO_INTERFACE("engineantibot", 0) + MACRO_INTERFACE("engineantibot") public: virtual void Init() = 0; diff --git a/src/engine/client.h b/src/engine/client.h index cab64464545..26780c0a601 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -31,7 +31,7 @@ struct CChecksumData; class IClient : public IInterface { - MACRO_INTERFACE("client", 0) + MACRO_INTERFACE("client") public: /* Constants: Client States STATE_OFFLINE - The client is offline. @@ -301,7 +301,7 @@ class IClient : public IInterface class IGameClient : public IInterface { - MACRO_INTERFACE("gameclient", 0) + MACRO_INTERFACE("gameclient") protected: public: virtual void OnConsoleInit() = 0; diff --git a/src/engine/config.h b/src/engine/config.h index 8155212b039..79b5b3257d5 100644 --- a/src/engine/config.h +++ b/src/engine/config.h @@ -7,7 +7,7 @@ class IConfigManager : public IInterface { - MACRO_INTERFACE("config", 0) + MACRO_INTERFACE("config") public: typedef void (*SAVECALLBACKFUNC)(IConfigManager *pConfig, void *pUserData); diff --git a/src/engine/console.h b/src/engine/console.h index bac6cf7a837..464c163489a 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -16,7 +16,7 @@ struct CChecksumData; class IConsole : public IInterface { - MACRO_INTERFACE("console", 0) + MACRO_INTERFACE("console") public: // TODO: rework/cleanup enum diff --git a/src/engine/demo.h b/src/engine/demo.h index 8e5da1b390a..240551de26d 100644 --- a/src/engine/demo.h +++ b/src/engine/demo.h @@ -64,7 +64,7 @@ struct CMapInfo class IDemoPlayer : public IInterface { - MACRO_INTERFACE("demoplayer", 0) + MACRO_INTERFACE("demoplayer") public: class CInfo { @@ -105,7 +105,7 @@ class IDemoPlayer : public IInterface class IDemoRecorder : public IInterface { - MACRO_INTERFACE("demorecorder", 0) + MACRO_INTERFACE("demorecorder") public: virtual ~IDemoRecorder() {} virtual bool IsRecording() const = 0; @@ -116,7 +116,7 @@ class IDemoRecorder : public IInterface class IDemoEditor : public IInterface { - MACRO_INTERFACE("demoeditor", 0) + MACRO_INTERFACE("demoeditor") public: virtual void Slice(const char *pDemo, const char *pDst, int StartTick, int EndTick, DEMOFUNC_FILTER pfnFilter, void *pUser) = 0; }; diff --git a/src/engine/discord.h b/src/engine/discord.h index 69fbcfe85fa..92e4401fad2 100644 --- a/src/engine/discord.h +++ b/src/engine/discord.h @@ -5,7 +5,7 @@ class IDiscord : public IInterface { - MACRO_INTERFACE("discord", 0) + MACRO_INTERFACE("discord") public: virtual void Update() = 0; diff --git a/src/engine/editor.h b/src/engine/editor.h index a8f8b16ae27..745868062a6 100644 --- a/src/engine/editor.h +++ b/src/engine/editor.h @@ -6,7 +6,7 @@ class IEditor : public IInterface { - MACRO_INTERFACE("editor", 0) + MACRO_INTERFACE("editor") public: virtual ~IEditor() {} virtual void Init() = 0; diff --git a/src/engine/engine.h b/src/engine/engine.h index 328cde7d055..937a2a2722a 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -11,7 +11,7 @@ class ILogger; class IEngine : public IInterface { - MACRO_INTERFACE("engine", 0) + MACRO_INTERFACE("engine") protected: class CJobPool m_JobPool; diff --git a/src/engine/favorites.h b/src/engine/favorites.h index af5a411718f..4515bd9a8bd 100644 --- a/src/engine/favorites.h +++ b/src/engine/favorites.h @@ -12,7 +12,7 @@ class IConfigManager; class IFavorites : public IInterface { - MACRO_INTERFACE("favorites", 0) + MACRO_INTERFACE("favorites") protected: virtual void OnConfigSave(IConfigManager *pConfigManager) = 0; diff --git a/src/engine/friends.h b/src/engine/friends.h index 0ab67a27572..17a8bdbca8e 100644 --- a/src/engine/friends.h +++ b/src/engine/friends.h @@ -17,7 +17,7 @@ struct CFriendInfo class IFriends : public IInterface { - MACRO_INTERFACE("friends", 0) + MACRO_INTERFACE("friends") public: enum { diff --git a/src/engine/ghost.h b/src/engine/ghost.h index 41d3ad051ff..581374878b9 100644 --- a/src/engine/ghost.h +++ b/src/engine/ghost.h @@ -17,7 +17,7 @@ class CGhostInfo class IGhostRecorder : public IInterface { - MACRO_INTERFACE("ghostrecorder", 0) + MACRO_INTERFACE("ghostrecorder") public: virtual ~IGhostRecorder() {} @@ -30,7 +30,7 @@ class IGhostRecorder : public IInterface class IGhostLoader : public IInterface { - MACRO_INTERFACE("ghostloader", 0) + MACRO_INTERFACE("ghostloader") public: virtual ~IGhostLoader() {} diff --git a/src/engine/graphics.h b/src/engine/graphics.h index 67043c5b30f..5021422618b 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -232,7 +232,7 @@ typedef std::function Date: Wed, 29 Nov 2023 20:39:54 +0100 Subject: [PATCH 051/198] Remove unused enum literal `MAX_PRINT_CB` --- src/engine/console.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/engine/console.h b/src/engine/console.h index 464c163489a..ecbd8066021 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -34,8 +34,6 @@ class IConsole : public IInterface TEMPCMD_HELP_LENGTH = 192, TEMPCMD_PARAMS_LENGTH = 96, - MAX_PRINT_CB = 4, - CLIENT_ID_GAME = -2, CLIENT_ID_NO_GAME = -3, }; From a1eb0ddd4ce6d1c059ca65fc7f352cf37cf9f813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 29 Nov 2023 20:39:25 +0100 Subject: [PATCH 052/198] Also print command in console when identical to previous The command currently being executed was not being printed to the console if it's identical to the previous history entry. --- src/game/client/components/console.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 3e067ed3072..f6458dc53b8 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -254,11 +254,11 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) { char *pEntry = m_History.Allocate(m_Input.GetLength() + 1); str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1); - // print out the user's commands before they get run - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString()); - m_pGameConsole->PrintLine(m_Type, aBuf); } + // print out the user's commands before they get run + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString()); + m_pGameConsole->PrintLine(m_Type, aBuf); } ExecuteLine(m_Input.GetString()); m_Input.Clear(); From 159ddf553462f0f7822d3d1df3fdf84e25b4cbe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 22 Nov 2023 23:07:08 +0100 Subject: [PATCH 053/198] Refactor config manager, move config variable handling Move all code for handling of config variables from console to config manager. The console no longer depends on the config manager, instead the config manager now depends on the console. Add `struct`s to manage config variables of different types (int, color and string). The config manager now keeps a list of all config variables, so usage of the preprocessor can be avoided except for code to initially create all config variables. Additionally, a separate list of just the game config variables (config variables with `CFGFLAG_GAME`) is kept to optimize the `ResetGameSettings` function, as this function is called whenever connecting to a server and should be fast. Previously, this function was even less efficient because it preformed a linear search for every individual game config variable to find the respective command data. Move console commands that opperate only on config variables (`reset`, `toggle` and `+toggle`) to config manager. Ensure that these commands only opperate on the desired config variables (client or server, respectively) by checking `IConsole::FlagMask`. Add `IConfigManager::SetReadOnly` function to set/unset config variables as read-only instead of manually overriding the command handlers for the `sv_rescue` and `sv_test_cmds` config variables. This also fixes that read-only config variables could still be changed by using the `reset` command. A console message is now printed when trying to change a read-only config variable. Removing the special handling for these two config variables is additionally useful so the console does not need to keep a pointer to config values and manager. Use a `CHeap` for the config variables, their help texts and the previous values of string config variables to avoid many separate allocations as well usage of static variables. Also use the heap to store the unknown commands instead of using `std::string`s. Properly trigger command chain when resetting config variables with the `reset` command and when resetting game settings on map loading. Closes #7461. Format default value for color variables as RGB/RGBA hex with dollar sign prefix. Closes #5523. Add log message when using `reset` with a variable that does not exist. Use `log_error` instead of `dbg_msg` when saving config file fails. Support unlimited number of config save callbacks instead of at most 16. The code also becomes more readable by using an `std::vector` instead of a fixed-size array and a separate num variable. Consistently name `MACRO_CONFIG_*` parameters when declaring the macros. Add `IConsole::CMDLINE_LENGTH` constant to represent the maximum length of the console input and thereby reduce usage of magic numbers for buffer sizes. --- src/engine/client/client.cpp | 2 +- src/engine/config.h | 3 +- src/engine/console.h | 4 +- src/engine/server/main.cpp | 6 +- src/engine/server/server.cpp | 16 - src/engine/server/server.h | 2 - src/engine/shared/config.cpp | 579 ++++++++++++++++++++++--- src/engine/shared/config.h | 44 +- src/engine/shared/console.cpp | 312 +------------ src/engine/shared/console.h | 10 +- src/game/client/components/console.cpp | 6 +- src/game/client/components/console.h | 6 +- src/game/client/gameclient.cpp | 2 +- src/game/server/gamecontext.cpp | 8 +- src/game/server/gamecontext.h | 3 + src/game/server/teehistorian.cpp | 2 +- 16 files changed, 573 insertions(+), 432 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index e16d3097bda..5d834d01412 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -4467,8 +4467,8 @@ int main(int argc, const char **argv) } pEngine->Init(); - pConfigManager->Init(); pConsole->Init(); + pConfigManager->Init(); // register all console commands pClient->RegisterCommands(); diff --git a/src/engine/config.h b/src/engine/config.h index 79b5b3257d5..bccf50437be 100644 --- a/src/engine/config.h +++ b/src/engine/config.h @@ -12,8 +12,9 @@ class IConfigManager : public IInterface typedef void (*SAVECALLBACKFUNC)(IConfigManager *pConfig, void *pUserData); virtual void Init() = 0; - virtual void Reset() = 0; virtual void Reset(const char *pScriptName) = 0; + virtual void ResetGameSettings() = 0; + virtual void SetReadOnly(const char *pScriptName, bool ReadOnly) = 0; virtual bool Save() = 0; virtual class CConfig *Values() = 0; diff --git a/src/engine/console.h b/src/engine/console.h index ecbd8066021..774e0635836 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -34,6 +34,8 @@ class IConsole : public IInterface TEMPCMD_HELP_LENGTH = 192, TEMPCMD_PARAMS_LENGTH = 96, + CMDLINE_LENGTH = 512, + CLIENT_ID_GAME = -2, CLIENT_ID_NO_GAME = -3, }; @@ -117,8 +119,6 @@ class IConsole : public IInterface virtual void SetAccessLevel(int AccessLevel) = 0; - virtual void ResetGameSettings() = 0; - static LEVEL ToLogLevel(int ConsoleLevel); static int ToLogLevelFilter(int ConsoleLevel); diff --git a/src/engine/server/main.cpp b/src/engine/server/main.cpp index 2f12200ab18..ea19875316f 100644 --- a/src/engine/server/main.cpp +++ b/src/engine/server/main.cpp @@ -153,8 +153,8 @@ int main(int argc, const char **argv) } pEngine->Init(); - pConfigManager->Init(); pConsole->Init(); + pConfigManager->Init(); // register all console commands pServer->RegisterCommands(); @@ -173,8 +173,8 @@ int main(int argc, const char **argv) if(argc > 1) pConsole->ParseArguments(argc - 1, &argv[1]); - pConsole->Register("sv_test_cmds", "", CFGFLAG_SERVER, CServer::ConTestingCommands, pConsole, "Turns testing commands aka cheats on/off (setting only works in initial config)"); - pConsole->Register("sv_rescue", "", CFGFLAG_SERVER, CServer::ConRescue, pConsole, "Allow /rescue command so players can teleport themselves out of freeze (setting only works in initial config)"); + pConfigManager->SetReadOnly("sv_test_cmds", true); + pConfigManager->SetReadOnly("sv_rescue", true); const int Mode = g_Config.m_Logappend ? IOFLAG_APPEND : IOFLAG_WRITE; if(g_Config.m_Logfile[0]) diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 359363a69ec..b6fff9c1ffa 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -3096,22 +3096,6 @@ int CServer::Run() return ErrorShutdown(); } -void CServer::ConTestingCommands(CConsole::IResult *pResult, void *pUser) -{ - CConsole *pThis = static_cast(pUser); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "Value: %d", pThis->Config()->m_SvTestingCommands); - pThis->Print(CConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); -} - -void CServer::ConRescue(CConsole::IResult *pResult, void *pUser) -{ - CConsole *pThis = static_cast(pUser); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "Value: %d", pThis->Config()->m_SvRescue); - pThis->Print(CConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); -} - void CServer::ConKick(IConsole::IResult *pResult, void *pUser) { if(pResult->NumArguments() > 1) diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 4c9e55922a9..6b0b3708a9e 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -413,8 +413,6 @@ class CServer : public IServer int Run(); - static void ConTestingCommands(IConsole::IResult *pResult, void *pUser); - static void ConRescue(IConsole::IResult *pResult, void *pUser); static void ConKick(IConsole::IResult *pResult, void *pUser); static void ConStatus(IConsole::IResult *pResult, void *pUser); static void ConShutdown(IConsole::IResult *pResult, void *pUser); diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp index ce930d27ff4..b262c493a42 100644 --- a/src/engine/shared/config.cpp +++ b/src/engine/shared/config.cpp @@ -1,65 +1,463 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ + +#include + #include #include +#include #include #include CConfig g_Config; -void EscapeParam(char *pDst, const char *pSrc, int Size) +static void EscapeParam(char *pDst, const char *pSrc, int Size) { str_escape(&pDst, pSrc, pDst + Size); } +struct SConfigVariable +{ + enum EVariableType + { + VAR_INT, + VAR_COLOR, + VAR_STRING, + }; + IConsole *m_pConsole; + const char *m_pScriptName; + EVariableType m_Type; + int m_Flags; + const char *m_pHelp; + // Note that this only applies to the console command and the SetValue function, + // but the underlying config variable can still be modified programatically. + bool m_ReadOnly = false; + + SConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp) : + m_pConsole(pConsole), + m_pScriptName(pScriptName), + m_Type(Type), + m_Flags(Flags), + m_pHelp(pHelp) + { + } + + virtual ~SConfigVariable() = default; + + virtual void Register() = 0; + virtual bool IsDefault() const = 0; + virtual void Serialize(char *pOut, size_t Size) const = 0; + virtual void ResetToDefault() = 0; + virtual void ResetToOld() = 0; + +protected: + void ExecuteLine(const char *pLine) + { + m_pConsole->ExecuteLine(pLine, (m_Flags & CFGFLAG_GAME) != 0 ? IConsole::CLIENT_ID_GAME : -1); + } + + bool CheckReadOnly() + { + if(!m_ReadOnly) + return false; + char aBuf[IConsole::CMDLINE_LENGTH + 64]; + str_format(aBuf, sizeof(aBuf), "The config variable '%s' cannot be changed right now.", m_pScriptName); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); + return true; + } +}; + +struct SIntConfigVariable : public SConfigVariable +{ + int *m_pVariable; + int m_Default; + int m_Min; + int m_Max; + int m_OldValue; + + SIntConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, int *pVariable, int Default, int Min, int Max) : + SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp), + m_pVariable(pVariable), + m_Default(Default), + m_Min(Min), + m_Max(Max), + m_OldValue(Default) + { + *m_pVariable = m_Default; + } + + ~SIntConfigVariable() override = default; + + static void CommandCallback(IConsole::IResult *pResult, void *pUserData) + { + SIntConfigVariable *pData = static_cast(pUserData); + + if(pResult->NumArguments()) + { + if(pData->CheckReadOnly()) + return; + + int Value = pResult->GetInteger(0); + + // do clamping + if(pData->m_Min != pData->m_Max) + { + if(Value < pData->m_Min) + Value = pData->m_Min; + if(pData->m_Max != 0 && Value > pData->m_Max) + Value = pData->m_Max; + } + + *pData->m_pVariable = Value; + if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME) + pData->m_OldValue = Value; + } + else + { + char aBuf[32]; + str_format(aBuf, sizeof(aBuf), "Value: %d", *pData->m_pVariable); + pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); + } + } + + void Register() override + { + m_pConsole->Register(m_pScriptName, "?i", m_Flags, CommandCallback, this, m_pHelp); + } + + bool IsDefault() const override + { + return *m_pVariable == m_Default; + } + + void Serialize(char *pOut, size_t Size, int Value) const + { + str_format(pOut, Size, "%s %i", m_pScriptName, Value); + } + + void Serialize(char *pOut, size_t Size) const override + { + Serialize(pOut, Size, *m_pVariable); + } + + void SetValue(int Value) + { + if(CheckReadOnly()) + return; + char aBuf[IConsole::CMDLINE_LENGTH]; + Serialize(aBuf, sizeof(aBuf), Value); + ExecuteLine(aBuf); + } + + void ResetToDefault() override + { + SetValue(m_Default); + } + + void ResetToOld() override + { + SetValue(m_OldValue); + } +}; + +struct SColorConfigVariable : public SConfigVariable +{ + unsigned *m_pVariable; + unsigned m_Default; + bool m_Light; + bool m_Alpha; + unsigned m_OldValue; + + SColorConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, unsigned *pVariable, unsigned Default) : + SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp), + m_pVariable(pVariable), + m_Default(Default), + m_Light(Flags & CFGFLAG_COLLIGHT), + m_Alpha(Flags & CFGFLAG_COLALPHA), + m_OldValue(Default) + { + *m_pVariable = m_Default; + } + + ~SColorConfigVariable() override = default; + + static void CommandCallback(IConsole::IResult *pResult, void *pUserData) + { + SColorConfigVariable *pData = static_cast(pUserData); + + if(pResult->NumArguments()) + { + if(pData->CheckReadOnly()) + return; + + const ColorHSLA Color = pResult->GetColor(0, pData->m_Light); + const unsigned Value = Color.Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha); + + *pData->m_pVariable = Value; + if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME) + pData->m_OldValue = Value; + } + else + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Value: %u", *pData->m_pVariable); + pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); + + ColorHSLA Hsla = ColorHSLA(*pData->m_pVariable, true); + if(pData->m_Light) + Hsla = Hsla.UnclampLighting(); + str_format(aBuf, sizeof(aBuf), "H: %d°, S: %d%%, L: %d%%", round_truncate(Hsla.h * 360), round_truncate(Hsla.s * 100), round_truncate(Hsla.l * 100)); + pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); + + const ColorRGBA Rgba = color_cast(Hsla); + str_format(aBuf, sizeof(aBuf), "R: %d, G: %d, B: %d, #%06X", round_truncate(Rgba.r * 255), round_truncate(Rgba.g * 255), round_truncate(Rgba.b * 255), Rgba.Pack(false)); + pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); + + if(pData->m_Alpha) + { + str_format(aBuf, sizeof(aBuf), "A: %d%%", round_truncate(Hsla.a * 100)); + pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); + } + } + } + + void Register() override + { + m_pConsole->Register(m_pScriptName, "?i", m_Flags, CommandCallback, this, m_pHelp); + } + + bool IsDefault() const override + { + return *m_pVariable == m_Default; + } + + void Serialize(char *pOut, size_t Size, unsigned Value) const + { + str_format(pOut, Size, "%s %u", m_pScriptName, Value); + } + + void Serialize(char *pOut, size_t Size) const override + { + Serialize(pOut, Size, *m_pVariable); + } + + void SetValue(unsigned Value) + { + if(CheckReadOnly()) + return; + char aBuf[IConsole::CMDLINE_LENGTH]; + Serialize(aBuf, sizeof(aBuf), Value); + ExecuteLine(aBuf); + } + + void ResetToDefault() override + { + SetValue(m_Default); + } + + void ResetToOld() override + { + SetValue(m_OldValue); + } +}; + +struct SStringConfigVariable : public SConfigVariable +{ + char *m_pStr; + const char *m_pDefault; + size_t m_MaxSize; + char *m_pOldValue; + + SStringConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, char *pStr, const char *pDefault, size_t MaxSize, char *pOldValue) : + SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp), + m_pStr(pStr), + m_pDefault(pDefault), + m_MaxSize(MaxSize), + m_pOldValue(pOldValue) + { + str_copy(m_pStr, m_pDefault, m_MaxSize); + str_copy(m_pOldValue, m_pDefault, m_MaxSize); + } + + ~SStringConfigVariable() override = default; + + static void CommandCallback(IConsole::IResult *pResult, void *pUserData) + { + SStringConfigVariable *pData = static_cast(pUserData); + + if(pResult->NumArguments()) + { + if(pData->CheckReadOnly()) + return; + + const char *pString = pResult->GetString(0); + if(!str_utf8_check(pString)) + { + char aTemp[4]; + size_t Length = 0; + while(*pString) + { + size_t Size = str_utf8_encode(aTemp, static_cast(*pString++)); + if(Length + Size < pData->m_MaxSize) + { + mem_copy(pData->m_pStr + Length, aTemp, Size); + Length += Size; + } + else + break; + } + pData->m_pStr[Length] = '\0'; + } + else + str_copy(pData->m_pStr, pString, pData->m_MaxSize); + + if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME) + str_copy(pData->m_pOldValue, pData->m_pStr, pData->m_MaxSize); + } + else + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "Value: %s", pData->m_pStr); + pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); + } + } + + void Register() override + { + m_pConsole->Register(m_pScriptName, "?r", m_Flags, CommandCallback, this, m_pHelp); + } + + bool IsDefault() const override + { + return str_comp(m_pStr, m_pDefault) == 0; + } + + void Serialize(char *pOut, size_t Size, const char *pValue) const + { + str_copy(pOut, m_pScriptName, Size); + str_append(pOut, " \"", Size); + const int OutLen = str_length(pOut); + EscapeParam(pOut + OutLen, pValue, Size - OutLen - 1); // -1 to ensure space for final quote + str_append(pOut, "\"", Size); + } + + void Serialize(char *pOut, size_t Size) const override + { + Serialize(pOut, Size, m_pStr); + } + + void SetValue(const char *pValue) + { + if(CheckReadOnly()) + return; + char aBuf[2048]; + Serialize(aBuf, sizeof(aBuf), pValue); + ExecuteLine(aBuf); + } + + void ResetToDefault() override + { + SetValue(m_pDefault); + } + + void ResetToOld() override + { + SetValue(m_pOldValue); + } +}; + CConfigManager::CConfigManager() { - m_pStorage = 0; + m_pConsole = nullptr; + m_pStorage = nullptr; m_ConfigFile = 0; - m_NumCallbacks = 0; m_Failed = false; } void CConfigManager::Init() { + m_pConsole = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); - Reset(); -} -void CConfigManager::Reset() -{ -#define MACRO_CONFIG_INT(Name, ScriptName, def, min, max, flags, desc) g_Config.m_##Name = def; -#define MACRO_CONFIG_COL(Name, ScriptName, def, flags, desc) MACRO_CONFIG_INT(Name, ScriptName, def, 0, 0, flags, desc) -#define MACRO_CONFIG_STR(Name, ScriptName, len, def, flags, desc) str_copy(g_Config.m_##Name, def, len); + const auto &&AddVariable = [this](SConfigVariable *pVariable) { + m_vpAllVariables.push_back(pVariable); + if((pVariable->m_Flags & CFGFLAG_GAME) != 0) + m_vpGameVariables.push_back(pVariable); + pVariable->Register(); + }; + +#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Flags, Desc) \ + { \ + const char *pHelp = Min == Max ? Desc " (default: " #Def ")" : Max == 0 ? Desc " (default: " #Def ", min: " #Min ")" : Desc " (default: " #Def ", min: " #Min ", max: " #Max ")"; \ + AddVariable(m_ConfigHeap.Allocate(m_pConsole, #ScriptName, SConfigVariable::VAR_INT, Flags, pHelp, &g_Config.m_##Name, Def, Min, Max)); \ + } + +#define MACRO_CONFIG_COL(Name, ScriptName, Def, Flags, Desc) \ + { \ + const size_t HelpSize = (size_t)str_length(Desc) + 32; \ + char *pHelp = static_cast(m_ConfigHeap.Allocate(HelpSize)); \ + const bool Alpha = ((Flags)&CFGFLAG_COLALPHA) != 0; \ + str_format(pHelp, HelpSize, "%s (default: $%0*X)", Desc, Alpha ? 8 : 6, color_cast(ColorHSLA(Def, Alpha)).Pack(Alpha)); \ + AddVariable(m_ConfigHeap.Allocate(m_pConsole, #ScriptName, SConfigVariable::VAR_COLOR, Flags, pHelp, &g_Config.m_##Name, Def)); \ + } + +#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Flags, Desc) \ + { \ + const size_t HelpSize = (size_t)str_length(Desc) + str_length(Def) + 64; \ + char *pHelp = static_cast(m_ConfigHeap.Allocate(HelpSize)); \ + str_format(pHelp, HelpSize, "%s (default: \"%s\", max length: %d)", Desc, Def, Len - 1); \ + char *pOldValue = static_cast(m_ConfigHeap.Allocate(Len)); \ + AddVariable(m_ConfigHeap.Allocate(m_pConsole, #ScriptName, SConfigVariable::VAR_STRING, Flags, pHelp, g_Config.m_##Name, Def, Len, pOldValue)); \ + } #include "config_variables.h" #undef MACRO_CONFIG_INT #undef MACRO_CONFIG_COL #undef MACRO_CONFIG_STR + + m_pConsole->Register("reset", "s[config-name]", CFGFLAG_SERVER | CFGFLAG_CLIENT | CFGFLAG_STORE, Con_Reset, this, "Reset a config to its default value"); + m_pConsole->Register("toggle", "s[config-option] i[value 1] i[value 2]", CFGFLAG_SERVER | CFGFLAG_CLIENT, Con_Toggle, this, "Toggle config value"); + m_pConsole->Register("+toggle", "s[config-option] i[value 1] i[value 2]", CFGFLAG_CLIENT, Con_ToggleStroke, this, "Toggle config value via keypress"); } void CConfigManager::Reset(const char *pScriptName) { -#define MACRO_CONFIG_INT(Name, ScriptName, def, min, max, flags, desc) \ - if(str_comp(pScriptName, #ScriptName) == 0) \ - { \ - g_Config.m_##Name = def; \ - return; \ - }; -#define MACRO_CONFIG_COL(Name, ScriptName, def, flags, desc) MACRO_CONFIG_INT(Name, ScriptName, def, 0, 0, flags, desc) -#define MACRO_CONFIG_STR(Name, ScriptName, len, def, flags, desc) \ - if(str_comp(pScriptName, #ScriptName) == 0) \ - { \ - str_copy(g_Config.m_##Name, def, len); \ - return; \ - }; + for(SConfigVariable *pVariable : m_vpAllVariables) + { + if((pVariable->m_Flags & m_pConsole->FlagMask()) != 0 && str_comp(pScriptName, pVariable->m_pScriptName) == 0) + { + pVariable->ResetToDefault(); + return; + } + } -#include "config_variables.h" + char aBuf[IConsole::CMDLINE_LENGTH + 32]; + str_format(aBuf, sizeof(aBuf), "Invalid command: '%s'.", pScriptName); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); +} -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_COL -#undef MACRO_CONFIG_STR +void CConfigManager::ResetGameSettings() +{ + for(SConfigVariable *pVariable : m_vpGameVariables) + { + pVariable->ResetToOld(); + } +} + +void CConfigManager::SetReadOnly(const char *pScriptName, bool ReadOnly) +{ + for(SConfigVariable *pVariable : m_vpAllVariables) + { + if(str_comp(pScriptName, pVariable->m_pScriptName) == 0) + { + pVariable->m_ReadOnly = ReadOnly; + return; + } + } + char aBuf[IConsole::CMDLINE_LENGTH + 32]; + str_format(aBuf, sizeof(aBuf), "Invalid command for SetReadOnly: '%s'", pScriptName); + dbg_assert(false, aBuf); } bool CConfigManager::Save() @@ -72,47 +470,30 @@ bool CConfigManager::Save() if(!m_ConfigFile) { - dbg_msg("config", "ERROR: opening %s failed", aConfigFileTmp); + log_error("config", "ERROR: opening %s failed", aConfigFileTmp); return false; } m_Failed = false; - char aLineBuf[1024 * 2]; - char aEscapeBuf[1024 * 2]; - -#define MACRO_CONFIG_INT(Name, ScriptName, def, min, max, flags, desc) \ - if((flags)&CFGFLAG_SAVE && g_Config.m_##Name != (def)) \ - { \ - str_format(aLineBuf, sizeof(aLineBuf), "%s %i", #ScriptName, g_Config.m_##Name); \ - WriteLine(aLineBuf); \ - } -#define MACRO_CONFIG_COL(Name, ScriptName, def, flags, desc) \ - if((flags)&CFGFLAG_SAVE && g_Config.m_##Name != (def)) \ - { \ - str_format(aLineBuf, sizeof(aLineBuf), "%s %u", #ScriptName, g_Config.m_##Name); \ - WriteLine(aLineBuf); \ - } -#define MACRO_CONFIG_STR(Name, ScriptName, len, def, flags, desc) \ - if((flags)&CFGFLAG_SAVE && str_comp(g_Config.m_##Name, def) != 0) \ - { \ - EscapeParam(aEscapeBuf, g_Config.m_##Name, sizeof(aEscapeBuf)); \ - str_format(aLineBuf, sizeof(aLineBuf), "%s \"%s\"", #ScriptName, aEscapeBuf); \ - WriteLine(aLineBuf); \ + char aLineBuf[2048]; + for(const SConfigVariable *pVariable : m_vpAllVariables) + { + if((pVariable->m_Flags & CFGFLAG_SAVE) != 0 && !pVariable->IsDefault()) + { + pVariable->Serialize(aLineBuf, sizeof(aLineBuf)); + WriteLine(aLineBuf); + } } -#include "config_variables.h" - -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_COL -#undef MACRO_CONFIG_STR - - for(int i = 0; i < m_NumCallbacks; i++) - m_aCallbacks[i].m_pfnFunc(this, m_aCallbacks[i].m_pUserData); + for(const auto &Callback : m_vCallbacks) + { + Callback.m_pfnFunc(this, Callback.m_pUserData); + } - for(const auto &Command : m_vUnknownCommands) + for(const char *pCommand : m_vpUnknownCommands) { - WriteLine(Command.c_str()); + WriteLine(pCommand); } if(io_sync(m_ConfigFile) != 0) @@ -127,13 +508,13 @@ bool CConfigManager::Save() if(m_Failed) { - dbg_msg("config", "ERROR: writing to %s failed", aConfigFileTmp); + log_error("config", "ERROR: writing to %s failed", aConfigFileTmp); return false; } if(!m_pStorage->RenameFile(aConfigFileTmp, CONFIG_FILE, IStorage::TYPE_SAVE)) { - dbg_msg("config", "ERROR: renaming %s to " CONFIG_FILE " failed", aConfigFileTmp); + log_error("config", "ERROR: renaming %s to " CONFIG_FILE " failed", aConfigFileTmp); return false; } @@ -142,10 +523,7 @@ bool CConfigManager::Save() void CConfigManager::RegisterCallback(SAVECALLBACKFUNC pfnFunc, void *pUserData) { - dbg_assert(m_NumCallbacks < MAX_CALLBACKS, "too many config callbacks"); - m_aCallbacks[m_NumCallbacks].m_pfnFunc = pfnFunc; - m_aCallbacks[m_NumCallbacks].m_pUserData = pUserData; - m_NumCallbacks++; + m_vCallbacks.emplace_back(pfnFunc, pUserData); } void CConfigManager::WriteLine(const char *pLine) @@ -160,7 +538,76 @@ void CConfigManager::WriteLine(const char *pLine) void CConfigManager::StoreUnknownCommand(const char *pCommand) { - m_vUnknownCommands.emplace_back(pCommand); + m_vpUnknownCommands.push_back(m_ConfigHeap.StoreString(pCommand)); +} + +void CConfigManager::Con_Reset(IConsole::IResult *pResult, void *pUserData) +{ + static_cast(pUserData)->Reset(pResult->GetString(0)); +} + +void CConfigManager::Con_Toggle(IConsole::IResult *pResult, void *pUserData) +{ + CConfigManager *pConfigManager = static_cast(pUserData); + IConsole *pConsole = pConfigManager->m_pConsole; + + const char *pScriptName = pResult->GetString(0); + for(SConfigVariable *pVariable : pConfigManager->m_vpAllVariables) + { + if((pVariable->m_Flags & pConsole->FlagMask()) == 0 || + str_comp(pScriptName, pVariable->m_pScriptName) != 0) + { + continue; + } + + if(pVariable->m_Type == SConfigVariable::VAR_INT) + { + SIntConfigVariable *pIntVariable = static_cast(pVariable); + pIntVariable->SetValue(*pIntVariable->m_pVariable == pResult->GetInteger(1) ? pResult->GetInteger(2) : pResult->GetInteger(1)); + } + else if(pVariable->m_Type == SConfigVariable::VAR_COLOR) + { + SColorConfigVariable *pColorVariable = static_cast(pVariable); + const float Darkest = pColorVariable->m_Light ? 0.5f : 0.0f; + const ColorHSLA Value = *pColorVariable->m_pVariable == pResult->GetColor(1, pColorVariable->m_Light).Pack(Darkest, pColorVariable->m_Alpha) ? pResult->GetColor(2, pColorVariable->m_Light) : pResult->GetColor(1, pColorVariable->m_Light); + pColorVariable->SetValue(Value.Pack(Darkest, pColorVariable->m_Alpha)); + } + else if(pVariable->m_Type == SConfigVariable::VAR_STRING) + { + SStringConfigVariable *pStringVariable = static_cast(pVariable); + pStringVariable->SetValue(str_comp(pStringVariable->m_pStr, pResult->GetString(1)) == 0 ? pResult->GetString(2) : pResult->GetString(1)); + } + return; + } + + char aBuf[IConsole::CMDLINE_LENGTH + 32]; + str_format(aBuf, sizeof(aBuf), "Invalid command: '%s'.", pScriptName); + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); +} + +void CConfigManager::Con_ToggleStroke(IConsole::IResult *pResult, void *pUserData) +{ + CConfigManager *pConfigManager = static_cast(pUserData); + IConsole *pConsole = pConfigManager->m_pConsole; + + const char *pScriptName = pResult->GetString(1); + for(SConfigVariable *pVariable : pConfigManager->m_vpAllVariables) + { + if((pVariable->m_Flags & pConsole->FlagMask()) == 0 || + pVariable->m_Type != SConfigVariable::VAR_INT || + str_comp(pScriptName, pVariable->m_pScriptName) != 0) + { + continue; + } + + SIntConfigVariable *pIntVariable = static_cast(pVariable); + pIntVariable->SetValue(pResult->GetInteger(0) == 0 ? pResult->GetInteger(3) : pResult->GetInteger(2)); + return; + } + + char aBuf[IConsole::CMDLINE_LENGTH + 32]; + str_format(aBuf, sizeof(aBuf), "Invalid command: '%s'.", pScriptName); + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); } IConfigManager *CreateConfigManager() { return new CConfigManager; } diff --git a/src/engine/shared/config.h b/src/engine/shared/config.h index 63c640fb604..826c0dd6165 100644 --- a/src/engine/shared/config.h +++ b/src/engine/shared/config.h @@ -4,9 +4,11 @@ #define ENGINE_SHARED_CONFIG_H #include + #include +#include +#include -#include #include // include protocol for MAX_CLIENT used in config_variables @@ -20,13 +22,13 @@ class CConfig { public: -#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Save, Desc) \ +#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Flags, Desc) \ static constexpr int ms_##Name = Def; \ int m_##Name; -#define MACRO_CONFIG_COL(Name, ScriptName, Def, Save, Desc) \ +#define MACRO_CONFIG_COL(Name, ScriptName, Def, Flags, Desc) \ static constexpr unsigned ms_##Name = Def; \ unsigned m_##Name; -#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Save, Desc) \ +#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Flags, Desc) \ static constexpr const char *ms_p##Name = Def; \ char m_##Name[Len]; // Flawfinder: ignore #include "config_variables.h" @@ -58,31 +60,41 @@ enum class CConfigManager : public IConfigManager { - enum - { - MAX_CALLBACKS = 16 - }; + IConsole *m_pConsole; + class IStorage *m_pStorage; - struct CCallback + IOHANDLE m_ConfigFile; + bool m_Failed; + + struct SCallback { SAVECALLBACKFUNC m_pfnFunc; void *m_pUserData; + + SCallback(SAVECALLBACKFUNC pfnFunc, void *pUserData) : + m_pfnFunc(pfnFunc), + m_pUserData(pUserData) + { + } }; + std::vector m_vCallbacks; - class IStorage *m_pStorage; - IOHANDLE m_ConfigFile; - bool m_Failed; - CCallback m_aCallbacks[MAX_CALLBACKS]; - int m_NumCallbacks; + std::vector m_vpAllVariables; + std::vector m_vpGameVariables; + std::vector m_vpUnknownCommands; + CHeap m_ConfigHeap; - std::vector m_vUnknownCommands; + static void Con_Reset(IConsole::IResult *pResult, void *pUserData); + static void Con_Toggle(IConsole::IResult *pResult, void *pUserData); + static void Con_ToggleStroke(IConsole::IResult *pResult, void *pUserData); public: CConfigManager(); void Init() override; - void Reset() override; void Reset(const char *pScriptName) override; + void ResetGameSettings() override; + void SetReadOnly(const char *pScriptName, bool ReadOnly) override; bool Save() override; CConfig *Values() override { return &g_Config; } diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 39b2bae3548..f89bf6810aa 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -459,7 +459,7 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID, bo { if(Stroke) { - char aBuf[96]; + char aBuf[CMDLINE_LENGTH + 64]; str_format(aBuf, sizeof(aBuf), "Command '%s' cannot be executed from a map.", Result.m_pCommand); Print(OUTPUT_LEVEL_STANDARD, "console", aBuf); } @@ -468,7 +468,7 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID, bo { if(Stroke) { - char aBuf[96]; + char aBuf[CMDLINE_LENGTH + 64]; str_format(aBuf, sizeof(aBuf), "Command '%s' cannot be executed from a non-map config file.", Result.m_pCommand); Print(OUTPUT_LEVEL_STANDARD, "console", aBuf); str_format(aBuf, sizeof(aBuf), "Hint: Put the command in '%s.cfg' instead of '%s.map.cfg' ", g_Config.m_SvMap, g_Config.m_SvMap); @@ -489,8 +489,8 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID, bo { if(ParseArgs(&Result, pCommand->m_pParams)) { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Invalid arguments... Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); + char aBuf[TEMPCMD_NAME_LENGTH + TEMPCMD_PARAMS_LENGTH + 32]; + str_format(aBuf, sizeof(aBuf), "Invalid arguments. Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); Print(OUTPUT_LEVEL_STANDARD, "chatresp", aBuf); } else if(m_StoreCommands && pCommand->m_Flags & CFGFLAG_STORE) @@ -532,7 +532,7 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID, bo } else if(Stroke) { - char aBuf[256]; + char aBuf[CMDLINE_LENGTH + 32]; str_format(aBuf, sizeof(aBuf), "Access for command %s denied.", Result.m_pCommand); Print(OUTPUT_LEVEL_STANDARD, "console", aBuf); } @@ -543,7 +543,7 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID, bo // ends at the first whitespace, which breaks for unknown commands (filenames) containing spaces. if(!m_pfnUnknownCommandCallback(pStr, m_pUnknownCommandUserdata)) { - char aBuf[512 + 32]; + char aBuf[CMDLINE_LENGTH + 32]; str_format(aBuf, sizeof(aBuf), "No such command: %s.", Result.m_pCommand); Print(OUTPUT_LEVEL_STANDARD, "chatresp", aBuf); } @@ -655,15 +655,10 @@ void CConsole::Con_Exec(IResult *pResult, void *pUserData) ((CConsole *)pUserData)->ExecuteFile(pResult->GetString(0), -1, true, IStorage::TYPE_ALL); } -void CConsole::Con_Reset(IResult *pResult, void *pUserData) -{ - ((CConsole *)pUserData)->ConfigManager()->Reset(pResult->GetString(0)); -} - void CConsole::ConCommandAccess(IResult *pResult, void *pUser) { CConsole *pConsole = static_cast(pUser); - char aBuf[128]; + char aBuf[CMDLINE_LENGTH + 64]; CCommand *pCommand = pConsole->FindCommand(pResult->GetString(0), CFGFLAG_SERVER); if(pCommand) { @@ -738,136 +733,6 @@ void CConsole::ConUserCommandStatus(IResult *pResult, void *pUser) pConsole->ConCommandStatus(&Result, pConsole); } -struct CIntVariableData -{ - IConsole *m_pConsole; - int *m_pVariable; - int m_Min; - int m_Max; - int m_OldValue; -}; - -struct CColVariableData -{ - IConsole *m_pConsole; - unsigned *m_pVariable; - bool m_Light; - bool m_Alpha; - unsigned m_OldValue; -}; - -struct CStrVariableData -{ - IConsole *m_pConsole; - char *m_pStr; - int m_MaxSize; - char *m_pOldValue; -}; - -static void IntVariableCommand(IConsole::IResult *pResult, void *pUserData) -{ - CIntVariableData *pData = (CIntVariableData *)pUserData; - - if(pResult->NumArguments()) - { - int Val = pResult->GetInteger(0); - - // do clamping - if(pData->m_Min != pData->m_Max) - { - if(Val < pData->m_Min) - Val = pData->m_Min; - if(pData->m_Max != 0 && Val > pData->m_Max) - Val = pData->m_Max; - } - - *(pData->m_pVariable) = Val; - if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME) - pData->m_OldValue = Val; - } - else - { - char aBuf[32]; - str_format(aBuf, sizeof(aBuf), "Value: %d", *(pData->m_pVariable)); - pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); - } -} - -static void ColVariableCommand(IConsole::IResult *pResult, void *pUserData) -{ - CColVariableData *pData = (CColVariableData *)pUserData; - - if(pResult->NumArguments()) - { - ColorHSLA Col = pResult->GetColor(0, pData->m_Light); - int Val = Col.Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha); - - *(pData->m_pVariable) = Val; - if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME) - pData->m_OldValue = Val; - } - else - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Value: %u", *(pData->m_pVariable)); - pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); - - ColorHSLA Hsla(*(pData->m_pVariable), true); - if(pData->m_Light) - Hsla = Hsla.UnclampLighting(); - str_format(aBuf, sizeof(aBuf), "H: %d°, S: %d%%, L: %d%%", round_truncate(Hsla.h * 360), round_truncate(Hsla.s * 100), round_truncate(Hsla.l * 100)); - pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); - - ColorRGBA Rgba = color_cast(Hsla); - str_format(aBuf, sizeof(aBuf), "R: %d, G: %d, B: %d, #%06X", round_truncate(Rgba.r * 255), round_truncate(Rgba.g * 255), round_truncate(Rgba.b * 255), Rgba.Pack(false)); - pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); - - if(pData->m_Alpha) - { - str_format(aBuf, sizeof(aBuf), "A: %d%%", round_truncate(Hsla.a * 100)); - pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); - } - } -} - -static void StrVariableCommand(IConsole::IResult *pResult, void *pUserData) -{ - CStrVariableData *pData = (CStrVariableData *)pUserData; - - if(pResult->NumArguments()) - { - const char *pString = pResult->GetString(0); - if(!str_utf8_check(pString)) - { - char aTemp[4]; - int Length = 0; - while(*pString) - { - int Size = str_utf8_encode(aTemp, static_cast(*pString++)); - if(Length + Size < pData->m_MaxSize) - { - mem_copy(pData->m_pStr + Length, aTemp, Size); - Length += Size; - } - else - break; - } - pData->m_pStr[Length] = 0; - } - else - str_copy(pData->m_pStr, pString, pData->m_MaxSize); - - if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME) - str_copy(pData->m_pOldValue, pData->m_pStr, pData->m_MaxSize); - } - else - { - char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), "Value: %s", pData->m_pStr); - pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); - } -} - void CConsole::TraverseChain(FCommandCallback *ppfnCallback, void **ppUserData) { while(*ppfnCallback == Con_Chain) @@ -878,85 +743,6 @@ void CConsole::TraverseChain(FCommandCallback *ppfnCallback, void **ppUserData) } } -void CConsole::ConToggle(IConsole::IResult *pResult, void *pUser) -{ - CConsole *pConsole = static_cast(pUser); - char aBuf[512 + 32] = {0}; - CCommand *pCommand = pConsole->FindCommand(pResult->GetString(0), pConsole->m_FlagMask); - if(pCommand) - { - FCommandCallback pfnCallback = pCommand->m_pfnCallback; - void *pUserData = pCommand->m_pUserData; - TraverseChain(&pfnCallback, &pUserData); - if(pfnCallback == IntVariableCommand) - { - CIntVariableData *pData = static_cast(pUserData); - int Val = *(pData->m_pVariable) == pResult->GetInteger(1) ? pResult->GetInteger(2) : pResult->GetInteger(1); - str_format(aBuf, sizeof(aBuf), "%s %i", pResult->GetString(0), Val); - pConsole->ExecuteLine(aBuf); - aBuf[0] = 0; - } - else if(pfnCallback == StrVariableCommand) - { - CStrVariableData *pData = static_cast(pUserData); - const char *pStr = !str_comp(pData->m_pStr, pResult->GetString(1)) ? pResult->GetString(2) : pResult->GetString(1); - str_format(aBuf, sizeof(aBuf), "%s \"", pResult->GetString(0)); - char *pDst = aBuf + str_length(aBuf); - str_escape(&pDst, pStr, aBuf + sizeof(aBuf)); - str_append(aBuf, "\""); - pConsole->ExecuteLine(aBuf); - aBuf[0] = 0; - } - else if(pfnCallback == ColVariableCommand) - { - CColVariableData *pData = static_cast(pUserData); - bool Light = pData->m_Light; - float Darkest = Light ? 0.5f : 0.0f; - bool Alpha = pData->m_Alpha; - unsigned Cur = *pData->m_pVariable; - ColorHSLA Val = Cur == pResult->GetColor(1, Light).Pack(Darkest, Alpha) ? pResult->GetColor(2, Light) : pResult->GetColor(1, Light); - - str_format(aBuf, sizeof(aBuf), "%s %u", pResult->GetString(0), Val.Pack(Darkest, Alpha)); - pConsole->ExecuteLine(aBuf); - aBuf[0] = 0; - } - else - str_format(aBuf, sizeof(aBuf), "Invalid command: '%s'.", pResult->GetString(0)); - } - else - str_format(aBuf, sizeof(aBuf), "No such command: '%s'.", pResult->GetString(0)); - - if(aBuf[0] != 0) - pConsole->Print(OUTPUT_LEVEL_STANDARD, "console", aBuf); -} - -void CConsole::ConToggleStroke(IConsole::IResult *pResult, void *pUser) -{ - CConsole *pConsole = static_cast(pUser); - char aBuf[512 + 32] = {0}; - CCommand *pCommand = pConsole->FindCommand(pResult->GetString(1), pConsole->m_FlagMask); - if(pCommand) - { - FCommandCallback pfnCallback = pCommand->m_pfnCallback; - void *pUserData = pCommand->m_pUserData; - TraverseChain(&pfnCallback, &pUserData); - if(pfnCallback == IntVariableCommand) - { - int Val = pResult->GetInteger(0) == 0 ? pResult->GetInteger(3) : pResult->GetInteger(2); - str_format(aBuf, sizeof(aBuf), "%s %i", pResult->GetString(1), Val); - pConsole->ExecuteLine(aBuf); - aBuf[0] = 0; - } - else - str_format(aBuf, sizeof(aBuf), "Invalid command: '%s'.", pResult->GetString(1)); - } - else - str_format(aBuf, sizeof(aBuf), "No such command: '%s'.", pResult->GetString(1)); - - if(aBuf[0] != 0) - pConsole->Print(OUTPUT_LEVEL_STANDARD, "console", aBuf); -} - CConsole::CConsole(int FlagMask) { m_FlagMask = FlagMask; @@ -977,10 +763,6 @@ CConsole::CConsole(int FlagMask) // register some basic commands Register("echo", "r[text]", CFGFLAG_SERVER, Con_Echo, this, "Echo the text"); Register("exec", "r[file]", CFGFLAG_SERVER | CFGFLAG_CLIENT, Con_Exec, this, "Execute the specified file"); - Register("reset", "s[config-name]", CFGFLAG_SERVER | CFGFLAG_CLIENT | CFGFLAG_STORE, Con_Reset, this, "Reset a config to its default value"); - - Register("toggle", "s[config-option] i[value 1] i[value 2]", CFGFLAG_SERVER | CFGFLAG_CLIENT, ConToggle, this, "Toggle config value"); - Register("+toggle", "s[config-option] i[value 1] i[value 2]", CFGFLAG_CLIENT, ConToggleStroke, this, "Toggle config value via keypress"); Register("access_level", "s[command] ?i[accesslevel]", CFGFLAG_SERVER, ConCommandAccess, this, "Specify command accessibility (admin = 0, moderator = 1, helper = 2, all = 3)"); Register("access_status", "i[accesslevel]", CFGFLAG_SERVER, ConCommandStatus, this, "List all commands which are accessible for admin = 0, moderator = 1, helper = 2, all = 3"); @@ -1018,39 +800,7 @@ CConsole::~CConsole() void CConsole::Init() { - m_pConfigManager = Kernel()->RequestInterface(); - m_pConfig = m_pConfigManager->Values(); m_pStorage = Kernel()->RequestInterface(); - -// TODO: this should disappear -#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Flags, Desc) \ - { \ - static CIntVariableData Data = {this, &g_Config.m_##Name, Min, Max, Def}; \ - Register(#ScriptName, "?i", Flags, IntVariableCommand, &Data, \ - Min == Max ? Desc " (default: " #Def ")" : Max == 0 ? Desc " (default: " #Def ", min: " #Min ")" : Desc " (default: " #Def ", min: " #Min ", max: " #Max ")"); \ - } - -#define MACRO_CONFIG_COL(Name, ScriptName, Def, Flags, Desc) \ - { \ - static CColVariableData Data = {this, &g_Config.m_##Name, static_cast((Flags)&CFGFLAG_COLLIGHT), \ - static_cast((Flags)&CFGFLAG_COLALPHA), Def}; \ - Register(#ScriptName, "?i", Flags, ColVariableCommand, &Data, Desc " (default: " #Def ")"); \ - } - -#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Flags, Desc) \ - { \ - static char s_aOldValue[Len] = Def; \ - static CStrVariableData Data = {this, g_Config.m_##Name, Len, s_aOldValue}; \ - static char s_aHelp[256]; \ - str_format(s_aHelp, sizeof(s_aHelp), "%s (default: \"%s\", max length: %d)", Desc, Def, Len - 1); \ - Register(#ScriptName, "?r", Flags, StrVariableCommand, &Data, s_aHelp); \ - } - -#include "config_variables.h" - -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_COL -#undef MACRO_CONFIG_STR } void CConsole::ParseArguments(int NumArgs, const char **ppArguments) @@ -1274,54 +1024,6 @@ const IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int Fl std::unique_ptr CreateConsole(int FlagMask) { return std::make_unique(FlagMask); } -void CConsole::ResetGameSettings() -{ -#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Flags, Desc) \ - { \ - if(((Flags)&CFGFLAG_GAME) == CFGFLAG_GAME) \ - { \ - CCommand *pCommand = FindCommand(#ScriptName, CFGFLAG_GAME); \ - void *pUserData = pCommand->m_pUserData; \ - FCommandCallback pfnCallback = pCommand->m_pfnCallback; \ - TraverseChain(&pfnCallback, &pUserData); \ - CIntVariableData *pData = (CIntVariableData *)pUserData; \ - *pData->m_pVariable = pData->m_OldValue; \ - } \ - } - -#define MACRO_CONFIG_COL(Name, ScriptName, Def, Flags, Desc) \ - { \ - if(((Flags)&CFGFLAG_GAME) == CFGFLAG_GAME) \ - { \ - CCommand *pCommand = FindCommand(#ScriptName, CFGFLAG_GAME); \ - void *pUserData = pCommand->m_pUserData; \ - FCommandCallback pfnCallback = pCommand->m_pfnCallback; \ - TraverseChain(&pfnCallback, &pUserData); \ - CColVariableData *pData = (CColVariableData *)pUserData; \ - *pData->m_pVariable = pData->m_OldValue; \ - } \ - } - -#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Flags, Desc) \ - { \ - if(((Flags)&CFGFLAG_GAME) == CFGFLAG_GAME) \ - { \ - CCommand *pCommand = FindCommand(#ScriptName, CFGFLAG_GAME); \ - void *pUserData = pCommand->m_pUserData; \ - FCommandCallback pfnCallback = pCommand->m_pfnCallback; \ - TraverseChain(&pfnCallback, &pUserData); \ - CStrVariableData *pData = (CStrVariableData *)pUserData; \ - str_copy(pData->m_pStr, pData->m_pOldValue, pData->m_MaxSize); \ - } \ - } - -#include "config_variables.h" - -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_COL -#undef MACRO_CONFIG_STR -} - int CConsole::CResult::GetVictim() const { return m_Victim; diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index d16770c4665..f5c4c97eae1 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -46,8 +46,6 @@ class CConsole : public IConsole }; CExecFile *m_pFirstExec; - IConfigManager *m_pConfigManager; - CConfig *m_pConfig; IStorage *m_pStorage; int m_AccessLevel; @@ -59,9 +57,6 @@ class CConsole : public IConsole static void Con_Chain(IResult *pResult, void *pUserData); static void Con_Echo(IResult *pResult, void *pUserData); static void Con_Exec(IResult *pResult, void *pUserData); - static void Con_Reset(IResult *pResult, void *pUserData); - static void ConToggle(IResult *pResult, void *pUser); - static void ConToggleStroke(IResult *pResult, void *pUser); static void ConCommandAccess(IResult *pResult, void *pUser); static void ConCommandStatus(IConsole::IResult *pResult, void *pUser); @@ -194,9 +189,6 @@ class CConsole : public IConsole bool m_Cheated; public: - IConfigManager *ConfigManager() { return m_pConfigManager; } - CConfig *Config() { return m_pConfig; } - CConsole(int FlagMask); ~CConsole(); @@ -225,7 +217,7 @@ class CConsole : public IConsole void InitChecksum(CChecksumData *pData) const override; void SetAccessLevel(int AccessLevel) override { m_AccessLevel = clamp(AccessLevel, (int)(ACCESS_LEVEL_ADMIN), (int)(ACCESS_LEVEL_USER)); } - void ResetGameSettings() override; + // DDRace static void ConUserCommandStatus(IConsole::IResult *pResult, void *pUser); diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index f6458dc53b8..7c2ae047078 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -227,7 +227,7 @@ void CGameConsole::CInstance::PossibleArgumentsCompleteCallback(int Index, const if(pInstance->m_CompletionChosenArgument == Index) { // get command - char aBuf[512]; + char aBuf[IConsole::CMDLINE_LENGTH]; StrCopyUntilSpace(aBuf, sizeof(aBuf), pInstance->GetString()); str_append(aBuf, " "); @@ -256,7 +256,7 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1); } // print out the user's commands before they get run - char aBuf[256]; + char aBuf[IConsole::CMDLINE_LENGTH + 3]; str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString()); m_pGameConsole->PrintLine(m_Type, aBuf); } @@ -400,7 +400,7 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) // find the current command { - char aBuf[512]; + char aBuf[IConsole::CMDLINE_LENGTH]; StrCopyUntilSpace(aBuf, sizeof(aBuf), GetString()); const IConsole::CCommandInfo *pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands()); diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index b088f064295..cadaefd95aa 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -40,7 +40,7 @@ class CGameConsole : public CComponent CStaticRingBuffer m_History; char *m_pHistoryEntry; - CLineInputBuffered<512> m_Input; + CLineInputBuffered m_Input; const char *m_pName; int m_Type; int m_BacklogCurPage; @@ -59,9 +59,9 @@ class CGameConsole : public CComponent CGameConsole *m_pGameConsole; - char m_aCompletionBuffer[128]; + char m_aCompletionBuffer[IConsole::CMDLINE_LENGTH]; int m_CompletionChosen; - char m_aCompletionBufferArgument[128]; + char m_aCompletionBufferArgument[IConsole::CMDLINE_LENGTH]; int m_CompletionChosenArgument; int m_CompletionFlagmask; float m_CompletionRenderOffset; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 5cd59e236e2..10db5357469 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -530,7 +530,7 @@ void CGameClient::OnConnected() m_GameWorld.m_WorldConfig.m_InfiniteAmmo = true; mem_zero(&m_GameInfo, sizeof(m_GameInfo)); m_PredictedDummyID = -1; - Console()->ResetGameSettings(); + ConfigManager()->ResetGameSettings(); LoadMapSettings(); if(Client()->State() != IClient::STATE_DEMOPLAYBACK && g_Config.m_ClAutoDemoOnConnect) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 2f7cf788b60..f81ca1af6e4 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -3422,7 +3422,8 @@ void CGameContext::ConchainSettingUpdate(IConsole::IResult *pResult, void *pUser void CGameContext::OnConsoleInit() { m_pServer = Kernel()->RequestInterface(); - m_pConfig = Kernel()->RequestInterface()->Values(); + m_pConfigManager = Kernel()->RequestInterface(); + m_pConfig = m_pConfigManager->Values(); m_pConsole = Kernel()->RequestInterface(); m_pEngine = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); @@ -3477,7 +3478,8 @@ void CGameContext::OnInit(const void *pPersistentData) const CPersistentData *pPersistent = (const CPersistentData *)pPersistentData; m_pServer = Kernel()->RequestInterface(); - m_pConfig = Kernel()->RequestInterface()->Values(); + m_pConfigManager = Kernel()->RequestInterface(); + m_pConfig = m_pConfigManager->Values(); m_pConsole = Kernel()->RequestInterface(); m_pEngine = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); @@ -3949,7 +3951,7 @@ void CGameContext::OnShutdown(void *pPersistentData) } DeleteTempfile(); - Console()->ResetGameSettings(); + ConfigManager()->ResetGameSettings(); Collision()->Dest(); delete m_pController; m_pController = 0; diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 02c53a352a7..b5c30f09eab 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -47,6 +47,7 @@ enum }; class CCharacter; +class IConfigManager; class CConfig; class CHeap; class CPlayer; @@ -77,6 +78,7 @@ struct CSnapContext class CGameContext : public IGameServer { IServer *m_pServer; + IConfigManager *m_pConfigManager; CConfig *m_pConfig; IConsole *m_pConsole; IEngine *m_pEngine; @@ -155,6 +157,7 @@ class CGameContext : public IGameServer public: IServer *Server() const { return m_pServer; } + IConfigManager *ConfigManager() const { return m_pConfigManager; } CConfig *Config() { return m_pConfig; } IConsole *Console() { return m_pConsole; } IEngine *Engine() { return m_pEngine; } diff --git a/src/game/server/teehistorian.cpp b/src/game/server/teehistorian.cpp index 083ccd65d99..355916b0254 100644 --- a/src/game/server/teehistorian.cpp +++ b/src/game/server/teehistorian.cpp @@ -157,7 +157,7 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo) First = false; \ } -#define MACRO_CONFIG_COL(Name, ScriptName, Def, Save, Desc) MACRO_CONFIG_INT(Name, ScriptName, Def, 0, 0, Save, Desc) +#define MACRO_CONFIG_COL(Name, ScriptName, Def, Flags, Desc) MACRO_CONFIG_INT(Name, ScriptName, Def, 0, 0, Flags, Desc) #define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Flags, Desc) \ if((Flags)&CFGFLAG_SERVER && !((Flags)&CFGFLAG_NONTEEHISTORIC) && str_comp(pGameInfo->m_pConfig->m_##Name, (Def)) != 0) \ From 1d7c0fad49773c35eaf750578bb4cb9cf44c08b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 30 Nov 2023 21:27:40 +0100 Subject: [PATCH 054/198] Check SHA256 of downloaded maps and community icons Add `CHttpRequest::ExpectSha256` function to specify expected SHA256 for HTTP downloads. If the download completes with a different SHA256 hash than expected, then the download fails and the file is deleted. Closes #7485. --- src/engine/client/client.cpp | 1 + src/engine/shared/http.cpp | 21 ++++++++++++++++++++ src/engine/shared/http.h | 9 ++++++++- src/game/client/components/menus.h | 2 +- src/game/client/components/menus_browser.cpp | 5 +++-- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index e16d3097bda..3fb82cd46ab 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -1533,6 +1533,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) m_pMapdownloadTask = HttpGetFile(pMapUrl ? pMapUrl : aUrl, Storage(), m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE); m_pMapdownloadTask->Timeout(CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, 0, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime}); m_pMapdownloadTask->MaxResponseSize(1024 * 1024 * 1024); // 1 GiB + m_pMapdownloadTask->ExpectSha256(*pMapSha256); Engine()->AddJob(m_pMapdownloadTask); } else diff --git a/src/engine/shared/http.cpp b/src/engine/shared/http.cpp index 3e812463d01..090e9aabd55 100644 --- a/src/engine/shared/http.cpp +++ b/src/engine/shared/http.cpp @@ -122,6 +122,7 @@ bool HttpHasIpresolveBug() CHttpRequest::CHttpRequest(const char *pUrl) { str_copy(m_aUrl, pUrl); + sha256_init(&m_ActualSha256); } CHttpRequest::~CHttpRequest() @@ -311,6 +312,9 @@ size_t CHttpRequest::OnData(char *pData, size_t DataSize) { return 0; } + + sha256_update(&m_ActualSha256, pData, DataSize); + if(!m_WriteToFile) { if(DataSize == 0) @@ -358,6 +362,23 @@ int CHttpRequest::OnCompletion(int State) if(m_Abort) State = HTTP_ABORTED; + if(State == HTTP_DONE && m_ExpectedSha256 != SHA256_ZEROED) + { + const SHA256_DIGEST ActualSha256 = sha256_finish(&m_ActualSha256); + if(ActualSha256 != m_ExpectedSha256) + { + if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE) + { + char aActualSha256[SHA256_MAXSTRSIZE]; + sha256_str(ActualSha256, aActualSha256, sizeof(aActualSha256)); + char aExpectedSha256[SHA256_MAXSTRSIZE]; + sha256_str(m_ExpectedSha256, aExpectedSha256, sizeof(aExpectedSha256)); + dbg_msg("http", "SHA256 mismatch: got=%s, expected=%s, url=%s", aActualSha256, aExpectedSha256, m_aUrl); + } + State = HTTP_ERROR; + } + } + if(m_WriteToFile) { if(m_File && io_close(m_File) != 0) diff --git a/src/engine/shared/http.h b/src/engine/shared/http.h index dc9cbe25eea..eb90fcaf118 100644 --- a/src/engine/shared/http.h +++ b/src/engine/shared/http.h @@ -1,9 +1,12 @@ #ifndef ENGINE_SHARED_HTTP_H #define ENGINE_SHARED_HTTP_H +#include + +#include + #include #include -#include typedef struct _json_value json_value; class IStorage; @@ -58,6 +61,9 @@ class CHttpRequest : public IJob int64_t m_MaxResponseSize = -1; REQUEST m_Type = REQUEST::GET; + SHA256_CTX m_ActualSha256; + SHA256_DIGEST m_ExpectedSha256 = SHA256_ZEROED; + bool m_WriteToFile = false; uint64_t m_ResponseLength = 0; @@ -105,6 +111,7 @@ class CHttpRequest : public IJob void LogProgress(HTTPLOG LogProgress) { m_LogProgress = LogProgress; } void IpResolve(IPRESOLVE IpResolve) { m_IpResolve = IpResolve; } void WriteToFile(IStorage *pStorage, const char *pDest, int StorageType); + void ExpectSha256(const SHA256_DIGEST &Sha256) { m_ExpectedSha256 = Sha256; } void Head() { m_Type = REQUEST::HEAD; } void Post(const unsigned char *pData, size_t DataLength) { diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index af17e5f6e37..e2fadf14822 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -542,7 +542,7 @@ class CMenus : public CComponent int OnCompletion(int State) override; public: - CCommunityIconDownloadJob(CMenus *pMenus, const char *pCommunityId, const char *pUrl); + CCommunityIconDownloadJob(CMenus *pMenus, const char *pCommunityId, const char *pUrl, const SHA256_DIGEST &Sha256); }; struct SCommunityIcon { diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 171ef66d509..34d0250a11d 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -1821,11 +1821,12 @@ int CMenus::CCommunityIconDownloadJob::OnCompletion(int State) return State; } -CMenus::CCommunityIconDownloadJob::CCommunityIconDownloadJob(CMenus *pMenus, const char *pCommunityId, const char *pUrl) : +CMenus::CCommunityIconDownloadJob::CCommunityIconDownloadJob(CMenus *pMenus, const char *pCommunityId, const char *pUrl, const SHA256_DIGEST &Sha256) : CHttpRequest(pUrl), CAbstractCommunityIconJob(pMenus, pCommunityId, IStorage::TYPE_SAVE) { WriteToFile(pMenus->Storage(), m_aPath, IStorage::TYPE_SAVE); + ExpectSha256(Sha256); Timeout(CTimeout{0, 0, 0, 0}); LogProgress(HTTPLOG::FAILURE); } @@ -2005,7 +2006,7 @@ void CMenus::UpdateCommunityIcons() }); if(pExistingDownload == m_CommunityIconDownloadJobs.end() && (ExistingIcon == m_vCommunityIcons.end() || ExistingIcon->m_Sha256 != Community.IconSha256())) { - std::shared_ptr pJob = std::make_shared(this, Community.Id(), Community.IconUrl()); + std::shared_ptr pJob = std::make_shared(this, Community.Id(), Community.IconUrl(), Community.IconSha256()); Engine()->AddJob(pJob); m_CommunityIconDownloadJobs.push_back(pJob); } From 7a6e368b7fbb0012f00c1cd7cb7356b4e41748d3 Mon Sep 17 00:00:00 2001 From: furo Date: Tue, 21 Nov 2023 01:47:11 +0100 Subject: [PATCH 055/198] Scale TargetX and TargetY with camera zoom --- src/game/client/components/controls.cpp | 4 ++++ src/game/server/ddracechat.cpp | 3 +-- src/game/server/ddracecommands.cpp | 3 +-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index fc4c590bc83..81e415d5e3c 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -266,6 +266,10 @@ int CControls::SnapInput(int *pData) if(!m_aInputDirectionLeft[g_Config.m_ClDummy] && m_aInputDirectionRight[g_Config.m_ClDummy]) m_aInputData[g_Config.m_ClDummy].m_Direction = 1; + // scale TargetX, TargetY by zoom. + m_aInputData[g_Config.m_ClDummy].m_TargetX *= m_pClient->m_Camera.m_Zoom; + m_aInputData[g_Config.m_ClDummy].m_TargetY *= m_pClient->m_Camera.m_Zoom; + // dummy copy moves if(g_Config.m_ClDummyCopyMoves) { diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 5253a2bc099..acfc32adead 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -1524,8 +1524,7 @@ void CGameContext::ConTele(IConsole::IResult *pResult, void *pUserData) vec2 Pos = pPlayer->m_ViewPos; if(pResult->NumArguments() == 0 && !pPlayer->IsPaused()) { - vec2 ZoomScale = vec2(pPlayer->m_ShowDistance.x / 1400.0f, pPlayer->m_ShowDistance.y / 800.0f); - Pos = Pos + (vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY) * ZoomScale); + Pos = Pos + vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); } else if(pResult->NumArguments() > 0) { diff --git a/src/game/server/ddracecommands.cpp b/src/game/server/ddracecommands.cpp index ae58a51ed83..8f3981e6315 100644 --- a/src/game/server/ddracecommands.cpp +++ b/src/game/server/ddracecommands.cpp @@ -408,8 +408,7 @@ void CGameContext::ConTeleport(IConsole::IResult *pResult, void *pUserData) vec2 Pos = pSelf->m_apPlayers[TeleTo]->m_ViewPos; if(!pPlayer->IsPaused() && !pResult->NumArguments()) { - vec2 ZoomScale = vec2(pPlayer->m_ShowDistance.x / 1400.0f, pPlayer->m_ShowDistance.y / 800.0f); - Pos = Pos + (vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY) * ZoomScale); + Pos = Pos + vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); } pSelf->Teleport(pChr, Pos); pChr->UnFreeze(); From a87db4f026d58c5ebe4cf6913871f52555bff57a Mon Sep 17 00:00:00 2001 From: furo Date: Tue, 21 Nov 2023 01:55:35 +0100 Subject: [PATCH 056/198] Move tele cursor behaviour to `/tc` and `/telecursor` --- src/game/ddracechat.h | 2 ++ src/game/server/ddracechat.cpp | 50 +++++++++++++++++++++++++++++- src/game/server/ddracecommands.cpp | 2 +- src/game/server/gamecontext.h | 1 + 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/game/ddracechat.h b/src/game/ddracechat.h index 957c44385bd..16f86fc106e 100644 --- a/src/game/ddracechat.h +++ b/src/game/ddracechat.h @@ -63,6 +63,8 @@ CHAT_COMMAND("rescue", "", CFGFLAG_CHAT | CFGFLAG_SERVER, ConRescue, this, "Tele CHAT_COMMAND("tp", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTele, this, "Teleport yourself to player or to where you are spectating if no player name is given") CHAT_COMMAND("lasttp", "", CFGFLAG_CHAT | CFGFLAG_SERVER, ConLastTele, this, "Teleport yourself to the last location you teleported to.") CHAT_COMMAND("teleport", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTele, this, "Teleport yourself to player or to where you are spectating if no player name is given") +CHAT_COMMAND("tc", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeleCursor, this, "Teleport yourself to player or to where you are spectating/or looking if no player name is given") +CHAT_COMMAND("telecursor", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeleCursor, this, "Teleport yourself to player or to where you are spectating/or looking if no player name is given") CHAT_COMMAND("unsolo", "", CFGFLAG_CHAT, ConPracticeUnSolo, this, "Puts you out of solo part") CHAT_COMMAND("undeep", "", CFGFLAG_CHAT, ConPracticeUnDeep, this, "Puts you out of deep freeze") diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index acfc32adead..8da773c9b4d 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -1521,10 +1521,58 @@ void CGameContext::ConTele(IConsole::IResult *pResult, void *pUserData) return; } + vec2 Pos = pPlayer->m_ViewPos; + if(pResult->NumArguments() > 0) + { + int ClientID; + for(ClientID = 0; ClientID < MAX_CLIENTS; ClientID++) + { + if(str_comp(pResult->GetString(0), pSelf->Server()->ClientName(ClientID)) == 0) + break; + } + if(ClientID == MAX_CLIENTS) + { + pSelf->SendChatTarget(pPlayer->GetCID(), "No player with this name found."); + return; + } + CPlayer *pPlayerTo = pSelf->m_apPlayers[ClientID]; + if(!pPlayerTo) + return; + CCharacter *pChrTo = pPlayerTo->GetCharacter(); + if(!pChrTo) + return; + Pos = pChrTo->m_Pos; + } + pSelf->Teleport(pChr, Pos); + pChr->UnFreeze(); + pChr->Core()->m_Vel = vec2(0, 0); + pPlayer->m_LastTeleTee.Save(pChr); +} + +void CGameContext::ConTeleCursor(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + if(!CheckClientID(pResult->m_ClientID)) + return; + CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientID]; + if(!pPlayer) + return; + CCharacter *pChr = pPlayer->GetCharacter(); + if(!pChr) + return; + + CGameTeams &Teams = pSelf->m_pController->Teams(); + int Team = pSelf->GetDDRaceTeam(pResult->m_ClientID); + if(!Teams.IsPractice(Team)) + { + pSelf->SendChatTarget(pPlayer->GetCID(), "You're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled."); + return; + } + vec2 Pos = pPlayer->m_ViewPos; if(pResult->NumArguments() == 0 && !pPlayer->IsPaused()) { - Pos = Pos + vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); + Pos += vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); } else if(pResult->NumArguments() > 0) { diff --git a/src/game/server/ddracecommands.cpp b/src/game/server/ddracecommands.cpp index 8f3981e6315..27eacf94e55 100644 --- a/src/game/server/ddracecommands.cpp +++ b/src/game/server/ddracecommands.cpp @@ -408,7 +408,7 @@ void CGameContext::ConTeleport(IConsole::IResult *pResult, void *pUserData) vec2 Pos = pSelf->m_apPlayers[TeleTo]->m_ViewPos; if(!pPlayer->IsPaused() && !pResult->NumArguments()) { - Pos = Pos + vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); + Pos += vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); } pSelf->Teleport(pChr, Pos); pChr->UnFreeze(); diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 02c53a352a7..e01caba57cf 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -452,6 +452,7 @@ class CGameContext : public IGameServer static void ConSetTimerType(IConsole::IResult *pResult, void *pUserData); static void ConRescue(IConsole::IResult *pResult, void *pUserData); static void ConTele(IConsole::IResult *pResult, void *pUserData); + static void ConTeleCursor(IConsole::IResult *pResult, void *pUserData); static void ConLastTele(IConsole::IResult *pResult, void *pUserData); static void ConPracticeUnSolo(IConsole::IResult *pResult, void *pUserData); static void ConPracticeUnDeep(IConsole::IResult *pResult, void *pUserData); From 8b589f3c4964e4ab443e63d3839ef76f1a23118a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 1 Dec 2023 20:04:48 +0100 Subject: [PATCH 057/198] Check for invalid unicode in filenames etc. on Windows Add stricter error handling when converting between UTF-16 (wide characters) and UTF-8 (multi-byte) on Windows. The `windows_wide_to_utf8` function now returns an `std::optional`, which will be empty if the argument contains invalid UTF-16. Files/folders with names containing invalid UTF-16 are now ignored on Windows. It was previously not possible to use these files either, as converting their names to UTF-8 changed the invalid codepoints to unicode replacement characters. The `windows_utf8_to_wide` function now fails with an assertion error if the argument contains invalid UTF-8, as this should never happen. Closes #7486. --- src/base/system.cpp | 62 +++++++++++++++++++++++++------------ src/base/system.h | 7 +++-- src/engine/client/input.cpp | 2 +- src/test/str.cpp | 5 +-- 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/base/system.cpp b/src/base/system.cpp index 9402051c8c5..b52cf4d9747 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -111,8 +111,8 @@ IOHANDLE io_current_exe() { return 0; } - const std::string path = windows_wide_to_utf8(wide_path); - return io_open(path.c_str(), IOFLAG_READ); + const std::optional path = windows_wide_to_utf8(wide_path); + return path.has_value() ? io_open(path.value().c_str(), IOFLAG_READ) : 0; #elif defined(CONF_PLATFORM_MACOS) char path[IO_MAX_PATH_LENGTH]; uint32_t path_size = sizeof(path); @@ -1456,9 +1456,9 @@ std::string windows_format_system_message(unsigned long error) if(FormatMessageW(flags, NULL, error, 0, (LPWSTR)&wide_message, 0, NULL) == 0) return "unknown error"; - std::string message = windows_wide_to_utf8(wide_message); + std::optional message = windows_wide_to_utf8(wide_message); LocalFree(wide_message); - return message; + return message.value_or("invalid error"); } #endif @@ -2108,8 +2108,13 @@ void fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user) do { - const std::string current_entry = windows_wide_to_utf8(finddata.cFileName); - if(cb(current_entry.c_str(), (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, type, user)) + const std::optional current_entry = windows_wide_to_utf8(finddata.cFileName); + if(!current_entry.has_value()) + { + log_error("filesystem", "ERROR: file/folder name containing invalid UTF-16 found in folder '%s'", dir); + continue; + } + if(cb(current_entry.value().c_str(), (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, type, user)) break; } while(FindNextFileW(handle, &finddata)); @@ -2155,10 +2160,15 @@ void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int t do { - const std::string current_entry = windows_wide_to_utf8(finddata.cFileName); + const std::optional current_entry = windows_wide_to_utf8(finddata.cFileName); + if(!current_entry.has_value()) + { + log_error("filesystem", "ERROR: file/folder name containing invalid UTF-16 found in folder '%s'", dir); + continue; + } CFsFileInfo info; - info.m_pName = current_entry.c_str(); + info.m_pName = current_entry.value().c_str(); info.m_TimeCreated = filetime_to_unixtime(&finddata.ftCreationTime); info.m_TimeModified = filetime_to_unixtime(&finddata.ftLastWriteTime); @@ -2212,8 +2222,14 @@ int fs_storage_path(const char *appname, char *path, int max) path[0] = '\0'; return -1; } - const std::string home = windows_wide_to_utf8(wide_home); - str_format(path, max, "%s/%s", home.c_str(), appname); + const std::optional home = windows_wide_to_utf8(wide_home); + if(!home.has_value()) + { + log_error("filesystem", "ERROR: the APPDATA environment variable contains invalid UTF-16"); + path[0] = '\0'; + return -1; + } + str_format(path, max, "%s/%s", home.value().c_str(), appname); return 0; #else char *home = getenv("HOME"); @@ -2386,8 +2402,13 @@ char *fs_getcwd(char *buffer, int buffer_size) buffer[0] = '\0'; return nullptr; } - const std::string current_dir = windows_wide_to_utf8(wide_current_dir.c_str()); - str_copy(buffer, current_dir.c_str(), buffer_size); + const std::optional current_dir = windows_wide_to_utf8(wide_current_dir.c_str()); + if(!current_dir.has_value()) + { + buffer[0] = '\0'; + return nullptr; + } + str_copy(buffer, current_dir.value().c_str(), buffer_size); return buffer; #else char *result = getcwd(buffer, buffer_size); @@ -4405,8 +4426,8 @@ void os_locale_str(char *locale, size_t length) wchar_t wide_buffer[LOCALE_NAME_MAX_LENGTH]; dbg_assert(GetUserDefaultLocaleName(wide_buffer, std::size(wide_buffer)) > 0, "GetUserDefaultLocaleName failure"); - const std::string buffer = windows_wide_to_utf8(wide_buffer); - str_copy(locale, buffer.c_str(), length); + const std::optional buffer = windows_wide_to_utf8(wide_buffer); + str_copy(locale, buffer.value_or("en-US").c_str(), length); #elif defined(CONF_PLATFORM_MACOS) CFLocaleRef locale_ref = CFLocaleCopyCurrent(); CFStringRef locale_identifier_ref = static_cast(CFLocaleGetValue(locale_ref, kCFLocaleIdentifier)); @@ -4531,20 +4552,23 @@ std::wstring windows_utf8_to_wide(const char *str) const int orig_length = str_length(str); if(orig_length == 0) return L""; - const int size_needed = MultiByteToWideChar(CP_UTF8, 0, str, orig_length, nullptr, 0); + const int size_needed = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, orig_length, nullptr, 0); + dbg_assert(size_needed > 0, "Invalid UTF-8 passed to windows_utf8_to_wide"); std::wstring wide_string(size_needed, L'\0'); - dbg_assert(MultiByteToWideChar(CP_UTF8, 0, str, orig_length, wide_string.data(), size_needed) == size_needed, "MultiByteToWideChar failure"); + dbg_assert(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, orig_length, wide_string.data(), size_needed) == size_needed, "MultiByteToWideChar failure"); return wide_string; } -std::string windows_wide_to_utf8(const wchar_t *wide_str) +std::optional windows_wide_to_utf8(const wchar_t *wide_str) { const int orig_length = wcslen(wide_str); if(orig_length == 0) return ""; - const int size_needed = WideCharToMultiByte(CP_UTF8, 0, wide_str, orig_length, nullptr, 0, nullptr, nullptr); + const int size_needed = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide_str, orig_length, nullptr, 0, nullptr, nullptr); + if(size_needed == 0) + return {}; std::string string(size_needed, '\0'); - dbg_assert(WideCharToMultiByte(CP_UTF8, 0, wide_str, orig_length, string.data(), size_needed, nullptr, nullptr) == size_needed, "WideCharToMultiByte failure"); + dbg_assert(WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide_str, orig_length, string.data(), size_needed, nullptr, nullptr) == size_needed, "WideCharToMultiByte failure"); return string; } diff --git a/src/base/system.h b/src/base/system.h index b10cf5660db..abfe7c0273f 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #ifdef __MINGW32__ @@ -2727,6 +2728,7 @@ class CCmdlineFix * @return The argument as a wide character string. * * @remark The argument string must be zero-terminated. + * @remark Fails with assertion error if passed utf8 is invalid. */ std::wstring windows_utf8_to_wide(const char *str); @@ -2736,11 +2738,12 @@ std::wstring windows_utf8_to_wide(const char *str); * * @param wide_str The wide character string to convert. * - * @return The argument as a utf8 encoded string. + * @return The argument as a utf8 encoded string, wrapped in an optional. + * The optional is empty, if the wide string contains invalid codepoints. * * @remark The argument string must be zero-terminated. */ -std::string windows_wide_to_utf8(const wchar_t *wide_str); +std::optional windows_wide_to_utf8(const wchar_t *wide_str); /** * This is a RAII wrapper to initialize/uninitialize the Windows COM library, diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp index 7d9b63ab8c1..7de5a9e31d7 100644 --- a/src/engine/client/input.cpp +++ b/src/engine/client/input.cpp @@ -800,7 +800,7 @@ void CInput::ProcessSystemMessage(SDL_SysWMmsg *pMsg) for(DWORD i = pCandidateList->dwPageStart; i < pCandidateList->dwCount && (int)m_vCandidates.size() < (int)pCandidateList->dwPageSize; i++) { LPCWSTR pCandidate = (LPCWSTR)((DWORD_PTR)pCandidateList + pCandidateList->dwOffset[i]); - m_vCandidates.push_back(std::move(windows_wide_to_utf8(pCandidate))); + m_vCandidates.push_back(std::move(windows_wide_to_utf8(pCandidate).value_or(""))); } m_CandidateSelectedIndex = pCandidateList->dwSelection - pCandidateList->dwPageStart; } diff --git a/src/test/str.cpp b/src/test/str.cpp index bb450214942..a586e81106c 100644 --- a/src/test/str.cpp +++ b/src/test/str.cpp @@ -1107,9 +1107,10 @@ TEST(Str, WindowsUtf8WideConversion) static_assert(std::size(apUtf8Strings) == std::size(apWideStrings)); for(size_t i = 0; i < std::size(apUtf8Strings); i++) { - const std::string ConvertedUtf8 = windows_wide_to_utf8(apWideStrings[i]); + const std::optional ConvertedUtf8 = windows_wide_to_utf8(apWideStrings[i]); const std::wstring ConvertedWide = windows_utf8_to_wide(apUtf8Strings[i]); - EXPECT_STREQ(ConvertedUtf8.c_str(), apUtf8Strings[i]); + ASSERT_TRUE(ConvertedUtf8.has_value()); + EXPECT_STREQ(ConvertedUtf8.value().c_str(), apUtf8Strings[i]); EXPECT_STREQ(ConvertedWide.c_str(), apWideStrings[i]); } } From cd9fc2964d197d08450ab422394c1045cef7db69 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Fri, 1 Dec 2023 22:50:00 +0100 Subject: [PATCH 058/198] Use SetEmote() --- src/game/server/entities/character.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 7f177835cc5..1c0c5a1ae40 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -734,8 +734,7 @@ void CCharacter::PreTick() // set emote if(m_EmoteStop < Server()->Tick()) { - m_EmoteType = m_pPlayer->GetDefaultEmote(); - m_EmoteStop = -1; + SetEmote(m_pPlayer->GetDefaultEmote(), -1); } DDRaceTick(); @@ -968,8 +967,7 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon) { if(Dmg) { - m_EmoteType = EMOTE_PAIN; - m_EmoteStop = Server()->Tick() + 500 * Server()->TickSpeed() / 1000; + SetEmote(EMOTE_PAIN, Server()->Tick() + 500 * Server()->TickSpeed() / 1000); } vec2 Temp = m_Core.m_Vel + Force; From 0e684e10d2671a87eca0837df7c3edd6cddb1b25 Mon Sep 17 00:00:00 2001 From: BurnyLlama Date: Fri, 1 Dec 2023 22:48:23 +0100 Subject: [PATCH 059/198] Added check if demo recorder is already active. Closes #7568. --- src/engine/client/client.cpp | 7 +++++++ src/engine/server/server.cpp | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 9a81e0e5dbf..a9c974eba13 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -3771,6 +3771,13 @@ class IDemoRecorder *CClient::DemoRecorder(int Recorder) void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData) { CClient *pSelf = (CClient *)pUserData; + + if(pSelf->m_aDemoRecorder[RECORDER_MANUAL].IsRecording()) + { + pSelf->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Demo recorder already recording"); + return; + } + if(pResult->NumArguments()) pSelf->DemoRecorder_Start(pResult->GetString(0), false, RECORDER_MANUAL, true); else diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index b6fff9c1ffa..12999a717e7 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -3456,6 +3456,12 @@ void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) CServer *pServer = (CServer *)pUser; char aFilename[IO_MAX_PATH_LENGTH]; + if(pServer->IsRecording(MAX_CLIENTS)) + { + pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Demo recorder already recording"); + return; + } + if(pResult->NumArguments()) str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pResult->GetString(0)); else From d9a726eb4839a308df46dd35a8025a88a69ef52a Mon Sep 17 00:00:00 2001 From: furo Date: Sat, 2 Dec 2023 11:10:20 +0100 Subject: [PATCH 060/198] Stop all recording of server demos on shutdown. --- src/engine/server.h | 1 + src/engine/server/server.cpp | 36 +++++++++++++++++---------------- src/engine/server/server.h | 1 + src/game/server/gamecontext.cpp | 3 +++ 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/engine/server.h b/src/engine/server.h index b504a10442e..7f8e1306b04 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -245,6 +245,7 @@ class IServer : public IInterface virtual void StartRecord(int ClientID) = 0; virtual void StopRecord(int ClientID) = 0; virtual bool IsRecording(int ClientID) = 0; + virtual void StopDemos() = 0; virtual void GetClientAddr(int ClientID, NETADDR *pAddr) const = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 12999a717e7..80cb9672612 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -2581,23 +2581,6 @@ int CServer::LoadMap(const char *pMapName) if(!m_pMap->Load(aBuf)) return 0; - // stop recording when we change map - for(int i = 0; i < MAX_CLIENTS + 1; i++) - { - if(!m_aDemoRecorder[i].IsRecording()) - continue; - - m_aDemoRecorder[i].Stop(); - - // remove tmp demos - if(i < MAX_CLIENTS) - { - char aPath[256]; - str_format(aPath, sizeof(aPath), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, i); - Storage()->RemoveFile(aPath, IStorage::TYPE_SAVE); - } - } - // reinit snapshot ids m_IDPool.TimeoutIDs(); @@ -3451,6 +3434,25 @@ bool CServer::IsRecording(int ClientID) return m_aDemoRecorder[ClientID].IsRecording(); } +void CServer::StopDemos() +{ + for(int i = 0; i < MAX_CLIENTS + 1; i++) + { + if(!m_aDemoRecorder[i].IsRecording()) + continue; + + m_aDemoRecorder[i].Stop(); + + // remove tmp demos + if(i < MAX_CLIENTS) + { + char aPath[256]; + str_format(aPath, sizeof(aPath), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, i); + Storage()->RemoveFile(aPath, IStorage::TYPE_SAVE); + } + } +} + void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) { CServer *pServer = (CServer *)pUser; diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 6b0b3708a9e..a5dcb34ae10 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -410,6 +410,7 @@ class CServer : public IServer void StartRecord(int ClientID) override; void StopRecord(int ClientID) override; bool IsRecording(int ClientID) override; + void StopDemos() override; int Run(); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index f81ca1af6e4..56b7f3e4440 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -3950,6 +3950,9 @@ void CGameContext::OnShutdown(void *pPersistentData) aio_free(m_pTeeHistorianFile); } + // Stop any demos being recorded. + Server()->StopDemos(); + DeleteTempfile(); ConfigManager()->ResetGameSettings(); Collision()->Dest(); From b352545621369c162c9dc3a3baa24b8a8c3dbee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 28 Nov 2023 21:10:32 +0100 Subject: [PATCH 061/198] Improve integration test script Fix valgrind errors not being detected as failures when the tests otherwise finish, as the valgrind results are printed to stderr and not to the logfiles. Additionally, valgrind failures were not being detected because the error summary is not printed to stderr immediately but only after some time when valgrind has processed the summary. Find random unused port to bind to by opening a socket on port 0 and closing it immediately. This gives a port that will very likely be unused because the system cycles through all ports before reusing old ones obtained this way. Add `wait_for_launch` function to reduce duplicate code. Wait for server to launch before starting clients. Improve log messages. --- scripts/integration_test.sh | 123 +++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/scripts/integration_test.sh b/scripts/integration_test.sh index 759f55f0e1d..09895662d70 100755 --- a/scripts/integration_test.sh +++ b/scripts/integration_test.sh @@ -7,14 +7,14 @@ for arg in "$@" do if [ "$arg" == "-h" ] || [ "$arg" == "--help" ] then - echo "usage: $(basename "$0") [OPTION..] [build dir]" + echo "usage: $(basename "$0") [OPTION..]" echo "description:" echo " Runs a simple integration test of the client and server" - echo " binaries from the given build dir" + echo " binaries from the current build directory." echo "options:" - echo " --help|-h show this help" - echo " --verbose|-v verbose output" - echo " --valgrind-memcheck use valgrind's memcheck to run server and client" + echo " --help|-h show this help" + echo " --verbose|-v verbose output" + echo " --valgrind-memcheck use valgrind's memcheck to run server and client" exit 0 elif [ "$arg" == "-v" ] || [ "$arg" == "--verbose" ] then @@ -23,22 +23,23 @@ do then arg_valgrind_memcheck=1 else - echo "Error: unknown arg '$arg'" + echo "Error: unknown argument '$arg'" exit 1 fi done if [ ! -f DDNet ] then - echo "Error: client binary not found DDNet' not found" + echo "[-] Error: client binary 'DDNet' not found" exit 1 fi if [ ! -f DDNet-Server ] then - echo "Error: server binary not found DDNet-Server' not found" + echo "[-] Error: server binary 'DDNet-Server' not found" exit 1 fi +echo "[*] Setup" got_killed=0 function kill_all() { @@ -51,24 +52,27 @@ function kill_all() { if [ "$arg_verbose" == "1" ] then - echo "[*] shutting down test clients and server ..." + echo "[*] Shutting down test clients and server" fi - sleep 1 + if [[ ! -f fail_server.txt ]] then - echo "[*] shutting down server" + echo "[*] Shutting down server" echo "shutdown" > server.fifo fi + sleep 1 + local i for ((i=1;i<3;i++)) do if [[ ! -f fail_client$i.txt ]] then - echo "[*] shutting down client$i" + echo "[*] Shutting down client$i" echo "quit" > "client$i.fifo" fi done + sleep 1 } function cleanup() { @@ -85,8 +89,8 @@ function fail() echo "[-] $1 exited with code $2" } -# TODO: check for open ports instead -port=17822 +# Get unused port from the system by binding to port 0 and immediately closing the socket again +port=$(python3 -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'); if [[ $OSTYPE == 'darwin'* ]]; then DETECT_LEAKS=0 @@ -100,13 +104,18 @@ export LSAN_OPTIONS=suppressions=../lsan.supp:print_suppressions=0 function print_results() { if [ "$arg_valgrind_memcheck" == "1" ]; then - if grep "ERROR SUMMARY" server.log client1.log client2.log | grep -q -v "ERROR SUMMARY: 0"; then - grep "^==" server.log client1.log client2.log + # Wait to ensure that the error summary was written to the stderr files because valgrind takes some time + # TODO: Instead wait for all started processes to finish + sleep 20 + if grep "== ERROR SUMMARY: " stderr_server.txt stderr_client1.txt stderr_client2.txt | grep -q -v "ERROR SUMMARY: 0"; then + echo "[-] Error: Valgrind has detected the following errors:" + grep "^==" stderr_server.txt stderr_client1.txt stderr_client2.txt return 1 fi else if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)" then + echo "[-] Error: ASAN has detected the following errors:" cat SAN.* return 1 fi @@ -127,39 +136,54 @@ cd integration_test || exit 1 echo $'add_path ../data' } > storage.cfg +tool="" +client_args="cl_download_skins 0; + gfx_fullscreen 0; + snd_enable 0;" + if [ "$arg_valgrind_memcheck" == "1" ]; then tool="valgrind --tool=memcheck --gen-suppressions=all --suppressions=../memcheck.supp --track-origins=yes" - client_args="cl_menu_map \"\";" -else - tool="" - client_args="" + client_args="$client_args cl_menu_map \"\";" fi function wait_for_fifo() { local fifo="$1" local tries="$2" local fails=0 - # give the client time to launch and create the fifo file - # but assume after X secs that the client crashed before + # give the server/client time to launch and create the fifo file + # but assume after X secs that the server/client crashed before # being able to create the file while [[ ! -p "$fifo" ]] do fails="$((fails+1))" if [ "$arg_verbose" == "1" ] then - echo "[!] client fifos not found (attempts $fails/$tries)" + echo "[!] Note: $fifo not found (attempts $fails/$tries)" fi if [ "$fails" -gt "$tries" ] then + echo "[-] Error: $(basename "$fifo" .fifo) possibly crashed on launch" + kill_all print_results - echo "[-] Error: client possibly crashed on launch" exit 1 fi sleep 1 done } -echo "[*] launch server" +function wait_for_launch() { + local fifo="$1" + local baseDuration="$2" + if [ "$arg_valgrind_memcheck" == "1" ]; then + wait_for_fifo "$fifo" $((40 * baseDuration)) + sleep $((8 * baseDuration)) + else + wait_for_fifo "$fifo" $((10 * baseDuration)) + sleep "$baseDuration" + fi +} + +echo "[*] Launch server" $tool ../DDNet-Server \ "sv_input_fifo server.fifo; sv_rcon_password rcon; @@ -169,50 +193,34 @@ $tool ../DDNet-Server \ sv_register 0; sv_port $port" > stdout_server.txt 2> stderr_server.txt || fail server "$?" & -echo "[*] launch client 1" +wait_for_launch server.fifo 1 + +echo "[*] Launch client 1" $tool ../DDNet \ "cl_input_fifo client1.fifo; player_name client1; - cl_download_skins 0; - gfx_fullscreen 0; - snd_enable 0; logfile client1.log; $client_args connect localhost:$port" > stdout_client1.txt 2> stderr_client1.txt || fail client1 "$?" & -if [ "$arg_valgrind_memcheck" == "1" ]; then - wait_for_fifo client1.fifo 180 - sleep 40 -else - wait_for_fifo client1.fifo 50 - sleep 1 -fi +wait_for_launch client1.fifo 5 -echo "[*] start demo recording" +echo "[*] Start demo recording" echo "record server" > server.fifo echo "record client1" > client1.fifo sleep 1 -echo "[*] launch client 2" +echo "[*] Launch client 2" $tool ../DDNet \ "cl_input_fifo client2.fifo; player_name client2; - cl_download_skins 0; - gfx_fullscreen 0; - snd_enable 0; logfile client2.log; $client_args connect localhost:$port" > stdout_client2.txt 2> stderr_client2.txt || fail client2 "$?" & -if [ "$arg_valgrind_memcheck" == "1" ]; then - wait_for_fifo client2.fifo 180 - sleep 40 -else - wait_for_fifo client2.fifo 50 - sleep 2 -fi +wait_for_launch client2.fifo 5 -echo "[*] test chat and chat commands" +echo "[*] Test chat and chat commands" echo "say hello world" > client1.fifo echo "rcon_auth rcon" > client1.fifo sleep 1 @@ -236,7 +244,7 @@ say "/mc EOF sleep 1 -echo "[*] test rcon commands" +echo "[*] Test rcon commands" tr -d '\n' > client1.fifo << EOF rcon say hello from admin; rcon broadcast test; @@ -247,12 +255,12 @@ unban_all; EOF sleep 1 -echo "[*] stop demo recording" +echo "[*] Stop demo recording" echo "stoprecord" > server.fifo echo "stoprecord" > client1.fifo sleep 1 -echo "[*] test map change" +echo "[*] Test map change" echo "rcon sv_map Tutorial" > client1.fifo if [ "$arg_valgrind_memcheck" == "1" ]; then sleep 60 @@ -260,7 +268,7 @@ else sleep 15 fi -echo "[*] play demos" +echo "[*] Play demos" echo "play demos/server.demo" > client1.fifo echo "play demos/client1.demo" > client2.fifo if [ "$arg_valgrind_memcheck" == "1" ]; then @@ -271,8 +279,6 @@ fi # Kill all processes first so all outputs are fully written kill_all -wait -sleep 1 if ! grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2} ([0-9]{2}:){2}[0-9]{2} I chat: 0:-2:client1: hello world$' server.log then @@ -331,7 +337,7 @@ do fi if [ ! -f "$logfile" ] then - echo "[-] Error: logfile '$logfile' not found." + echo "[-] Error: logfile '$logfile' not found" touch fail_logs.txt continue fi @@ -366,10 +372,9 @@ then cat "$fail" done print_results - echo "[-] Test failed. See errors above." + echo "[-] Test failed. See errors above" exit 1 -else - echo "[*] all tests passed" fi +echo "[*] All tests passed" print_results || exit 1 From d70fc84e3876bcef4f6da8ad2700d0b118776bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 27 Nov 2023 22:29:14 +0100 Subject: [PATCH 062/198] Properly initialize all `CClient` member variables Additionally, ensure snapshots are cleared and dummy is disconnected when disconnecting programatically with `DisconnectWithReason`. Add `NETADDR_ZEROED` and `UUID_ZEROED` for more convenient initialization without using `mem_zero`. --- src/base/system.cpp | 3 + src/base/system.h | 2 + src/engine/client.h | 35 ++++--- src/engine/client/client.cpp | 114 +++------------------ src/engine/client/client.h | 152 ++++++++++++++-------------- src/engine/client/serverbrowser.cpp | 9 +- src/engine/client/smooth_time.cpp | 1 + src/engine/shared/uuid_manager.cpp | 4 + src/engine/shared/uuid_manager.h | 2 + 9 files changed, 121 insertions(+), 201 deletions(-) diff --git a/src/base/system.cpp b/src/base/system.cpp index b52cf4d9747..7018e5b5982 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -963,6 +963,9 @@ int64_t time_freq() } /* ----- network ----- */ + +const NETADDR NETADDR_ZEROED = {NETTYPE_INVALID, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0}; + static void netaddr_to_sockaddr_in(const NETADDR *src, struct sockaddr_in *dest) { mem_zero(dest, sizeof(struct sockaddr_in)); diff --git a/src/base/system.h b/src/base/system.h index abfe7c0273f..08a9e392c8c 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -777,6 +777,8 @@ typedef struct NETADDR bool operator!=(const NETADDR &other) const { return !(*this == other); } } NETADDR; +extern const NETADDR NETADDR_ZEROED; + #ifdef CONF_FAMILY_UNIX /** * @ingroup Network-General diff --git a/src/engine/client.h b/src/engine/client.h index 26780c0a601..bc265e000d5 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -69,31 +69,30 @@ class IClient : public IInterface protected: // quick access to state of the client - EClientState m_State; - ELoadingStateDetail m_LoadingStateDetail; + EClientState m_State = IClient::STATE_OFFLINE; + ELoadingStateDetail m_LoadingStateDetail = LOADING_STATE_DETAIL_INITIAL; int64_t m_StateStartTime; // quick access to time variables - int m_aPrevGameTick[NUM_DUMMIES]; - int m_aCurGameTick[NUM_DUMMIES]; - float m_aGameIntraTick[NUM_DUMMIES]; - float m_aGameTickTime[NUM_DUMMIES]; - float m_aGameIntraTickSincePrev[NUM_DUMMIES]; + int m_aPrevGameTick[NUM_DUMMIES] = {0, 0}; + int m_aCurGameTick[NUM_DUMMIES] = {0, 0}; + float m_aGameIntraTick[NUM_DUMMIES] = {0.0f, 0.0f}; + float m_aGameTickTime[NUM_DUMMIES] = {0.0f, 0.0f}; + float m_aGameIntraTickSincePrev[NUM_DUMMIES] = {0.0f, 0.0f}; - int m_aPredTick[NUM_DUMMIES]; - float m_aPredIntraTick[NUM_DUMMIES]; + int m_aPredTick[NUM_DUMMIES] = {0, 0}; + float m_aPredIntraTick[NUM_DUMMIES] = {0.0f, 0.0f}; - float m_LocalTime; - float m_GlobalTime; - float m_RenderFrameTime; + float m_LocalTime = 0.0f; + float m_GlobalTime = 0.0f; + float m_RenderFrameTime = 0.0001f; + float m_FrameTimeAvg = 0.0001f; - float m_FrameTimeAvg; + TMapLoadingCallbackFunc m_MapLoadingCBFunc = nullptr; - TMapLoadingCallbackFunc m_MapLoadingCBFunc; - - char m_aNews[3000]; - int m_Points; - int64_t m_ReconnectTime; + char m_aNews[3000] = ""; + int m_Points = -1; + int64_t m_ReconnectTime = 0; public: class CSnapItem diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index a9c974eba13..3e4e1dfe77a 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -77,104 +77,21 @@ static const ColorRGBA gs_ClientNetworkErrPrintColor{1.0f, 0.25f, 0.25f, 1.0f}; CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta, true, [&]() { UpdateDemoIntraTimers(); }) { + m_StateStartTime = time_get(); for(auto &DemoRecorder : m_aDemoRecorder) DemoRecorder = CDemoRecorder(&m_SnapshotDelta); - - m_RenderFrameTime = 0.0001f; m_LastRenderTime = time_get(); - - m_SnapCrcErrors = 0; - m_AutoScreenshotRecycle = false; - m_AutoStatScreenshotRecycle = false; - m_AutoCSVRecycle = false; - m_EditorActive = false; - - m_aAckGameTick[0] = -1; - m_aAckGameTick[1] = -1; - m_aCurrentRecvTick[0] = 0; - m_aCurrentRecvTick[1] = 0; - m_aRconAuthed[0] = 0; - m_aRconAuthed[1] = 0; - m_aRconUsername[0] = '\0'; - m_aRconPassword[0] = '\0'; - m_aPassword[0] = '\0'; - - // version-checking - m_aVersionStr[0] = '0'; - m_aVersionStr[1] = '\0'; - - // pinging - m_PingStartTime = 0; - - m_aCurrentMap[0] = 0; - - m_aCmdConnect[0] = 0; - - // map download - m_aMapdownloadFilename[0] = 0; - m_aMapdownloadFilenameTemp[0] = 0; - m_aMapdownloadName[0] = 0; - m_pMapdownloadTask = NULL; - m_MapdownloadFileTemp = 0; - m_MapdownloadChunk = 0; - m_MapdownloadSha256Present = false; - m_MapdownloadSha256 = SHA256_ZEROED; - m_MapdownloadCrc = 0; - m_MapdownloadAmount = -1; - m_MapdownloadTotalsize = -1; - - m_MapDetailsPresent = false; - m_aMapDetailsName[0] = 0; - m_MapDetailsSha256 = SHA256_ZEROED; - m_MapDetailsCrc = 0; - m_aMapDetailsUrl[0] = 0; - IStorage::FormatTmpPath(m_aDDNetInfoTmp, sizeof(m_aDDNetInfoTmp), DDNET_INFO_FILE); - m_pDDNetInfoTask = NULL; - m_aNews[0] = '\0'; - m_aMapDownloadUrl[0] = '\0'; - m_Points = -1; - - m_CurrentServerInfoRequestTime = -1; - m_CurrentServerPingInfoType = -1; - m_CurrentServerPingBasicToken = -1; - m_CurrentServerPingToken = -1; - mem_zero(&m_CurrentServerPingUuid, sizeof(m_CurrentServerPingUuid)); - m_CurrentServerCurrentPingTime = -1; - m_CurrentServerNextPingTime = -1; - - m_aCurrentInput[0] = 0; - m_aCurrentInput[1] = 0; - m_LastDummy = false; - - mem_zero(&m_aInputs, sizeof(m_aInputs)); - - m_State = IClient::STATE_OFFLINE; - m_StateStartTime = time_get(); - m_aConnectAddressStr[0] = 0; - + mem_zero(m_aInputs, sizeof(m_aInputs)); mem_zero(m_aapSnapshots, sizeof(m_aapSnapshots)); - m_aSnapshotStorage[0].Init(); - m_aSnapshotStorage[1].Init(); - m_aReceivedSnapshots[0] = 0; - m_aReceivedSnapshots[1] = 0; - m_aSnapshotParts[0] = 0; - m_aSnapshotParts[1] = 0; - - m_VersionInfo.m_State = CVersionInfo::STATE_INIT; - - if(g_Config.m_ClDummy == 0) - m_LastDummyConnectTime = 0; - - m_ReconnectTime = 0; - - m_GenerateTimeoutSeed = true; - - m_FrameTimeAvg = 0.0001f; - m_BenchmarkFile = 0; - m_BenchmarkStopTime = 0; - + for(auto &SnapshotStorage : m_aSnapshotStorage) + SnapshotStorage.Init(); + mem_zero(m_aDemorecSnapshotHolders, sizeof(m_aDemorecSnapshotHolders)); + mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); mem_zero(&m_Checksum, sizeof(m_Checksum)); + for(auto &GameTime : m_aGameTime) + GameTime.Init(0); + m_PredictedTime.Init(0); } // ----- send functions ----- @@ -610,6 +527,8 @@ void CClient::DisconnectWithReason(const char *pReason) if(pReason != nullptr && pReason[0] == '\0') pReason = nullptr; + DummyDisconnect(pReason); + char aBuf[512]; str_format(aBuf, sizeof(aBuf), "disconnecting. reason='%s'", pReason ? pReason : "unknown"); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, gs_ClientNetworkPrintColor); @@ -657,16 +576,14 @@ void CClient::DisconnectWithReason(const char *pReason) mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); // clear snapshots - m_aapSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = 0; - m_aapSnapshots[g_Config.m_ClDummy][SNAP_PREV] = 0; - m_aReceivedSnapshots[g_Config.m_ClDummy] = 0; + m_aapSnapshots[0][SNAP_CURRENT] = 0; + m_aapSnapshots[0][SNAP_PREV] = 0; + m_aReceivedSnapshots[0] = 0; } void CClient::Disconnect() { m_ButtonRender = false; - if(m_DummyConnected) - DummyDisconnect(0); if(m_State != IClient::STATE_OFFLINE) DisconnectWithReason(0); @@ -2273,7 +2190,6 @@ void CClient::LoadDDNetInfo() NETADDR Addr; if(!net_addr_from_str(&Addr, ConnectingIp)) { - m_HaveGlobalTcpAddr = true; m_GlobalTcpAddr = Addr; log_debug("info", "got global tcp ip address: %s", (const char *)ConnectingIp); } @@ -4735,7 +4651,7 @@ int CClient::UdpConnectivity(int NetType) break; case CONNECTIVITY::ADDRESS_KNOWN: GlobalUdpAddr.port = 0; - if(m_HaveGlobalTcpAddr && NetType == (int)m_GlobalTcpAddr.type && net_addr_comp(&m_GlobalTcpAddr, &GlobalUdpAddr) != 0) + if(NetType == (int)m_GlobalTcpAddr.type && net_addr_comp(&m_GlobalTcpAddr, &GlobalUdpAddr) != 0) { NewConnectivity = CONNECTIVITY_DIFFERING_UDP_TCP_IP_ADDRESSES; break; diff --git a/src/engine/client/client.h b/src/engine/client/client.h index f989e7b1f4c..1d6e23ee19b 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -48,11 +48,11 @@ class IUpdater; class CServerCapabilities { public: - bool m_ChatTimeoutCode; - bool m_AnyPlayerFlag; - bool m_PingEx; - bool m_AllowDummy; - bool m_SyncWeaponInput; + bool m_ChatTimeoutCode = false; + bool m_AnyPlayerFlag = false; + bool m_PingEx = false; + bool m_AllowDummy = false; + bool m_SyncWeaponInput = false; }; class CClient : public IClient, public CDemoPlayer::IListener @@ -86,78 +86,76 @@ class CClient : public IClient, public CDemoPlayer::IListener CFriends m_Friends; CFriends m_Foes; - char m_aConnectAddressStr[MAX_SERVER_ADDRESSES * NETADDR_MAXSTRSIZE]; + char m_aConnectAddressStr[MAX_SERVER_ADDRESSES * NETADDR_MAXSTRSIZE] = ""; - CUuid m_ConnectionID; + CUuid m_ConnectionID = UUID_ZEROED; - bool m_HaveGlobalTcpAddr = false; - NETADDR m_GlobalTcpAddr; + NETADDR m_GlobalTcpAddr = NETADDR_ZEROED; - uint64_t m_aSnapshotParts[NUM_DUMMIES]; - int64_t m_LocalStartTime; - int64_t m_GlobalStartTime; + uint64_t m_aSnapshotParts[NUM_DUMMIES] = {0, 0}; + int64_t m_LocalStartTime = 0; + int64_t m_GlobalStartTime = 0; IGraphics::CTextureHandle m_DebugFont; int64_t m_LastRenderTime; - int m_SnapCrcErrors; - bool m_AutoScreenshotRecycle; - bool m_AutoStatScreenshotRecycle; - bool m_AutoCSVRecycle; - bool m_EditorActive; - bool m_SoundInitFailed; - - int m_aAckGameTick[NUM_DUMMIES]; - int m_aCurrentRecvTick[NUM_DUMMIES]; - int m_aRconAuthed[NUM_DUMMIES]; - char m_aRconUsername[32]; - char m_aRconPassword[sizeof(g_Config.m_SvRconPassword)]; - int m_UseTempRconCommands; - char m_aPassword[sizeof(g_Config.m_Password)]; - bool m_SendPassword; + int m_SnapCrcErrors = 0; + bool m_AutoScreenshotRecycle = false; + bool m_AutoStatScreenshotRecycle = false; + bool m_AutoCSVRecycle = false; + bool m_EditorActive = false; + bool m_SoundInitFailed = false; + + int m_aAckGameTick[NUM_DUMMIES] = {-1, -1}; + int m_aCurrentRecvTick[NUM_DUMMIES] = {0, 0}; + int m_aRconAuthed[NUM_DUMMIES] = {0, 0}; + char m_aRconUsername[32] = ""; + char m_aRconPassword[sizeof(g_Config.m_SvRconPassword)] = ""; + int m_UseTempRconCommands = 0; + char m_aPassword[sizeof(g_Config.m_Password)] = ""; + bool m_SendPassword = false; bool m_ButtonRender = false; // version-checking - char m_aVersionStr[10]; + char m_aVersionStr[10] = "0"; // pinging - int64_t m_PingStartTime; + int64_t m_PingStartTime = 0; - char m_aCurrentMap[IO_MAX_PATH_LENGTH]; - char m_aCurrentMapPath[IO_MAX_PATH_LENGTH]; + char m_aCurrentMap[IO_MAX_PATH_LENGTH] = ""; + char m_aCurrentMapPath[IO_MAX_PATH_LENGTH] = ""; - char m_aTimeoutCodes[NUM_DUMMIES][32]; - bool m_aCodeRunAfterJoin[NUM_DUMMIES]; - bool m_GenerateTimeoutSeed; + char m_aTimeoutCodes[NUM_DUMMIES][32] = {"", ""}; + bool m_aCodeRunAfterJoin[NUM_DUMMIES] = {false, false}; + bool m_GenerateTimeoutSeed = true; - // - char m_aCmdConnect[256]; - char m_aCmdPlayDemo[IO_MAX_PATH_LENGTH]; - char m_aCmdEditMap[IO_MAX_PATH_LENGTH]; + char m_aCmdConnect[256] = ""; + char m_aCmdPlayDemo[IO_MAX_PATH_LENGTH] = ""; + char m_aCmdEditMap[IO_MAX_PATH_LENGTH] = ""; // map download - char m_aMapDownloadUrl[256]; - std::shared_ptr m_pMapdownloadTask; - char m_aMapdownloadFilename[256]; - char m_aMapdownloadFilenameTemp[256]; - char m_aMapdownloadName[256]; - IOHANDLE m_MapdownloadFileTemp; - int m_MapdownloadChunk; - int m_MapdownloadCrc; - int m_MapdownloadAmount; - int m_MapdownloadTotalsize; - bool m_MapdownloadSha256Present; - SHA256_DIGEST m_MapdownloadSha256; - - bool m_MapDetailsPresent; - char m_aMapDetailsName[256]; - int m_MapDetailsCrc; - SHA256_DIGEST m_MapDetailsSha256; - char m_aMapDetailsUrl[256]; + char m_aMapDownloadUrl[256] = ""; + std::shared_ptr m_pMapdownloadTask = nullptr; + char m_aMapdownloadFilename[256] = ""; + char m_aMapdownloadFilenameTemp[256] = ""; + char m_aMapdownloadName[256] = ""; + IOHANDLE m_MapdownloadFileTemp = 0; + int m_MapdownloadChunk = 0; + int m_MapdownloadCrc = 0; + int m_MapdownloadAmount = -1; + int m_MapdownloadTotalsize = -1; + bool m_MapdownloadSha256Present = false; + SHA256_DIGEST m_MapdownloadSha256 = SHA256_ZEROED; + + bool m_MapDetailsPresent = false; + char m_aMapDetailsName[256] = ""; + int m_MapDetailsCrc = 0; + SHA256_DIGEST m_MapDetailsSha256 = SHA256_ZEROED; + char m_aMapDetailsUrl[256] = ""; char m_aDDNetInfoTmp[64]; - std::shared_ptr m_pDDNetInfoTask; + std::shared_ptr m_pDDNetInfoTask = nullptr; // time CSmoothTime m_aGameTime[NUM_DUMMIES]; @@ -173,9 +171,11 @@ class CClient : public IClient, public CDemoPlayer::IListener int64_t m_Time; } m_aInputs[NUM_DUMMIES][200]; - int m_aCurrentInput[NUM_DUMMIES]; - bool m_LastDummy; - bool m_DummySendConnInfo; + int m_aCurrentInput[NUM_DUMMIES] = {0, 0}; + bool m_LastDummy = false; + bool m_DummySendConnInfo = false; + bool m_DummyConnected = false; + int m_LastDummyConnectTime = 0; // graphs CGraph m_InputtimeMarginGraph; @@ -186,9 +186,9 @@ class CClient : public IClient, public CDemoPlayer::IListener CSnapshotStorage m_aSnapshotStorage[NUM_DUMMIES]; CSnapshotStorage::CHolder *m_aapSnapshots[NUM_DUMMIES][NUM_SNAPSHOT_TYPES]; - int m_aReceivedSnapshots[NUM_DUMMIES]; + int m_aReceivedSnapshots[NUM_DUMMIES] = {0, 0}; char m_aaSnapshotIncomingData[NUM_DUMMIES][CSnapshot::MAX_SIZE]; - int m_aSnapshotIncomingDataSize[NUM_DUMMIES]; + int m_aSnapshotIncomingDataSize[NUM_DUMMIES] = {0, 0}; CSnapshotStorage::CHolder m_aDemorecSnapshotHolders[NUM_SNAPSHOT_TYPES]; char m_aaaDemorecSnapshotData[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; @@ -198,19 +198,19 @@ class CClient : public IClient, public CDemoPlayer::IListener std::deque> m_EditJobs; // - bool m_CanReceiveServerCapabilities; - bool m_ServerSentCapabilities; + bool m_CanReceiveServerCapabilities = false; + bool m_ServerSentCapabilities = false; CServerCapabilities m_ServerCapabilities; CServerInfo m_CurrentServerInfo; - int64_t m_CurrentServerInfoRequestTime; // >= 0 should request, == -1 got info + int64_t m_CurrentServerInfoRequestTime = -1; // >= 0 should request, == -1 got info - int m_CurrentServerPingInfoType; - int m_CurrentServerPingBasicToken; - int m_CurrentServerPingToken; - CUuid m_CurrentServerPingUuid; - int64_t m_CurrentServerCurrentPingTime; // >= 0 request running - int64_t m_CurrentServerNextPingTime; // >= 0 should request + int m_CurrentServerPingInfoType = -1; + int m_CurrentServerPingBasicToken = -1; + int m_CurrentServerPingToken = -1; + CUuid m_CurrentServerPingUuid = UUID_ZEROED; + int64_t m_CurrentServerCurrentPingTime = -1; // >= 0 request running + int64_t m_CurrentServerNextPingTime = -1; // >= 0 should request // version info struct CVersionInfo @@ -222,19 +222,19 @@ class CClient : public IClient, public CDemoPlayer::IListener STATE_READY, }; - int m_State; + int m_State = STATE_INIT; } m_VersionInfo; std::vector m_vWarnings; CFifo m_Fifo; - IOHANDLE m_BenchmarkFile; - int64_t m_BenchmarkStopTime; + IOHANDLE m_BenchmarkFile = 0; + int64_t m_BenchmarkStopTime = 0; CChecksum m_Checksum; int m_OwnExecutableSize = 0; - IOHANDLE m_OwnExecutable; + IOHANDLE m_OwnExecutable = 0; // favorite command handling bool m_FavoritesGroup = false; @@ -310,8 +310,6 @@ class CClient : public IClient, public CDemoPlayer::IListener bool DummyConnected() override; bool DummyConnecting() override; bool DummyAllowed() override; - int m_DummyConnected; - int m_LastDummyConnectTime; void GetServerInfo(CServerInfo *pServerInfo) const override; void ServerInfoRequest(); diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index 236f9d6c30d..332bb7aff4b 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -57,17 +57,10 @@ CServerBrowser::CServerBrowser() : m_ppServerlist = nullptr; m_pSortedServerlist = nullptr; - m_pFirstReqServer = nullptr; // request list - m_pLastReqServer = nullptr; - m_NumRequests = 0; - m_NeedResort = false; m_Sorthash = 0; - m_NumSortedServers = 0; m_NumSortedServersCapacity = 0; - m_NumSortedPlayers = 0; - m_NumServers = 0; m_NumServerCapacity = 0; m_ServerlistType = 0; @@ -76,6 +69,8 @@ CServerBrowser::CServerBrowser() : m_pDDNetInfo = nullptr; m_DDNetInfoUpdateTime = 0; + + CleanUp(); } CServerBrowser::~CServerBrowser() diff --git a/src/engine/client/smooth_time.cpp b/src/engine/client/smooth_time.cpp index 9a9675fe770..e917f1a19e2 100644 --- a/src/engine/client/smooth_time.cpp +++ b/src/engine/client/smooth_time.cpp @@ -13,6 +13,7 @@ void CSmoothTime::Init(int64_t Target) m_Current = Target; m_Target = Target; m_Margin = 0; + m_SpikeCounter = 0; m_aAdjustSpeed[ADJUSTDIRECTION_DOWN] = 0.3f; m_aAdjustSpeed[ADJUSTDIRECTION_UP] = 0.3f; } diff --git a/src/engine/shared/uuid_manager.cpp b/src/engine/shared/uuid_manager.cpp index d9b0bc8e029..4dfb9010060 100644 --- a/src/engine/shared/uuid_manager.cpp +++ b/src/engine/shared/uuid_manager.cpp @@ -10,6 +10,10 @@ static const CUuid TEEWORLDS_NAMESPACE = {{// "e05ddaaa-c4e6-4cfb-b642-5d48e80c0 0xe0, 0x5d, 0xda, 0xaa, 0xc4, 0xe6, 0x4c, 0xfb, 0xb6, 0x42, 0x5d, 0x48, 0xe8, 0x0c, 0x00, 0x29}}; +const CUuid UUID_ZEROED = {{// "00000000-0000-0000-0000-000000000000" + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + CUuid RandomUuid() { CUuid Result; diff --git a/src/engine/shared/uuid_manager.h b/src/engine/shared/uuid_manager.h index 44016a16cd4..c1c607e750f 100644 --- a/src/engine/shared/uuid_manager.h +++ b/src/engine/shared/uuid_manager.h @@ -24,6 +24,8 @@ struct CUuid bool operator<(const CUuid &Other) const { return mem_comp(m_aData, Other.m_aData, sizeof(m_aData)) < 0; } }; +extern const CUuid UUID_ZEROED; + CUuid RandomUuid(); CUuid CalculateUuid(const char *pName); // The buffer length should be at least UUID_MAXSTRSIZE. From 732b213c09614096bcefb132adbd3af426cb9a1d Mon Sep 17 00:00:00 2001 From: furo Date: Sat, 2 Dec 2023 18:49:15 +0100 Subject: [PATCH 063/198] Fix bug when using favourites in combination with prefixes. --- src/game/client/components/menus_settings.cpp | 2 +- src/game/client/components/skins.cpp | 4 ++-- src/game/client/components/skins.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 8037feec542..df1b8b1a7f7 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -745,7 +745,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) for(const auto &it : m_SkinFavorites) { - const CSkin *pSkinToBeSelected = m_pClient->m_Skins.FindOrNullptr(it.c_str()); + const CSkin *pSkinToBeSelected = m_pClient->m_Skins.FindOrNullptr(it.c_str(), true); if(pSkinToBeSelected == nullptr || !SkinNotFiltered(pSkinToBeSelected)) continue; diff --git a/src/game/client/components/skins.cpp b/src/game/client/components/skins.cpp index f8991e878e9..a400daf763a 100644 --- a/src/game/client/components/skins.cpp +++ b/src/game/client/components/skins.cpp @@ -373,14 +373,14 @@ const CSkin *CSkins::Find(const char *pName) } } -const CSkin *CSkins::FindOrNullptr(const char *pName) +const CSkin *CSkins::FindOrNullptr(const char *pName, bool IgnorePrefix) { const char *pSkinPrefix = m_aEventSkinPrefix[0] ? m_aEventSkinPrefix : g_Config.m_ClSkinPrefix; if(g_Config.m_ClVanillaSkinsOnly && !IsVanillaSkin(pName)) { return nullptr; } - else if(pSkinPrefix && pSkinPrefix[0]) + else if(pSkinPrefix && pSkinPrefix[0] && !IgnorePrefix) { char aBuf[24]; str_format(aBuf, sizeof(aBuf), "%s_%s", pSkinPrefix, pName); diff --git a/src/game/client/components/skins.h b/src/game/client/components/skins.h index 92c24ea3730..677350fca49 100644 --- a/src/game/client/components/skins.h +++ b/src/game/client/components/skins.h @@ -64,7 +64,7 @@ class CSkins : public CComponent void Refresh(TSkinLoadedCBFunc &&SkinLoadedFunc); int Num(); std::unordered_map> &GetSkinsUnsafe() { return m_Skins; } - const CSkin *FindOrNullptr(const char *pName); + const CSkin *FindOrNullptr(const char *pName, bool IgnorePrefix = false); const CSkin *Find(const char *pName); bool IsDownloadingSkins() { return m_DownloadingSkins; } From 026ddc3f3a24c78bead88c6706d4c7ff7ac053f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 9 Oct 2022 17:53:38 +0200 Subject: [PATCH 064/198] Extract common expression in variable `pSnapshot` --- src/engine/client/client.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 3e4e1dfe77a..6c67618bb97 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -685,9 +685,10 @@ void CClient::LoadDebugFont() void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem) const { dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); - const CSnapshotItem *pSnapshotItem = m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(Index); - pItem->m_DataSize = m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemSize(Index); - pItem->m_Type = m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemType(Index); + const CSnapshot *pSnapshot = m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap; + const CSnapshotItem *pSnapshotItem = pSnapshot->GetItem(Index); + pItem->m_DataSize = pSnapshot->GetItemSize(Index); + pItem->m_Type = pSnapshot->GetItemType(Index); pItem->m_ID = pSnapshotItem->ID(); return (void *)pSnapshotItem->Data(); } From 93d669143d053f036a885d72e8aa4041b509d596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 9 Oct 2022 17:57:43 +0200 Subject: [PATCH 065/198] Use `nullptr` instead of `0` and `0x0` --- src/engine/client/client.cpp | 4 ++-- src/engine/server/server.cpp | 4 ++-- src/engine/shared/snapshot.cpp | 24 ++++++++++++------------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 6c67618bb97..fcb18fdaa94 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -702,7 +702,7 @@ int CClient::SnapItemSize(int SnapID, int Index) const const void *CClient::SnapFindItem(int SnapID, int Type, int ID) const { if(!m_aapSnapshots[g_Config.m_ClDummy][SnapID]) - return 0x0; + return nullptr; return m_aapSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->FindItem(Type, ID); } @@ -1701,7 +1701,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) const CSnapshot *pDeltaShot = CSnapshot::EmptySnapshot(); if(DeltaTick >= 0) { - int DeltashotSize = m_aSnapshotStorage[Conn].Get(DeltaTick, 0, &pDeltaShot, 0); + int DeltashotSize = m_aSnapshotStorage[Conn].Get(DeltaTick, nullptr, &pDeltaShot, nullptr); if(DeltashotSize < 0) { diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 80cb9672612..072c1ccbddf 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1020,7 +1020,7 @@ void CServer::DoSnapshot() int DeltaTick = -1; const CSnapshot *pDeltashot = CSnapshot::EmptySnapshot(); { - int DeltashotSize = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, 0, &pDeltashot, 0); + int DeltashotSize = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, nullptr, &pDeltashot, nullptr); if(DeltashotSize >= 0) DeltaTick = m_aClients[i].m_LastAckedSnapshot; else @@ -1687,7 +1687,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) m_aClients[ClientID].m_SnapRate = CClient::SNAPRATE_FULL; int64_t TagTime; - if(m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0) + if(m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, nullptr, nullptr) >= 0) m_aClients[ClientID].m_Latency = (int)(((time_get() - TagTime) * 1000) / time_freq()); // add message to report the input timing diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index 5449a071692..762a67c08b6 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -465,8 +465,8 @@ int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const vo void CSnapshotStorage::Init() { - m_pFirst = 0; - m_pLast = 0; + m_pFirst = nullptr; + m_pLast = nullptr; } void CSnapshotStorage::PurgeAll() @@ -481,8 +481,8 @@ void CSnapshotStorage::PurgeAll() } // no more snapshots in storage - m_pFirst = 0; - m_pLast = 0; + m_pFirst = nullptr; + m_pLast = nullptr; } void CSnapshotStorage::PurgeUntil(int Tick) @@ -501,14 +501,14 @@ void CSnapshotStorage::PurgeUntil(int Tick) break; m_pFirst = pNext; - pNext->m_pPrev = 0x0; + pNext->m_pPrev = nullptr; pHolder = pNext; } // no more snapshots in storage - m_pFirst = 0; - m_pLast = 0; + m_pFirst = nullptr; + m_pLast = nullptr; } void CSnapshotStorage::Add(int Tick, int64_t Tagtime, int DataSize, const void *pData, int AltDataSize, const void *pAltData) @@ -538,12 +538,12 @@ void CSnapshotStorage::Add(int Tick, int64_t Tagtime, int DataSize, const void * } else { - pHolder->m_pAltSnap = 0; + pHolder->m_pAltSnap = nullptr; pHolder->m_AltSnapSize = 0; } // link - pHolder->m_pNext = 0; + pHolder->m_pNext = nullptr; pHolder->m_pPrev = m_pLast; if(m_pLast) m_pLast->m_pNext = pHolder; @@ -605,7 +605,7 @@ int *CSnapshotBuilder::GetItemData(int Key) if(GetItem(i)->Key() == Key) return GetItem(i)->Data(); } - return 0; + return nullptr; } int CSnapshotBuilder::Finish(void *pSnapData) @@ -657,7 +657,7 @@ void *CSnapshotBuilder::NewItem(int Type, int ID, int Size) { if(ID == -1) { - return 0; + return nullptr; } if(m_DataSize + sizeof(CSnapshotItem) + Size >= CSnapshot::MAX_SIZE || @@ -665,7 +665,7 @@ void *CSnapshotBuilder::NewItem(int Type, int ID, int Size) { dbg_assert(m_DataSize < CSnapshot::MAX_SIZE, "too much data"); dbg_assert(m_NumItems < CSnapshot::MAX_ITEMS, "too many items"); - return 0; + return nullptr; } bool Extended = false; From a0736e4f24a11fe0d5a17be3f23e876f6e6c1c09 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Sun, 3 Dec 2023 21:36:45 +0100 Subject: [PATCH 066/198] Make intersect TeleNr arg optional gets rid of the need to define a unused variable --- src/game/client/components/players.cpp | 3 +-- src/game/client/prediction/entities/laser.cpp | 4 +-- src/game/collision.cpp | 26 ++++++++++++------- src/game/collision.h | 4 +-- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 87cd222a047..e4ef6aec83d 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -202,8 +202,7 @@ void CPlayers::RenderHookCollLine( DoBreak = true; } - int TeleNr = 0; - int Hit = Collision()->IntersectLineTeleHook(OldPos, NewPos, &FinishPos, 0x0, &TeleNr); + int Hit = Collision()->IntersectLineTeleHook(OldPos, NewPos, &FinishPos, 0x0); if(!DoBreak && Hit) { diff --git a/src/game/client/prediction/entities/laser.cpp b/src/game/client/prediction/entities/laser.cpp index 1f377192a18..6b0fb9247d3 100644 --- a/src/game/client/prediction/entities/laser.cpp +++ b/src/game/client/prediction/entities/laser.cpp @@ -99,11 +99,9 @@ void CLaser::DoBounce() vec2 Coltile; int Res; - int z; - vec2 To = m_Pos + m_Dir * m_Energy; - Res = Collision()->IntersectLineTeleWeapon(m_Pos, To, &Coltile, &To, &z); + Res = Collision()->IntersectLineTeleWeapon(m_Pos, To, &Coltile, &To); if(Res) { diff --git a/src/game/collision.cpp b/src/game/collision.cpp index ef5edb1171c..5306d589b69 100644 --- a/src/game/collision.cpp +++ b/src/game/collision.cpp @@ -336,11 +336,14 @@ int CCollision::IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, int iy = round_to_int(Pos.y); int Index = GetPureMapIndex(Pos); - if(g_Config.m_SvOldTeleportHook) - *pTeleNr = IsTeleport(Index); - else - *pTeleNr = IsTeleportHook(Index); - if(*pTeleNr) + if(pTeleNr) + { + if(g_Config.m_SvOldTeleportHook) + *pTeleNr = IsTeleport(Index); + else + *pTeleNr = IsTeleportHook(Index); + } + if(pTeleNr && *pTeleNr) { if(pOutCollision) *pOutCollision = Pos; @@ -391,11 +394,14 @@ int CCollision::IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollisio int iy = round_to_int(Pos.y); int Index = GetPureMapIndex(Pos); - if(g_Config.m_SvOldTeleportWeapons) - *pTeleNr = IsTeleport(Index); - else - *pTeleNr = IsTeleportWeapon(Index); - if(*pTeleNr) + if(pTeleNr) + { + if(g_Config.m_SvOldTeleportWeapons) + *pTeleNr = IsTeleport(Index); + else + *pTeleNr = IsTeleportWeapon(Index); + } + if(pTeleNr && *pTeleNr) { if(pOutCollision) *pOutCollision = Pos; diff --git a/src/game/collision.h b/src/game/collision.h index fa697f6ed33..ceccc03d4d9 100644 --- a/src/game/collision.h +++ b/src/game/collision.h @@ -39,8 +39,8 @@ class CCollision int GetWidth() const { return m_Width; } int GetHeight() const { return m_Height; } int IntersectLine(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const; - int IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const; - int IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const; + int IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr = nullptr) const; + int IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr = nullptr) const; void MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces) const; void MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, vec2 Elasticity, bool *pGrounded = nullptr) const; bool TestBox(vec2 Pos, vec2 Size) const; From a0d8c6c3c6b10e4e5d73988880c8933ce7be3fd8 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Mon, 4 Dec 2023 02:52:23 +0100 Subject: [PATCH 067/198] Reintroduce `m_HaveGlobalTcpAddr` CC #7575 --- src/engine/client/client.cpp | 3 ++- src/engine/client/client.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 3e4e1dfe77a..0fd05da35ce 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -2190,6 +2190,7 @@ void CClient::LoadDDNetInfo() NETADDR Addr; if(!net_addr_from_str(&Addr, ConnectingIp)) { + m_HaveGlobalTcpAddr = true; m_GlobalTcpAddr = Addr; log_debug("info", "got global tcp ip address: %s", (const char *)ConnectingIp); } @@ -4651,7 +4652,7 @@ int CClient::UdpConnectivity(int NetType) break; case CONNECTIVITY::ADDRESS_KNOWN: GlobalUdpAddr.port = 0; - if(NetType == (int)m_GlobalTcpAddr.type && net_addr_comp(&m_GlobalTcpAddr, &GlobalUdpAddr) != 0) + if(m_HaveGlobalTcpAddr && NetType == (int)m_GlobalTcpAddr.type && net_addr_comp(&m_GlobalTcpAddr, &GlobalUdpAddr) != 0) { NewConnectivity = CONNECTIVITY_DIFFERING_UDP_TCP_IP_ADDRESSES; break; diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 1d6e23ee19b..8b4601ac2d9 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -90,6 +90,7 @@ class CClient : public IClient, public CDemoPlayer::IListener CUuid m_ConnectionID = UUID_ZEROED; + bool m_HaveGlobalTcpAddr = false; NETADDR m_GlobalTcpAddr = NETADDR_ZEROED; uint64_t m_aSnapshotParts[NUM_DUMMIES] = {0, 0}; From dc6893ef28e3b1543e36695398895a31ae70f612 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Mon, 4 Dec 2023 02:54:38 +0100 Subject: [PATCH 068/198] Make `dbg_assert` take a boolean --- src/base/system.cpp | 2 +- src/base/system.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/system.cpp b/src/base/system.cpp index 7018e5b5982..b2a904a1c48 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -182,7 +182,7 @@ bool dbg_assert_has_failed() return dbg_assert_failing.load(std::memory_order_acquire); } -void dbg_assert_imp(const char *filename, int line, int test, const char *msg) +void dbg_assert_imp(const char *filename, int line, bool test, const char *msg) { if(!test) { diff --git a/src/base/system.h b/src/base/system.h index 08a9e392c8c..447c8ef7fdc 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -73,7 +73,7 @@ * @see dbg_break */ #define dbg_assert(test, msg) dbg_assert_imp(__FILE__, __LINE__, test, msg) -void dbg_assert_imp(const char *filename, int line, int test, const char *msg); +void dbg_assert_imp(const char *filename, int line, bool test, const char *msg); #ifdef __clang_analyzer__ #include From a9ccc6956bd245223b0ad54f55623c2c69976876 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Mon, 4 Dec 2023 02:38:08 +0100 Subject: [PATCH 069/198] Make error message about invalid UTF-16 more precise Error out on invalid UTF-16 in locale name --- src/base/system.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/base/system.cpp b/src/base/system.cpp index b2a904a1c48..4255afa228d 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -1461,7 +1461,7 @@ std::string windows_format_system_message(unsigned long error) std::optional message = windows_wide_to_utf8(wide_message); LocalFree(wide_message); - return message.value_or("invalid error"); + return message.value_or("(invalid UTF-16 in error message)"); } #endif @@ -4430,7 +4430,8 @@ void os_locale_str(char *locale, size_t length) dbg_assert(GetUserDefaultLocaleName(wide_buffer, std::size(wide_buffer)) > 0, "GetUserDefaultLocaleName failure"); const std::optional buffer = windows_wide_to_utf8(wide_buffer); - str_copy(locale, buffer.value_or("en-US").c_str(), length); + dbg_assert(buffer.has_value(), "GetUserDefaultLocaleName returned invalid UTF-16"); + str_copy(locale, buffer.value().c_str(), length); #elif defined(CONF_PLATFORM_MACOS) CFLocaleRef locale_ref = CFLocaleCopyCurrent(); CFStringRef locale_identifier_ref = static_cast(CFLocaleGetValue(locale_ref, kCFLocaleIdentifier)); From 8f9a44514f86b73a8f02e934d907b30c6e56f4aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 21 Jun 2023 20:50:28 +0200 Subject: [PATCH 070/198] Remove unnecessary `RangeCheck` function --- src/engine/shared/snapshot.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index 762a67c08b6..ad704d923b9 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -352,13 +352,6 @@ int CSnapshotDelta::CreateDelta(const CSnapshot *pFrom, CSnapshot *pTo, void *pD return (int)((char *)pData - (char *)pDstData); } -static int RangeCheck(void *pEnd, void *pPtr, int Size) -{ - if((const char *)pPtr + Size > (const char *)pEnd) - return -1; - return 0; -} - int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize) { CData *pDelta = (CData *)pSrcData; @@ -428,7 +421,7 @@ int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const vo ItemSize = (*pData++) * sizeof(int32_t); } - if(ItemSize < 0 || RangeCheck(pEnd, pData, ItemSize)) + if(ItemSize < 0 || (const char *)pEnd - (const char *)pData < ItemSize) return -205; const int Key = (Type << 16) | ID; From 04728085612850061e32fd6dac757e4ce6ad4403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 12 Nov 2023 12:06:40 +0100 Subject: [PATCH 071/198] Add assertions for `CSnapshotDelta::SetStaticsize` parameters --- src/engine/shared/snapshot.cpp | 10 +++++----- src/engine/shared/snapshot.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index ad704d923b9..da6be8ea8f0 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -4,8 +4,8 @@ #include "compression.h" #include "uuid_manager.h" -#include #include +#include #include #include @@ -256,10 +256,10 @@ CSnapshotDelta::CSnapshotDelta(const CSnapshotDelta &Old) mem_zero(&m_Empty, sizeof(m_Empty)); } -void CSnapshotDelta::SetStaticsize(int ItemType, int Size) +void CSnapshotDelta::SetStaticsize(int ItemType, size_t Size) { - if(ItemType < 0 || ItemType >= MAX_NETOBJSIZES) - return; + dbg_assert(ItemType >= 0 && ItemType < MAX_NETOBJSIZES, "ItemType invalid"); + dbg_assert(Size <= (size_t)std::numeric_limits::max(), "Size invalid"); m_aItemSizes[ItemType] = Size; } @@ -416,7 +416,7 @@ int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const vo { if(pData + 1 > pEnd) return -103; - if(*pData < 0 || (size_t)*pData > INT_MAX / sizeof(int32_t)) + if(*pData < 0 || (size_t)*pData > std::numeric_limits::max() / sizeof(int32_t)) return -204; ItemSize = (*pData++) * sizeof(int32_t); } diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h index 0388e533f9e..c861a9962c8 100644 --- a/src/engine/shared/snapshot.h +++ b/src/engine/shared/snapshot.h @@ -95,7 +95,7 @@ class CSnapshotDelta CSnapshotDelta(const CSnapshotDelta &Old); int GetDataRate(int Index) const { return m_aSnapshotDataRate[Index]; } int GetDataUpdates(int Index) const { return m_aSnapshotDataUpdates[Index]; } - void SetStaticsize(int ItemType, int Size); + void SetStaticsize(int ItemType, size_t Size); const CData *EmptyDelta() const; int CreateDelta(const class CSnapshot *pFrom, class CSnapshot *pTo, void *pDstData); int UnpackDelta(const class CSnapshot *pFrom, class CSnapshot *pTo, const void *pSrcData, int DataSize); From f5ed4159b7a24db3d7142c3d8ce58dfe5bf7c147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 12 Nov 2023 12:12:53 +0100 Subject: [PATCH 072/198] Mark functions and pointers as `const` --- src/engine/shared/snapshot.cpp | 4 ++-- src/engine/shared/snapshot.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index da6be8ea8f0..72993e32c56 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -269,7 +269,7 @@ const CSnapshotDelta::CData *CSnapshotDelta::EmptyDelta() const } // TODO: OPT: this should be made much faster -int CSnapshotDelta::CreateDelta(const CSnapshot *pFrom, CSnapshot *pTo, void *pDstData) +int CSnapshotDelta::CreateDelta(const CSnapshot *pFrom, const CSnapshot *pTo, void *pDstData) { CData *pDelta = (CData *)pDstData; int *pData = (int *)pDelta->m_aData; @@ -612,7 +612,7 @@ int CSnapshotBuilder::Finish(void *pSnapData) return pSnap->TotalSize(); } -int CSnapshotBuilder::GetTypeFromIndex(int Index) +int CSnapshotBuilder::GetTypeFromIndex(int Index) const { return CSnapshot::MAX_TYPE - Index; } diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h index c861a9962c8..6036cc8a3a4 100644 --- a/src/engine/shared/snapshot.h +++ b/src/engine/shared/snapshot.h @@ -97,8 +97,8 @@ class CSnapshotDelta int GetDataUpdates(int Index) const { return m_aSnapshotDataUpdates[Index]; } void SetStaticsize(int ItemType, size_t Size); const CData *EmptyDelta() const; - int CreateDelta(const class CSnapshot *pFrom, class CSnapshot *pTo, void *pDstData); - int UnpackDelta(const class CSnapshot *pFrom, class CSnapshot *pTo, const void *pSrcData, int DataSize); + int CreateDelta(const CSnapshot *pFrom, const CSnapshot *pTo, void *pDstData); + int UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize); }; // CSnapshotStorage @@ -152,7 +152,7 @@ class CSnapshotBuilder void AddExtendedItemType(int Index); int GetExtendedItemTypeIndex(int TypeID); - int GetTypeFromIndex(int Index); + int GetTypeFromIndex(int Index) const; bool m_Sixup; From 0ff2c5b7726b543924da6e446bcc8bc1f0e3208c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 12 Nov 2023 12:15:15 +0100 Subject: [PATCH 073/198] Use `size_t` and add assertions for `CSnapshotStorage::Add` --- src/engine/shared/snapshot.cpp | 15 ++++++--------- src/engine/shared/snapshot.h | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index 72993e32c56..ca7d4900a53 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -504,16 +504,13 @@ void CSnapshotStorage::PurgeUntil(int Tick) m_pLast = nullptr; } -void CSnapshotStorage::Add(int Tick, int64_t Tagtime, int DataSize, const void *pData, int AltDataSize, const void *pAltData) +void CSnapshotStorage::Add(int Tick, int64_t Tagtime, size_t DataSize, const void *pData, size_t AltDataSize, const void *pAltData) { - // allocate memory for holder + snapshot_data - int TotalSize = sizeof(CHolder) + DataSize; - - if(AltDataSize > 0) - { - TotalSize += AltDataSize; - } + dbg_assert(DataSize <= (size_t)CSnapshot::MAX_SIZE, "Snapshot data size invalid"); + dbg_assert(AltDataSize <= (size_t)CSnapshot::MAX_SIZE, "Alt snapshot data size invalid"); + // allocate memory for holder + snapshot data + const size_t TotalSize = sizeof(CHolder) + DataSize + AltDataSize; CHolder *pHolder = (CHolder *)malloc(TotalSize); // set data @@ -523,7 +520,7 @@ void CSnapshotStorage::Add(int Tick, int64_t Tagtime, int DataSize, const void * pHolder->m_pSnap = (CSnapshot *)(pHolder + 1); mem_copy(pHolder->m_pSnap, pData, DataSize); - if(AltDataSize > 0) // create alternative if wanted + if(AltDataSize) // create alternative if wanted { pHolder->m_pAltSnap = (CSnapshot *)(((char *)pHolder->m_pSnap) + DataSize); mem_copy(pHolder->m_pAltSnap, pAltData, AltDataSize); diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h index 6036cc8a3a4..07eaf7b353d 100644 --- a/src/engine/shared/snapshot.h +++ b/src/engine/shared/snapshot.h @@ -130,7 +130,7 @@ class CSnapshotStorage void Init(); void PurgeAll(); void PurgeUntil(int Tick); - void Add(int Tick, int64_t Tagtime, int DataSize, const void *pData, int AltDataSize, const void *pAltData); + void Add(int Tick, int64_t Tagtime, size_t DataSize, const void *pData, size_t AltDataSize, const void *pAltData); int Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData); }; From 0c6978f553a42b3d39fe59d8a90188b630678887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 27 Nov 2023 23:02:49 +0100 Subject: [PATCH 074/198] Support arbitrary number of kernel interfaces, assert on errors Support registering arbitrary number of interfaces with `IKernel` instead at most 32. Assert when incorrect arguments are passed to `IKernel` functions instead of returning a `bool`, which was not being handled in all cases. These functions are not expected to fail expect on programming errors. Consistently order and format creation and registration of kernel interfaces in client and server. --- src/engine/client/client.cpp | 92 +++++++++++++------------------ src/engine/kernel.h | 12 ++--- src/engine/server/main.cpp | 46 ++++++++-------- src/engine/shared/kernel.cpp | 102 +++++++++++++---------------------- 4 files changed, 102 insertions(+), 150 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 3e4e1dfe77a..ade6cc73502 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -2648,7 +2648,6 @@ void CClient::InitInterfaces() m_pEngine = Kernel()->RequestInterface(); m_pEditor = Kernel()->RequestInterface(); m_pFavorites = Kernel()->RequestInterface(); - //m_pGraphics = Kernel()->RequestInterface(); m_pSound = Kernel()->RequestInterface(); m_pGameClient = Kernel()->RequestInterface(); m_pInput = Kernel()->RequestInterface(); @@ -2704,19 +2703,14 @@ void CClient::Run() } // init graphics + m_pGraphics = CreateEngineGraphicsThreaded(); + Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics + Kernel()->RegisterInterface(static_cast(m_pGraphics), false); + if(m_pGraphics->Init() != 0) { - m_pGraphics = CreateEngineGraphicsThreaded(); - - bool RegisterFail = false; - RegisterFail = RegisterFail || !Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics - RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast(m_pGraphics), false); - - if(RegisterFail || m_pGraphics->Init() != 0) - { - dbg_msg("client", "couldn't init graphics"); - ShowMessageBox("Graphics Error", "The graphics could not be initialized."); - return; - } + dbg_msg("client", "couldn't init graphics"); + ShowMessageBox("Graphics Error", "The graphics could not be initialized."); + return; } // make sure the first frame just clears everything to prevent undesired colors when waiting for io @@ -4318,17 +4312,17 @@ int main(int argc, const char **argv) // create the components IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2 * std::thread::hardware_concurrency() + 2); - IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT).release(); + pKernel->RegisterInterface(pEngine, false); + CleanerFunctions.emplace([pEngine]() { + // Engine has to be destroyed before the graphics so that skin download thread can finish + delete pEngine; + }); + IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_CLIENT, argc, (const char **)argv); - IConfigManager *pConfigManager = CreateConfigManager(); - IEngineSound *pEngineSound = CreateEngineSound(); - IEngineInput *pEngineInput = CreateEngineInput(); - IEngineTextRender *pEngineTextRender = CreateEngineTextRender(); - IEngineMap *pEngineMap = CreateEngineMap(); - IDiscord *pDiscord = CreateDiscord(); - ISteam *pSteam = CreateSteam(); + pKernel->RegisterInterface(pStorage); pFutureAssertionLogger->Set(CreateAssertionLogger(pStorage, GAME_NAME)); + #if defined(CONF_EXCEPTION_HANDLING) char aBufPath[IO_MAX_PATH_LENGTH]; char aBufName[IO_MAX_PATH_LENGTH]; @@ -4348,47 +4342,37 @@ int main(int argc, const char **argv) return -1; } - { - bool RegisterFail = false; - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine, false); + IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT).release(); + pKernel->RegisterInterface(pConsole); - CleanerFunctions.emplace([pEngine]() { - // Has to be before destroying graphics so that skin download thread can finish - delete pEngine; - }); + IConfigManager *pConfigManager = CreateConfigManager(); + pKernel->RegisterInterface(pConfigManager); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfigManager); + IEngineSound *pEngineSound = CreateEngineSound(); + pKernel->RegisterInterface(pEngineSound); // IEngineSound + pKernel->RegisterInterface(static_cast(pEngineSound), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineSound); // IEngineSound - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineSound), false); + IEngineInput *pEngineInput = CreateEngineInput(); + pKernel->RegisterInterface(pEngineInput); // IEngineInput + pKernel->RegisterInterface(static_cast(pEngineInput), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineInput); // IEngineInput - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineInput), false); + IEngineTextRender *pEngineTextRender = CreateEngineTextRender(); + pKernel->RegisterInterface(pEngineTextRender); // IEngineTextRender + pKernel->RegisterInterface(static_cast(pEngineTextRender), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineTextRender); // IEngineTextRender - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineTextRender), false); + IEngineMap *pEngineMap = CreateEngineMap(); + pKernel->RegisterInterface(pEngineMap); // IEngineMap + pKernel->RegisterInterface(static_cast(pEngineMap), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMap); // IEngineMap - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap), false); + IDiscord *pDiscord = CreateDiscord(); + pKernel->RegisterInterface(pDiscord); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor(), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateFavorites().release()); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient()); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pDiscord); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pSteam); + ISteam *pSteam = CreateSteam(); + pKernel->RegisterInterface(pSteam); - if(RegisterFail) - { - const char *pError = "Failed to register an interface."; - dbg_msg("client", "%s", pError); - pClient->ShowMessageBox("Kernel Error", pError); - PerformAllCleanup(); - return -1; - } - } + pKernel->RegisterInterface(CreateEditor(), false); + pKernel->RegisterInterface(CreateFavorites().release()); + pKernel->RegisterInterface(CreateGameClient()); pEngine->Init(); pConsole->Init(); diff --git a/src/engine/kernel.h b/src/engine/kernel.h index 03ef8f070f4..95cab9f7122 100644 --- a/src/engine/kernel.h +++ b/src/engine/kernel.h @@ -35,8 +35,8 @@ public: \ class IKernel { // hide the implementation - virtual bool RegisterInterfaceImpl(const char *pInterfaceName, IInterface *pInterface, bool Destroy) = 0; - virtual bool ReregisterInterfaceImpl(const char *pInterfaceName, IInterface *pInterface) = 0; + virtual void RegisterInterfaceImpl(const char *pInterfaceName, IInterface *pInterface, bool Destroy) = 0; + virtual void ReregisterInterfaceImpl(const char *pInterfaceName, IInterface *pInterface) = 0; virtual IInterface *RequestInterfaceImpl(const char *pInterfaceName) = 0; public: @@ -46,14 +46,14 @@ class IKernel // templated access to handle pointer conversions and interface names template - bool RegisterInterface(TINTERFACE *pInterface, bool Destroy = true) + void RegisterInterface(TINTERFACE *pInterface, bool Destroy = true) { - return RegisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface, Destroy); + RegisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface, Destroy); } template - bool ReregisterInterface(TINTERFACE *pInterface) + void ReregisterInterface(TINTERFACE *pInterface) { - return ReregisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface); + ReregisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface); } // Usage example: diff --git a/src/engine/server/main.cpp b/src/engine/server/main.cpp index ea19875316f..a50b0d81a9b 100644 --- a/src/engine/server/main.cpp +++ b/src/engine/server/main.cpp @@ -110,17 +110,17 @@ int main(int argc, const char **argv) pServer->SetLoggers(pFutureFileLogger, std::move(pStdoutLogger)); IKernel *pKernel = IKernel::Create(); + pKernel->RegisterInterface(pServer); // create the components IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2 * std::thread::hardware_concurrency() + 2); - IEngineMap *pEngineMap = CreateEngineMap(); - IGameServer *pGameServer = CreateGameServer(); - IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON).release(); + pKernel->RegisterInterface(pEngine); + IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_SERVER, argc, argv); - IConfigManager *pConfigManager = CreateConfigManager(); - IEngineAntibot *pEngineAntibot = CreateEngineAntibot(); + pKernel->RegisterInterface(pStorage); pFutureAssertionLogger->Set(CreateAssertionLogger(pStorage, GAME_NAME)); + #if defined(CONF_EXCEPTION_HANDLING) char aBuf[IO_MAX_PATH_LENGTH]; char aBufName[IO_MAX_PATH_LENGTH]; @@ -131,26 +131,22 @@ int main(int argc, const char **argv) set_exception_handler_log_file(aBuf); #endif - { - bool RegisterFail = false; - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pServer); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMap); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pGameServer); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfigManager); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineAntibot); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineAntibot), false); - - if(RegisterFail) - { - delete pKernel; - return -1; - } - } + IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON).release(); + pKernel->RegisterInterface(pConsole); + + IConfigManager *pConfigManager = CreateConfigManager(); + pKernel->RegisterInterface(pConfigManager); + + IEngineMap *pEngineMap = CreateEngineMap(); + pKernel->RegisterInterface(pEngineMap); // IEngineMap + pKernel->RegisterInterface(static_cast(pEngineMap), false); + + IEngineAntibot *pEngineAntibot = CreateEngineAntibot(); + pKernel->RegisterInterface(pEngineAntibot); // IEngineAntibot + pKernel->RegisterInterface(static_cast(pEngineAntibot), false); + + IGameServer *pGameServer = CreateGameServer(); + pKernel->RegisterInterface(pGameServer); pEngine->Init(); pConsole->Init(); diff --git a/src/engine/shared/kernel.cpp b/src/engine/shared/kernel.cpp index f578cb68505..bdeb46ee3a0 100644 --- a/src/engine/shared/kernel.cpp +++ b/src/engine/shared/kernel.cpp @@ -1,23 +1,29 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ + #include + #include +#include + class CKernel : public IKernel { - enum - { - MAX_INTERFACES = 32, - }; - class CInterfaceInfo { public: - CInterfaceInfo() + CInterfaceInfo() : + m_pInterface(nullptr), + m_AutoDestroy(false) + { + m_aName[0] = '\0'; + } + + CInterfaceInfo(const char *pName, IInterface *pInterface, bool AutoDestroy) : + m_pInterface(pInterface), + m_AutoDestroy(AutoDestroy) { - m_aName[0] = 0; - m_pInterface = 0x0; - m_AutoDestroy = false; + str_copy(m_aName, pName); } char m_aName[64]; @@ -25,98 +31,64 @@ class CKernel : public IKernel bool m_AutoDestroy; }; - CInterfaceInfo m_aInterfaces[MAX_INTERFACES]; - int m_NumInterfaces; + std::vector m_vInterfaces; CInterfaceInfo *FindInterfaceInfo(const char *pName) { - for(int i = 0; i < m_NumInterfaces; i++) + for(CInterfaceInfo &Info : m_vInterfaces) { - if(str_comp(pName, m_aInterfaces[i].m_aName) == 0) - return &m_aInterfaces[i]; + if(str_comp(pName, Info.m_aName) == 0) + return &Info; } - return 0x0; + return nullptr; } public: - CKernel() - { - m_NumInterfaces = 0; - } + CKernel() = default; void Shutdown() override { - for(int i = m_NumInterfaces - 1; i >= 0; i--) + for(int i = (int)m_vInterfaces.size() - 1; i >= 0; --i) { - if(m_aInterfaces[i].m_AutoDestroy) - m_aInterfaces[i].m_pInterface->Shutdown(); + if(m_vInterfaces[i].m_AutoDestroy) + { + m_vInterfaces[i].m_pInterface->Shutdown(); + } } } ~CKernel() override { // delete interfaces in reverse order just the way it would happen to objects on the stack - for(int i = m_NumInterfaces - 1; i >= 0; i--) + for(int i = (int)m_vInterfaces.size() - 1; i >= 0; --i) { - if(m_aInterfaces[i].m_AutoDestroy) + if(m_vInterfaces[i].m_AutoDestroy) { - delete m_aInterfaces[i].m_pInterface; - m_aInterfaces[i].m_pInterface = 0; + delete m_vInterfaces[i].m_pInterface; + m_vInterfaces[i].m_pInterface = nullptr; } } } - bool RegisterInterfaceImpl(const char *pName, IInterface *pInterface, bool Destroy) override + void RegisterInterfaceImpl(const char *pName, IInterface *pInterface, bool Destroy) override { - // TODO: More error checks here - if(!pInterface) - { - dbg_msg("kernel", "ERROR: couldn't register interface %s. null pointer given", pName); - return false; - } - - if(m_NumInterfaces == MAX_INTERFACES) - { - dbg_msg("kernel", "ERROR: couldn't register interface '%s'. maximum of interfaces reached", pName); - return false; - } - - if(FindInterfaceInfo(pName) != 0) - { - dbg_msg("kernel", "ERROR: couldn't register interface '%s'. interface already exists", pName); - return false; - } + dbg_assert(str_length(pName) < (int)sizeof(CInterfaceInfo().m_aName), "Interface name too long"); + dbg_assert(FindInterfaceInfo(pName) == nullptr, "Duplicate interface name"); pInterface->m_pKernel = this; - m_aInterfaces[m_NumInterfaces].m_pInterface = pInterface; - str_copy(m_aInterfaces[m_NumInterfaces].m_aName, pName); - m_aInterfaces[m_NumInterfaces].m_AutoDestroy = Destroy; - m_NumInterfaces++; - - return true; + m_vInterfaces.emplace_back(pName, pInterface, Destroy); } - bool ReregisterInterfaceImpl(const char *pName, IInterface *pInterface) override + void ReregisterInterfaceImpl(const char *pName, IInterface *pInterface) override { - if(FindInterfaceInfo(pName) == 0) - { - dbg_msg("kernel", "ERROR: couldn't reregister interface '%s'. interface doesn't exist", pName); - return false; - } - + dbg_assert(FindInterfaceInfo(pName) != nullptr, "Cannot reregister interface that is not registered"); pInterface->m_pKernel = this; - - return true; } IInterface *RequestInterfaceImpl(const char *pName) override { CInterfaceInfo *pInfo = FindInterfaceInfo(pName); - if(!pInfo) - { - dbg_msg("kernel", "failed to find interface with the name '%s'", pName); - return 0; - } + dbg_assert(pInfo != nullptr, "Interface not found"); return pInfo->m_pInterface; } }; From d8ec6a0a3518e8524325929872864b8b14706c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 4 Dec 2023 21:59:34 +0100 Subject: [PATCH 075/198] Fix heap-use-after-free when popup opens another popup The popup menu render function can open/close other popups, which may resize the vector of popup menus and thus invalidate the current popup menu variable before the popup is closed. We therefore store the popup UI element ID in a separate variable to avoid the access to the potentially invalidated popup menu variable after the popup menu is rendered. Also prevent invalidated popup menu from being rendered for one frame after it is closed by clicking outside. Closes #7565. --- src/game/client/ui.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index a02b4d238e6..ae47c3b674c 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -1368,27 +1368,30 @@ void CUI::RenderPopupMenus() for(size_t i = 0; i < m_vPopupMenus.size(); ++i) { const SPopupMenu &PopupMenu = m_vPopupMenus[i]; + const SPopupMenuId *pID = PopupMenu.m_pID; const bool Inside = MouseInside(&PopupMenu.m_Rect); const bool Active = i == m_vPopupMenus.size() - 1; if(Active) - SetHotItem(PopupMenu.m_pID); + SetHotItem(pID); - if(CheckActiveItem(PopupMenu.m_pID)) + if(CheckActiveItem(pID)) { if(!MouseButton(0)) { if(!Inside) { - ClosePopupMenu(PopupMenu.m_pID); + ClosePopupMenu(pID); + --i; + continue; } SetActiveItem(nullptr); } } - else if(HotItem() == PopupMenu.m_pID) + else if(HotItem() == pID) { if(MouseButton(0)) - SetActiveItem(PopupMenu.m_pID); + SetActiveItem(pID); } CUIRect PopupRect = PopupMenu.m_Rect; @@ -1397,9 +1400,11 @@ void CUI::RenderPopupMenus() PopupRect.Draw(PopupMenu.m_Props.m_BackgroundColor, PopupMenu.m_Props.m_Corners, 3.0f); PopupRect.Margin(SPopupMenu::POPUP_MARGIN, &PopupRect); + // The popup render function can open/close popups, which may resize the vector and thus + // invalidate the variable PopupMenu. We therefore store pID in a separate variable. EPopupMenuFunctionResult Result = PopupMenu.m_pfnFunc(PopupMenu.m_pContext, PopupRect, Active); if(Result != POPUP_KEEP_OPEN || (Active && ConsumeHotkey(HOTKEY_ESCAPE))) - ClosePopupMenu(PopupMenu.m_pID, Result == POPUP_CLOSE_CURRENT_AND_DESCENDANTS); + ClosePopupMenu(pID, Result == POPUP_CLOSE_CURRENT_AND_DESCENDANTS); } } From 7e8c2aa4be1cd1a77cf69116913b00168dec15da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 4 Dec 2023 22:21:53 +0100 Subject: [PATCH 076/198] Fix incorrect sound index usage for editor sound preview The invalid sound index is `-1` but the check in the editor for the sound preview assumed it was `0`. We should use a type-safe wrapper to avoid this in the future, like for texture handles. --- src/game/editor/editor.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 073d693e2c8..a87c6532db4 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -907,7 +907,7 @@ void CEditor::DoAudioPreview(CUIRect View, const void *pPlayPauseButtonID, const } else { - if(SampleID != m_ToolbarPreviewSound && m_ToolbarPreviewSound && Sound()->IsPlaying(m_ToolbarPreviewSound)) + if(SampleID != m_ToolbarPreviewSound && m_ToolbarPreviewSound >= 0 && Sound()->IsPlaying(m_ToolbarPreviewSound)) Sound()->Pause(m_ToolbarPreviewSound); Sound()->Play(CSounds::CHN_GUI, SampleID, ISound::FLAG_PREVIEW); @@ -1351,7 +1351,7 @@ void CEditor::DoToolbarSounds(CUIRect ToolBar) if(m_SelectedSound >= 0 && (size_t)m_SelectedSound < m_Map.m_vpSounds.size()) { const std::shared_ptr pSelectedSound = m_Map.m_vpSounds[m_SelectedSound]; - if(pSelectedSound->m_SoundID != m_ToolbarPreviewSound && m_ToolbarPreviewSound && Sound()->IsPlaying(m_ToolbarPreviewSound)) + if(pSelectedSound->m_SoundID != m_ToolbarPreviewSound && m_ToolbarPreviewSound >= 0 && Sound()->IsPlaying(m_ToolbarPreviewSound)) Sound()->Stop(m_ToolbarPreviewSound); m_ToolbarPreviewSound = pSelectedSound->m_SoundID; @@ -7966,15 +7966,15 @@ void CEditor::OnWindowResize() void CEditor::OnClose() { - if(m_ToolbarPreviewSound && Sound()->IsPlaying(m_ToolbarPreviewSound)) + if(m_ToolbarPreviewSound >= 0 && Sound()->IsPlaying(m_ToolbarPreviewSound)) Sound()->Pause(m_ToolbarPreviewSound); - if(m_FilePreviewSound && Sound()->IsPlaying(m_FilePreviewSound)) + if(m_FilePreviewSound >= 0 && Sound()->IsPlaying(m_FilePreviewSound)) Sound()->Pause(m_FilePreviewSound); } void CEditor::OnDialogClose() { - if(m_FilePreviewSound) + if(m_FilePreviewSound >= 0) { Sound()->UnloadSample(m_FilePreviewSound); m_FilePreviewSound = -1; From 96e4c5f7cd2da8df2efae3b3d16a8b44a52b1c29 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Mon, 2 Oct 2023 15:14:38 +0200 Subject: [PATCH 077/198] Add editor undo/redo feature --- CMakeLists.txt | 9 + src/engine/shared/config_variables.h | 1 + src/engine/textrender.h | 2 + src/game/client/ui.cpp | 122 +- src/game/client/ui.h | 20 +- src/game/editor/auto_map.cpp | 10 +- src/game/editor/editor.cpp | 907 +++++---- src/game/editor/editor.h | 33 +- src/game/editor/editor_action.h | 30 + src/game/editor/editor_actions.cpp | 2065 ++++++++++++++++++++ src/game/editor/editor_actions.h | 648 ++++++ src/game/editor/editor_history.cpp | 65 + src/game/editor/editor_history.h | 37 + src/game/editor/editor_props.cpp | 282 +++ src/game/editor/editor_trackers.cpp | 632 ++++++ src/game/editor/editor_trackers.h | 271 +++ src/game/editor/mapitems.h | 125 ++ src/game/editor/mapitems/envelope.h | 1 + src/game/editor/mapitems/layer.h | 1 + src/game/editor/mapitems/layer_front.cpp | 5 + src/game/editor/mapitems/layer_front.h | 1 + src/game/editor/mapitems/layer_game.cpp | 5 + src/game/editor/mapitems/layer_game.h | 1 + src/game/editor/mapitems/layer_quads.cpp | 28 +- src/game/editor/mapitems/layer_quads.h | 1 + src/game/editor/mapitems/layer_sounds.cpp | 29 +- src/game/editor/mapitems/layer_sounds.h | 1 + src/game/editor/mapitems/layer_speedup.cpp | 111 +- src/game/editor/mapitems/layer_speedup.h | 27 + src/game/editor/mapitems/layer_switch.cpp | 97 +- src/game/editor/mapitems/layer_switch.h | 27 + src/game/editor/mapitems/layer_tele.cpp | 88 +- src/game/editor/mapitems/layer_tele.h | 27 + src/game/editor/mapitems/layer_tiles.cpp | 311 ++- src/game/editor/mapitems/layer_tiles.h | 25 +- src/game/editor/mapitems/layer_tune.cpp | 75 +- src/game/editor/mapitems/layer_tune.h | 25 + src/game/editor/popups.cpp | 481 ++--- src/game/editor/tileart.cpp | 21 +- src/game/mapitems.cpp | 10 + src/game/mapitems.h | 1 + 41 files changed, 5870 insertions(+), 788 deletions(-) create mode 100644 src/game/editor/editor_action.h create mode 100644 src/game/editor/editor_actions.cpp create mode 100644 src/game/editor/editor_actions.h create mode 100644 src/game/editor/editor_history.cpp create mode 100644 src/game/editor/editor_history.h create mode 100644 src/game/editor/editor_props.cpp create mode 100644 src/game/editor/editor_trackers.cpp create mode 100644 src/game/editor/editor_trackers.h create mode 100644 src/game/editor/mapitems.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8ae171148..72954eeddea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2285,13 +2285,22 @@ if(CLIENT) component.h editor.cpp editor.h + editor_action.h + editor_actions.cpp + editor_actions.h + editor_history.cpp + editor_history.h editor_object.cpp editor_object.h + editor_props.cpp + editor_trackers.cpp + editor_trackers.h explanations.cpp map_grid.cpp map_grid.h map_view.cpp map_view.h + mapitems.h mapitems/envelope.cpp mapitems/envelope.h mapitems/image.cpp diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 536ed952dfc..e016280f5a2 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -248,6 +248,7 @@ MACRO_CONFIG_INT(ClRefreshRateInactive, cl_refresh_rate_inactive, 120, 0, 10000, MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "Open the map editor") MACRO_CONFIG_INT(ClEditorDilate, cl_editor_dilate, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically dilates embedded images") MACRO_CONFIG_STR(ClSkinFilterString, cl_skin_filter_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Skin filtering string") +MACRO_CONFIG_INT(ClEditorMaxHistory, cl_editor_max_history, 50, 1, 500, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Maximum number of undo actions in the editor history (not shared between editor, envelope editor and server settings editor)") MACRO_CONFIG_INT(ClAutoDemoRecord, cl_auto_demo_record, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically record demos") MACRO_CONFIG_INT(ClAutoDemoOnConnect, cl_auto_demo_on_connect, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Only start a new demo when connect while automatically record demos") diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 9862d7a88b6..85fa991f01a 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -140,6 +140,8 @@ MAYBE_UNUSED static const char *FONT_ICON_DICE_FIVE = "\xEF\x94\xA3"; MAYBE_UNUSED static const char *FONT_ICON_DICE_SIX = "\xEF\x94\xA6"; MAYBE_UNUSED static const char *FONT_ICON_LAYER_GROUP = "\xEF\x97\xBD"; +MAYBE_UNUSED static const char *FONT_ICON_UNDO = "\xEF\x8B\xAA"; +MAYBE_UNUSED static const char *FONT_ICON_REDO = "\xEF\x8B\xB9"; } // end namespace FontIcons enum ETextCursorSelectionMode diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index a02b4d238e6..4a47fe80b35 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -490,19 +490,37 @@ int CUI::DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRe return ReturnValue; } -int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY) +EEditState CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY) { + static const void *s_pEditing = nullptr; + if(MouseHovered(pRect)) SetHotItem(pID); + EEditState Res = EEditState::EDITING; + if(HotItem() == pID && MouseButtonClicked(0)) + { SetActiveItem(pID); + if(!s_pEditing) + { + s_pEditing = pID; + Res = EEditState::START; + } + } if(CheckActiveItem(pID) && !MouseButton(0)) + { SetActiveItem(nullptr); + if(s_pEditing == pID) + { + s_pEditing = nullptr; + Res = EEditState::END; + } + } - if(!CheckActiveItem(pID)) - return 0; + if(!CheckActiveItem(pID) && Res == EEditState::EDITING) + return EEditState::NONE; if(Input()->ShiftIsPressed()) m_MouseSlow = true; @@ -512,7 +530,7 @@ int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float * if(pY) *pY = clamp(m_MouseY - pRect->y, 0.0f, pRect->h); - return 1; + return Res; } void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed) @@ -981,12 +999,19 @@ int CUI::DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pTex } int64_t CUI::DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props) +{ + return DoValueSelectorWithState(pID, pRect, pLabel, Current, Min, Max, Props).m_Value; +} + +SEditResult CUI::DoValueSelectorWithState(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props) { // logic static float s_Value; static CLineInputNumber s_NumberInput; static const void *s_pLastTextID = pID; const bool Inside = MouseInside(pRect); + static const void *s_pEditing = nullptr; + EEditState State = EEditState::NONE; if(Inside) SetHotItem(pID); @@ -1091,7 +1116,23 @@ int64_t CUI::DoValueSelector(const void *pID, const CUIRect *pRect, const char * if(!m_ValueSelectorTextMode) s_NumberInput.Clear(); - return Current; + if(s_pEditing == pID) + State = EEditState::EDITING; + + bool MouseLocked = CheckMouseLock(); + if((MouseLocked || m_ValueSelectorTextMode) && !s_pEditing) + { + State = EEditState::START; + s_pEditing = pID; + } + + if(!CheckMouseLock() && !m_ValueSelectorTextMode && s_pEditing == pID) + { + State = EEditState::END; + s_pEditing = nullptr; + } + + return SEditResult{State, Current}; } float CUI::DoScrollbarV(const void *pID, const CUIRect *pRect, float Current) @@ -1686,6 +1727,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View { SColorPickerPopupContext *pColorPicker = static_cast(pContext); CUI *pUI = pColorPicker->m_pUI; + pColorPicker->m_State = EEditState::NONE; CUIRect ColorsArea, HueArea, BottomArea, ModeButtonArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect; @@ -1759,10 +1801,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View HuePartialArea.Draw4(TL, TL, BL, BL, IGraphics::CORNER_NONE, 0.0f); } - const auto &&RenderAlphaSelector = [&](unsigned OldA) -> unsigned { + const auto &&RenderAlphaSelector = [&](unsigned OldA) -> SEditResult { if(pColorPicker->m_Alpha) { - return pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", OldA, 0, 255); + return pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", OldA, 0, 255); } else { @@ -1770,7 +1812,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View str_format(aBuf, sizeof(aBuf), "A: %d", OldA); pUI->DoLabel(&AlphaRect, aBuf, 10.0f, TEXTALIGN_MC); AlphaRect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.65f), IGraphics::CORNER_ALL, 3.0f); - return OldA; + return {EEditState::NONE, OldA}; } }; @@ -1782,10 +1824,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View const unsigned OldV = (unsigned)(PickerColorHSV.v * 255.0f); const unsigned OldA = (unsigned)(PickerColorHSV.a * 255.0f); - const unsigned H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); - const unsigned S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); - const unsigned V = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "V:", OldV, 0, 255); - const unsigned A = RenderAlphaSelector(OldA); + const auto [StateH, H] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); + const auto [StateS, S] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); + const auto [StateV, V] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "V:", OldV, 0, 255); + const auto [StateA, A] = RenderAlphaSelector(OldA); if(OldH != H || OldS != S || OldV != V || OldA != A) { @@ -1793,6 +1835,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View PickerColorHSL = color_cast(PickerColorHSV); PickerColorRGB = color_cast(PickerColorHSL); } + + for(auto State : {StateH, StateS, StateV, StateA}) + { + if(State != EEditState::NONE) + { + pColorPicker->m_State = State; + break; + } + } } else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_RGBA) { @@ -1801,10 +1852,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View const unsigned OldB = (unsigned)(PickerColorRGB.b * 255.0f); const unsigned OldA = (unsigned)(PickerColorRGB.a * 255.0f); - const unsigned R = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "R:", OldR, 0, 255); - const unsigned G = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "G:", OldG, 0, 255); - const unsigned B = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "B:", OldB, 0, 255); - const unsigned A = RenderAlphaSelector(OldA); + const auto [StateR, R] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "R:", OldR, 0, 255); + const auto [StateG, G] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "G:", OldG, 0, 255); + const auto [StateB, B] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "B:", OldB, 0, 255); + const auto [StateA, A] = RenderAlphaSelector(OldA); if(OldR != R || OldG != G || OldB != B || OldA != A) { @@ -1812,6 +1863,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View PickerColorHSL = color_cast(PickerColorRGB); PickerColorHSV = color_cast(PickerColorHSL); } + + for(auto State : {StateR, StateG, StateB, StateA}) + { + if(State != EEditState::NONE) + { + pColorPicker->m_State = State; + break; + } + } } else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_HSLA) { @@ -1820,10 +1880,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View const unsigned OldL = (unsigned)(PickerColorHSL.l * 255.0f); const unsigned OldA = (unsigned)(PickerColorHSL.a * 255.0f); - const unsigned H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); - const unsigned S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); - const unsigned L = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "L:", OldL, 0, 255); - const unsigned A = RenderAlphaSelector(OldA); + const auto [StateH, H] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); + const auto [StateS, S] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); + const auto [StateL, L] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "L:", OldL, 0, 255); + const auto [StateA, A] = RenderAlphaSelector(OldA); if(OldH != H || OldS != S || OldL != L || OldA != A) { @@ -1831,6 +1891,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View PickerColorHSV = color_cast(PickerColorHSL); PickerColorRGB = color_cast(PickerColorHSL); } + + for(auto State : {StateH, StateS, StateL, StateA}) + { + if(State != EEditState::NONE) + { + pColorPicker->m_State = State; + break; + } + } } else { @@ -1842,7 +1911,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View Props.m_IsHex = true; Props.m_HexPrefix = pColorPicker->m_Alpha ? 8 : 6; const unsigned OldHex = PickerColorRGB.PackAlphaLast(pColorPicker->m_Alpha); - const unsigned Hex = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", OldHex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props); + auto [HexState, Hex] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", OldHex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props); if(OldHex != Hex) { const float OldAlpha = PickerColorRGB.a; @@ -1853,21 +1922,28 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View PickerColorHSV = color_cast(PickerColorHSL); } + if(HexState != EEditState::NONE) + pColorPicker->m_State = HexState; + // Logic float PickerX, PickerY; - if(pUI->DoPickerLogic(&pColorPicker->m_ColorPickerId, &ColorsArea, &PickerX, &PickerY)) + EEditState ColorPickerRes = pUI->DoPickerLogic(&pColorPicker->m_ColorPickerId, &ColorsArea, &PickerX, &PickerY); + if(ColorPickerRes != EEditState::NONE) { PickerColorHSV.y = PickerX / ColorsArea.w; PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h; PickerColorHSL = color_cast(PickerColorHSV); PickerColorRGB = color_cast(PickerColorHSL); + pColorPicker->m_State = ColorPickerRes; } - if(pUI->DoPickerLogic(&pColorPicker->m_HuePickerId, &HueArea, &PickerX, &PickerY)) + EEditState HuePickerRes = pUI->DoPickerLogic(&pColorPicker->m_HuePickerId, &HueArea, &PickerX, &PickerY); + if(HuePickerRes != EEditState::NONE) { PickerColorHSV.x = 1.0f - PickerY / HueArea.h; PickerColorHSL = color_cast(PickerColorHSV); PickerColorRGB = color_cast(PickerColorHSL); + pColorPicker->m_State = HuePickerRes; } // Marker Color Area diff --git a/src/game/client/ui.h b/src/game/client/ui.h index 80a7e26dfe6..324d4cff524 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -18,6 +18,22 @@ class IClient; class IGraphics; class IKernel; +enum class EEditState +{ + NONE, + START, + EDITING, + END, + ONE_GO +}; + +template +struct SEditResult +{ + EEditState m_State; + T m_Value; +}; + struct SUIAnimator { bool m_Active; @@ -488,7 +504,7 @@ class CUI int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect); int DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted); - int DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY); + EEditState DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY); void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp = false, float ScrollSpeed = 10.0f); static vec2 CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, const float *pBiggestCharHeight = nullptr); @@ -505,6 +521,7 @@ class CUI int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding = 0.0f, bool TransparentInactive = false, bool Enabled = true); // value selector + SEditResult DoValueSelectorWithState(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props = {}); int64_t DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props = {}); bool IsValueSelectorTextMode() const { return m_ValueSelectorTextMode; } void SetValueSelectorTextMode(bool TextMode) { m_ValueSelectorTextMode = TextMode; } @@ -620,6 +637,7 @@ class CUI const char m_ColorPickerId = 0; const char m_aValueSelectorIds[5] = {0}; CButtonContainer m_aModeButtons[(int)MODE_HSLA + 1]; + EEditState m_State; }; void ShowPopupColorPicker(float X, float Y, SColorPickerPopupContext *pContext); diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp index f8cf86ed0c9..4f58f8e0fda 100644 --- a/src/game/editor/auto_map.cpp +++ b/src/game/editor/auto_map.cpp @@ -9,6 +9,7 @@ #include "auto_map.h" #include "editor.h" // TODO: only needs CLayerTiles +#include "editor_actions.h" // Based on triple32inc from https://github.com/skeeto/hash-prospector/tree/79a6074062a84907df6e45b756134b74e2956760 static uint32_t HashUInt32(uint32_t Num) @@ -173,9 +174,9 @@ void CAutoMapper::Load(const char *pTileName) { Value = CPosRule::NOTINDEX; CIndexInfo NewIndexInfo1 = {0, 0, false}; - //CIndexInfo NewIndexInfo2 = {-1, 0}; + // CIndexInfo NewIndexInfo2 = {-1, 0}; vNewIndexList.push_back(NewIndexInfo1); - //vNewIndexList.push_back(NewIndexInfo2); + // vNewIndexList.push_back(NewIndexInfo2); } else if(!str_comp(aValue, "INDEX") || !str_comp(aValue, "NOTINDEX")) { @@ -425,8 +426,10 @@ void CAutoMapper::ProceedLocalized(CLayerTiles *pLayer, int ConfigID, int Seed, { CTile *pIn = &pUpdateLayer->m_pTiles[(y - UpdateFromY) * pUpdateLayer->m_Width + x - UpdateFromX]; CTile *pOut = &pLayer->m_pTiles[y * pLayer->m_Width + x]; + CTile Previous = *pOut; pOut->m_Index = pIn->m_Index; pOut->m_Flags = pIn->m_Flags; + pLayer->RecordStateChange(x, y, Previous, *pOut); } } @@ -442,6 +445,7 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedO Seed = rand(); CConfiguration *pConf = &m_vConfigs[ConfigID]; + pLayer->ClearHistory(); // for every run: copy tiles, automap, overwrite tiles for(size_t h = 0; h < pConf->m_vRuns.size(); ++h) @@ -534,8 +538,10 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedO if(RespectRules && (pIndexRule->m_RandomProbability >= 1.0f || HashLocation(Seed, h, i, x + SeedOffsetX, y + SeedOffsetY) < HASH_MAX * pIndexRule->m_RandomProbability)) { + CTile Previous = *pTile; pTile->m_Index = pIndexRule->m_ID; pTile->m_Flags = pIndexRule->m_Flag; + pLayer->RecordStateChange(x, y, Previous, *pTile); } } } diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 073d693e2c8..5d002e85f88 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -32,11 +32,13 @@ #include #include +#include #include #include #include "auto_map.h" #include "editor.h" +#include "editor_actions.h" #include #include @@ -328,7 +330,7 @@ void CEditor::RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture, Graphics()->QuadsEnd(); } -int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree, bool IsHex, int Corners, ColorRGBA *pColor, bool ShowValue) +SEditResult CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree, bool IsHex, int Corners, ColorRGBA *pColor, bool ShowValue) { // logic static float s_Value; @@ -337,6 +339,8 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in static void *s_pLastTextID = pID; const bool Inside = UI()->MouseInside(pRect); const int Base = IsHex ? 16 : 10; + static bool s_Editing = false; + EEditState State = EEditState::EDITING; if(UI()->MouseButton(1) && UI()->HotItem() == pID) { @@ -445,7 +449,20 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in if(!s_TextMode) s_NumberInput.Clear(); - return Current; + bool MouseLocked = UI()->CheckMouseLock(); + if((MouseLocked || s_TextMode) && !s_Editing) + { + State = EEditState::START; + s_Editing = true; + } + + if(!MouseLocked && !s_TextMode && s_Editing) + { + State = EEditState::END; + s_Editing = false; + } + + return SEditResult{State, Current}; } std::shared_ptr CEditor::GetSelectedGroup() const @@ -576,8 +593,14 @@ void CEditor::DeleteSelectedQuads() if(!pLayer) return; + std::vector vSelectedQuads(m_vSelectedQuads); + std::vector vDeletedQuads; + for(int i = 0; i < (int)m_vSelectedQuads.size(); ++i) { + auto const &Quad = pLayer->m_vQuads[m_vSelectedQuads[i]]; + vDeletedQuads.push_back(Quad); + pLayer->m_vQuads.erase(pLayer->m_vQuads.begin() + m_vSelectedQuads[i]); for(int j = i + 1; j < (int)m_vSelectedQuads.size(); ++j) if(m_vSelectedQuads[j] > m_vSelectedQuads[i]) @@ -586,6 +609,8 @@ void CEditor::DeleteSelectedQuads() m_vSelectedQuads.erase(m_vSelectedQuads.begin() + i); i--; } + + m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, m_vSelectedLayers[0], vSelectedQuads, vDeletedQuads)); } bool CEditor::IsQuadSelected(int Index) const @@ -714,6 +739,39 @@ bool CEditor::IsTangentSelected() const return IsTangentInSelected() || IsTangentOutSelected(); } +std::pair CEditor::EnvGetSelectedTimeAndValue() const +{ + if(m_SelectedEnvelope < 0 || m_SelectedEnvelope >= (int)m_Map.m_vpEnvelopes.size()) + return {}; + + std::shared_ptr pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope]; + int CurrentTime; + int CurrentValue; + if(IsTangentInSelected()) + { + auto [SelectedIndex, SelectedChannel] = m_SelectedTangentInPoint; + + CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]; + CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel]; + } + else if(IsTangentOutSelected()) + { + auto [SelectedIndex, SelectedChannel] = m_SelectedTangentOutPoint; + + CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]; + CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel]; + } + else + { + auto [SelectedIndex, SelectedChannel] = m_vSelectedEnvelopePoints.front(); + + CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time; + CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel]; + } + + return std::pair{CurrentTime, CurrentValue}; +} + bool CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUser) { CEditor *pEditor = (CEditor *)pUser; @@ -1101,6 +1159,23 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); + // undo/redo group + TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); + static int s_UndoButton = 0; + if(DoButton_FontIcon(&s_UndoButton, FONT_ICON_UNDO, m_EditorHistory.CanUndo() - 1, &Button, 0, "[ctrl+z] Undo last action", IGraphics::CORNER_L)) + { + m_EditorHistory.Undo(); + } + + TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); + static int s_RedoButton = 0; + if(DoButton_FontIcon(&s_RedoButton, FONT_ICON_REDO, m_EditorHistory.CanRedo() - 1, &Button, 0, "[ctrl+y] Redo last action", IGraphics::CORNER_R)) + { + m_EditorHistory.Redo(); + } + + TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); + // brush manipulation { int Enabled = m_pBrush->IsEmpty() ? -1 : 0; @@ -1144,7 +1219,8 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) } TB_Top.VSplitLeft(30.0f, &Button, &TB_Top); - s_RotationAmount = UiDoValueSelector(&s_RotationAmount, &Button, "", s_RotationAmount, TileLayer ? 90 : 1, 359, TileLayer ? 90 : 1, TileLayer ? 10.0f : 2.0f, "Rotation of the brush in degrees. Use left mouse button to drag and change the value. Hold shift to be more precise.", true, false, IGraphics::CORNER_NONE); + auto RotationAmountRes = UiDoValueSelector(&s_RotationAmount, &Button, "", s_RotationAmount, TileLayer ? 90 : 1, 359, TileLayer ? 90 : 1, TileLayer ? 10.0f : 2.0f, "Rotation of the brush in degrees. Use left mouse button to drag and change the value. Hold shift to be more precise.", true, false, IGraphics::CORNER_NONE); + s_RotationAmount = RotationAmountRes.m_Value; TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); static int s_CwButton = 0; @@ -1309,24 +1385,9 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) } if(pLayer->m_Type == LAYERTYPE_QUADS) - { - std::shared_ptr pLayerQuads = std::static_pointer_cast(pLayer); - - int Width = 64; - int Height = 64; - if(pLayerQuads->m_Image >= 0) - { - Width = m_Map.m_vpImages[pLayerQuads->m_Image]->m_Width; - Height = m_Map.m_vpImages[pLayerQuads->m_Image]->m_Height; - } - - pLayerQuads->NewQuad(x, y, Width, Height); - } + m_EditorHistory.Execute(std::make_shared(this, m_SelectedGroup, m_vSelectedLayers[0], x, y)); else if(pLayer->m_Type == LAYERTYPE_SOUNDS) - { - std::shared_ptr pLayerSounds = std::static_pointer_cast(pLayer); - pLayerSounds->NewSource(x, y); - } + m_EditorHistory.Execute(std::make_shared(this, m_SelectedGroup, m_vSelectedLayers[0], x, y)); } TB_Bottom.VSplitLeft(5.0f, &Button, &TB_Bottom); } @@ -1468,7 +1529,7 @@ void CEditor::DoSoundSource(CSoundSource *pSource, int Index) Graphics()->QuadsDraw(&QuadItem, 1); } -void CEditor::DoQuad(CQuad *pQuad, int Index) +void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, int Index) { enum { @@ -1529,6 +1590,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) // check if we only should move pivot if(s_Operation == OP_MOVE_PIVOT) { + m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); float x = wx; float y = wy; if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid) @@ -1537,7 +1599,6 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) int OffsetX = f2fx(x) - pQuad->m_aPoints[4].x; int OffsetY = f2fx(y) - pQuad->m_aPoints[4].y; - std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); for(auto &Selected : m_vSelectedQuads) { CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected]; @@ -1547,6 +1608,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) } else if(s_Operation == OP_MOVE_ALL) { + m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); // move all points including pivot float x = wx; float y = wy; @@ -1556,7 +1618,6 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) int OffsetX = f2fx(x) - pQuad->m_aPoints[4].x; int OffsetY = f2fx(y) - pQuad->m_aPoints[4].y; - std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); for(auto &Selected : m_vSelectedQuads) { CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected]; @@ -1569,7 +1630,8 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) } else if(s_Operation == OP_ROTATE) { - std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); + m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); + for(size_t i = 0; i < m_vSelectedQuads.size(); ++i) { CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]]; @@ -1624,6 +1686,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) UI()->DisableMouseLock(); s_Operation = OP_NONE; UI()->SetActiveItem(nullptr); + m_QuadTracker.EndQuadTrack(); } else if(UI()->MouseButton(1)) { @@ -1632,7 +1695,6 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) UI()->SetActiveItem(nullptr); // Reset points to old position - std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); for(size_t i = 0; i < m_vSelectedQuads.size(); ++i) { CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]]; @@ -1652,6 +1714,10 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) else SelectQuad(Index); } + else if(s_Operation == OP_MOVE_PIVOT || s_Operation == OP_MOVE_ALL) + { + m_QuadTracker.EndQuadTrack(); + } UI()->DisableMouseLock(); s_Operation = OP_NONE; @@ -1668,7 +1734,6 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) s_Operation = OP_ROTATE; s_RotateAngle = 0; - std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); s_vvRotatePoints.clear(); s_vvRotatePoints.resize(m_vSelectedQuads.size()); for(size_t i = 0; i < m_vSelectedQuads.size(); ++i) @@ -1727,7 +1792,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) Graphics()->QuadsDraw(&QuadItem, 1); } -void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) +void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int V) { void *pID = &pQuad->m_aPoints[V]; @@ -1786,6 +1851,8 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) if(s_Operation == OP_MOVEPOINT) { + m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); + float x = wx; float y = wy; if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid) @@ -1805,6 +1872,12 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) } else if(s_Operation == OP_MOVEUV) { + int SelectedPoints = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3); + + m_QuadTracker.BeginQuadPointPropTrack(pLayer, m_vSelectedQuads, SelectedPoints); + m_QuadTracker.AddQuadPointPropTrack(EQuadPointProp::PROP_TEX_U); + m_QuadTracker.AddQuadPointPropTrack(EQuadPointProp::PROP_TEX_V); + for(int m = 0; m < 4; m++) { if(IsQuadPointSelected(QuadIndex, m)) @@ -1852,6 +1925,15 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) SelectQuadPoint(QuadIndex, V); } + if(s_Operation == OP_MOVEPOINT) + { + m_QuadTracker.EndQuadTrack(); + } + else if(s_Operation == OP_MOVEUV) + { + m_QuadTracker.EndQuadPointPropTrackAll(); + } + UI()->DisableMouseLock(); UI()->SetActiveItem(nullptr); } @@ -2081,6 +2163,7 @@ void CEditor::DoQuadKnife(int QuadIndex) pResult->m_aPoints[4].y = ((pResult->m_aPoints[0].y + pResult->m_aPoints[3].y) / 2 + (pResult->m_aPoints[1].y + pResult->m_aPoints[2].y) / 2) / 2; m_QuadKnifeCount = 0; + m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, m_vSelectedLayers[0])); } // Render @@ -2677,6 +2760,8 @@ void CEditor::DoMapEditor(CUIRect View) std::shared_ptr pBrush = m_pBrush->IsEmpty() ? nullptr : m_pBrush->m_vpLayers[BrushIndex]; apEditLayers[k]->FillSelection(m_pBrush->IsEmpty(), pBrush, r); } + std::shared_ptr Action = std::make_shared(this, m_SelectedGroup); + m_EditorHistory.RecordAction(Action); } else { @@ -2707,6 +2792,7 @@ void CEditor::DoMapEditor(CUIRect View) size_t BrushIndex = k; if(m_pBrush->m_vpLayers.size() != NumEditLayers) BrushIndex = 0; + if(apEditLayers[k]->m_Type == m_pBrush->m_vpLayers[BrushIndex]->m_Type) apEditLayers[k]->BrushPlace(m_pBrush->m_vpLayers[BrushIndex], wx, wy); } @@ -2785,9 +2871,9 @@ void CEditor::DoMapEditor(CUIRect View) for(size_t i = 0; i < pLayer->m_vQuads.size(); i++) { for(int v = 0; v < 4; v++) - DoQuadPoint(&pLayer->m_vQuads[i], i, v); + DoQuadPoint(pLayer, &pLayer->m_vQuads[i], i, v); - DoQuad(&pLayer->m_vQuads[i], i); + DoQuad(pLayer, &pLayer->m_vQuads[i], i); } Graphics()->QuadsEnd(); } @@ -2883,9 +2969,9 @@ void CEditor::DoMapEditor(CUIRect View) } } - // do panning if(UI()->CheckActiveItem(s_pEditorID)) { + // do panning if(s_Operation == OP_PAN_WORLD) MapView()->OffsetWorld(-vec2(m_MouseDeltaX, m_MouseDeltaY) * m_MouseWScale); else if(s_Operation == OP_PAN_EDITOR) @@ -2894,6 +2980,14 @@ void CEditor::DoMapEditor(CUIRect View) // release mouse if(!UI()->MouseButton(0)) { + if(s_Operation == OP_BRUSH_DRAW) + { + std::shared_ptr Action = std::make_shared(this, m_SelectedGroup); + + if(!Action->IsEmpty()) // Avoid recording tile draw action when placing quads only + m_EditorHistory.RecordAction(Action); + } + s_Operation = OP_NONE; UI()->SetActiveItem(nullptr); } @@ -3009,238 +3103,6 @@ void CEditor::SetHotQuadPoint(const std::shared_ptr &pLayer) UI()->SetHotItem(pMinPoint); } -int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color) -{ - int Change = -1; - - for(int i = 0; pProps[i].m_pName; i++) - { - CUIRect Slot; - pToolBox->HSplitTop(13.0f, &Slot, pToolBox); - CUIRect Label, Shifter; - Slot.VSplitMid(&Label, &Shifter); - Shifter.HMargin(1.0f, &Shifter); - UI()->DoLabel(&Label, pProps[i].m_pName, 10.0f, TEXTALIGN_ML); - - if(pProps[i].m_Type == PROPTYPE_INT_STEP) - { - CUIRect Inc, Dec; - char aBuf[64]; - - Shifter.VSplitRight(10.0f, &Shifter, &Inc); - Shifter.VSplitLeft(10.0f, &Dec, &Shifter); - str_from_int(pProps[i].m_Value, aBuf); - int NewValue = UiDoValueSelector((char *)&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0, &Color); - if(NewValue != pProps[i].m_Value) - { - *pNewVal = NewValue; - Change = i; - } - if(DoButton_ButtonDec((char *)&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Decrease")) - { - *pNewVal = pProps[i].m_Value - 1; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, nullptr, 0, &Inc, 0, "Increase")) - { - *pNewVal = pProps[i].m_Value + 1; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_BOOL) - { - CUIRect No, Yes; - Shifter.VSplitMid(&No, &Yes); - if(DoButton_ButtonDec(&pIDs[i], "No", !pProps[i].m_Value, &No, 0, "")) - { - *pNewVal = 0; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i]) + 1, "Yes", pProps[i].m_Value, &Yes, 0, "")) - { - *pNewVal = 1; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_INT_SCROLL) - { - int NewValue = UiDoValueSelector(&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text."); - if(NewValue != pProps[i].m_Value) - { - *pNewVal = NewValue; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_ANGLE_SCROLL) - { - CUIRect Inc, Dec; - Shifter.VSplitRight(10.0f, &Shifter, &Inc); - Shifter.VSplitLeft(10.0f, &Dec, &Shifter); - const bool Shift = Input()->ShiftIsPressed(); - int Step = Shift ? 1 : 45; - int Value = pProps[i].m_Value; - - int NewValue = UiDoValueSelector(&pIDs[i], &Shifter, "", Value, pProps[i].m_Min, pProps[i].m_Max, Shift ? 1 : 45, Shift ? 1.0f : 10.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0); - if(DoButton_ButtonDec(&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Decrease")) - { - NewValue = (std::ceil((pProps[i].m_Value / (float)Step)) - 1) * Step; - if(NewValue < 0) - NewValue += 360; - } - if(DoButton_ButtonInc(&pIDs[i] + 2, nullptr, 0, &Inc, 0, "Increase")) - NewValue = (pProps[i].m_Value + Step) / Step * Step; - - if(NewValue != pProps[i].m_Value) - { - *pNewVal = NewValue % 360; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_COLOR) - { - const auto &&SetColor = [&](ColorRGBA NewColor) { - const int NewValue = NewColor.PackAlphaLast(); - if(NewValue != pProps[i].m_Value) - { - *pNewVal = NewValue; - Change = i; - } - }; - DoColorPickerButton(&pIDs[i], &Shifter, ColorRGBA::UnpackAlphaLast(pProps[i].m_Value), SetColor); - } - else if(pProps[i].m_Type == PROPTYPE_IMAGE) - { - const char *pName; - if(pProps[i].m_Value < 0) - pName = "None"; - else - pName = m_Map.m_vpImages[pProps[i].m_Value]->m_aName; - - if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) - PopupSelectImageInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); - - int r = PopupSelectImageResult(); - if(r >= -1) - { - *pNewVal = r; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_SHIFT) - { - CUIRect Left, Right, Up, Down; - Shifter.VSplitMid(&Left, &Up, 2.0f); - Left.VSplitLeft(10.0f, &Left, &Shifter); - Shifter.VSplitRight(10.0f, &Shifter, &Right); - Shifter.Draw(ColorRGBA(1, 1, 1, 0.5f), 0, 0.0f); - UI()->DoLabel(&Shifter, "X", 10.0f, TEXTALIGN_MC); - Up.VSplitLeft(10.0f, &Up, &Shifter); - Shifter.VSplitRight(10.0f, &Shifter, &Down); - Shifter.Draw(ColorRGBA(1, 1, 1, 0.5f), 0, 0.0f); - UI()->DoLabel(&Shifter, "Y", 10.0f, TEXTALIGN_MC); - if(DoButton_ButtonDec(&pIDs[i], "-", 0, &Left, 0, "Left")) - { - *pNewVal = DIRECTION_LEFT; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i]) + 3, "+", 0, &Right, 0, "Right")) - { - *pNewVal = DIRECTION_RIGHT; - Change = i; - } - if(DoButton_ButtonDec(((char *)&pIDs[i]) + 1, "-", 0, &Up, 0, "Up")) - { - *pNewVal = DIRECTION_UP; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, "+", 0, &Down, 0, "Down")) - { - *pNewVal = DIRECTION_DOWN; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_SOUND) - { - const char *pName; - if(pProps[i].m_Value < 0) - pName = "None"; - else - pName = m_Map.m_vpSounds[pProps[i].m_Value]->m_aName; - - if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) - PopupSelectSoundInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); - - int r = PopupSelectSoundResult(); - if(r >= -1) - { - *pNewVal = r; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_AUTOMAPPER) - { - const char *pName; - if(pProps[i].m_Value < 0 || pProps[i].m_Min < 0 || pProps[i].m_Min >= (int)m_Map.m_vpImages.size()) - pName = "None"; - else - pName = m_Map.m_vpImages[pProps[i].m_Min]->m_AutoMapper.GetConfigName(pProps[i].m_Value); - - if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) - PopupSelectConfigAutoMapInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); - - int r = PopupSelectConfigAutoMapResult(); - if(r >= -1) - { - *pNewVal = r; - Change = i; - } - } - else if(pProps[i].m_Type == PROPTYPE_ENVELOPE) - { - CUIRect Inc, Dec; - char aBuf[8]; - int CurValue = pProps[i].m_Value; - - Shifter.VSplitRight(10.0f, &Shifter, &Inc); - Shifter.VSplitLeft(10.0f, &Dec, &Shifter); - - if(CurValue <= 0) - str_copy(aBuf, "None:"); - else if(m_Map.m_vpEnvelopes[CurValue - 1]->m_aName[0]) - { - str_format(aBuf, sizeof(aBuf), "%s:", m_Map.m_vpEnvelopes[CurValue - 1]->m_aName); - if(!str_endswith(aBuf, ":")) - { - aBuf[sizeof(aBuf) - 2] = ':'; - aBuf[sizeof(aBuf) - 1] = '\0'; - } - } - else - aBuf[0] = '\0'; - - int NewVal = UiDoValueSelector((char *)&pIDs[i], &Shifter, aBuf, CurValue, 0, m_Map.m_vpEnvelopes.size(), 1, 1.0f, "Set Envelope", false, false, IGraphics::CORNER_NONE); - if(NewVal != CurValue) - { - *pNewVal = NewVal; - Change = i; - } - - if(DoButton_ButtonDec((char *)&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Previous Envelope")) - { - *pNewVal = pProps[i].m_Value - 1; - Change = i; - } - if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, nullptr, 0, &Inc, 0, "Next Envelope")) - { - *pNewVal = pProps[i].m_Value + 1; - Change = i; - } - } - } - - return Change; -} - void CEditor::DoColorPickerButton(const void *pID, const CUIRect *pRect, ColorRGBA Color, const std::function &SetColor) { CUIRect ColorRect; @@ -3261,6 +3123,7 @@ void CEditor::DoColorPickerButton(const void *pID, const CUIRect *pRect, ColorRG std::optional ParsedColor = color_parse(pClipboard); if(ParsedColor) { + m_ColorPickerPopupContext.m_State = EEditState::ONE_GO; SetColor(ParsedColor.value()); } } @@ -3292,6 +3155,13 @@ void CEditor::DoColorPickerButton(const void *pID, const CUIRect *pRect, ColorRG else { m_pColorPickerPopupActiveID = nullptr; + if(m_ColorPickerPopupContext.m_State == EEditState::EDITING) + { + ColorRGBA c = color_cast(m_ColorPickerPopupContext.m_HsvaColor); + m_ColorPickerPopupContext.m_State = EEditState::END; + SetColor(c); + m_ColorPickerPopupContext.m_State = EEditState::NONE; + } } } @@ -3319,6 +3189,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) OP_GROUP_DRAG }; static int s_Operation = OP_NONE; + static int s_PreviousOperation = OP_NONE; static const void *s_pDraggedButton = 0; static float s_InitialMouseY = 0; static float s_InitialCutHeight = 0; @@ -3332,6 +3203,14 @@ void CEditor::RenderLayers(CUIRect LayersBox) bool AnyButtonActive = false; std::vector vButtonsPerGroup; + auto SetOperation = [](int Operation) { + if(Operation != s_Operation) + { + s_PreviousOperation = s_Operation; + s_Operation = Operation; + } + }; + vButtonsPerGroup.reserve(m_Map.m_vpGroups.size()); for(const std::shared_ptr &pGroup : m_Map.m_vpGroups) { @@ -3339,7 +3218,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) } if(!UI()->CheckActiveItem(s_pDraggedButton)) - s_Operation = OP_NONE; + SetOperation(OP_NONE); if(s_Operation == OP_LAYER_DRAG || s_Operation == OP_GROUP_DRAG) { @@ -3437,14 +3316,14 @@ void CEditor::RenderLayers(CUIRect LayersBox) { s_InitialMouseY = UI()->MouseY(); s_InitialCutHeight = s_InitialMouseY - UnscrolledLayersBox.y; - s_Operation = OP_CLICK; + SetOperation(OP_CLICK); if(g != m_SelectedGroup) SelectLayer(0, g); } if(Abrupted) - s_Operation = OP_NONE; + SetOperation(OP_NONE); if(s_Operation == OP_CLICK) { @@ -3477,7 +3356,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) if(!m_Map.m_vpGroups[g]->m_vpLayers.empty() && Input()->MouseDoubleClick()) m_Map.m_vpGroups[g]->m_Collapse ^= 1; - s_Operation = OP_NONE; + SetOperation(OP_NONE); } if(s_Operation == OP_GROUP_DRAG && Clicked) @@ -3485,7 +3364,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) } else if(s_pDraggedButton == &m_Map.m_vpGroups[g]) { - s_Operation = OP_NONE; + SetOperation(OP_NONE); } } @@ -3589,7 +3468,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) { s_InitialMouseY = UI()->MouseY(); s_InitialCutHeight = s_InitialMouseY - UnscrolledLayersBox.y; - s_Operation = OP_CLICK; + SetOperation(OP_CLICK); if(!Input()->ShiftIsPressed() && !IsLayerSelected) { @@ -3598,7 +3477,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) } if(Abrupted) - s_Operation = OP_NONE; + SetOperation(OP_NONE); if(s_Operation == OP_CLICK) { @@ -3649,23 +3528,33 @@ void CEditor::RenderLayers(CUIRect LayersBox) bool AllTile = true; for(size_t j = 0; AllTile && j < m_vSelectedLayers.size(); j++) { - if(m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[m_vSelectedLayers[j]]->m_Type == LAYERTYPE_TILES) + int LayerIndex = m_vSelectedLayers[j]; + if(m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[LayerIndex]->m_Type == LAYERTYPE_TILES) + { s_LayerPopupContext.m_vpLayers.push_back(std::static_pointer_cast(m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers[m_vSelectedLayers[j]])); + s_LayerPopupContext.m_vLayerIndices.push_back(LayerIndex); + } else AllTile = false; } - // Don't allow editing if all selected layers are tile layers + // Don't allow editing if all selected layers are not tile layers if(!AllTile) + { s_LayerPopupContext.m_vpLayers.clear(); + s_LayerPopupContext.m_vLayerIndices.clear(); + } } else + { s_LayerPopupContext.m_vpLayers.clear(); + s_LayerPopupContext.m_vLayerIndices.clear(); + } UI()->DoPopupMenu(&s_LayerPopupContext, UI()->MouseX(), UI()->MouseY(), 120, 270, &s_LayerPopupContext, PopupLayer); } - s_Operation = OP_NONE; + SetOperation(OP_NONE); } if(s_Operation == OP_LAYER_DRAG && Clicked) @@ -3675,7 +3564,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) } else if(s_pDraggedButton == m_Map.m_vpGroups[g]->m_vpLayers[i].get()) { - s_Operation = OP_NONE; + SetOperation(OP_NONE); } } @@ -3755,16 +3644,28 @@ void CEditor::RenderLayers(CUIRect LayersBox) auto InsertPosition = std::find(m_Map.m_vpGroups.begin(), m_Map.m_vpGroups.end(), pNextGroup); m_Map.m_vpGroups.insert(InsertPosition, pSelectedGroup); - m_SelectedGroup = InsertPosition - m_Map.m_vpGroups.begin(); + auto Pos = std::find(m_Map.m_vpGroups.begin(), m_Map.m_vpGroups.end(), pSelectedGroup); + m_SelectedGroup = Pos - m_Map.m_vpGroups.begin(); + m_Map.OnModify(); } + static int s_InitialGroupIndex; + static std::vector s_vInitialLayerIndices; + if(MoveLayers || MoveGroup) - s_Operation = OP_NONE; + SetOperation(OP_NONE); if(StartDragLayer) - s_Operation = OP_LAYER_DRAG; + { + SetOperation(OP_LAYER_DRAG); + s_InitialGroupIndex = m_SelectedGroup; + s_vInitialLayerIndices = std::vector(m_vSelectedLayers); + } if(StartDragGroup) - s_Operation = OP_GROUP_DRAG; + { + s_InitialGroupIndex = m_SelectedGroup; + SetOperation(OP_GROUP_DRAG); + } if(s_Operation == OP_LAYER_DRAG || s_Operation == OP_GROUP_DRAG) { @@ -3847,13 +3748,41 @@ void CEditor::RenderLayers(CUIRect LayersBox) { m_Map.NewGroup(); m_SelectedGroup = m_Map.m_vpGroups.size() - 1; + m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, false)); } } s_ScrollRegion.End(); if(!AnyButtonActive) - s_Operation = OP_NONE; + SetOperation(OP_NONE); + + if(s_Operation == OP_NONE) + { + if(s_PreviousOperation == OP_GROUP_DRAG) + { + s_PreviousOperation = OP_NONE; + m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, EGroupProp::PROP_ORDER, s_InitialGroupIndex, m_SelectedGroup)); + } + else if(s_PreviousOperation == OP_LAYER_DRAG) + { + if(s_InitialGroupIndex != m_SelectedGroup) + { + m_EditorHistory.RecordAction(std::make_shared(this, s_InitialGroupIndex, s_vInitialLayerIndices, m_SelectedGroup, m_vSelectedLayers)); + } + else + { + std::vector> vpActions; + for(int k = 0; k < (int)m_vSelectedLayers.size(); k++) + { + int LayerIndex = m_vSelectedLayers[k]; + vpActions.push_back(std::make_shared(this, m_SelectedGroup, LayerIndex, ELayerProp::PROP_ORDER, s_vInitialLayerIndices[k], LayerIndex)); + } + m_EditorHistory.RecordAction(std::make_shared(CEditorActionBulk(this, vpActions))); + } + s_PreviousOperation = OP_NONE; + } + } } bool CEditor::SelectLayerByTile() @@ -4172,7 +4101,7 @@ void CEditor::SelectGameLayer() } } -void CEditor::SortImages() +std::vector CEditor::SortImages() { static const auto &&s_ImageNameComparator = [](const std::shared_ptr &pLhs, const std::shared_ptr &pRhs) { return str_comp(pLhs->m_aName, pRhs->m_aName) < 0; @@ -4199,7 +4128,11 @@ void CEditor::SortImages() if(*pIndex >= 0) *pIndex = vSortedIndex[*pIndex]; }); + + return vSortedIndex; } + + return std::vector(); } void CEditor::RenderImagesList(CUIRect ToolBox) @@ -4320,7 +4253,7 @@ void CEditor::RenderImagesList(CUIRect ToolBox) if(Result == 2) { const std::shared_ptr pImg = m_Map.m_vpImages[m_SelectedImage]; - const int Height = !pImg->m_External && IsVanillaImage(pImg->m_aName) ? 107 : pImg->m_External ? 73 : 90; + const int Height = !pImg->m_External && IsVanillaImage(pImg->m_aName) ? 107 : (pImg->m_External ? 73 : 90); static SPopupMenuId s_PopupImageId; UI()->DoPopupMenu(&s_PopupImageId, UI()->MouseX(), UI()->MouseY(), 140, Height, this, PopupImage); } @@ -5300,6 +5233,14 @@ void CEditor::RenderStatusbar(CUIRect View, CUIRect *pTooltipRect) m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS ? EXTRAEDITOR_NONE : EXTRAEDITOR_SERVER_SETTINGS; } + View.VSplitRight(10.0f, &View, nullptr); + View.VSplitRight(100.0f, &View, &Button); + static int s_HistoryButton = 0; + if(DoButton_Editor(&s_HistoryButton, "History", ButtonsDisabled ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_HISTORY, &Button, 0, "Toggles the editor history view.") == 1) + { + m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_HISTORY ? EXTRAEDITOR_NONE : EXTRAEDITOR_HISTORY; + } + View.VSplitRight(10.0f, pTooltipRect, nullptr); } @@ -5652,18 +5593,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(m_SelectedEnvelope >= 0 && m_SelectedEnvelope < (int)m_Map.m_vpEnvelopes.size()) pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope]; - enum - { - OP_NONE, - OP_SELECT, - OP_DRAG_POINT, - OP_DRAG_POINT_X, - OP_DRAG_POINT_Y, - OP_CONTEXT_MENU, - OP_BOX_SELECT, - OP_SCALE - }; - static int s_Operation = OP_NONE; + static EEnvelopeEditorOp s_Operation = EEnvelopeEditorOp::OP_NONE; static std::vector s_vAccurateDragValuesX = {}; static std::vector s_vAccurateDragValuesY = {}; static float s_MouseXStart = 0.0f; @@ -5688,6 +5618,23 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) CUIRect Button; std::shared_ptr pNewEnv = nullptr; + // redo button + ToolBar.VSplitRight(25.0f, &ToolBar, &Button); + static int s_RedoButton = 0; + if(DoButton_FontIcon(&s_RedoButton, FONT_ICON_REDO, m_EnvelopeEditorHistory.CanRedo() ? 0 : -1, &Button, 0, "[Ctrl+Y] Redo last action", IGraphics::CORNER_R, 11.0f) == 1) + { + m_EnvelopeEditorHistory.Redo(); + } + + // undo button + ToolBar.VSplitRight(25.0f, &ToolBar, &Button); + ToolBar.VSplitRight(10.0f, &ToolBar, nullptr); + static int s_UndoButton = 0; + if(DoButton_FontIcon(&s_UndoButton, FONT_ICON_UNDO, m_EnvelopeEditorHistory.CanUndo() ? 0 : -1, &Button, 0, "[Ctrl+Z] Undo last action", IGraphics::CORNER_L, 11.0f) == 1) + { + m_EnvelopeEditorHistory.Undo(); + } + ToolBar.VSplitRight(50.0f, &ToolBar, &Button); static int s_NewSoundButton = 0; if(DoButton_Editor(&s_NewSoundButton, "Sound+", 0, &Button, 0, "Creates a new sound envelope")) @@ -5722,6 +5669,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static int s_DeleteButton = 0; if(DoButton_Editor(&s_DeleteButton, "✗", 0, &Button, 0, "Delete this envelope")) { + m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope)); m_Map.DeleteEnvelope(m_SelectedEnvelope); if(m_SelectedEnvelope >= (int)m_Map.m_vpEnvelopes.size()) m_SelectedEnvelope = m_Map.m_vpEnvelopes.size() - 1; @@ -5734,6 +5682,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static int s_MoveRightButton = 0; if(DoButton_Ex(&s_MoveRightButton, "→", 0, &Button, 0, "Move this envelope to the right", IGraphics::CORNER_R)) { + m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope, CEditorActionEnvelopeEdit::EEditType::ORDER, m_SelectedEnvelope, m_SelectedEnvelope + 1)); m_Map.SwapEnvelopes(m_SelectedEnvelope, m_SelectedEnvelope + 1); m_SelectedEnvelope = clamp(m_SelectedEnvelope + 1, 0, m_Map.m_vpEnvelopes.size() - 1); pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope]; @@ -5744,6 +5693,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static int s_MoveLeftButton = 0; if(DoButton_Ex(&s_MoveLeftButton, "←", 0, &Button, 0, "Move this envelope to the left", IGraphics::CORNER_L)) { + m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope, CEditorActionEnvelopeEdit::EEditType::ORDER, m_SelectedEnvelope, m_SelectedEnvelope - 1)); m_Map.SwapEnvelopes(m_SelectedEnvelope - 1, m_SelectedEnvelope); m_SelectedEnvelope = clamp(m_SelectedEnvelope - 1, 0, m_Map.m_vpEnvelopes.size() - 1); pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope]; @@ -5794,6 +5744,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) pNewEnv->AddPoint(0, 0); pNewEnv->AddPoint(1000, 0); } + + m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, pNewEnv)); } CUIRect Shifter, Inc, Dec; @@ -5806,13 +5758,12 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ColorRGBA EnvColor = ColorRGBA(1, 1, 1, 0.5f); if(!m_Map.m_vpEnvelopes.empty()) { - EnvColor = IsEnvelopeUsed(m_SelectedEnvelope) ? - ColorRGBA(1, 0.7f, 0.7f, 0.5f) : - ColorRGBA(0.7f, 1, 0.7f, 0.5f); + EnvColor = IsEnvelopeUsed(m_SelectedEnvelope) ? ColorRGBA(1, 0.7f, 0.7f, 0.5f) : ColorRGBA(0.7f, 1, 0.7f, 0.5f); } static int s_EnvelopeSelector = 0; - int NewValue = UiDoValueSelector(&s_EnvelopeSelector, &Shifter, aBuf, m_SelectedEnvelope + 1, 1, m_Map.m_vpEnvelopes.size(), 1, 1.0f, "Select Envelope", false, false, IGraphics::CORNER_NONE, &EnvColor, false); + auto NewValueRes = UiDoValueSelector(&s_EnvelopeSelector, &Shifter, aBuf, m_SelectedEnvelope + 1, 1, m_Map.m_vpEnvelopes.size(), 1, 1.0f, "Select Envelope", false, false, IGraphics::CORNER_NONE, &EnvColor, false); + int NewValue = NewValueRes.m_Value; if(NewValue - 1 != m_SelectedEnvelope) { m_SelectedEnvelope = NewValue - 1; @@ -5921,7 +5872,10 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ToolBar.VSplitLeft(12.0f, &Button, &ToolBar); static int s_SyncButton; if(DoButton_Editor(&s_SyncButton, pEnvelope->m_Synchronized ? "X" : "", 0, &Button, 0, "Synchronize envelope animation to game time (restarts when you touch the start line)")) + { + m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope, CEditorActionEnvelopeEdit::EEditType::SYNC, pEnvelope->m_Synchronized, !pEnvelope->m_Synchronized)); pEnvelope->m_Synchronized = !pEnvelope->m_Synchronized; + } ToolBar.VSplitLeft(4.0f, nullptr, &ToolBar); ToolBar.VSplitLeft(40.0f, &Button, &ToolBar); @@ -5931,7 +5885,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { UI()->SetHotItem(&s_EnvelopeEditorID); - if(s_Operation == OP_NONE && (UI()->MouseButton(2) || (UI()->MouseButton(0) && Input()->ModifierIsPressed()))) + if(s_Operation == EEnvelopeEditorOp::OP_NONE && (UI()->MouseButton(2) || (UI()->MouseButton(0) && Input()->ModifierIsPressed()))) { m_OffsetEnvelopeX += UI()->MouseDeltaX() / Graphics()->ScreenWidth() * UI()->Screen()->w / View.w; m_OffsetEnvelopeY -= UI()->MouseDeltaY() / Graphics()->ScreenHeight() * UI()->Screen()->h / View.h; @@ -5986,19 +5940,17 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } if(!TimeFound) - pEnvelope->AddPoint(FixedTime, - f2fx(Channels.r), f2fx(Channels.g), - f2fx(Channels.b), f2fx(Channels.a)); + m_EnvelopeEditorHistory.Execute(std::make_shared(this, m_SelectedEnvelope, FixedTime, Channels)); if(FixedTime < 0) RemoveTimeOffsetEnvelope(pEnvelope); m_Map.OnModify(); } - else if(s_Operation != OP_BOX_SELECT && !Input()->ModifierIsPressed()) + else if(s_Operation != EEnvelopeEditorOp::OP_BOX_SELECT && !Input()->ModifierIsPressed()) { static int s_BoxSelectID = 0; UI()->SetActiveItem(&s_BoxSelectID); - s_Operation = OP_BOX_SELECT; + s_Operation = EEnvelopeEditorOp::OP_BOX_SELECT; s_MouseXStart = UI()->MouseX(); s_MouseYStart = UI()->MouseY(); } @@ -6214,7 +6166,13 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(CurveButton.x >= View.x) { if(DoButton_Editor(pID, pTypeName, 0, &CurveButton, 0, "Switch curve type (N = step, L = linear, S = slow, F = fast, M = smooth, B = bezier)")) + { + int PrevCurve = pEnvelope->m_vPoints[i].m_Curvetype; pEnvelope->m_vPoints[i].m_Curvetype = (pEnvelope->m_vPoints[i].m_Curvetype + 1) % NUM_CURVETYPES; + + m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, + m_SelectedEnvelope, i, 0, CEditorActionEnvelopeEditPoint::EEditType::CURVE_TYPE, PrevCurve, pEnvelope->m_vPoints[i].m_Curvetype)); + } } } } @@ -6275,8 +6233,16 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) UI()->DoPopupMenu(&s_PopupEnvPointId, UI()->MouseX(), UI()->MouseY(), 150, 56 + (pEnvelope->GetChannels() == 4 ? 16.0f : 0.0f), this, PopupEnvPoint); }; - if(s_Operation == OP_NONE) + if(s_Operation == EEnvelopeEditorOp::OP_NONE) + { SetHotEnvelopePoint(View, pEnvelope, s_ActiveChannels); + if(!UI()->MouseButton(0)) + m_EnvOpTracker.Stop(false); + } + else + { + m_EnvOpTracker.Begin(s_Operation); + } UI()->ClipEnable(&View); Graphics()->TextureClear(); @@ -6286,7 +6252,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(!(s_ActiveChannels & (1 << c))) continue; - for(size_t i = 0; i < pEnvelope->m_vPoints.size(); i++) + for(int i = 0; i < (int)pEnvelope->m_vPoints.size(); i++) { // point handle { @@ -6316,27 +6282,27 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { m_ShowEnvelopePreview = SHOWENV_SELECTED; - if(s_Operation == OP_SELECT) + if(s_Operation == EEnvelopeEditorOp::OP_SELECT) { float dx = s_MouseXStart - UI()->MouseX(); float dy = s_MouseYStart - UI()->MouseY(); if(dx * dx + dy * dy > 20.0f) { - s_Operation = OP_DRAG_POINT; + s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT; if(!IsEnvPointSelected(i, c)) SelectEnvPoint(i, c); } } - if(s_Operation == OP_DRAG_POINT || s_Operation == OP_DRAG_POINT_X || s_Operation == OP_DRAG_POINT_Y) + if(s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT || s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT_X || s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT_Y) { if(Input()->ShiftIsPressed()) { - if(s_Operation == OP_DRAG_POINT || s_Operation == OP_DRAG_POINT_Y) + if(s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT || s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT_Y) { - s_Operation = OP_DRAG_POINT_X; + s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT_X; s_vAccurateDragValuesX.clear(); for(auto [SelectedIndex, _] : m_vSelectedEnvelopePoints) s_vAccurateDragValuesX.push_back(pEnvelope->m_vPoints[SelectedIndex].m_Time); @@ -6385,9 +6351,9 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } else { - if(s_Operation == OP_DRAG_POINT || s_Operation == OP_DRAG_POINT_X) + if(s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT || s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT_X) { - s_Operation = OP_DRAG_POINT_Y; + s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT_Y; s_vAccurateDragValuesY.clear(); for(auto [SelectedIndex, SelectedChannel] : m_vSelectedEnvelopePoints) s_vAccurateDragValuesY.push_back(pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel]); @@ -6411,7 +6377,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } } - if(s_Operation == OP_CONTEXT_MENU) + if(s_Operation == EEnvelopeEditorOp::OP_CONTEXT_MENU) { if(!UI()->MouseButton(1)) { @@ -6426,7 +6392,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) UI()->DoPopupMenu(&s_PopupEnvPointMultiId, UI()->MouseX(), UI()->MouseY(), 80, 22, this, PopupEnvPointMulti); } UI()->SetActiveItem(nullptr); - s_Operation = OP_NONE; + s_Operation = EEnvelopeEditorOp::OP_NONE; } } else if(!UI()->MouseButton(0)) @@ -6434,7 +6400,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) UI()->SetActiveItem(nullptr); m_SelectedQuadEnvelope = -1; - if(s_Operation == OP_SELECT) + if(s_Operation == EEnvelopeEditorOp::OP_SELECT) { if(Input()->ShiftIsPressed()) ToggleEnvPoint(i, c); @@ -6442,7 +6408,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) SelectEnvPoint(i, c); } - s_Operation = OP_NONE; + s_Operation = EEnvelopeEditorOp::OP_NONE; m_Map.OnModify(); } @@ -6453,7 +6419,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(UI()->MouseButton(0)) { UI()->SetActiveItem(pID); - s_Operation = OP_SELECT; + s_Operation = EEnvelopeEditorOp::OP_SELECT; m_SelectedQuadEnvelope = m_SelectedEnvelope; s_MouseXStart = UI()->MouseX(); @@ -6463,12 +6429,11 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { if(Input()->ShiftIsPressed()) { - pEnvelope->m_vPoints.erase(pEnvelope->m_vPoints.begin() + i); - m_Map.OnModify(); + m_EnvelopeEditorHistory.Execute(std::make_shared(this, m_SelectedEnvelope, i)); } else { - s_Operation = OP_CONTEXT_MENU; + s_Operation = EEnvelopeEditorOp::OP_CONTEXT_MENU; if(!IsEnvPointSelected(i, c)) SelectEnvPoint(i, c); UI()->SetActiveItem(pID); @@ -6488,6 +6453,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } // tangent handles for bezier curves + if(i >= 0 && i < (int)pEnvelope->m_vPoints.size()) { // Out-Tangent handle if(pEnvelope->m_vPoints[i].m_Curvetype == CURVETYPE_BEZIER) @@ -6522,14 +6488,14 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { m_ShowEnvelopePreview = SHOWENV_SELECTED; - if(s_Operation == OP_SELECT) + if(s_Operation == EEnvelopeEditorOp::OP_SELECT) { float dx = s_MouseXStart - UI()->MouseX(); float dy = s_MouseYStart - UI()->MouseY(); if(dx * dx + dy * dy > 20.0f) { - s_Operation = OP_DRAG_POINT; + s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT; s_vAccurateDragValuesX = {static_cast(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c])}; s_vAccurateDragValuesY = {static_cast(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c])}; @@ -6539,7 +6505,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } } - if(s_Operation == OP_DRAG_POINT) + if(s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT) { float DeltaX = ScreenToEnvelopeDX(View, UI()->MouseDeltaX()) * (Input()->ModifierIsPressed() ? 50.0f : 1000.0f); float DeltaY = ScreenToEnvelopeDY(View, UI()->MouseDeltaY()) * (Input()->ModifierIsPressed() ? 51.2f : 1024.0f); @@ -6554,7 +6520,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) s_vAccurateDragValuesX[0] = clamp(s_vAccurateDragValuesX[0], 0, f2fxt(ScreenToEnvelopeX(View, View.x + View.w)) - pEnvelope->m_vPoints[i].m_Time); } - if(s_Operation == OP_CONTEXT_MENU) + if(s_Operation == EEnvelopeEditorOp::OP_CONTEXT_MENU) { if(!UI()->MouseButton(1)) { @@ -6571,10 +6537,10 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) UI()->SetActiveItem(nullptr); m_SelectedQuadEnvelope = -1; - if(s_Operation == OP_SELECT) + if(s_Operation == EEnvelopeEditorOp::OP_SELECT) SelectTangentOutPoint(i, c); - s_Operation = OP_NONE; + s_Operation = EEnvelopeEditorOp::OP_NONE; m_Map.OnModify(); } @@ -6585,7 +6551,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(UI()->MouseButton(0)) { UI()->SetActiveItem(pID); - s_Operation = OP_SELECT; + s_Operation = EEnvelopeEditorOp::OP_SELECT; m_SelectedQuadEnvelope = m_SelectedEnvelope; s_MouseXStart = UI()->MouseX(); @@ -6602,7 +6568,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } else { - s_Operation = OP_CONTEXT_MENU; + s_Operation = EEnvelopeEditorOp::OP_CONTEXT_MENU; SelectTangentOutPoint(i, c); UI()->SetActiveItem(pID); } @@ -6654,14 +6620,14 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { m_ShowEnvelopePreview = SHOWENV_SELECTED; - if(s_Operation == OP_SELECT) + if(s_Operation == EEnvelopeEditorOp::OP_SELECT) { float dx = s_MouseXStart - UI()->MouseX(); float dy = s_MouseYStart - UI()->MouseY(); if(dx * dx + dy * dy > 20.0f) { - s_Operation = OP_DRAG_POINT; + s_Operation = EEnvelopeEditorOp::OP_DRAG_POINT; s_vAccurateDragValuesX = {static_cast(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c])}; s_vAccurateDragValuesY = {static_cast(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c])}; @@ -6671,7 +6637,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } } - if(s_Operation == OP_DRAG_POINT) + if(s_Operation == EEnvelopeEditorOp::OP_DRAG_POINT) { float DeltaX = ScreenToEnvelopeDX(View, UI()->MouseDeltaX()) * (Input()->ModifierIsPressed() ? 50.0f : 1000.0f); float DeltaY = ScreenToEnvelopeDY(View, UI()->MouseDeltaY()) * (Input()->ModifierIsPressed() ? 51.2f : 1024.0f); @@ -6686,7 +6652,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) s_vAccurateDragValuesX[0] = clamp(s_vAccurateDragValuesX[0], f2fxt(ScreenToEnvelopeX(View, View.x)) - pEnvelope->m_vPoints[i].m_Time, 0); } - if(s_Operation == OP_CONTEXT_MENU) + if(s_Operation == EEnvelopeEditorOp::OP_CONTEXT_MENU) { if(!UI()->MouseButton(1)) { @@ -6703,10 +6669,10 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) UI()->SetActiveItem(nullptr); m_SelectedQuadEnvelope = -1; - if(s_Operation == OP_SELECT) + if(s_Operation == EEnvelopeEditorOp::OP_SELECT) SelectTangentInPoint(i, c); - s_Operation = OP_NONE; + s_Operation = EEnvelopeEditorOp::OP_NONE; m_Map.OnModify(); } @@ -6717,7 +6683,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(UI()->MouseButton(0)) { UI()->SetActiveItem(pID); - s_Operation = OP_SELECT; + s_Operation = EEnvelopeEditorOp::OP_SELECT; m_SelectedQuadEnvelope = m_SelectedEnvelope; s_MouseXStart = UI()->MouseX(); @@ -6734,7 +6700,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } else { - s_Operation = OP_CONTEXT_MENU; + s_Operation = EEnvelopeEditorOp::OP_CONTEXT_MENU; SelectTangentInPoint(i, c); UI()->SetActiveItem(pID); } @@ -6766,9 +6732,9 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static float s_MidpointY = 0.0f; static std::vector s_vInitialPositionsX; static std::vector s_vInitialPositionsY; - if(s_Operation == OP_NONE && Input()->KeyIsPressed(KEY_S) && !Input()->ModifierIsPressed() && !m_vSelectedEnvelopePoints.empty()) + if(s_Operation == EEnvelopeEditorOp::OP_NONE && Input()->KeyIsPressed(KEY_S) && !Input()->ModifierIsPressed() && !m_vSelectedEnvelopePoints.empty()) { - s_Operation = OP_SCALE; + s_Operation = EEnvelopeEditorOp::OP_SCALE; s_ScaleFactorX = 1.0f; s_ScaleFactorY = 1.0f; auto [FirstPointIndex, FirstPointChannel] = m_vSelectedEnvelopePoints.front(); @@ -6798,7 +6764,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) s_MidpointY = (MaximumY - MinimumY) / 2.0f + MinimumY; } - if(s_Operation == OP_SCALE) + if(s_Operation == EEnvelopeEditorOp::OP_SCALE) { str_copy(m_aTooltip, "Press shift to scale the time. Press alt to scale along midpoint. Press ctrl to be more precise."); @@ -6873,7 +6839,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(UI()->MouseButton(0)) { - s_Operation = OP_NONE; + s_Operation = EEnvelopeEditorOp::OP_NONE; + m_EnvOpTracker.Stop(false); } else if(UI()->MouseButton(1) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE)) { @@ -6888,12 +6855,12 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] = std::round(s_vInitialPositionsY[k]); } RemoveTimeOffsetEnvelope(pEnvelope); - s_Operation = OP_NONE; + s_Operation = EEnvelopeEditorOp::OP_NONE; } } // handle box selection - if(s_Operation == OP_BOX_SELECT) + if(s_Operation == EEnvelopeEditorOp::OP_BOX_SELECT) { IGraphics::CLineItem aLines[4] = { {s_MouseXStart, s_MouseYStart, UI()->MouseX(), s_MouseYStart}, @@ -6908,7 +6875,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(!UI()->MouseButton(0)) { - s_Operation = OP_NONE; + s_Operation = EEnvelopeEditorOp::OP_NONE; UI()->SetActiveItem(nullptr); float TimeStart = ScreenToEnvelopeX(View, s_MouseXStart); @@ -6955,7 +6922,7 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd static CListBox s_ListBox; s_ListBox.SetActive(m_Dialog == DIALOG_NONE && !UI()->IsPopupOpen()); - const bool GotSelection = s_ListBox.Active() && s_CommandSelectedIndex >= 0 && (size_t)s_CommandSelectedIndex < m_Map.m_vSettings.size(); + bool GotSelection = s_ListBox.Active() && s_CommandSelectedIndex >= 0 && (size_t)s_CommandSelectedIndex < m_Map.m_vSettings.size(); const bool CurrentInputValid = ValidateServerSetting(m_SettingsCommandInput.GetString()); CUIRect ToolBar, Button, Label, List, DragBar; @@ -6974,6 +6941,8 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd static int s_DeleteButton = 0; if(DoButton_FontIcon(&s_DeleteButton, FONT_ICON_TRASH, GotSelection ? 0 : -1, &Button, 0, "[Delete] Delete the selected command from the command list.", IGraphics::CORNER_ALL, 9.0f) == 1 || (GotSelection && CLineInput::GetActiveInput() == nullptr && m_Dialog == DIALOG_NONE && UI()->ConsumeHotkey(CUI::HOTKEY_DELETE))) { + m_ServerSettingsHistory.RecordAction(std::make_shared(this, CEditorCommandAction::EType::DELETE, &s_CommandSelectedIndex, s_CommandSelectedIndex, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand)); + m_Map.m_vSettings.erase(m_Map.m_vSettings.begin() + s_CommandSelectedIndex); if(s_CommandSelectedIndex >= (int)m_Map.m_vSettings.size()) s_CommandSelectedIndex = m_Map.m_vSettings.size() - 1; @@ -6989,6 +6958,8 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd static int s_DownButton = 0; if(DoButton_FontIcon(&s_DownButton, FONT_ICON_SORT_DOWN, CanMoveDown ? 0 : -1, &Button, 0, "[Alt+Down] Move the selected command down.", IGraphics::CORNER_R, 11.0f) == 1 || (CanMoveDown && Input()->AltIsPressed() && UI()->ConsumeHotkey(CUI::HOTKEY_DOWN))) { + m_ServerSettingsHistory.RecordAction(std::make_shared(this, CEditorCommandAction::EType::MOVE_DOWN, &s_CommandSelectedIndex, s_CommandSelectedIndex)); + std::swap(m_Map.m_vSettings[s_CommandSelectedIndex], m_Map.m_vSettings[s_CommandSelectedIndex + 1]); s_CommandSelectedIndex++; m_Map.OnModify(); @@ -7002,12 +6973,33 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd static int s_UpButton = 0; if(DoButton_FontIcon(&s_UpButton, FONT_ICON_SORT_UP, CanMoveUp ? 0 : -1, &Button, 0, "[Alt+Up] Move the selected command up.", IGraphics::CORNER_L, 11.0f) == 1 || (CanMoveUp && Input()->AltIsPressed() && UI()->ConsumeHotkey(CUI::HOTKEY_UP))) { + m_ServerSettingsHistory.RecordAction(std::make_shared(this, CEditorCommandAction::EType::MOVE_UP, &s_CommandSelectedIndex, s_CommandSelectedIndex)); + std::swap(m_Map.m_vSettings[s_CommandSelectedIndex], m_Map.m_vSettings[s_CommandSelectedIndex - 1]); s_CommandSelectedIndex--; m_Map.OnModify(); s_ListBox.ScrollToSelected(); } + // redo button + ToolBar.VSplitRight(25.0f, &ToolBar, &Button); + static int s_RedoButton = 0; + if(DoButton_FontIcon(&s_RedoButton, FONT_ICON_REDO, m_ServerSettingsHistory.CanRedo() ? 0 : -1, &Button, 0, "[Ctrl+Y] Redo command edit", IGraphics::CORNER_R, 11.0f) == 1 || (CanMoveDown && Input()->AltIsPressed() && UI()->ConsumeHotkey(CUI::HOTKEY_DOWN))) + { + m_ServerSettingsHistory.Redo(); + } + + // undo button + ToolBar.VSplitRight(25.0f, &ToolBar, &Button); + ToolBar.VSplitRight(5.0f, &ToolBar, nullptr); + static int s_UndoButton = 0; + if(DoButton_FontIcon(&s_UndoButton, FONT_ICON_UNDO, m_ServerSettingsHistory.CanUndo() ? 0 : -1, &Button, 0, "[Ctrl+Z] Undo command edit", IGraphics::CORNER_L, 11.0f) == 1 || (CanMoveUp && Input()->AltIsPressed() && UI()->ConsumeHotkey(CUI::HOTKEY_UP))) + { + m_ServerSettingsHistory.Undo(); + } + + GotSelection = s_ListBox.Active() && s_CommandSelectedIndex >= 0 && (size_t)s_CommandSelectedIndex < m_Map.m_vSettings.size(); + // update button ToolBar.VSplitRight(25.0f, &ToolBar, &Button); const bool CanUpdate = GotSelection && CurrentInputValid && str_comp(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, m_SettingsCommandInput.GetString()) != 0; @@ -7026,12 +7018,15 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd } if(Found) { + m_ServerSettingsHistory.RecordAction(std::make_shared(this, CEditorCommandAction::EType::DELETE, &s_CommandSelectedIndex, s_CommandSelectedIndex, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand)); m_Map.m_vSettings.erase(m_Map.m_vSettings.begin() + s_CommandSelectedIndex); s_CommandSelectedIndex = i > s_CommandSelectedIndex ? i - 1 : i; } else { - str_copy(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, m_SettingsCommandInput.GetString()); + const char *pStr = m_SettingsCommandInput.GetString(); + m_ServerSettingsHistory.RecordAction(std::make_shared(this, CEditorCommandAction::EType::EDIT, &s_CommandSelectedIndex, s_CommandSelectedIndex, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, pStr)); + str_copy(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, pStr); } m_Map.OnModify(); s_ListBox.ScrollToSelected(); @@ -7060,6 +7055,7 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd { m_Map.m_vSettings.emplace_back(m_SettingsCommandInput.GetString()); s_CommandSelectedIndex = m_Map.m_vSettings.size() - 1; + m_ServerSettingsHistory.RecordAction(std::make_shared(this, CEditorCommandAction::EType::ADD, &s_CommandSelectedIndex, s_CommandSelectedIndex, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand)); m_Map.OnModify(); } s_ListBox.ScrollToSelected(); @@ -7097,6 +7093,157 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd } } +void CEditor::RenderEditorHistory(CUIRect View) +{ + enum EHistoryType + { + EDITOR_HISTORY, + ENVELOPE_HISTORY, + SERVER_SETTINGS_HISTORY + }; + + static EHistoryType s_HistoryType = EDITOR_HISTORY; + static int s_ActionSelectedIndex = 0; + static CListBox s_ListBox; + s_ListBox.SetActive(m_Dialog == DIALOG_NONE && !UI()->IsPopupOpen()); + + const bool GotSelection = s_ListBox.Active() && s_ActionSelectedIndex >= 0 && (size_t)s_ActionSelectedIndex < m_Map.m_vSettings.size(); + + CUIRect ToolBar, Button, Label, List, DragBar; + View.HSplitTop(22.0f, &DragBar, nullptr); + DragBar.y -= 2.0f; + DragBar.w += 2.0f; + DragBar.h += 4.0f; + RenderExtraEditorDragBar(View, DragBar); + View.HSplitTop(20.0f, &ToolBar, &View); + View.HSplitTop(2.0f, nullptr, &List); + ToolBar.HMargin(2.0f, &ToolBar); + + CUIRect TypeButtons, HistoryTypeButton; + const int HistoryTypeBtnSize = 70.0f; + ToolBar.VSplitLeft(3 * HistoryTypeBtnSize, &TypeButtons, &Label); + + // history type buttons + { + TypeButtons.VSplitLeft(HistoryTypeBtnSize, &HistoryTypeButton, &TypeButtons); + static int s_EditorHistoryButton = 0; + if(DoButton_Ex(&s_EditorHistoryButton, "Editor", s_HistoryType == EDITOR_HISTORY, &HistoryTypeButton, 0, "Show map editor history.", IGraphics::CORNER_L)) + { + s_HistoryType = EDITOR_HISTORY; + } + + TypeButtons.VSplitLeft(HistoryTypeBtnSize, &HistoryTypeButton, &TypeButtons); + static int s_EnvelopeEditorHistoryButton = 0; + if(DoButton_Ex(&s_EnvelopeEditorHistoryButton, "Envelope", s_HistoryType == ENVELOPE_HISTORY, &HistoryTypeButton, 0, "Show envelope editor history.", IGraphics::CORNER_NONE)) + { + s_HistoryType = ENVELOPE_HISTORY; + } + + TypeButtons.VSplitLeft(HistoryTypeBtnSize, &HistoryTypeButton, &TypeButtons); + static int s_ServerSettingsHistoryButton = 0; + if(DoButton_Ex(&s_ServerSettingsHistoryButton, "Settings", s_HistoryType == SERVER_SETTINGS_HISTORY, &HistoryTypeButton, 0, "Show server settings editor history.", IGraphics::CORNER_R)) + { + s_HistoryType = SERVER_SETTINGS_HISTORY; + } + } + + SLabelProperties InfoProps; + InfoProps.m_MaxWidth = ToolBar.w - 60.f; + InfoProps.m_EllipsisAtEnd = true; + Label.VSplitLeft(8.0f, nullptr, &Label); + UI()->DoLabel(&Label, "Editor history. Click on an action to undo all actions above.", 10.0f, TEXTALIGN_ML, InfoProps); + + CEditorHistory *pCurrentHistory; + if(s_HistoryType == EDITOR_HISTORY) + pCurrentHistory = &m_EditorHistory; + else if(s_HistoryType == ENVELOPE_HISTORY) + pCurrentHistory = &m_EnvelopeEditorHistory; + else if(s_HistoryType == SERVER_SETTINGS_HISTORY) + pCurrentHistory = &m_ServerSettingsHistory; + else + return; + + // delete button + ToolBar.VSplitRight(25.0f, &ToolBar, &Button); + ToolBar.VSplitRight(5.0f, &ToolBar, nullptr); + static int s_DeleteButton = 0; + if(DoButton_FontIcon(&s_DeleteButton, FONT_ICON_TRASH, (!pCurrentHistory->m_vpUndoActions.empty() || !pCurrentHistory->m_vpRedoActions.empty()) ? 0 : -1, &Button, 0, "Clear the history.", IGraphics::CORNER_ALL, 9.0f) == 1 || (GotSelection && CLineInput::GetActiveInput() == nullptr && m_Dialog == DIALOG_NONE && UI()->ConsumeHotkey(CUI::HOTKEY_DELETE))) + { + pCurrentHistory->Clear(); + s_ActionSelectedIndex = 0; + } + + // actions list + int RedoSize = (int)pCurrentHistory->m_vpRedoActions.size(); + int UndoSize = (int)pCurrentHistory->m_vpUndoActions.size(); + s_ActionSelectedIndex = RedoSize; + s_ListBox.DoStart(15.0f, RedoSize + UndoSize, 1, 3, s_ActionSelectedIndex, &List); + + for(int i = 0; i < RedoSize; i++) + { + const CListboxItem Item = s_ListBox.DoNextItem(&pCurrentHistory->m_vpRedoActions[i], s_ActionSelectedIndex >= 0 && s_ActionSelectedIndex == i); + if(!Item.m_Visible) + continue; + + Item.m_Rect.VMargin(5.0f, &Label); + + SLabelProperties Props; + Props.m_MaxWidth = Label.w; + Props.m_EllipsisAtEnd = true; + TextRender()->TextColor({.5f, .5f, .5f}); + TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor()); + UI()->DoLabel(&Label, pCurrentHistory->m_vpRedoActions[i]->DisplayText(), 10.0f, TEXTALIGN_ML, Props); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + } + + for(int i = 0; i < UndoSize; i++) + { + const CListboxItem Item = s_ListBox.DoNextItem(&pCurrentHistory->m_vpUndoActions[UndoSize - i - 1], s_ActionSelectedIndex >= RedoSize && s_ActionSelectedIndex == (i + RedoSize)); + if(!Item.m_Visible) + continue; + + Item.m_Rect.VMargin(5.0f, &Label); + + SLabelProperties Props; + Props.m_MaxWidth = Label.w; + Props.m_EllipsisAtEnd = true; + UI()->DoLabel(&Label, pCurrentHistory->m_vpUndoActions[UndoSize - i - 1]->DisplayText(), 10.0f, TEXTALIGN_ML, Props); + } + + { // Base action "Loaded map" that cannot be undone + static int s_BaseAction; + const CListboxItem Item = s_ListBox.DoNextItem(&s_BaseAction, s_ActionSelectedIndex == RedoSize + UndoSize); + if(Item.m_Visible) + { + Item.m_Rect.VMargin(5.0f, &Label); + + UI()->DoLabel(&Label, "Loaded map", 10.0f, TEXTALIGN_ML); + } + } + + const int NewSelected = s_ListBox.DoEnd(); + if(s_ActionSelectedIndex != NewSelected) + { + // Figure out if we should undo or redo some actions + // Undo everything until the selected index + if(NewSelected > s_ActionSelectedIndex) + { + for(int i = 0; i < (NewSelected - s_ActionSelectedIndex); i++) + { + pCurrentHistory->Undo(); + } + } + else + { + for(int i = 0; i < (s_ActionSelectedIndex - NewSelected); i++) + { + pCurrentHistory->Redo(); + } + } + s_ActionSelectedIndex = NewSelected; + } +} + void CEditor::RenderExtraEditorDragBar(CUIRect View, CUIRect DragBar) { enum EDragOperation @@ -7250,6 +7397,12 @@ void CEditor::Render() if(m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr) { + // handle undo/redo hotkeys + if(Input()->KeyPress(KEY_Z) && Input()->ModifierIsPressed()) + UndoLastAction(); + if(Input()->KeyPress(KEY_Y) && Input()->ModifierIsPressed()) + RedoLastAction(); + // handle brush save/load hotkeys for(int i = KEY_1; i <= KEY_0; i++) { @@ -7416,6 +7569,10 @@ void CEditor::Render() { RenderServerSettingsEditor(ExtraEditor, s_ShowServerSettingsEditorLast); } + else if(m_ActiveExtraEditor == EXTRAEDITOR_HISTORY) + { + RenderEditorHistory(ExtraEditor); + } s_ShowServerSettingsEditorLast = m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS; } RenderStatusbar(StatusBar, &TooltipRect); @@ -7583,6 +7740,15 @@ void CEditor::Reset(bool CreateDefault) m_ResetZoomEnvelope = true; m_SettingsCommandInput.Clear(); + + m_EditorHistory.Clear(); + m_EnvelopeEditorHistory.Clear(); + m_ServerSettingsHistory.Clear(); + + m_QuadTracker.m_pEditor = this; + + m_EnvOpTracker.m_pEditor = this; + m_EnvOpTracker.Reset(); } int CEditor::GetTextureUsageFlag() @@ -7678,17 +7844,23 @@ void CEditor::PlaceBorderTiles() { std::shared_ptr pT = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_TILES)); - for(int i = 0; i < pT->m_Width * 2; ++i) - pT->m_pTiles[i].m_Index = 1; - for(int i = 0; i < pT->m_Width * pT->m_Height; ++i) { - if(i % pT->m_Width < 2 || i % pT->m_Width > pT->m_Width - 3) - pT->m_pTiles[i].m_Index = 1; + if(i % pT->m_Width < 2 || i % pT->m_Width > pT->m_Width - 3 || i < pT->m_Width * 2 || i > pT->m_Width * (pT->m_Height - 2)) + { + int x = i % pT->m_Width; + int y = i / pT->m_Width; + + CTile Current = pT->m_pTiles[i]; + Current.m_Index = 1; + pT->SetTile(x, y, Current); + } } - for(int i = (pT->m_Width * (pT->m_Height - 2)); i < pT->m_Width * pT->m_Height; ++i) - pT->m_pTiles[i].m_Index = 1; + int GameGroupIndex = std::find(m_Map.m_vpGroups.begin(), m_Map.m_vpGroups.end(), m_Map.m_pGameGroup) - m_Map.m_vpGroups.begin(); + m_EditorHistory.RecordAction(std::make_shared(this, GameGroupIndex), "Tool 'Make borders'"); + + m_Map.OnModify(); } void CEditor::HandleCursorMovement() @@ -8046,7 +8218,7 @@ bool CEditor::Load(const char *pFileName, int StorageType) return Result; } -bool CEditor::Append(const char *pFileName, int StorageType) +bool CEditor::Append(const char *pFileName, int StorageType, bool IgnoreHistory) { CEditorMap NewMap; NewMap.m_pEditor = this; @@ -8058,6 +8230,12 @@ bool CEditor::Append(const char *pFileName, int StorageType) if(!NewMap.Load(pFileName, StorageType, std::move(ErrorHandler))) return false; + CEditorActionAppendMap::SPrevInfo Info{ + (int)m_Map.m_vpGroups.size(), + (int)m_Map.m_vpImages.size(), + (int)m_Map.m_vpSounds.size(), + (int)m_Map.m_vpEnvelopes.size()}; + static const auto &&s_ReplaceIndex = [](int ToReplace, int ReplaceWith) { return [ToReplace, ReplaceWith](int *pIndex) { if(*pIndex == ToReplace) @@ -8124,10 +8302,33 @@ bool CEditor::Append(const char *pFileName, int StorageType) } NewMap.m_vpGroups.clear(); - SortImages(); + auto IndexMap = SortImages(); + + if(!IgnoreHistory) + m_EditorHistory.RecordAction(std::make_shared(this, pFileName, Info, IndexMap)); // all done \o/ return true; } IEditor *CreateEditor() { return new CEditor; } + +void CEditor::UndoLastAction() +{ + if(m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS) + m_ServerSettingsHistory.Undo(); + else if(m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES) + m_EnvelopeEditorHistory.Undo(); + else + m_EditorHistory.Undo(); +} + +void CEditor::RedoLastAction() +{ + if(m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS) + m_ServerSettingsHistory.Redo(); + else if(m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES) + m_EnvelopeEditorHistory.Redo(); + else + m_EditorHistory.Redo(); +} diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 9dcd36b7254..7eb0cfe2009 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -30,6 +30,8 @@ #include #include "auto_map.h" +#include "editor_history.h" +#include "editor_trackers.h" #include "map_view.h" #include "smooth_value.h" @@ -452,7 +454,7 @@ class CEditor : public IEditor bool Save(const char *pFilename) override; bool Load(const char *pFilename, int StorageType) override; bool HandleMapDrop(const char *pFilename, int StorageType) override; - bool Append(const char *pFilename, int StorageType); + bool Append(const char *pFilename, int StorageType, bool IgnoreHistory = false); void LoadCurrentMap(); void Render(); @@ -494,7 +496,10 @@ class CEditor : public IEditor bool IsTangentInSelected() const; bool IsTangentOutSelected() const; bool IsTangentSelected() const; + std::pair EnvGetSelectedTimeAndValue() const; + template + SEditResult DoPropertiesWithState(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f)); int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f)); CUI::SColorPickerPopupContext m_ColorPickerPopupContext; @@ -694,10 +699,11 @@ class CEditor : public IEditor EXTRAEDITOR_NONE = -1, EXTRAEDITOR_ENVELOPES, EXTRAEDITOR_SERVER_SETTINGS, + EXTRAEDITOR_HISTORY, NUM_EXTRAEDITORS, }; EExtraEditor m_ActiveExtraEditor = EXTRAEDITOR_NONE; - float m_aExtraEditorSplits[NUM_EXTRAEDITORS] = {250.0f, 250.0f}; + float m_aExtraEditorSplits[NUM_EXTRAEDITORS] = {250.0f, 250.0f, 250.0f}; enum EShowEnvelope { @@ -752,7 +758,7 @@ class CEditor : public IEditor CImageInfo m_TileartImageInfo; char m_aTileartFilename[IO_MAX_PATH_LENGTH]; - void AddTileart(); + void AddTileart(bool IgnoreHistory = false); void TileartCheckColors(); void PlaceBorderTiles(); @@ -779,7 +785,7 @@ class CEditor : public IEditor void RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture, float Size, float Brightness); - int UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree = false, bool IsHex = false, int corners = IGraphics::CORNER_ALL, ColorRGBA *pColor = nullptr, bool ShowValue = true); + SEditResult UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree = false, bool IsHex = false, int corners = IGraphics::CORNER_ALL, ColorRGBA *pColor = nullptr, bool ShowValue = true); static CUI::EPopupMenuFunctionResult PopupMenuFile(void *pContext, CUIRect View, bool Active); static CUI::EPopupMenuFunctionResult PopupMenuTools(void *pContext, CUIRect View, bool Active); @@ -789,6 +795,7 @@ class CEditor : public IEditor { CEditor *m_pEditor; std::vector> m_vpLayers; + std::vector m_vLayerIndices; CLayerTiles::SCommonPropState m_CommonPropState; }; static CUI::EPopupMenuFunctionResult PopupLayer(void *pContext, CUIRect View, bool Active); @@ -837,7 +844,7 @@ class CEditor : public IEditor void DoQuadEnvelopes(const std::vector &vQuads, IGraphics::CTextureHandle Texture = IGraphics::CTextureHandle()); void DoQuadEnvPoint(const CQuad *pQuad, int QIndex, int pIndex); - void DoQuadPoint(CQuad *pQuad, int QuadIndex, int v); + void DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int v); void SetHotQuadPoint(const std::shared_ptr &pLayer); float TriangleArea(vec2 A, vec2 B, vec2 C); @@ -849,7 +856,7 @@ class CEditor : public IEditor void DoMapEditor(CUIRect View); void DoToolbarLayers(CUIRect Toolbar); void DoToolbarSounds(CUIRect Toolbar); - void DoQuad(CQuad *pQuad, int Index); + void DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, int Index); ColorRGBA GetButtonColor(const void *pID, int Checked); bool ReplaceImage(const char *pFilename, int StorageType, bool CheckDuplicate); @@ -874,6 +881,7 @@ class CEditor : public IEditor void RenderEnvelopeEditor(CUIRect View); void RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEditorLast); + void RenderEditorHistory(CUIRect View); void RenderExtraEditorDragBar(CUIRect View, CUIRect DragBar); @@ -883,7 +891,7 @@ class CEditor : public IEditor void RenderFileDialog(); void SelectGameLayer(); - void SortImages(); + std::vector SortImages(); bool SelectLayerByTile(); void DoAudioPreview(CUIRect View, const void *pPlayPauseButtonID, const void *pStopButtonID, const void *pSeekBarID, const int SampleID); @@ -1002,6 +1010,17 @@ class CEditor : public IEditor unsigned char m_SwitchNum; unsigned char m_SwitchDelay; + + // Undo/Redo + CEditorHistory m_EditorHistory; + CEditorHistory m_ServerSettingsHistory; + CEditorHistory m_EnvelopeEditorHistory; + CQuadEditTracker m_QuadTracker; + CEnvelopeEditorOperationTracker m_EnvOpTracker; + +private: + void UndoLastAction(); + void RedoLastAction(); }; // make sure to inline this function diff --git a/src/game/editor/editor_action.h b/src/game/editor/editor_action.h new file mode 100644 index 00000000000..d4fb124df45 --- /dev/null +++ b/src/game/editor/editor_action.h @@ -0,0 +1,30 @@ +#ifndef GAME_EDITOR_EDITOR_ACTION_H +#define GAME_EDITOR_EDITOR_ACTION_H + +#include + +class CEditor; + +class IEditorAction +{ +public: + IEditorAction(CEditor *pEditor) : + m_pEditor(pEditor) {} + + IEditorAction() = default; + + virtual ~IEditorAction() = default; + + virtual void Undo() = 0; + virtual void Redo() = 0; + + virtual bool IsEmpty() { return false; } + + const char *DisplayText() const { return m_aDisplayText; } + +protected: + CEditor *m_pEditor; + char m_aDisplayText[256]; +}; + +#endif diff --git a/src/game/editor/editor_actions.cpp b/src/game/editor/editor_actions.cpp new file mode 100644 index 00000000000..07da003668d --- /dev/null +++ b/src/game/editor/editor_actions.cpp @@ -0,0 +1,2065 @@ +#include "editor_actions.h" +#include + +CEditorBrushDrawAction::CEditorBrushDrawAction(CEditor *pEditor, int Group) : + IEditorAction(pEditor), m_Group(Group) +{ + auto &Map = pEditor->m_Map; + for(size_t k = 0; k < Map.m_vpGroups[Group]->m_vpLayers.size(); k++) + { + auto pLayer = Map.m_vpGroups[Group]->m_vpLayers[k]; + + if(pLayer->m_Type == LAYERTYPE_TILES) + { + auto pLayerTiles = std::static_pointer_cast(pLayer); + + if(pLayer == Map.m_pTeleLayer) + { + if(!Map.m_pTeleLayer->m_History.empty()) + { + m_TeleTileChanges = std::map(Map.m_pTeleLayer->m_History); + Map.m_pTeleLayer->ClearHistory(); + } + } + else if(pLayer == Map.m_pTuneLayer) + { + if(!Map.m_pTuneLayer->m_History.empty()) + { + m_TuneTileChanges = std::map(Map.m_pTuneLayer->m_History); + Map.m_pTuneLayer->ClearHistory(); + } + } + else if(pLayer == Map.m_pSwitchLayer) + { + if(!Map.m_pSwitchLayer->m_History.empty()) + { + m_SwitchTileChanges = std::map(Map.m_pSwitchLayer->m_History); + Map.m_pSwitchLayer->ClearHistory(); + } + } + else if(pLayer == Map.m_pSpeedupLayer) + { + if(!Map.m_pSpeedupLayer->m_History.empty()) + { + m_SpeedupTileChanges = std::map(Map.m_pSpeedupLayer->m_History); + Map.m_pSpeedupLayer->ClearHistory(); + } + } + + if(!pLayerTiles->m_TilesHistory.empty()) + { + m_vTileChanges.emplace_back(std::make_pair(k, std::map(pLayerTiles->m_TilesHistory))); + pLayerTiles->ClearHistory(); + } + } + } + + SetInfos(); + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Brush draw (x%d) on %d layers", m_TotalTilesDrawn, m_TotalLayers); +} + +void CEditorBrushDrawAction::SetInfos() +{ + m_TotalTilesDrawn = 0; + m_TotalLayers = 0; + + // Process normal tiles + for(auto const &Pair : m_vTileChanges) + { + int Layer = Pair.first; + std::shared_ptr pLayer = m_pEditor->m_Map.m_vpGroups[m_Group]->m_vpLayers[Layer]; + m_TotalLayers++; + + if(pLayer->m_Type == LAYERTYPE_TILES) + { + std::shared_ptr pLayerTiles = std::static_pointer_cast(pLayer); + auto Changes = Pair.second; + for(auto &Change : Changes) + { + m_TotalTilesDrawn += Change.second.size(); + } + } + } + + // Process speedup tiles + for(auto const &SpeedupChange : m_SpeedupTileChanges) + { + m_TotalTilesDrawn += SpeedupChange.second.size(); + } + + // Process tele tiles + for(auto const &TeleChange : m_TeleTileChanges) + { + m_TotalTilesDrawn += TeleChange.second.size(); + } + + // Process switch tiles + for(auto const &SwitchChange : m_SwitchTileChanges) + { + m_TotalTilesDrawn += SwitchChange.second.size(); + } + + // Process tune tiles + for(auto const &TuneChange : m_TuneTileChanges) + { + m_TotalTilesDrawn += TuneChange.second.size(); + } + + m_TotalLayers += !m_SpeedupTileChanges.empty(); + m_TotalLayers += !m_SwitchTileChanges.empty(); + m_TotalLayers += !m_TeleTileChanges.empty(); + m_TotalLayers += !m_TuneTileChanges.empty(); +} + +bool CEditorBrushDrawAction::IsEmpty() +{ + return m_vTileChanges.empty() && m_SpeedupTileChanges.empty() && m_SwitchTileChanges.empty() && m_TeleTileChanges.empty() && m_TuneTileChanges.empty(); +} + +void CEditorBrushDrawAction::Undo() +{ + Apply(true); +} + +void CEditorBrushDrawAction::Redo() +{ + Apply(false); +} + +void CEditorBrushDrawAction::Apply(bool Undo) +{ + auto &Map = m_pEditor->m_Map; + + // Process normal tiles + for(auto const &Pair : m_vTileChanges) + { + int Layer = Pair.first; + std::shared_ptr pLayer = Map.m_vpGroups[m_Group]->m_vpLayers[Layer]; + + if(pLayer->m_Type == LAYERTYPE_TILES) + { + std::shared_ptr pLayerTiles = std::static_pointer_cast(pLayer); + auto Changes = Pair.second; + for(auto &Change : Changes) + { + int y = Change.first; + auto Line = Change.second; + for(auto &Tile : Line) + { + int x = Tile.first; + STileStateChange State = Tile.second; + pLayerTiles->SetTileIgnoreHistory(x, y, Undo ? State.m_Previous : State.m_Current); + } + } + } + } + + // Process speedup tiles + for(auto const &SpeedupChange : m_SpeedupTileChanges) + { + int y = SpeedupChange.first; + auto Line = SpeedupChange.second; + for(auto &Tile : Line) + { + int x = Tile.first; + int Index = y * Map.m_pSpeedupLayer->m_Width + x; + SSpeedupTileStateChange State = Tile.second; + SSpeedupTileStateChange::SData Data = Undo ? State.m_Previous : State.m_Current; + + Map.m_pSpeedupLayer->m_pSpeedupTile[Index].m_Force = Data.m_Force; + Map.m_pSpeedupLayer->m_pSpeedupTile[Index].m_MaxSpeed = Data.m_MaxSpeed; + Map.m_pSpeedupLayer->m_pSpeedupTile[Index].m_Angle = Data.m_Angle; + Map.m_pSpeedupLayer->m_pSpeedupTile[Index].m_Type = Data.m_Type; + Map.m_pSpeedupLayer->m_pTiles[Index].m_Index = Data.m_Index; + } + } + + // Process tele tiles + for(auto const &TeleChange : m_TeleTileChanges) + { + int y = TeleChange.first; + auto Line = TeleChange.second; + for(auto &Tile : Line) + { + int x = Tile.first; + int Index = y * Map.m_pTeleLayer->m_Width + x; + STeleTileStateChange State = Tile.second; + STeleTileStateChange::SData Data = Undo ? State.m_Previous : State.m_Current; + + Map.m_pTeleLayer->m_pTeleTile[Index].m_Number = Data.m_Number; + Map.m_pTeleLayer->m_pTeleTile[Index].m_Type = Data.m_Type; + Map.m_pTeleLayer->m_pTiles[Index].m_Index = Data.m_Index; + } + } + + // Process switch tiles + for(auto const &SwitchChange : m_SwitchTileChanges) + { + int y = SwitchChange.first; + auto Line = SwitchChange.second; + for(auto &Tile : Line) + { + int x = Tile.first; + int Index = y * Map.m_pSwitchLayer->m_Width + x; + SSwitchTileStateChange State = Tile.second; + SSwitchTileStateChange::SData Data = Undo ? State.m_Previous : State.m_Current; + + Map.m_pSwitchLayer->m_pSwitchTile[Index].m_Number = Data.m_Number; + Map.m_pSwitchLayer->m_pSwitchTile[Index].m_Type = Data.m_Type; + Map.m_pSwitchLayer->m_pSwitchTile[Index].m_Flags = Data.m_Flags; + Map.m_pSwitchLayer->m_pSwitchTile[Index].m_Delay = Data.m_Delay; + Map.m_pSwitchLayer->m_pTiles[Index].m_Index = Data.m_Index; + } + } + + // Process tune tiles + for(auto const &TuneChange : m_TuneTileChanges) + { + int y = TuneChange.first; + auto Line = TuneChange.second; + for(auto &Tile : Line) + { + int x = Tile.first; + int Index = y * Map.m_pTuneLayer->m_Width + x; + STuneTileStateChange State = Tile.second; + STuneTileStateChange::SData Data = Undo ? State.m_Previous : State.m_Current; + + Map.m_pTuneLayer->m_pTuneTile[Index].m_Number = Data.m_Number; + Map.m_pTuneLayer->m_pTuneTile[Index].m_Type = Data.m_Type; + Map.m_pTuneLayer->m_pTiles[Index].m_Index = Data.m_Index; + } + } +} + +// ------------------------------------------- + +CEditorActionQuadPlace::CEditorActionQuadPlace(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector &vBrush) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_vBrush(vBrush) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Quad place (x%d)", (int)m_vBrush.size()); +} + +void CEditorActionQuadPlace::Undo() +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + for(size_t k = 0; k < m_vBrush.size(); k++) + pLayerQuads->m_vQuads.pop_back(); + + m_pEditor->m_Map.OnModify(); +} +void CEditorActionQuadPlace::Redo() +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + for(auto &Brush : m_vBrush) + pLayerQuads->m_vQuads.push_back(Brush); + + m_pEditor->m_Map.OnModify(); +} + +CEditorActionSoundPlace::CEditorActionSoundPlace(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector &vBrush) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_vBrush(vBrush) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Sound place (x%d)", (int)m_vBrush.size()); +} + +void CEditorActionSoundPlace::Undo() +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + for(size_t k = 0; k < m_vBrush.size(); k++) + pLayerSounds->m_vSources.pop_back(); + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionSoundPlace::Redo() +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + for(auto &Brush : m_vBrush) + pLayerSounds->m_vSources.push_back(Brush); + + m_pEditor->m_Map.OnModify(); +} + +// --------------------------------------------------------------------------------------- + +CEditorActionDeleteQuad::CEditorActionDeleteQuad(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector const &vQuadsIndices, std::vector const &vDeletedQuads) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_vQuadsIndices(vQuadsIndices), m_vDeletedQuads(vDeletedQuads) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete quad (x%d)", (int)m_vDeletedQuads.size()); +} + +void CEditorActionDeleteQuad::Undo() +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + for(size_t k = 0; k < m_vQuadsIndices.size(); k++) + { + pLayerQuads->m_vQuads.insert(pLayerQuads->m_vQuads.begin() + m_vQuadsIndices[k], m_vDeletedQuads[k]); + } +} + +void CEditorActionDeleteQuad::Redo() +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + std::vector vQuads(m_vQuadsIndices); + + for(int i = 0; i < (int)vQuads.size(); ++i) + { + pLayerQuads->m_vQuads.erase(pLayerQuads->m_vQuads.begin() + vQuads[i]); + for(int j = i + 1; j < (int)vQuads.size(); ++j) + if(vQuads[j] > vQuads[i]) + vQuads[j]--; + + vQuads.erase(vQuads.begin() + i); + + i--; + } +} + +// --------------------------------------------------------------------------------------- + +CEditorActionEditQuadPoint::CEditorActionEditQuadPoint(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, std::vector const &vPreviousPoints, std::vector const &vCurrentPoints) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_vPreviousPoints(vPreviousPoints), m_vCurrentPoints(vCurrentPoints) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit quad points"); +} + +void CEditorActionEditQuadPoint::Undo() +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + CQuad &Quad = pLayerQuads->m_vQuads[m_QuadIndex]; + for(int k = 0; k < 5; k++) + Quad.m_aPoints[k] = m_vPreviousPoints[k]; +} + +void CEditorActionEditQuadPoint::Redo() +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + CQuad &Quad = pLayerQuads->m_vQuads[m_QuadIndex]; + for(int k = 0; k < 5; k++) + Quad.m_aPoints[k] = m_vCurrentPoints[k]; +} + +CEditorActionEditQuadProp::CEditorActionEditQuadProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, EQuadProp Prop, int Previous, int Current) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_Prop(Prop), m_Previous(Previous), m_Current(Current) +{ + static const char *s_apNames[] = { + "order", + "pos X", + "pos Y", + "pos env", + "pos env offset", + "color env", + "color env offset"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit quad %s property in layer %d of group %d", s_apNames[(int)m_Prop], m_LayerIndex, m_GroupIndex); +} + +void CEditorActionEditQuadProp::Undo() +{ + Apply(m_Previous); +} + +void CEditorActionEditQuadProp::Redo() +{ + Apply(m_Current); +} + +void CEditorActionEditQuadProp::Apply(int Value) +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + CQuad &Quad = pLayerQuads->m_vQuads[m_QuadIndex]; + if(m_Prop == EQuadProp::PROP_POS_ENV) + Quad.m_PosEnv = Value; + else if(m_Prop == EQuadProp::PROP_POS_ENV_OFFSET) + Quad.m_PosEnvOffset = Value; + else if(m_Prop == EQuadProp::PROP_COLOR_ENV) + Quad.m_ColorEnv = Value; + else if(m_Prop == EQuadProp::PROP_COLOR_ENV_OFFSET) + Quad.m_ColorEnvOffset = Value; +} + +CEditorActionEditQuadPointProp::CEditorActionEditQuadPointProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, int PointIndex, EQuadPointProp Prop, int Previous, int Current) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_PointIndex(PointIndex), m_Prop(Prop), m_Previous(Previous), m_Current(Current) +{ + static const char *s_apNames[] = { + "pos X", + "pos Y", + "color", + "tex U", + "tex V"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit quad point %s property in layer %d of group %d", s_apNames[(int)m_Prop], m_LayerIndex, m_GroupIndex); +} + +void CEditorActionEditQuadPointProp::Undo() +{ + Apply(m_Previous); +} + +void CEditorActionEditQuadPointProp::Redo() +{ + Apply(m_Current); +} + +void CEditorActionEditQuadPointProp::Apply(int Value) +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + CQuad &Quad = pLayerQuads->m_vQuads[m_QuadIndex]; + + if(m_Prop == EQuadPointProp::PROP_COLOR) + { + const ColorRGBA ColorPick = ColorRGBA::UnpackAlphaLast(Value); + + Quad.m_aColors[m_PointIndex].r = ColorPick.r * 255.0f; + Quad.m_aColors[m_PointIndex].g = ColorPick.g * 255.0f; + Quad.m_aColors[m_PointIndex].b = ColorPick.b * 255.0f; + Quad.m_aColors[m_PointIndex].a = ColorPick.a * 255.0f; + + m_pEditor->m_ColorPickerPopupContext.m_RgbaColor = ColorPick; + m_pEditor->m_ColorPickerPopupContext.m_HslaColor = color_cast(ColorPick); + m_pEditor->m_ColorPickerPopupContext.m_HsvaColor = color_cast(m_pEditor->m_ColorPickerPopupContext.m_HslaColor); + } + else if(m_Prop == EQuadPointProp::PROP_TEX_U) + { + Quad.m_aTexcoords[m_PointIndex].x = Value; + } + else if(m_Prop == EQuadPointProp::PROP_TEX_V) + { + Quad.m_aTexcoords[m_PointIndex].y = Value; + } +} + +// --------------------------------------------------------------------------------------- + +CEditorActionBulk::CEditorActionBulk(CEditor *pEditor, const std::vector> &vpActions, const char *pDisplay, bool Reverse) : + IEditorAction(pEditor), m_vpActions(vpActions), m_Reverse(Reverse) +{ + // Assuming we only use bulk for actions of same type, if no display was provided + if(!pDisplay) + { + const char *pBaseDisplay = m_vpActions[0]->DisplayText(); + if(m_vpActions.size() == 1) + str_copy(m_aDisplayText, pBaseDisplay); + else + str_format(m_aDisplayText, sizeof(m_aDisplayText), "%s (x%d)", pBaseDisplay, (int)m_vpActions.size()); + } + else + { + str_copy(m_aDisplayText, pDisplay); + } +} + +void CEditorActionBulk::Undo() +{ + if(m_Reverse) + { + for(auto pIt = m_vpActions.rbegin(); pIt != m_vpActions.rend(); pIt++) + { + auto &pAction = *pIt; + pAction->Undo(); + } + } + else + { + for(auto &pAction : m_vpActions) + { + pAction->Undo(); + } + } +} + +void CEditorActionBulk::Redo() +{ + for(auto &pAction : m_vpActions) + { + pAction->Redo(); + } +} + +// --------- + +CEditorActionAutoMap::CEditorActionAutoMap(CEditor *pEditor, int GroupIndex, int LayerIndex, const EditorTileStateChangeHistory &Changes) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_Changes(Changes) +{ + ComputeInfos(); + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Auto map (x%d)", m_TotalChanges); +} + +void CEditorActionAutoMap::Undo() +{ + Apply(true); +} + +void CEditorActionAutoMap::Redo() +{ + Apply(false); +} + +void CEditorActionAutoMap::Apply(bool Undo) +{ + auto &Map = m_pEditor->m_Map; + std::shared_ptr pLayerTiles = std::static_pointer_cast(m_pLayer); + for(auto &Change : m_Changes) + { + int y = Change.first; + auto Line = Change.second; + for(auto &Tile : Line) + { + int x = Tile.first; + STileStateChange State = Tile.second; + pLayerTiles->SetTileIgnoreHistory(x, y, Undo ? State.m_Previous : State.m_Current); + } + } + + Map.OnModify(); +} + +void CEditorActionAutoMap::ComputeInfos() +{ + m_TotalChanges = 0; + for(auto &Line : m_Changes) + m_TotalChanges += Line.second.size(); +} + +// --------- + +CEditorActionLayerBase::CEditorActionLayerBase(CEditor *pEditor, int GroupIndex, int LayerIndex) : + IEditorAction(pEditor), m_GroupIndex(GroupIndex), m_LayerIndex(LayerIndex) +{ + m_pLayer = pEditor->m_Map.m_vpGroups[GroupIndex]->m_vpLayers[LayerIndex]; +} + +// ---------- + +CEditorActionAddLayer::CEditorActionAddLayer(CEditor *pEditor, int GroupIndex, int LayerIndex, bool Duplicate) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_Duplicate(Duplicate) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "%s %s layer in group %d", m_Duplicate ? "Duplicate" : "New", m_pLayer->TypeName(), m_GroupIndex); +} + +void CEditorActionAddLayer::Undo() +{ + // Undo: remove layer from vector but keep it in case we want to add it back + auto &vLayers = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_vpLayers; + vLayers.erase(vLayers.begin() + m_LayerIndex); + + m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_Collapse = false; + if(m_LayerIndex >= (int)vLayers.size()) + m_pEditor->SelectLayer(vLayers.size() - 1, m_GroupIndex); + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionAddLayer::Redo() +{ + // Redo: add back the removed layer contained in this class + auto &vLayers = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_vpLayers; + vLayers.insert(vLayers.begin() + m_LayerIndex, m_pLayer); + + m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_Collapse = false; + m_pEditor->SelectLayer(m_LayerIndex, m_GroupIndex); + m_pEditor->m_Map.OnModify(); +} + +CEditorActionDeleteLayer::CEditorActionDeleteLayer(CEditor *pEditor, int GroupIndex, int LayerIndex) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete %s layer of group %d", m_pLayer->TypeName(), m_GroupIndex); +} + +void CEditorActionDeleteLayer::Redo() +{ + // Redo: remove layer from vector but keep it in case we want to add it back + auto &vLayers = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_vpLayers; + + if(m_pLayer->m_Type == LAYERTYPE_TILES) + { + std::shared_ptr pLayerTiles = std::static_pointer_cast(m_pLayer); + if(pLayerTiles->m_Front) + m_pEditor->m_Map.m_pFrontLayer = nullptr; + else if(pLayerTiles->m_Tele) + m_pEditor->m_Map.m_pTeleLayer = nullptr; + else if(pLayerTiles->m_Speedup) + m_pEditor->m_Map.m_pSpeedupLayer = nullptr; + else if(pLayerTiles->m_Switch) + m_pEditor->m_Map.m_pSwitchLayer = nullptr; + else if(pLayerTiles->m_Tune) + m_pEditor->m_Map.m_pTuneLayer = nullptr; + } + + m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->DeleteLayer(m_LayerIndex); + + m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_Collapse = false; + if(m_LayerIndex >= (int)vLayers.size()) + m_pEditor->SelectLayer(vLayers.size() - 1, m_GroupIndex); + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionDeleteLayer::Undo() +{ + // Undo: add back the removed layer contained in this class + auto &vLayers = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_vpLayers; + + if(m_pLayer->m_Type == LAYERTYPE_TILES) + { + std::shared_ptr pLayerTiles = std::static_pointer_cast(m_pLayer); + if(pLayerTiles->m_Front) + m_pEditor->m_Map.m_pFrontLayer = std::static_pointer_cast(m_pLayer); + else if(pLayerTiles->m_Tele) + m_pEditor->m_Map.m_pTeleLayer = std::static_pointer_cast(m_pLayer); + else if(pLayerTiles->m_Speedup) + m_pEditor->m_Map.m_pSpeedupLayer = std::static_pointer_cast(m_pLayer); + else if(pLayerTiles->m_Switch) + m_pEditor->m_Map.m_pSwitchLayer = std::static_pointer_cast(m_pLayer); + else if(pLayerTiles->m_Tune) + m_pEditor->m_Map.m_pTuneLayer = std::static_pointer_cast(m_pLayer); + } + + vLayers.insert(vLayers.begin() + m_LayerIndex, m_pLayer); + + m_pEditor->m_Map.m_vpGroups[m_GroupIndex]->m_Collapse = false; + m_pEditor->SelectLayer(m_LayerIndex, m_GroupIndex); + m_pEditor->m_Map.OnModify(); +} + +CEditorActionGroup::CEditorActionGroup(CEditor *pEditor, int GroupIndex, bool Delete) : + IEditorAction(pEditor), m_GroupIndex(GroupIndex), m_Delete(Delete) +{ + m_pGroup = pEditor->m_Map.m_vpGroups[GroupIndex]; + if(m_Delete) + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete group %d", m_GroupIndex); + else + str_format(m_aDisplayText, sizeof(m_aDisplayText), "New group"); +} + +void CEditorActionGroup::Undo() +{ + if(m_Delete) + { + // Undo: add back the group + m_pEditor->m_Map.m_vpGroups.insert(m_pEditor->m_Map.m_vpGroups.begin() + m_GroupIndex, m_pGroup); + m_pEditor->m_SelectedGroup = m_GroupIndex; + m_pEditor->m_Map.OnModify(); + } + else + { + // Undo: delete the group + m_pEditor->m_Map.DeleteGroup(m_GroupIndex); + m_pEditor->m_SelectedGroup = maximum(0, m_GroupIndex - 1); + } + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionGroup::Redo() +{ + if(!m_Delete) + { + // Redo: add back the group + m_pEditor->m_Map.m_vpGroups.insert(m_pEditor->m_Map.m_vpGroups.begin() + m_GroupIndex, m_pGroup); + m_pEditor->m_SelectedGroup = m_GroupIndex; + } + else + { + // Redo: delete the group + m_pEditor->m_Map.DeleteGroup(m_GroupIndex); + m_pEditor->m_SelectedGroup = maximum(0, m_GroupIndex - 1); + } + + m_pEditor->m_Map.OnModify(); +} + +CEditorActionEditGroupProp::CEditorActionEditGroupProp(CEditor *pEditor, int GroupIndex, EGroupProp Prop, int Previous, int Current) : + IEditorAction(pEditor), m_GroupIndex(GroupIndex), m_Prop(Prop), m_Previous(Previous), m_Current(Current) +{ + static const char *s_apNames[] = { + "order", + "pos X", + "pos Y", + "para X", + "para Y", + "use clipping", + "clip X", + "clip Y", + "clip W", + "clip H"}; + + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit group %d %s property", m_GroupIndex, s_apNames[(int)Prop]); +} + +void CEditorActionEditGroupProp::Undo() +{ + auto pGroup = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]; + + if(m_Prop == EGroupProp::PROP_ORDER) + { + int CurrentOrder = m_Current; + bool Dir = m_Current > m_Previous; + while(CurrentOrder != m_Previous) + { + CurrentOrder = m_pEditor->m_Map.SwapGroups(CurrentOrder, Dir ? CurrentOrder - 1 : CurrentOrder + 1); + } + m_pEditor->m_SelectedGroup = m_Previous; + } + else + Apply(m_Previous); +} + +void CEditorActionEditGroupProp::Redo() +{ + auto pGroup = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]; + + if(m_Prop == EGroupProp::PROP_ORDER) + { + int CurrentOrder = m_Previous; + bool Dir = m_Previous > m_Current; + while(CurrentOrder != m_Current) + { + CurrentOrder = m_pEditor->m_Map.SwapGroups(CurrentOrder, Dir ? CurrentOrder - 1 : CurrentOrder + 1); + } + m_pEditor->m_SelectedGroup = m_Current; + } + else + Apply(m_Current); +} + +void CEditorActionEditGroupProp::Apply(int Value) +{ + auto pGroup = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]; + + if(m_Prop == EGroupProp::PROP_POS_X) + pGroup->m_OffsetX = Value; + if(m_Prop == EGroupProp::PROP_POS_Y) + pGroup->m_OffsetY = Value; + if(m_Prop == EGroupProp::PROP_PARA_X) + pGroup->m_ParallaxX = Value; + if(m_Prop == EGroupProp::PROP_PARA_Y) + pGroup->m_ParallaxY = Value; + if(m_Prop == EGroupProp::PROP_USE_CLIPPING) + pGroup->m_UseClipping = Value; + if(m_Prop == EGroupProp::PROP_CLIP_X) + pGroup->m_ClipX = Value; + if(m_Prop == EGroupProp::PROP_CLIP_Y) + pGroup->m_ClipY = Value; + if(m_Prop == EGroupProp::PROP_CLIP_W) + pGroup->m_ClipW = Value; + if(m_Prop == EGroupProp::PROP_CLIP_H) + pGroup->m_ClipH = Value; + + m_pEditor->m_Map.OnModify(); +} + +template +CEditorActionEditLayerPropBase::CEditorActionEditLayerPropBase(CEditor *pEditor, int GroupIndex, int LayerIndex, E Prop, int Previous, int Current) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_Prop(Prop), m_Previous(Previous), m_Current(Current) +{ +} + +CEditorActionEditLayerProp::CEditorActionEditLayerProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerProp Prop, int Previous, int Current) : + CEditorActionEditLayerPropBase(pEditor, GroupIndex, LayerIndex, Prop, Previous, Current) +{ + static const char *s_apNames[] = { + "group", + "order", + "HQ"}; + + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit layer %d in group %d %s property", m_LayerIndex, m_GroupIndex, s_apNames[(int)m_Prop]); +} + +void CEditorActionEditLayerProp::Undo() +{ + auto pCurrentGroup = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]; + + if(m_Prop == ELayerProp::PROP_ORDER) + { + m_pEditor->SelectLayer(pCurrentGroup->SwapLayers(m_Current, m_Previous)); + } + else + Apply(m_Previous); +} + +void CEditorActionEditLayerProp::Redo() +{ + auto pCurrentGroup = m_pEditor->m_Map.m_vpGroups[m_GroupIndex]; + + if(m_Prop == ELayerProp::PROP_ORDER) + { + m_pEditor->SelectLayer(pCurrentGroup->SwapLayers(m_Previous, m_Current)); + } + else + Apply(m_Current); +} + +void CEditorActionEditLayerProp::Apply(int Value) +{ + if(m_Prop == ELayerProp::PROP_GROUP) + { + auto pCurrentGroup = m_pEditor->m_Map.m_vpGroups[Value == m_Previous ? m_Current : m_Previous]; + auto Position = std::find(pCurrentGroup->m_vpLayers.begin(), pCurrentGroup->m_vpLayers.end(), m_pLayer); + if(Position != pCurrentGroup->m_vpLayers.end()) + pCurrentGroup->m_vpLayers.erase(Position); + m_pEditor->m_Map.m_vpGroups[Value]->m_vpLayers.push_back(m_pLayer); + m_pEditor->m_SelectedGroup = Value; + m_pEditor->SelectLayer(m_pEditor->m_Map.m_vpGroups[Value]->m_vpLayers.size() - 1); + } + else if(m_Prop == ELayerProp::PROP_HQ) + { + m_pLayer->m_Flags = Value; + } + + m_pEditor->m_Map.OnModify(); +} + +CEditorActionEditLayerTilesProp::CEditorActionEditLayerTilesProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ETilesProp Prop, int Previous, int Current) : + CEditorActionEditLayerPropBase(pEditor, GroupIndex, LayerIndex, Prop, Previous, Current) +{ + static const char *s_apNames[] = { + "width", + "height", + "shift", + "shift by", + "image", + "color", + "color env", + "color env offset", + "automapper", + "seed"}; + + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit tiles layer %d in group %d %s property", m_LayerIndex, m_GroupIndex, s_apNames[(int)Prop]); +} + +void CEditorActionEditLayerTilesProp::SetSavedLayers(const std::map> &SavedLayers) +{ + m_SavedLayers = std::map(SavedLayers); +} + +void CEditorActionEditLayerTilesProp::Undo() +{ + std::shared_ptr pLayerTiles = std::static_pointer_cast(m_pLayer); + std::shared_ptr pSavedLayerTiles = nullptr; + + if(m_Prop == ETilesProp::PROP_WIDTH || m_Prop == ETilesProp::PROP_HEIGHT) + { + if(m_Prop == ETilesProp::PROP_HEIGHT) + pLayerTiles->Resize(pLayerTiles->m_Width, m_Previous); + else if(m_Prop == ETilesProp::PROP_WIDTH) + pLayerTiles->Resize(m_Previous, pLayerTiles->m_Height); + + RestoreLayer(LAYERTYPE_TILES, pLayerTiles); + if(pLayerTiles->m_Game || pLayerTiles->m_Front || pLayerTiles->m_Switch || pLayerTiles->m_Speedup || pLayerTiles->m_Tune) + { + if(m_pEditor->m_Map.m_pFrontLayer && !pLayerTiles->m_Front) + RestoreLayer(LAYERTYPE_FRONT, m_pEditor->m_Map.m_pFrontLayer); + if(m_pEditor->m_Map.m_pTeleLayer && !pLayerTiles->m_Tele) + RestoreLayer(LAYERTYPE_TELE, m_pEditor->m_Map.m_pTeleLayer); + if(m_pEditor->m_Map.m_pSwitchLayer && !pLayerTiles->m_Switch) + RestoreLayer(LAYERTYPE_SWITCH, m_pEditor->m_Map.m_pSwitchLayer); + if(m_pEditor->m_Map.m_pSpeedupLayer && !pLayerTiles->m_Speedup) + RestoreLayer(LAYERTYPE_SPEEDUP, m_pEditor->m_Map.m_pSpeedupLayer); + if(m_pEditor->m_Map.m_pTuneLayer && !pLayerTiles->m_Tune) + RestoreLayer(LAYERTYPE_TUNE, m_pEditor->m_Map.m_pTuneLayer); + if(!pLayerTiles->m_Game) + RestoreLayer(LAYERTYPE_GAME, m_pEditor->m_Map.m_pGameLayer); + } + } + else if(m_Prop == ETilesProp::PROP_SHIFT) + { + RestoreLayer(LAYERTYPE_TILES, pLayerTiles); + } + else if(m_Prop == ETilesProp::PROP_SHIFT_BY) + { + m_pEditor->m_ShiftBy = m_Previous; + } + else if(m_Prop == ETilesProp::PROP_IMAGE) + { + if(m_Previous == -1) + { + pLayerTiles->m_Image = -1; + } + else + { + pLayerTiles->m_Image = m_Previous % m_pEditor->m_Map.m_vpImages.size(); + pLayerTiles->m_AutoMapperConfig = -1; + } + } + else if(m_Prop == ETilesProp::PROP_COLOR) + { + const ColorRGBA ColorPick = ColorRGBA::UnpackAlphaLast(m_Previous); + + pLayerTiles->m_Color.r = ColorPick.r * 255.0f; + pLayerTiles->m_Color.g = ColorPick.g * 255.0f; + pLayerTiles->m_Color.b = ColorPick.b * 255.0f; + pLayerTiles->m_Color.a = ColorPick.a * 255.0f; + + m_pEditor->m_ColorPickerPopupContext.m_RgbaColor = ColorPick; + m_pEditor->m_ColorPickerPopupContext.m_HslaColor = color_cast(ColorPick); + m_pEditor->m_ColorPickerPopupContext.m_HsvaColor = color_cast(m_pEditor->m_ColorPickerPopupContext.m_HslaColor); + } + else if(m_Prop == ETilesProp::PROP_COLOR_ENV) + { + pLayerTiles->m_ColorEnv = m_Previous; + } + else if(m_Prop == ETilesProp::PROP_COLOR_ENV_OFFSET) + { + pLayerTiles->m_ColorEnvOffset = m_Previous; + } + else if(m_Prop == ETilesProp::PROP_AUTOMAPPER) + { + pLayerTiles->m_AutoMapperConfig = m_Previous; + } + else if(m_Prop == ETilesProp::PROP_SEED) + { + pLayerTiles->m_Seed = m_Previous; + } + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionEditLayerTilesProp::Redo() +{ + std::shared_ptr pLayerTiles = std::static_pointer_cast(m_pLayer); + + if(m_Prop == ETilesProp::PROP_WIDTH || m_Prop == ETilesProp::PROP_HEIGHT) + { + if(m_Prop == ETilesProp::PROP_HEIGHT) + pLayerTiles->Resize(pLayerTiles->m_Width, m_Current); + else if(m_Prop == ETilesProp::PROP_WIDTH) + pLayerTiles->Resize(m_Current, pLayerTiles->m_Height); + + if(pLayerTiles->m_Game || pLayerTiles->m_Front || pLayerTiles->m_Switch || pLayerTiles->m_Speedup || pLayerTiles->m_Tune) + { + if(m_pEditor->m_Map.m_pFrontLayer && !pLayerTiles->m_Front) + m_pEditor->m_Map.m_pFrontLayer->Resize(pLayerTiles->m_Width, pLayerTiles->m_Height); + if(m_pEditor->m_Map.m_pTeleLayer && !pLayerTiles->m_Tele) + m_pEditor->m_Map.m_pTeleLayer->Resize(pLayerTiles->m_Width, pLayerTiles->m_Height); + if(m_pEditor->m_Map.m_pSwitchLayer && !pLayerTiles->m_Switch) + m_pEditor->m_Map.m_pSwitchLayer->Resize(pLayerTiles->m_Width, pLayerTiles->m_Height); + if(m_pEditor->m_Map.m_pSpeedupLayer && !pLayerTiles->m_Speedup) + m_pEditor->m_Map.m_pSpeedupLayer->Resize(pLayerTiles->m_Width, pLayerTiles->m_Height); + if(m_pEditor->m_Map.m_pTuneLayer && !pLayerTiles->m_Tune) + m_pEditor->m_Map.m_pTuneLayer->Resize(pLayerTiles->m_Width, pLayerTiles->m_Height); + if(!pLayerTiles->m_Game) + m_pEditor->m_Map.m_pGameLayer->Resize(pLayerTiles->m_Width, pLayerTiles->m_Height); + } + } + else if(m_Prop == ETilesProp::PROP_SHIFT) + { + pLayerTiles->Shift(m_Current); + } + else if(m_Prop == ETilesProp::PROP_SHIFT_BY) + { + m_pEditor->m_ShiftBy = m_Current; + } + else if(m_Prop == ETilesProp::PROP_IMAGE) + { + if(m_Current == -1) + { + pLayerTiles->m_Image = -1; + } + else + { + pLayerTiles->m_Image = m_Current % m_pEditor->m_Map.m_vpImages.size(); + pLayerTiles->m_AutoMapperConfig = -1; + } + } + else if(m_Prop == ETilesProp::PROP_COLOR) + { + const ColorRGBA ColorPick = ColorRGBA::UnpackAlphaLast(m_Current); + + pLayerTiles->m_Color.r = ColorPick.r * 255.0f; + pLayerTiles->m_Color.g = ColorPick.g * 255.0f; + pLayerTiles->m_Color.b = ColorPick.b * 255.0f; + pLayerTiles->m_Color.a = ColorPick.a * 255.0f; + + m_pEditor->m_ColorPickerPopupContext.m_RgbaColor = ColorPick; + m_pEditor->m_ColorPickerPopupContext.m_HslaColor = color_cast(ColorPick); + m_pEditor->m_ColorPickerPopupContext.m_HsvaColor = color_cast(m_pEditor->m_ColorPickerPopupContext.m_HslaColor); + } + else if(m_Prop == ETilesProp::PROP_COLOR_ENV) + { + pLayerTiles->m_ColorEnv = m_Current; + } + else if(m_Prop == ETilesProp::PROP_COLOR_ENV_OFFSET) + { + pLayerTiles->m_ColorEnvOffset = m_Current; + } + else if(m_Prop == ETilesProp::PROP_AUTOMAPPER) + { + pLayerTiles->m_AutoMapperConfig = m_Current; + } + else if(m_Prop == ETilesProp::PROP_SEED) + { + pLayerTiles->m_Seed = m_Current; + } + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionEditLayerTilesProp::RestoreLayer(int Layer, const std::shared_ptr &pLayerTiles) +{ + if(m_SavedLayers[Layer] != nullptr) + { + std::shared_ptr pSavedLayerTiles = std::static_pointer_cast(m_SavedLayers[Layer]); + mem_copy(pLayerTiles->m_pTiles, pSavedLayerTiles->m_pTiles, (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile)); + + if(pLayerTiles->m_Tele) + { + std::shared_ptr pLayerTele = std::static_pointer_cast(pLayerTiles); + std::shared_ptr pSavedLayerTele = std::static_pointer_cast(pSavedLayerTiles); + mem_copy(pLayerTele->m_pTeleTile, pSavedLayerTele->m_pTeleTile, (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTeleTile)); + } + else if(pLayerTiles->m_Speedup) + { + std::shared_ptr pLayerSpeedup = std::static_pointer_cast(pLayerTiles); + std::shared_ptr pSavedLayerSpeedup = std::static_pointer_cast(pSavedLayerTiles); + mem_copy(pLayerSpeedup->m_pSpeedupTile, pSavedLayerSpeedup->m_pSpeedupTile, (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CSpeedupTile)); + } + else if(pLayerTiles->m_Switch) + { + std::shared_ptr pLayerSwitch = std::static_pointer_cast(pLayerTiles); + std::shared_ptr pSavedLayerSwitch = std::static_pointer_cast(pSavedLayerTiles); + mem_copy(pLayerSwitch->m_pSwitchTile, pSavedLayerSwitch->m_pSwitchTile, (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CSwitchTile)); + } + else if(pLayerTiles->m_Tune) + { + std::shared_ptr pLayerTune = std::static_pointer_cast(pLayerTiles); + std::shared_ptr pSavedLayerTune = std::static_pointer_cast(pSavedLayerTiles); + mem_copy(pLayerTune->m_pTuneTile, pSavedLayerTune->m_pTuneTile, (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTuneTile)); + } + } +} + +CEditorActionEditLayerQuadsProp::CEditorActionEditLayerQuadsProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerQuadsProp Prop, int Previous, int Current) : + CEditorActionEditLayerPropBase(pEditor, GroupIndex, LayerIndex, Prop, Previous, Current) +{ + static const char *s_apNames[] = { + "image"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit quads layer %d in group %d %s property", m_LayerIndex, m_GroupIndex, s_apNames[(int)m_Prop]); +} + +void CEditorActionEditLayerQuadsProp::Undo() +{ + Apply(m_Previous); +} + +void CEditorActionEditLayerQuadsProp::Redo() +{ + Apply(m_Current); +} + +void CEditorActionEditLayerQuadsProp::Apply(int Value) +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + if(m_Prop == ELayerQuadsProp::PROP_IMAGE) + { + if(Value >= 0) + pLayerQuads->m_Image = Value % m_pEditor->m_Map.m_vpImages.size(); + else + pLayerQuads->m_Image = -1; + } + + m_pEditor->m_Map.OnModify(); +} + +// -------------------------------------------------------------- + +CEditorActionEditLayersGroupAndOrder::CEditorActionEditLayersGroupAndOrder(CEditor *pEditor, int GroupIndex, const std::vector &LayerIndices, int NewGroupIndex, const std::vector &NewLayerIndices) : + IEditorAction(pEditor), m_GroupIndex(GroupIndex), m_LayerIndices(LayerIndices), m_NewGroupIndex(NewGroupIndex), m_NewLayerIndices(NewLayerIndices) +{ + std::sort(m_LayerIndices.begin(), m_LayerIndices.end()); + std::sort(m_NewLayerIndices.begin(), m_NewLayerIndices.end()); + + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit layers group and order (x%d)", (int)m_LayerIndices.size()); +} + +void CEditorActionEditLayersGroupAndOrder::Undo() +{ + // Undo : restore group and order + auto &Map = m_pEditor->m_Map; + auto &pCurrentGroup = Map.m_vpGroups[m_NewGroupIndex]; + auto &pPreviousGroup = Map.m_vpGroups[m_GroupIndex]; + std::vector> vpLayers; + for(auto &LayerIndex : m_NewLayerIndices) + vpLayers.push_back(pCurrentGroup->m_vpLayers[LayerIndex]); + + int k = 0; + for(auto &pLayer : vpLayers) + { + pCurrentGroup->m_vpLayers.erase(std::find(pCurrentGroup->m_vpLayers.begin(), pCurrentGroup->m_vpLayers.end(), pLayer)); + pPreviousGroup->m_vpLayers.insert(pPreviousGroup->m_vpLayers.begin() + m_LayerIndices[k++], pLayer); + } + + m_pEditor->m_vSelectedLayers = m_LayerIndices; + m_pEditor->m_SelectedGroup = m_GroupIndex; +} + +void CEditorActionEditLayersGroupAndOrder::Redo() +{ + // Redo : move layers + auto &Map = m_pEditor->m_Map; + auto &pCurrentGroup = Map.m_vpGroups[m_GroupIndex]; + auto &pPreviousGroup = Map.m_vpGroups[m_NewGroupIndex]; + std::vector> vpLayers; + for(auto &LayerIndex : m_LayerIndices) + vpLayers.push_back(pCurrentGroup->m_vpLayers[LayerIndex]); + + int k = 0; + for(auto &pLayer : vpLayers) + { + pCurrentGroup->m_vpLayers.erase(std::find(pCurrentGroup->m_vpLayers.begin(), pCurrentGroup->m_vpLayers.end(), pLayer)); + pPreviousGroup->m_vpLayers.insert(pPreviousGroup->m_vpLayers.begin() + m_NewLayerIndices[k++], pLayer); + } + + m_pEditor->m_vSelectedLayers = m_NewLayerIndices; + m_pEditor->m_SelectedGroup = m_NewGroupIndex; +} + +// ----------------------------------- + +CEditorActionAppendMap::CEditorActionAppendMap(CEditor *pEditor, const char *pMapName, const SPrevInfo &PrevInfo, std::vector &vImageIndexMap) : + IEditorAction(pEditor), m_PrevInfo(PrevInfo), m_vImageIndexMap(vImageIndexMap) +{ + str_copy(m_aMapName, pMapName); + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Append %s", m_aMapName); +} + +void CEditorActionAppendMap::Undo() +{ + auto &Map = m_pEditor->m_Map; + // Undo append: + // - delete added groups + // - delete added envelopes + // - delete added images + // - delete added sounds + + // Delete added groups + while((int)Map.m_vpGroups.size() != m_PrevInfo.m_Groups) + { + Map.m_vpGroups.pop_back(); + } + + // Delete added envelopes + while((int)Map.m_vpEnvelopes.size() != m_PrevInfo.m_Envelopes) + { + Map.m_vpEnvelopes.pop_back(); + } + + // Delete added sounds + while((int)Map.m_vpSounds.size() != m_PrevInfo.m_Sounds) + { + Map.m_vpSounds.pop_back(); + } + + // Delete added images + // Images are sorted when appending, so we need to revert sorting before deleting the images + if(!m_vImageIndexMap.empty()) + { + std::vector vReverseIndexMap; + vReverseIndexMap.resize(m_vImageIndexMap.size()); + + for(int k = 0; k < (int)m_vImageIndexMap.size(); k++) + vReverseIndexMap[m_vImageIndexMap[k]] = k; + + std::vector> vpRevertedImages; + vpRevertedImages.resize(Map.m_vpImages.size()); + + for(int k = 0; k < (int)vReverseIndexMap.size(); k++) + { + vpRevertedImages[vReverseIndexMap[k]] = Map.m_vpImages[k]; + } + Map.m_vpImages = vpRevertedImages; + + Map.ModifyImageIndex([vReverseIndexMap](int *pIndex) { + if(*pIndex >= 0) + { + *pIndex = vReverseIndexMap[*pIndex]; + } + }); + } + + while((int)Map.m_vpImages.size() != m_PrevInfo.m_Images) + { + Map.m_vpImages.pop_back(); + } +} + +void CEditorActionAppendMap::Redo() +{ + // Redo is just re-appending the same map + m_pEditor->Append(m_aMapName, IStorage::TYPE_ALL, true); +} + +// --------------------------- + +CEditorActionTileArt::CEditorActionTileArt(CEditor *pEditor, int PreviousImageCount, const char *pTileArtFile, std::vector &vImageIndexMap) : + IEditorAction(pEditor), m_PreviousImageCount(PreviousImageCount), m_vImageIndexMap(vImageIndexMap) +{ + str_copy(m_aTileArtFile, pTileArtFile); + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Tile art"); +} + +void CEditorActionTileArt::Undo() +{ + auto &Map = m_pEditor->m_Map; + + // Delete added group + Map.m_vpGroups.pop_back(); + + // Delete added images + // Images are sorted when appending, so we need to revert sorting before deleting the images + if(!m_vImageIndexMap.empty()) + { + std::vector vReverseIndexMap; + vReverseIndexMap.resize(m_vImageIndexMap.size()); + + for(int k = 0; k < (int)m_vImageIndexMap.size(); k++) + vReverseIndexMap[m_vImageIndexMap[k]] = k; + + std::vector> vpRevertedImages; + vpRevertedImages.resize(Map.m_vpImages.size()); + + for(int k = 0; k < (int)vReverseIndexMap.size(); k++) + { + vpRevertedImages[vReverseIndexMap[k]] = Map.m_vpImages[k]; + } + Map.m_vpImages = vpRevertedImages; + + Map.ModifyImageIndex([vReverseIndexMap](int *pIndex) { + if(*pIndex >= 0) + { + *pIndex = vReverseIndexMap[*pIndex]; + } + }); + } + + while((int)Map.m_vpImages.size() != m_PreviousImageCount) + { + Map.m_vpImages.pop_back(); + } +} + +void CEditorActionTileArt::Redo() +{ + if(!m_pEditor->Graphics()->LoadPNG(&m_pEditor->m_TileartImageInfo, m_aTileArtFile, IStorage::TYPE_ALL)) + { + m_pEditor->ShowFileDialogError("Failed to load image from file '%s'.", m_aTileArtFile); + return; + } + + IStorage::StripPathAndExtension(m_aTileArtFile, m_pEditor->m_aTileartFilename, sizeof(m_pEditor->m_aTileartFilename)); + m_pEditor->AddTileart(true); +} + +// --------------------------------- + +CEditorCommandAction::CEditorCommandAction(CEditor *pEditor, EType Type, int *pSelectedCommandIndex, int CommandIndex, const char *pPreviousCommand, const char *pCurrentCommand) : + IEditorAction(pEditor), m_Type(Type), m_pSelectedCommandIndex(pSelectedCommandIndex), m_CommandIndex(CommandIndex) +{ + if(pPreviousCommand != nullptr) + m_PreviousCommand = std::string(pPreviousCommand); + if(pCurrentCommand != nullptr) + m_CurrentCommand = std::string(pCurrentCommand); + + switch(m_Type) + { + case EType::ADD: + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Add command"); + break; + case EType::EDIT: + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit command %d", m_CommandIndex); + break; + case EType::DELETE: + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete command %d", m_CommandIndex); + break; + case EType::MOVE_UP: + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Move command %d up", m_CommandIndex); + break; + case EType::MOVE_DOWN: + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Move command %d down", m_CommandIndex); + break; + default: + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit command %d", m_CommandIndex); + break; + } +} + +void CEditorCommandAction::Undo() +{ + auto &Map = m_pEditor->m_Map; + switch(m_Type) + { + case EType::DELETE: + { + Map.m_vSettings.insert(Map.m_vSettings.begin() + m_CommandIndex, m_PreviousCommand.c_str()); + *m_pSelectedCommandIndex = m_CommandIndex; + break; + } + case EType::ADD: + { + Map.m_vSettings.erase(Map.m_vSettings.begin() + m_CommandIndex); + *m_pSelectedCommandIndex = Map.m_vSettings.size() - 1; + break; + } + case EType::EDIT: + { + str_copy(Map.m_vSettings[m_CommandIndex].m_aCommand, m_PreviousCommand.c_str()); + *m_pSelectedCommandIndex = m_CommandIndex; + break; + } + case EType::MOVE_DOWN: + { + std::swap(Map.m_vSettings[m_CommandIndex], Map.m_vSettings[m_CommandIndex + 1]); + *m_pSelectedCommandIndex = m_CommandIndex; + break; + } + case EType::MOVE_UP: + { + std::swap(Map.m_vSettings[m_CommandIndex], Map.m_vSettings[m_CommandIndex - 1]); + *m_pSelectedCommandIndex = m_CommandIndex; + break; + } + } +} + +void CEditorCommandAction::Redo() +{ + auto &Map = m_pEditor->m_Map; + switch(m_Type) + { + case EType::DELETE: + { + Map.m_vSettings.erase(Map.m_vSettings.begin() + m_CommandIndex); + *m_pSelectedCommandIndex = Map.m_vSettings.size() - 1; + break; + } + case EType::ADD: + { + Map.m_vSettings.insert(Map.m_vSettings.begin() + m_CommandIndex, m_PreviousCommand.c_str()); + *m_pSelectedCommandIndex = m_CommandIndex; + break; + } + case EType::EDIT: + { + str_copy(Map.m_vSettings[m_CommandIndex].m_aCommand, m_CurrentCommand.c_str()); + *m_pSelectedCommandIndex = m_CommandIndex; + break; + } + case EType::MOVE_DOWN: + { + std::swap(Map.m_vSettings[m_CommandIndex], Map.m_vSettings[m_CommandIndex + 1]); + *m_pSelectedCommandIndex = m_CommandIndex; + break; + } + case EType::MOVE_UP: + { + std::swap(Map.m_vSettings[m_CommandIndex], Map.m_vSettings[m_CommandIndex - 1]); + *m_pSelectedCommandIndex = m_CommandIndex; + break; + } + } +} + +// ------------------------------------------------ + +CEditorActionEnvelopeAdd::CEditorActionEnvelopeAdd(CEditor *pEditor, const std::shared_ptr &pEnv) : + IEditorAction(pEditor), m_pEnv(pEnv) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Add new %s envelope", pEnv->Type() == CEnvelope::EType::COLOR ? "color" : (pEnv->Type() == CEnvelope::EType::POSITION ? "position" : "sound")); +} + +void CEditorActionEnvelopeAdd::Undo() +{ + // Undo is removing the envelope, which was added at the back of the list + m_pEditor->m_Map.m_vpEnvelopes.pop_back(); + m_pEditor->m_SelectedEnvelope = m_pEditor->m_Map.m_vpEnvelopes.size() - 1; +} + +void CEditorActionEnvelopeAdd::Redo() +{ + // Redo is adding back at the back the saved envelope + m_pEditor->m_Map.m_vpEnvelopes.push_back(m_pEnv); + m_pEditor->m_SelectedEnvelope = m_pEditor->m_Map.m_vpEnvelopes.size() - 1; +} + +CEditorActionEveloppeDelete::CEditorActionEveloppeDelete(CEditor *pEditor, int EnvelopeIndex) : + IEditorAction(pEditor), m_EnvelopeIndex(EnvelopeIndex), m_pEnv(pEditor->m_Map.m_vpEnvelopes[EnvelopeIndex]) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete envelope %d", m_EnvelopeIndex); +} + +void CEditorActionEveloppeDelete::Undo() +{ + // Undo is adding back the envelope + m_pEditor->m_Map.m_vpEnvelopes.insert(m_pEditor->m_Map.m_vpEnvelopes.begin() + m_EnvelopeIndex, m_pEnv); + m_pEditor->m_SelectedEnvelope = m_EnvelopeIndex; +} + +void CEditorActionEveloppeDelete::Redo() +{ + // Redo is erasing the same envelope index + m_pEditor->m_Map.m_vpEnvelopes.erase(m_pEditor->m_Map.m_vpEnvelopes.begin() + m_EnvelopeIndex); + if(m_pEditor->m_SelectedEnvelope >= (int)m_pEditor->m_Map.m_vpEnvelopes.size()) + m_pEditor->m_SelectedEnvelope = m_pEditor->m_Map.m_vpEnvelopes.size() - 1; +} + +CEditorActionEnvelopeEdit::CEditorActionEnvelopeEdit(CEditor *pEditor, int EnvelopeIndex, EEditType EditType, int Previous, int Current) : + IEditorAction(pEditor), m_EnvelopeIndex(EnvelopeIndex), m_EditType(EditType), m_Previous(Previous), m_Current(Current), m_pEnv(pEditor->m_Map.m_vpEnvelopes[EnvelopeIndex]) +{ + static const char *s_apNames[] = { + "sync", + "order"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit envelope %d %s", m_EnvelopeIndex, s_apNames[(int)m_EditType]); +} + +void CEditorActionEnvelopeEdit::Undo() +{ + switch(m_EditType) + { + case EEditType::ORDER: + { + m_pEditor->m_Map.SwapEnvelopes(m_Current, m_Previous); + break; + } + case EEditType::SYNC: + { + m_pEnv->m_Synchronized = m_Previous; + break; + } + } + m_pEditor->m_Map.OnModify(); + m_pEditor->m_SelectedEnvelope = m_EnvelopeIndex; +} + +void CEditorActionEnvelopeEdit::Redo() +{ + switch(m_EditType) + { + case EEditType::ORDER: + { + m_pEditor->m_Map.SwapEnvelopes(m_Previous, m_Current); + break; + } + case EEditType::SYNC: + { + m_pEnv->m_Synchronized = m_Current; + break; + } + } + m_pEditor->m_Map.OnModify(); + m_pEditor->m_SelectedEnvelope = m_EnvelopeIndex; +} + +CEditorActionEnvelopeEditPoint::CEditorActionEnvelopeEditPoint(CEditor *pEditor, int EnvelopeIndex, int PointIndex, int Channel, EEditType EditType, int Previous, int Current) : + IEditorAction(pEditor), m_EnvelopeIndex(EnvelopeIndex), m_PointIndex(PointIndex), m_Channel(Channel), m_EditType(EditType), m_Previous(Previous), m_Current(Current), m_pEnv(pEditor->m_Map.m_vpEnvelopes[EnvelopeIndex]) +{ + static const char *s_apNames[] = { + "time", + "value", + "curve type"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit %s of point %d (channel %d) of env %d", s_apNames[(int)m_EditType], m_PointIndex, m_Channel, m_EnvelopeIndex); +} + +void CEditorActionEnvelopeEditPoint::Undo() +{ + Apply(m_Previous); +} + +void CEditorActionEnvelopeEditPoint::Redo() +{ + Apply(m_Current); +} + +void CEditorActionEnvelopeEditPoint::Apply(int Value) +{ + if(m_EditType == EEditType::TIME) + { + m_pEnv->m_vPoints[m_PointIndex].m_Time = Value; + } + else if(m_EditType == EEditType::VALUE) + { + m_pEnv->m_vPoints[m_PointIndex].m_aValues[m_Channel] = Value; + + if(m_pEnv->GetChannels() == 4) + { + auto *pValues = m_pEnv->m_vPoints[m_PointIndex].m_aValues; + const ColorRGBA Color = ColorRGBA(fx2f(pValues[0]), fx2f(pValues[1]), fx2f(pValues[2]), fx2f(pValues[3])); + + m_pEditor->m_ColorPickerPopupContext.m_RgbaColor = Color; + m_pEditor->m_ColorPickerPopupContext.m_HslaColor = color_cast(Color); + m_pEditor->m_ColorPickerPopupContext.m_HsvaColor = color_cast(m_pEditor->m_ColorPickerPopupContext.m_HslaColor); + } + } + else if(m_EditType == EEditType::CURVE_TYPE) + { + m_pEnv->m_vPoints[m_PointIndex].m_Curvetype = Value; + } + + m_pEditor->m_Map.OnModify(); +} + +// ---- + +CEditorActionEditEnvelopePointValue::CEditorActionEditEnvelopePointValue(CEditor *pEditor, int EnvIndex, int PointIndex, int Channel, EType Type, int OldTime, int OldValue, int NewTime, int NewValue) : + IEditorAction(pEditor), m_EnvIndex(EnvIndex), m_PtIndex(PointIndex), m_Channel(Channel), m_Type(Type), m_OldTime(OldTime), m_OldValue(OldValue), m_NewTime(NewTime), m_NewValue(NewValue) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit point %d%s value (envelope %d, channel %d)", PointIndex, m_Type == EType::TANGENT_IN ? "tangent in" : (m_Type == EType::TANGENT_OUT ? "tangent out" : ""), m_EnvIndex, m_Channel); +} + +void CEditorActionEditEnvelopePointValue::Undo() +{ + Apply(true); +} + +void CEditorActionEditEnvelopePointValue::Redo() +{ + Apply(false); +} + +void CEditorActionEditEnvelopePointValue::Apply(bool Undo) +{ + float CurrentValue = fx2f(Undo ? m_OldValue : m_NewValue); + float CurrentTime = (Undo ? m_OldTime : m_NewTime) / 1000.0f; + + std::shared_ptr pEnvelope = m_pEditor->m_Map.m_vpEnvelopes[m_EnvIndex]; + if(m_Type == EType::TANGENT_IN) + { + pEnvelope->m_vPoints[m_PtIndex].m_Bezier.m_aInTangentDeltaX[m_Channel] = minimum(CurrentTime * 1000.0f - pEnvelope->m_vPoints[m_PtIndex].m_Time, 0); + pEnvelope->m_vPoints[m_PtIndex].m_Bezier.m_aInTangentDeltaY[m_Channel] = f2fx(CurrentValue) - pEnvelope->m_vPoints[m_PtIndex].m_aValues[m_Channel]; + } + else if(m_Type == EType::TANGENT_OUT) + { + pEnvelope->m_vPoints[m_PtIndex].m_Bezier.m_aOutTangentDeltaX[m_Channel] = maximum(CurrentTime * 1000.0f - pEnvelope->m_vPoints[m_PtIndex].m_Time, 0); + pEnvelope->m_vPoints[m_PtIndex].m_Bezier.m_aOutTangentDeltaY[m_Channel] = f2fx(CurrentValue) - pEnvelope->m_vPoints[m_PtIndex].m_aValues[m_Channel]; + } + else + { + if(pEnvelope->GetChannels() == 4) + CurrentValue = clamp(CurrentValue, 0.0f, 1.0f); + pEnvelope->m_vPoints[m_PtIndex].m_aValues[m_Channel] = f2fx(CurrentValue); + + if(m_PtIndex != 0) + { + pEnvelope->m_vPoints[m_PtIndex].m_Time = CurrentTime * 1000.0f; + + if(pEnvelope->m_vPoints[m_PtIndex].m_Time < pEnvelope->m_vPoints[m_PtIndex - 1].m_Time) + pEnvelope->m_vPoints[m_PtIndex].m_Time = pEnvelope->m_vPoints[m_PtIndex - 1].m_Time + 1; + if(static_cast(m_PtIndex) + 1 != pEnvelope->m_vPoints.size() && pEnvelope->m_vPoints[m_PtIndex].m_Time > pEnvelope->m_vPoints[m_PtIndex + 1].m_Time) + pEnvelope->m_vPoints[m_PtIndex].m_Time = pEnvelope->m_vPoints[m_PtIndex + 1].m_Time - 1; + } + else + { + pEnvelope->m_vPoints[m_PtIndex].m_Time = 0.0f; + } + } + + m_pEditor->m_Map.OnModify(); + m_pEditor->m_UpdateEnvPointInfo = true; +} + +// --------------------- + +CEditorActionResetEnvelopePointTangent::CEditorActionResetEnvelopePointTangent(CEditor *pEditor, int EnvIndex, int PointIndex, int Channel, bool In) : + IEditorAction(pEditor), m_EnvIndex(EnvIndex), m_PointIndex(PointIndex), m_Channel(Channel), m_In(In) +{ + std::shared_ptr pEnvelope = pEditor->m_Map.m_vpEnvelopes[EnvIndex]; + if(In) + { + m_Previous[0] = pEnvelope->m_vPoints[PointIndex].m_Bezier.m_aInTangentDeltaX[Channel]; + m_Previous[1] = pEnvelope->m_vPoints[PointIndex].m_Bezier.m_aInTangentDeltaY[Channel]; + } + else + { + m_Previous[0] = pEnvelope->m_vPoints[PointIndex].m_Bezier.m_aOutTangentDeltaX[Channel]; + m_Previous[1] = pEnvelope->m_vPoints[PointIndex].m_Bezier.m_aOutTangentDeltaY[Channel]; + } + + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Reset point %d of env %d tangent %s", m_PointIndex, m_EnvIndex, m_In ? "in" : "out"); +} + +void CEditorActionResetEnvelopePointTangent::Undo() +{ + std::shared_ptr pEnvelope = m_pEditor->m_Map.m_vpEnvelopes[m_EnvIndex]; + if(m_In) + { + pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aInTangentDeltaX[m_Channel] = m_Previous[0]; + pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aInTangentDeltaY[m_Channel] = m_Previous[1]; + } + else + { + pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aOutTangentDeltaX[m_Channel] = m_Previous[0]; + pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aOutTangentDeltaY[m_Channel] = m_Previous[1]; + } + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionResetEnvelopePointTangent::Redo() +{ + std::shared_ptr pEnvelope = m_pEditor->m_Map.m_vpEnvelopes[m_EnvIndex]; + if(m_In) + { + pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aInTangentDeltaX[m_Channel] = 0.0f; + pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aInTangentDeltaY[m_Channel] = 0.0f; + } + else + { + pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aOutTangentDeltaX[m_Channel] = 0.0f; + pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aOutTangentDeltaY[m_Channel] = 0.0f; + } + m_pEditor->m_Map.OnModify(); +} + +// ------------------ + +CEditorActionAddEnvelopePoint::CEditorActionAddEnvelopePoint(CEditor *pEditor, int EnvIndex, int Time, ColorRGBA Channels) : + IEditorAction(pEditor), m_EnvIndex(EnvIndex), m_Time(Time), m_Channels(Channels) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Add new point in envelope %d at time %f", m_EnvIndex, Time / 1000.0); +} + +void CEditorActionAddEnvelopePoint::Undo() +{ + // Delete added point + auto pEnvelope = m_pEditor->m_Map.m_vpEnvelopes[m_EnvIndex]; + auto pIt = std::find_if(pEnvelope->m_vPoints.begin(), pEnvelope->m_vPoints.end(), [this](const CEnvPoint_runtime &Point) { + return Point.m_Time == m_Time; + }); + if(pIt != pEnvelope->m_vPoints.end()) + { + pEnvelope->m_vPoints.erase(pIt); + } + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionAddEnvelopePoint::Redo() +{ + auto pEnvelope = m_pEditor->m_Map.m_vpEnvelopes[m_EnvIndex]; + pEnvelope->AddPoint(m_Time, + f2fx(m_Channels.r), f2fx(m_Channels.g), + f2fx(m_Channels.b), f2fx(m_Channels.a)); + + m_pEditor->m_Map.OnModify(); +} + +CEditorActionDeleteEnvelopePoint::CEditorActionDeleteEnvelopePoint(CEditor *pEditor, int EnvIndex, int PointIndex) : + IEditorAction(pEditor), m_EnvIndex(EnvIndex), m_PointIndex(PointIndex), m_Point(pEditor->m_Map.m_vpEnvelopes[EnvIndex]->m_vPoints[PointIndex]) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete point %d of envelope %d", m_PointIndex, m_EnvIndex); +} + +void CEditorActionDeleteEnvelopePoint::Undo() +{ + std::shared_ptr pEnvelope = m_pEditor->m_Map.m_vpEnvelopes[m_EnvIndex]; + pEnvelope->m_vPoints.insert(pEnvelope->m_vPoints.begin() + m_PointIndex, m_Point); + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionDeleteEnvelopePoint::Redo() +{ + std::shared_ptr pEnvelope = m_pEditor->m_Map.m_vpEnvelopes[m_EnvIndex]; + pEnvelope->m_vPoints.erase(pEnvelope->m_vPoints.begin() + m_PointIndex); + + auto pSelectedPointIt = std::find_if(m_pEditor->m_vSelectedEnvelopePoints.begin(), m_pEditor->m_vSelectedEnvelopePoints.end(), [this](const std::pair Pair) { + return Pair.first == m_PointIndex; + }); + + if(pSelectedPointIt != m_pEditor->m_vSelectedEnvelopePoints.end()) + m_pEditor->m_vSelectedEnvelopePoints.erase(pSelectedPointIt); + + m_pEditor->m_Map.OnModify(); +} + +// ------------------------------- + +CEditorActionEditLayerSoundsProp::CEditorActionEditLayerSoundsProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerSoundsProp Prop, int Previous, int Current) : + CEditorActionEditLayerPropBase(pEditor, GroupIndex, LayerIndex, Prop, Previous, Current) +{ + static const char *s_apNames[] = { + "sound"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit sounds layer %d in group %d %s property", m_LayerIndex, m_GroupIndex, s_apNames[(int)m_Prop]); +} + +void CEditorActionEditLayerSoundsProp::Undo() +{ + Apply(m_Previous); +} + +void CEditorActionEditLayerSoundsProp::Redo() +{ + Apply(m_Current); +} + +void CEditorActionEditLayerSoundsProp::Apply(int Value) +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + if(m_Prop == ELayerSoundsProp::PROP_SOUND) + { + if(Value >= 0) + pLayerSounds->m_Sound = Value % m_pEditor->m_Map.m_vpSounds.size(); + else + pLayerSounds->m_Sound = -1; + } + + m_pEditor->m_Map.OnModify(); +} + +// --- + +CEditorActionDeleteSoundSource::CEditorActionDeleteSoundSource(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_SourceIndex(SourceIndex) +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + m_Source = pLayerSounds->m_vSources[SourceIndex]; + + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Delete sound source %d in layer %d of group %d", SourceIndex, LayerIndex, GroupIndex); +} + +void CEditorActionDeleteSoundSource::Undo() +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + pLayerSounds->m_vSources.insert(pLayerSounds->m_vSources.begin() + m_SourceIndex, m_Source); + m_pEditor->m_SelectedSource = m_SourceIndex; + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionDeleteSoundSource::Redo() +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + pLayerSounds->m_vSources.erase(pLayerSounds->m_vSources.begin() + m_SourceIndex); + m_pEditor->m_SelectedSource--; + m_pEditor->m_Map.OnModify(); +} + +// --------------- + +CEditorActionEditSoundSource::CEditorActionEditSoundSource(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, EEditType Type, int Value) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_SourceIndex(SourceIndex), m_EditType(Type), m_CurrentValue(Value), m_pSavedObject(nullptr) +{ + Save(); + + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit sound source %d in layer %d of group %d", SourceIndex, LayerIndex, GroupIndex); +} + +void CEditorActionEditSoundSource::Undo() +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex]; + + if(m_EditType == EEditType::SHAPE) + { + CSoundShape *pSavedShape = (CSoundShape *)m_pSavedObject; + pSource->m_Shape.m_Type = pSavedShape->m_Type; + + // set default values + switch(pSource->m_Shape.m_Type) + { + case CSoundShape::SHAPE_CIRCLE: + { + pSource->m_Shape.m_Circle.m_Radius = pSavedShape->m_Circle.m_Radius; + break; + } + case CSoundShape::SHAPE_RECTANGLE: + { + pSource->m_Shape.m_Rectangle.m_Width = pSavedShape->m_Rectangle.m_Width; + pSource->m_Shape.m_Rectangle.m_Height = pSavedShape->m_Rectangle.m_Height; + break; + } + } + } + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionEditSoundSource::Redo() +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex]; + + if(m_EditType == EEditType::SHAPE) + { + pSource->m_Shape.m_Type = m_CurrentValue; + + // set default values + switch(pSource->m_Shape.m_Type) + { + case CSoundShape::SHAPE_CIRCLE: + { + pSource->m_Shape.m_Circle.m_Radius = 1000.0f; + break; + } + case CSoundShape::SHAPE_RECTANGLE: + { + pSource->m_Shape.m_Rectangle.m_Width = f2fx(1000.0f); + pSource->m_Shape.m_Rectangle.m_Height = f2fx(800.0f); + break; + } + } + } + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionEditSoundSource::Save() +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex]; + + if(m_EditType == EEditType::SHAPE) + { + CSoundShape *pShapeInfo = new CSoundShape; + pShapeInfo->m_Type = pSource->m_Shape.m_Type; + + switch(pSource->m_Shape.m_Type) + { + case CSoundShape::SHAPE_CIRCLE: + { + pShapeInfo->m_Circle.m_Radius = pSource->m_Shape.m_Circle.m_Radius; + break; + } + case CSoundShape::SHAPE_RECTANGLE: + { + pShapeInfo->m_Rectangle.m_Width = pSource->m_Shape.m_Rectangle.m_Width; + pShapeInfo->m_Rectangle.m_Height = pSource->m_Shape.m_Rectangle.m_Height; + break; + } + } + + m_pSavedObject = pShapeInfo; + } +} + +CEditorActionEditSoundSource::~CEditorActionEditSoundSource() +{ + if(m_EditType == EEditType::SHAPE) + { + CSoundShape *pSavedShape = (CSoundShape *)m_pSavedObject; + delete pSavedShape; + } +} + +// ----- + +CEditorActionEditSoundSourceProp::CEditorActionEditSoundSourceProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ESoundProp Prop, int Previous, int Current) : + CEditorActionEditLayerPropBase(pEditor, GroupIndex, LayerIndex, Prop, Previous, Current), m_SourceIndex(SourceIndex) +{ + static const char *s_apNames[] = { + "pos X", + "pos Y", + "loop", + "pan", + "time delay", + "falloff", + "pos env", + "pos env offset", + "sound env", + "sound env offset"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit sound source %d in layer %d of group %d %s property", SourceIndex, LayerIndex, GroupIndex, s_apNames[(int)Prop]); +} + +void CEditorActionEditSoundSourceProp::Undo() +{ + Apply(m_Previous); +} + +void CEditorActionEditSoundSourceProp::Redo() +{ + Apply(m_Current); +} + +void CEditorActionEditSoundSourceProp::Apply(int Value) +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex]; + + if(m_Prop == ESoundProp::PROP_POS_X) + { + pSource->m_Position.x = Value; + } + else if(m_Prop == ESoundProp::PROP_POS_Y) + { + pSource->m_Position.y = Value; + } + else if(m_Prop == ESoundProp::PROP_LOOP) + { + pSource->m_Loop = Value; + } + else if(m_Prop == ESoundProp::PROP_PAN) + { + pSource->m_Pan = Value; + } + else if(m_Prop == ESoundProp::PROP_TIME_DELAY) + { + pSource->m_TimeDelay = Value; + } + else if(m_Prop == ESoundProp::PROP_FALLOFF) + { + pSource->m_Falloff = Value; + } + else if(m_Prop == ESoundProp::PROP_POS_ENV) + { + pSource->m_PosEnv = Value; + } + else if(m_Prop == ESoundProp::PROP_POS_ENV_OFFSET) + { + pSource->m_PosEnvOffset = Value; + } + else if(m_Prop == ESoundProp::PROP_SOUND_ENV) + { + pSource->m_SoundEnv = Value; + } + else if(m_Prop == ESoundProp::PROP_SOUND_ENV_OFFSET) + { + pSource->m_SoundEnvOffset = Value; + } + + m_pEditor->m_Map.OnModify(); +} + +CEditorActionEditRectSoundSourceShapeProp::CEditorActionEditRectSoundSourceShapeProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ERectangleShapeProp Prop, int Previous, int Current) : + CEditorActionEditLayerPropBase(pEditor, GroupIndex, LayerIndex, Prop, Previous, Current), m_SourceIndex(SourceIndex) +{ + static const char *s_apNames[] = { + "width", + "height"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit sound source %d in layer %d of group %d sound shape %s property", m_SourceIndex, m_LayerIndex, m_GroupIndex, s_apNames[(int)Prop]); +} + +void CEditorActionEditRectSoundSourceShapeProp::Undo() +{ + Apply(m_Previous); +} + +void CEditorActionEditRectSoundSourceShapeProp::Redo() +{ + Apply(m_Current); +} + +void CEditorActionEditRectSoundSourceShapeProp::Apply(int Value) +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex]; + + if(m_Prop == ERectangleShapeProp::PROP_RECTANGLE_WIDTH) + { + pSource->m_Shape.m_Rectangle.m_Width = Value; + } + else if(m_Prop == ERectangleShapeProp::PROP_RECTANGLE_HEIGHT) + { + pSource->m_Shape.m_Rectangle.m_Height = Value; + } + + m_pEditor->m_Map.OnModify(); +} + +CEditorActionEditCircleSoundSourceShapeProp::CEditorActionEditCircleSoundSourceShapeProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ECircleShapeProp Prop, int Previous, int Current) : + CEditorActionEditLayerPropBase(pEditor, GroupIndex, LayerIndex, Prop, Previous, Current), m_SourceIndex(SourceIndex) +{ + static const char *s_apNames[] = { + "radius"}; + str_format(m_aDisplayText, sizeof(m_aDisplayText), "Edit sound source %d in layer %d of group %d sound shape %s property", m_SourceIndex, m_LayerIndex, m_GroupIndex, s_apNames[(int)Prop]); +} + +void CEditorActionEditCircleSoundSourceShapeProp::Undo() +{ + Apply(m_Previous); +} + +void CEditorActionEditCircleSoundSourceShapeProp::Redo() +{ + Apply(m_Current); +} + +void CEditorActionEditCircleSoundSourceShapeProp::Apply(int Value) +{ + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex]; + + if(m_Prop == ECircleShapeProp::PROP_CIRCLE_RADIUS) + { + pSource->m_Shape.m_Circle.m_Radius = Value; + } + + m_pEditor->m_Map.OnModify(); +} + +// -------------------------- + +CEditorActionNewEmptySound::CEditorActionNewEmptySound(CEditor *pEditor, int GroupIndex, int LayerIndex, int x, int y) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_X(x), m_Y(y) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "New sound in layer %d of group %d", LayerIndex, GroupIndex); +} + +void CEditorActionNewEmptySound::Undo() +{ + // Undo is simply deleting the added source + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + pLayerSounds->m_vSources.pop_back(); + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionNewEmptySound::Redo() +{ + auto &Map = m_pEditor->m_Map; + std::shared_ptr pLayerSounds = std::static_pointer_cast(m_pLayer); + pLayerSounds->NewSource(m_X, m_Y); + + Map.OnModify(); +} + +CEditorActionNewEmptyQuad::CEditorActionNewEmptyQuad(CEditor *pEditor, int GroupIndex, int LayerIndex, int x, int y) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex), m_X(x), m_Y(y) +{ + str_format(m_aDisplayText, sizeof(m_aDisplayText), "New quad in layer %d of group %d", LayerIndex, GroupIndex); +} + +void CEditorActionNewEmptyQuad::Undo() +{ + // Undo is simply deleting the added quad + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + pLayerQuads->m_vQuads.pop_back(); + + m_pEditor->m_Map.OnModify(); +} + +void CEditorActionNewEmptyQuad::Redo() +{ + auto &Map = m_pEditor->m_Map; + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + + int Width = 64; + int Height = 64; + if(pLayerQuads->m_Image >= 0) + { + Width = Map.m_vpImages[pLayerQuads->m_Image]->m_Width; + Height = Map.m_vpImages[pLayerQuads->m_Image]->m_Height; + } + + pLayerQuads->NewQuad(m_X, m_Y, Width, Height); + + Map.OnModify(); +} + +// ------------- + +CEditorActionNewQuad::CEditorActionNewQuad(CEditor *pEditor, int GroupIndex, int LayerIndex) : + CEditorActionLayerBase(pEditor, GroupIndex, LayerIndex) +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + m_Quad = pLayerQuads->m_vQuads[pLayerQuads->m_vQuads.size() - 1]; + + str_format(m_aDisplayText, sizeof(m_aDisplayText), "New quad in layer %d of group %d", LayerIndex, GroupIndex); +} + +void CEditorActionNewQuad::Undo() +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + pLayerQuads->m_vQuads.pop_back(); +} + +void CEditorActionNewQuad::Redo() +{ + std::shared_ptr pLayerQuads = std::static_pointer_cast(m_pLayer); + pLayerQuads->m_vQuads.emplace_back(m_Quad); +} diff --git a/src/game/editor/editor_actions.h b/src/game/editor/editor_actions.h new file mode 100644 index 00000000000..198e02a6f3a --- /dev/null +++ b/src/game/editor/editor_actions.h @@ -0,0 +1,648 @@ +#ifndef GAME_EDITOR_EDITOR_ACTIONS_H +#define GAME_EDITOR_EDITOR_ACTIONS_H + +#include "editor.h" +#include "editor_action.h" + +class CEditorActionLayerBase : public IEditorAction +{ +public: + CEditorActionLayerBase(CEditor *pEditor, int GroupIndex, int LayerIndex); + + virtual void Undo() override {} + virtual void Redo() override {} + +protected: + int m_GroupIndex; + int m_LayerIndex; + std::shared_ptr m_pLayer; +}; + +class CEditorBrushDrawAction : public IEditorAction +{ +public: + CEditorBrushDrawAction(CEditor *pEditor, int Group); + + void Undo() override; + void Redo() override; + bool IsEmpty() override; + +private: + int m_Group; + // m_vTileChanges is a list of changes for each layer that was modified. + // The std::pair is used to pair one layer (index) with its history (2D map). + // EditorTileStateChangeHistory is a 2D map, storing a change item at a specific y,x position. + std::vector>> m_vTileChanges; + EditorTileStateChangeHistory m_TeleTileChanges; + EditorTileStateChangeHistory m_SpeedupTileChanges; + EditorTileStateChangeHistory m_SwitchTileChanges; + EditorTileStateChangeHistory m_TuneTileChanges; + + int m_TotalTilesDrawn; + int m_TotalLayers; + + void Apply(bool Undo); + void SetInfos(); +}; + +// --------------------------------------------------------- + +class CEditorActionQuadPlace : public CEditorActionLayerBase +{ +public: + CEditorActionQuadPlace(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector &vBrush); + + void Undo() override; + void Redo() override; + +private: + std::vector m_vBrush; +}; + +class CEditorActionSoundPlace : public CEditorActionLayerBase +{ +public: + CEditorActionSoundPlace(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector &vBrush); + + void Undo() override; + void Redo() override; + +private: + std::vector m_vBrush; +}; + +// ------------------------------------------------------------- + +class CEditorActionDeleteQuad : public CEditorActionLayerBase +{ +public: + CEditorActionDeleteQuad(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector const &vQuadsIndices, std::vector const &vDeletedQuads); + + void Undo() override; + void Redo() override; + +private: + std::vector m_vQuadsIndices; + std::vector m_vDeletedQuads; +}; + +// ------------------------------------------------------------- + +class CEditorActionEditQuadPoint : public CEditorActionLayerBase +{ +public: + CEditorActionEditQuadPoint(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, std::vector const &vPreviousPoints, std::vector const &vCurrentPoints); + + void Undo() override; + void Redo() override; + +private: + int m_QuadIndex; + std::vector m_vPreviousPoints; + std::vector m_vCurrentPoints; +}; + +class CEditorActionEditQuadProp : public CEditorActionLayerBase +{ +public: + CEditorActionEditQuadProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, EQuadProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + int m_QuadIndex; + EQuadProp m_Prop; + int m_Previous; + int m_Current; + + void Apply(int Value); +}; + +class CEditorActionEditQuadPointProp : public CEditorActionLayerBase +{ +public: + CEditorActionEditQuadPointProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, int PointIndex, EQuadPointProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + int m_QuadIndex; + int m_PointIndex; + EQuadPointProp m_Prop; + int m_Previous; + int m_Current; + + void Apply(int Value); +}; + +// ------------------------------------------------------------- + +class CEditorActionBulk : public IEditorAction +{ +public: + CEditorActionBulk(CEditor *pEditor, const std::vector> &vpActions, const char *pDisplay = nullptr, bool Reverse = false); + + void Undo() override; + void Redo() override; + +private: + std::vector> m_vpActions; + std::string m_Display; + bool m_Reverse; +}; + +// + +class CEditorActionAutoMap : public CEditorActionLayerBase +{ +public: + CEditorActionAutoMap(CEditor *pEditor, int GroupIndex, int LayerIndex, const EditorTileStateChangeHistory &Changes); + + void Undo() override; + void Redo() override; + +private: + EditorTileStateChangeHistory m_Changes; + int m_TotalChanges; + + void ComputeInfos(); + void Apply(bool Undo); +}; + +// ------------------------------------------------------------- + +class CEditorActionAddLayer : public CEditorActionLayerBase +{ +public: + CEditorActionAddLayer(CEditor *pEditor, int GroupIndex, int LayerIndex, bool Duplicate = false); + + void Undo() override; + void Redo() override; + +private: + bool m_Duplicate; +}; + +class CEditorActionDeleteLayer : public CEditorActionLayerBase +{ +public: + CEditorActionDeleteLayer(CEditor *pEditor, int GroupIndex, int LayerIndex); + + void Undo() override; + void Redo() override; +}; + +class CEditorActionGroup : public IEditorAction +{ +public: + CEditorActionGroup(CEditor *pEditor, int GroupIndex, bool Delete); + + void Undo() override; + void Redo() override; + +private: + int m_GroupIndex; + bool m_Delete; + std::shared_ptr m_pGroup; +}; + +class CEditorActionEditGroupProp : public IEditorAction +{ +public: + CEditorActionEditGroupProp(CEditor *pEditor, int GroupIndex, EGroupProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + int m_GroupIndex; + EGroupProp m_Prop; + int m_Previous; + int m_Current; + + void Apply(int Value); +}; + +template +class CEditorActionEditLayerPropBase : public CEditorActionLayerBase +{ +public: + CEditorActionEditLayerPropBase(CEditor *pEditor, int GroupIndex, int LayerIndex, E Prop, int Previous, int Current); + + virtual void Undo() override {} + virtual void Redo() override {} + +protected: + E m_Prop; + int m_Previous; + int m_Current; +}; + +class CEditorActionEditLayerProp : public CEditorActionEditLayerPropBase +{ +public: + CEditorActionEditLayerProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + void Apply(int Value); +}; + +class CEditorActionEditLayerTilesProp : public CEditorActionEditLayerPropBase +{ +public: + CEditorActionEditLayerTilesProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ETilesProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + + void SetSavedLayers(const std::map> &SavedLayers); + +private: + std::map> m_SavedLayers; + + void RestoreLayer(int Layer, const std::shared_ptr &pLayerTiles); +}; + +class CEditorActionEditLayerQuadsProp : public CEditorActionEditLayerPropBase +{ +public: + CEditorActionEditLayerQuadsProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerQuadsProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + void Apply(int Value); +}; + +class CEditorActionEditLayersGroupAndOrder : public IEditorAction +{ +public: + CEditorActionEditLayersGroupAndOrder(CEditor *pEditor, int GroupIndex, const std::vector &LayerIndices, int NewGroupIndex, const std::vector &NewLayerIndices); + + void Undo() override; + void Redo() override; + +private: + int m_GroupIndex; + std::vector m_LayerIndices; + int m_NewGroupIndex; + std::vector m_NewLayerIndices; +}; + +// -------------- + +class CEditorActionAppendMap : public IEditorAction +{ +public: + struct SPrevInfo + { + int m_Groups; + int m_Images; + int m_Sounds; + int m_Envelopes; + }; + +public: + CEditorActionAppendMap(CEditor *pEditor, const char *pMapName, const SPrevInfo &PrevInfo, std::vector &vImageIndexMap); + + void Undo() override; + void Redo() override; + +private: + char m_aMapName[IO_MAX_PATH_LENGTH]; + SPrevInfo m_PrevInfo; + std::vector m_vImageIndexMap; +}; + +// -------------- + +class CEditorActionTileArt : public IEditorAction +{ +public: + CEditorActionTileArt(CEditor *pEditor, int PreviousImageCount, const char *pTileArtFile, std::vector &vImageIndexMap); + + void Undo() override; + void Redo() override; + +private: + int m_PreviousImageCount; + char m_aTileArtFile[IO_MAX_PATH_LENGTH]; + std::vector m_vImageIndexMap; +}; + +// ---------------------- + +class CEditorCommandAction : public IEditorAction +{ +public: + enum class EType + { + DELETE, + ADD, + EDIT, + MOVE_UP, + MOVE_DOWN + }; + + CEditorCommandAction(CEditor *pEditor, EType Type, int *pSelectedCommandIndex, int CommandIndex, const char *pPreviousCommand = nullptr, const char *pCurrentCommand = nullptr); + + void Undo() override; + void Redo() override; + +private: + EType m_Type; + int *m_pSelectedCommandIndex; + int m_CommandIndex; + std::string m_PreviousCommand; + std::string m_CurrentCommand; +}; + +// ------------------------------ + +class CEditorActionEnvelopeAdd : public IEditorAction +{ +public: + CEditorActionEnvelopeAdd(CEditor *pEditor, const std::shared_ptr &pEnv); + + void Undo() override; + void Redo() override; + +private: + std::shared_ptr m_pEnv; +}; + +class CEditorActionEveloppeDelete : public IEditorAction +{ +public: + CEditorActionEveloppeDelete(CEditor *pEditor, int EnvelopeIndex); + + void Undo() override; + void Redo() override; + +private: + int m_EnvelopeIndex; + std::shared_ptr m_pEnv; +}; + +class CEditorActionEnvelopeEdit : public IEditorAction +{ +public: + enum class EEditType + { + SYNC, + ORDER + }; + + CEditorActionEnvelopeEdit(CEditor *pEditor, int EnvelopeIndex, EEditType EditType, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + int m_EnvelopeIndex; + EEditType m_EditType; + int m_Previous; + int m_Current; + std::shared_ptr m_pEnv; +}; + +class CEditorActionEnvelopeEditPoint : public IEditorAction +{ +public: + enum class EEditType + { + TIME, + VALUE, + CURVE_TYPE, + HANDLE + }; + + CEditorActionEnvelopeEditPoint(CEditor *pEditor, int EnvelopeIndex, int PointIndex, int Channel, EEditType EditType, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + int m_EnvelopeIndex; + int m_PointIndex; + int m_Channel; + EEditType m_EditType; + int m_Previous; + int m_Current; + std::shared_ptr m_pEnv; + + void Apply(int Value); +}; + +class CEditorActionAddEnvelopePoint : public IEditorAction +{ +public: + CEditorActionAddEnvelopePoint(CEditor *pEditor, int EnvIndex, int Time, ColorRGBA Channels); + + void Undo() override; + void Redo() override; + +private: + int m_EnvIndex; + int m_Time; + ColorRGBA m_Channels; +}; + +class CEditorActionDeleteEnvelopePoint : public IEditorAction +{ +public: + CEditorActionDeleteEnvelopePoint(CEditor *pEditor, int EnvIndex, int PointIndex); + + void Undo() override; + void Redo() override; + +private: + int m_EnvIndex; + int m_PointIndex; + CEnvPoint_runtime m_Point; +}; + +class CEditorActionEditEnvelopePointValue : public IEditorAction +{ +public: + enum class EType + { + TANGENT_IN, + TANGENT_OUT, + POINT + }; + + CEditorActionEditEnvelopePointValue(CEditor *pEditor, int EnvIndex, int PointIndex, int Channel, EType Type, int OldTime, int OldValue, int NewTime, int NewValue); + + void Undo() override; + void Redo() override; + +private: + int m_EnvIndex; + int m_PtIndex; + int m_Channel; + EType m_Type; + int m_OldTime; + int m_OldValue; + int m_NewTime; + int m_NewValue; + + void Apply(bool Undo); +}; + +class CEditorActionResetEnvelopePointTangent : public IEditorAction +{ +public: + CEditorActionResetEnvelopePointTangent(CEditor *pEditor, int EnvIndex, int PointIndex, int Channel, bool In); + + void Undo() override; + void Redo() override; + +private: + int m_EnvIndex; + int m_PointIndex; + int m_Channel; + bool m_In; + int m_Previous[2]; +}; + +class CEditorActionEditLayerSoundsProp : public CEditorActionEditLayerPropBase +{ +public: + CEditorActionEditLayerSoundsProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerSoundsProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + void Apply(int Value); +}; + +class CEditorActionDeleteSoundSource : public CEditorActionLayerBase +{ +public: + CEditorActionDeleteSoundSource(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex); + + void Undo() override; + void Redo() override; + +private: + int m_SourceIndex; + CSoundSource m_Source; +}; + +class CEditorActionEditSoundSource : public CEditorActionLayerBase +{ +public: + enum class EEditType + { + SHAPE + }; + + CEditorActionEditSoundSource(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, EEditType Type, int Value); + ~CEditorActionEditSoundSource() override; + + void Undo() override; + void Redo() override; + +private: + int m_SourceIndex; + EEditType m_EditType; + int m_CurrentValue; + + std::vector m_vOriginalValues; + void *m_pSavedObject; + + void Save(); +}; + +class CEditorActionEditSoundSourceProp : public CEditorActionEditLayerPropBase +{ +public: + CEditorActionEditSoundSourceProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ESoundProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + int m_SourceIndex; + +private: + void Apply(int Value); +}; + +class CEditorActionEditRectSoundSourceShapeProp : public CEditorActionEditLayerPropBase +{ +public: + CEditorActionEditRectSoundSourceShapeProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ERectangleShapeProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + int m_SourceIndex; + +private: + void Apply(int Value); +}; + +class CEditorActionEditCircleSoundSourceShapeProp : public CEditorActionEditLayerPropBase +{ +public: + CEditorActionEditCircleSoundSourceShapeProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ECircleShapeProp Prop, int Previous, int Current); + + void Undo() override; + void Redo() override; + +private: + int m_SourceIndex; + +private: + void Apply(int Value); +}; + +class CEditorActionNewEmptySound : public CEditorActionLayerBase +{ +public: + CEditorActionNewEmptySound(CEditor *pEditor, int GroupIndex, int LayerIndex, int x, int y); + + void Undo() override; + void Redo() override; + +private: + int m_X; + int m_Y; +}; + +class CEditorActionNewEmptyQuad : public CEditorActionLayerBase +{ +public: + CEditorActionNewEmptyQuad(CEditor *pEditor, int GroupIndex, int LayerIndex, int x, int y); + + void Undo() override; + void Redo() override; + +private: + int m_X; + int m_Y; +}; + +class CEditorActionNewQuad : public CEditorActionLayerBase +{ +public: + CEditorActionNewQuad(CEditor *pEditor, int GroupIndex, int LayerIndex); + + void Undo() override; + void Redo() override; + +private: + CQuad m_Quad; +}; + +#endif diff --git a/src/game/editor/editor_history.cpp b/src/game/editor/editor_history.cpp new file mode 100644 index 00000000000..46110de2d55 --- /dev/null +++ b/src/game/editor/editor_history.cpp @@ -0,0 +1,65 @@ +#include + +#include "editor.h" +#include "editor_actions.h" +#include "editor_history.h" + +void CEditorHistory::RecordAction(const std::shared_ptr &pAction) +{ + RecordAction(pAction, nullptr); +} + +void CEditorHistory::Execute(const std::shared_ptr &pAction, const char *pDisplay) +{ + pAction->Redo(); + RecordAction(pAction, pDisplay); +} + +void CEditorHistory::RecordAction(const std::shared_ptr &pAction, const char *pDisplay) +{ + m_vpRedoActions.clear(); + + if((int)m_vpUndoActions.size() >= g_Config.m_ClEditorMaxHistory) + { + m_vpUndoActions.pop_front(); + } + + if(pDisplay == nullptr) + m_vpUndoActions.emplace_back(pAction); + else + m_vpUndoActions.emplace_back(std::make_shared(m_pEditor, std::vector>{pAction}, pDisplay)); +} + +bool CEditorHistory::Undo() +{ + if(m_vpUndoActions.empty()) + return false; + + auto pLastAction = m_vpUndoActions.back(); + m_vpUndoActions.pop_back(); + + pLastAction->Undo(); + + m_vpRedoActions.emplace_back(pLastAction); + return true; +} + +bool CEditorHistory::Redo() +{ + if(m_vpRedoActions.empty()) + return false; + + auto pLastAction = m_vpRedoActions.back(); + m_vpRedoActions.pop_back(); + + pLastAction->Redo(); + + m_vpUndoActions.emplace_back(pLastAction); + return true; +} + +void CEditorHistory::Clear() +{ + m_vpUndoActions.clear(); + m_vpRedoActions.clear(); +} diff --git a/src/game/editor/editor_history.h b/src/game/editor/editor_history.h new file mode 100644 index 00000000000..144147140f6 --- /dev/null +++ b/src/game/editor/editor_history.h @@ -0,0 +1,37 @@ +#ifndef GAME_EDITOR_EDITOR_HISTORY_H +#define GAME_EDITOR_EDITOR_HISTORY_H + +#include "editor_action.h" + +#include + +class CEditorHistory +{ +public: + CEditorHistory() + { + m_pEditor = nullptr; + } + + ~CEditorHistory() + { + Clear(); + } + + void RecordAction(const std::shared_ptr &pAction); + void RecordAction(const std::shared_ptr &pAction, const char *pDisplay); + void Execute(const std::shared_ptr &pAction, const char *pDisplay = nullptr); + + bool Undo(); + bool Redo(); + + void Clear(); + bool CanUndo() const { return !m_vpUndoActions.empty(); } + bool CanRedo() const { return !m_vpRedoActions.empty(); } + + CEditor *m_pEditor; + std::deque> m_vpUndoActions; + std::deque> m_vpRedoActions; +}; + +#endif diff --git a/src/game/editor/editor_props.cpp b/src/game/editor/editor_props.cpp new file mode 100644 index 00000000000..7d57b9c8f2f --- /dev/null +++ b/src/game/editor/editor_props.cpp @@ -0,0 +1,282 @@ +#include "editor.h" + +#include +#include + +int CEditor::DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color) +{ + auto Res = DoPropertiesWithState(pToolbox, pProps, pIDs, pNewVal, Color); + return Res.m_Value; +} + +template +SEditResult CEditor::DoPropertiesWithState(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color) +{ + int Change = -1; + EEditState State = EEditState::EDITING; + + for(int i = 0; pProps[i].m_pName; i++) + { + CUIRect Slot; + pToolBox->HSplitTop(13.0f, &Slot, pToolBox); + CUIRect Label, Shifter; + Slot.VSplitMid(&Label, &Shifter); + Shifter.HMargin(1.0f, &Shifter); + UI()->DoLabel(&Label, pProps[i].m_pName, 10.0f, TEXTALIGN_ML); + + if(pProps[i].m_Type == PROPTYPE_INT_STEP) + { + CUIRect Inc, Dec; + char aBuf[64]; + + Shifter.VSplitRight(10.0f, &Shifter, &Inc); + Shifter.VSplitLeft(10.0f, &Dec, &Shifter); + str_from_int(pProps[i].m_Value, aBuf); + auto NewValueRes = UiDoValueSelector((char *)&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0, &Color); + int NewValue = NewValueRes.m_Value; + if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING) + { + *pNewVal = NewValue; + Change = i; + State = NewValueRes.m_State; + } + if(DoButton_ButtonDec((char *)&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Decrease")) + { + *pNewVal = pProps[i].m_Value - 1; + Change = i; + State = EEditState::ONE_GO; + } + if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, nullptr, 0, &Inc, 0, "Increase")) + { + *pNewVal = pProps[i].m_Value + 1; + Change = i; + State = EEditState::ONE_GO; + } + } + else if(pProps[i].m_Type == PROPTYPE_BOOL) + { + CUIRect No, Yes; + Shifter.VSplitMid(&No, &Yes); + if(DoButton_ButtonDec(&pIDs[i], "No", !pProps[i].m_Value, &No, 0, "")) + { + *pNewVal = 0; + Change = i; + State = EEditState::ONE_GO; + } + if(DoButton_ButtonInc(((char *)&pIDs[i]) + 1, "Yes", pProps[i].m_Value, &Yes, 0, "")) + { + *pNewVal = 1; + Change = i; + State = EEditState::ONE_GO; + } + } + else if(pProps[i].m_Type == PROPTYPE_INT_SCROLL) + { + auto NewValueRes = UiDoValueSelector(&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text."); + int NewValue = NewValueRes.m_Value; + if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING) + { + *pNewVal = NewValue; + Change = i; + State = NewValueRes.m_State; + } + } + else if(pProps[i].m_Type == PROPTYPE_ANGLE_SCROLL) + { + CUIRect Inc, Dec; + Shifter.VSplitRight(10.0f, &Shifter, &Inc); + Shifter.VSplitLeft(10.0f, &Dec, &Shifter); + const bool Shift = Input()->ShiftIsPressed(); + int Step = Shift ? 1 : 45; + int Value = pProps[i].m_Value; + + auto NewValueRes = UiDoValueSelector(&pIDs[i], &Shifter, "", Value, pProps[i].m_Min, pProps[i].m_Max, Shift ? 1 : 45, Shift ? 1.0f : 10.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0); + int NewValue = NewValueRes.m_Value; + if(DoButton_ButtonDec(&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Decrease")) + { + NewValue = (std::ceil((pProps[i].m_Value / (float)Step)) - 1) * Step; + if(NewValue < 0) + NewValue += 360; + State = EEditState::ONE_GO; + } + if(DoButton_ButtonInc(&pIDs[i] + 2, nullptr, 0, &Inc, 0, "Increase")) + { + NewValue = (pProps[i].m_Value + Step) / Step * Step; + State = EEditState::ONE_GO; + } + + if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING) + { + *pNewVal = NewValue % 360; + Change = i; + State = NewValueRes.m_State; + } + } + else if(pProps[i].m_Type == PROPTYPE_COLOR) + { + const auto &&SetColor = [&](ColorRGBA NewColor) { + const int NewValue = NewColor.PackAlphaLast(); + if(NewValue != pProps[i].m_Value || m_ColorPickerPopupContext.m_State != EEditState::EDITING) + { + *pNewVal = NewValue; + Change = i; + State = m_ColorPickerPopupContext.m_State; + } + }; + DoColorPickerButton(&pIDs[i], &Shifter, ColorRGBA::UnpackAlphaLast(pProps[i].m_Value), SetColor); + } + else if(pProps[i].m_Type == PROPTYPE_IMAGE) + { + const char *pName; + if(pProps[i].m_Value < 0) + pName = "None"; + else + pName = m_Map.m_vpImages[pProps[i].m_Value]->m_aName; + + if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) + PopupSelectImageInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); + + int r = PopupSelectImageResult(); + if(r >= -1) + { + *pNewVal = r; + Change = i; + State = EEditState::ONE_GO; + } + } + else if(pProps[i].m_Type == PROPTYPE_SHIFT) + { + CUIRect Left, Right, Up, Down; + Shifter.VSplitMid(&Left, &Up, 2.0f); + Left.VSplitLeft(10.0f, &Left, &Shifter); + Shifter.VSplitRight(10.0f, &Shifter, &Right); + Shifter.Draw(ColorRGBA(1, 1, 1, 0.5f), 0, 0.0f); + UI()->DoLabel(&Shifter, "X", 10.0f, TEXTALIGN_MC); + Up.VSplitLeft(10.0f, &Up, &Shifter); + Shifter.VSplitRight(10.0f, &Shifter, &Down); + Shifter.Draw(ColorRGBA(1, 1, 1, 0.5f), 0, 0.0f); + UI()->DoLabel(&Shifter, "Y", 10.0f, TEXTALIGN_MC); + if(DoButton_ButtonDec(&pIDs[i], "-", 0, &Left, 0, "Left")) + { + *pNewVal = DIRECTION_LEFT; + Change = i; + State = EEditState::ONE_GO; + } + if(DoButton_ButtonInc(((char *)&pIDs[i]) + 3, "+", 0, &Right, 0, "Right")) + { + *pNewVal = DIRECTION_RIGHT; + Change = i; + State = EEditState::ONE_GO; + } + if(DoButton_ButtonDec(((char *)&pIDs[i]) + 1, "-", 0, &Up, 0, "Up")) + { + *pNewVal = DIRECTION_UP; + Change = i; + State = EEditState::ONE_GO; + } + if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, "+", 0, &Down, 0, "Down")) + { + *pNewVal = DIRECTION_DOWN; + Change = i; + State = EEditState::ONE_GO; + } + } + else if(pProps[i].m_Type == PROPTYPE_SOUND) + { + const char *pName; + if(pProps[i].m_Value < 0) + pName = "None"; + else + pName = m_Map.m_vpSounds[pProps[i].m_Value]->m_aName; + + if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) + PopupSelectSoundInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); + + int r = PopupSelectSoundResult(); + if(r >= -1) + { + *pNewVal = r; + Change = i; + State = EEditState::ONE_GO; + } + } + else if(pProps[i].m_Type == PROPTYPE_AUTOMAPPER) + { + const char *pName; + if(pProps[i].m_Value < 0 || pProps[i].m_Min < 0 || pProps[i].m_Min >= (int)m_Map.m_vpImages.size()) + pName = "None"; + else + pName = m_Map.m_vpImages[pProps[i].m_Min]->m_AutoMapper.GetConfigName(pProps[i].m_Value); + + if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) + PopupSelectConfigAutoMapInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); + + int r = PopupSelectConfigAutoMapResult(); + if(r >= -1) + { + *pNewVal = r; + Change = i; + State = EEditState::ONE_GO; + } + } + else if(pProps[i].m_Type == PROPTYPE_ENVELOPE) + { + CUIRect Inc, Dec; + char aBuf[8]; + int CurValue = pProps[i].m_Value; + + Shifter.VSplitRight(10.0f, &Shifter, &Inc); + Shifter.VSplitLeft(10.0f, &Dec, &Shifter); + + if(CurValue <= 0) + str_copy(aBuf, "None:"); + else if(m_Map.m_vpEnvelopes[CurValue - 1]->m_aName[0]) + { + str_format(aBuf, sizeof(aBuf), "%s:", m_Map.m_vpEnvelopes[CurValue - 1]->m_aName); + if(!str_endswith(aBuf, ":")) + { + aBuf[sizeof(aBuf) - 2] = ':'; + aBuf[sizeof(aBuf) - 1] = '\0'; + } + } + else + aBuf[0] = '\0'; + + auto NewValueRes = UiDoValueSelector((char *)&pIDs[i], &Shifter, aBuf, CurValue, 0, m_Map.m_vpEnvelopes.size(), 1, 1.0f, "Set Envelope", false, false, IGraphics::CORNER_NONE); + int NewVal = NewValueRes.m_Value; + if(NewVal != CurValue || NewValueRes.m_State != EEditState::EDITING) + { + *pNewVal = NewVal; + Change = i; + State = NewValueRes.m_State; + } + + if(DoButton_ButtonDec((char *)&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Previous Envelope")) + { + *pNewVal = pProps[i].m_Value - 1; + Change = i; + State = EEditState::ONE_GO; + } + if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, nullptr, 0, &Inc, 0, "Next Envelope")) + { + *pNewVal = pProps[i].m_Value + 1; + Change = i; + State = EEditState::ONE_GO; + } + } + } + + return SEditResult{State, static_cast(Change)}; +} + +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); diff --git a/src/game/editor/editor_trackers.cpp b/src/game/editor/editor_trackers.cpp new file mode 100644 index 00000000000..8f3660422fc --- /dev/null +++ b/src/game/editor/editor_trackers.cpp @@ -0,0 +1,632 @@ +#include "editor_trackers.h" + +#include +#include + +#include "editor.h" +#include "editor_actions.h" + +CQuadEditTracker::CQuadEditTracker() : + m_pEditor(nullptr), m_TrackedProp(EQuadProp::PROP_NONE) {} + +CQuadEditTracker::~CQuadEditTracker() +{ + m_InitalPoints.clear(); + m_vSelectedQuads.clear(); +} + +void CQuadEditTracker::BeginQuadTrack(const std::shared_ptr &pLayer, const std::vector &vSelectedQuads) +{ + if(m_Tracking) + return; + m_Tracking = true; + m_vSelectedQuads.clear(); + m_pLayer = pLayer; + // Init all points + for(auto QuadIndex : vSelectedQuads) + { + auto &pQuad = pLayer->m_vQuads[QuadIndex]; + m_InitalPoints[QuadIndex] = std::vector(pQuad.m_aPoints, pQuad.m_aPoints + 5); + m_vSelectedQuads.push_back(QuadIndex); + } +} + +void CQuadEditTracker::EndQuadTrack() +{ + if(!m_Tracking) + return; + m_Tracking = false; + + int GroupIndex = m_pEditor->m_SelectedGroup; + int LayerIndex = m_pEditor->m_vSelectedLayers[0]; + + // Record all moved stuff + std::vector> vpActions; + for(auto QuadIndex : m_vSelectedQuads) + { + auto &pQuad = m_pLayer->m_vQuads[QuadIndex]; + auto vCurrentPoints = std::vector(pQuad.m_aPoints, pQuad.m_aPoints + 5); + vpActions.push_back(std::make_shared(m_pEditor, GroupIndex, LayerIndex, QuadIndex, m_InitalPoints[QuadIndex], vCurrentPoints)); + } + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, vpActions)); +} + +void CQuadEditTracker::BeginQuadPropTrack(const std::shared_ptr &pLayer, const std::vector &vSelectedQuads, EQuadProp Prop) +{ + if(m_TrackedProp != EQuadProp::PROP_NONE) + return; + m_TrackedProp = Prop; + m_pLayer = pLayer; + m_vSelectedQuads = vSelectedQuads; + m_PreviousValues.clear(); + + for(auto QuadIndex : vSelectedQuads) + { + auto &Quad = pLayer->m_vQuads[QuadIndex]; + if(Prop == EQuadProp::PROP_POS_X || Prop == EQuadProp::PROP_POS_Y) + m_InitalPoints[QuadIndex] = std::vector(Quad.m_aPoints, Quad.m_aPoints + 5); + else if(Prop == EQuadProp::PROP_POS_ENV) + m_PreviousValues[QuadIndex] = Quad.m_PosEnv; + else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET) + m_PreviousValues[QuadIndex] = Quad.m_PosEnvOffset; + else if(Prop == EQuadProp::PROP_COLOR_ENV) + m_PreviousValues[QuadIndex] = Quad.m_ColorEnv; + else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET) + m_PreviousValues[QuadIndex] = Quad.m_ColorEnvOffset; + } +} +void CQuadEditTracker::EndQuadPropTrack(EQuadProp Prop) +{ + if(m_TrackedProp != Prop) + return; + m_TrackedProp = EQuadProp::PROP_NONE; + + std::vector> vpActions; + + int GroupIndex = m_pEditor->m_SelectedGroup; + int LayerIndex = m_pEditor->m_vSelectedLayers[0]; + + for(auto QuadIndex : m_vSelectedQuads) + { + auto &Quad = m_pLayer->m_vQuads[QuadIndex]; + if(Prop == EQuadProp::PROP_POS_X || Prop == EQuadProp::PROP_POS_Y) + { + auto vCurrentPoints = std::vector(Quad.m_aPoints, Quad.m_aPoints + 5); + vpActions.push_back(std::make_shared(m_pEditor, GroupIndex, LayerIndex, QuadIndex, m_InitalPoints[QuadIndex], vCurrentPoints)); + } + else + { + int Value = 0; + if(Prop == EQuadProp::PROP_POS_ENV) + Value = Quad.m_PosEnv; + else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET) + Value = Quad.m_PosEnvOffset; + else if(Prop == EQuadProp::PROP_COLOR_ENV) + Value = Quad.m_ColorEnv; + else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET) + Value = Quad.m_ColorEnvOffset; + + if(Value != m_PreviousValues[QuadIndex]) + vpActions.push_back(std::make_shared(m_pEditor, GroupIndex, LayerIndex, QuadIndex, Prop, m_PreviousValues[QuadIndex], Value)); + } + } + + if(!vpActions.empty()) + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, vpActions)); +} + +void CQuadEditTracker::BeginQuadPointPropTrack(const std::shared_ptr &pLayer, const std::vector &vSelectedQuads, int SelectedQuadPoints) +{ + if(!m_vTrackedProps.empty()) + return; + + m_pLayer = pLayer; + m_SelectedQuadPoints = SelectedQuadPoints; + m_vSelectedQuads = vSelectedQuads; + m_PreviousValuesPoint.clear(); + + for(auto QuadIndex : vSelectedQuads) + { + m_PreviousValuesPoint[QuadIndex] = std::vector>(4); + } +} + +void CQuadEditTracker::AddQuadPointPropTrack(EQuadPointProp Prop) +{ + if(std::find(m_vTrackedProps.begin(), m_vTrackedProps.end(), Prop) != m_vTrackedProps.end()) + return; + + m_vTrackedProps.push_back(Prop); + + for(auto QuadIndex : m_vSelectedQuads) + { + auto &Quad = m_pLayer->m_vQuads[QuadIndex]; + if(Prop == EQuadPointProp::PROP_POS_X || Prop == EQuadPointProp::PROP_POS_Y) + m_InitalPoints[QuadIndex] = std::vector(Quad.m_aPoints, Quad.m_aPoints + 5); + else if(Prop == EQuadPointProp::PROP_COLOR) + { + for(int v = 0; v < 4; v++) + { + if(m_SelectedQuadPoints & (1 << v)) + { + int Color = PackColor(Quad.m_aColors[v]); + m_PreviousValuesPoint[QuadIndex][v][Prop] = Color; + } + } + } + else if(Prop == EQuadPointProp::PROP_TEX_U) + { + for(int v = 0; v < 4; v++) + { + if(m_SelectedQuadPoints & (1 << v)) + { + m_PreviousValuesPoint[QuadIndex][v][Prop] = Quad.m_aTexcoords[v].x; + } + } + } + else if(Prop == EQuadPointProp::PROP_TEX_V) + { + for(int v = 0; v < 4; v++) + { + if(m_SelectedQuadPoints & (1 << v)) + { + m_PreviousValuesPoint[QuadIndex][v][Prop] = Quad.m_aTexcoords[v].y; + } + } + } + } +} + +void CQuadEditTracker::EndQuadPointPropTrack(EQuadPointProp Prop) +{ + auto It = std::find(m_vTrackedProps.begin(), m_vTrackedProps.end(), Prop); + if(It == m_vTrackedProps.end()) + return; + + m_vTrackedProps.erase(It); + + std::vector> vpActions; + + int GroupIndex = m_pEditor->m_SelectedGroup; + int LayerIndex = m_pEditor->m_vSelectedLayers[0]; + + for(auto QuadIndex : m_vSelectedQuads) + { + auto &Quad = m_pLayer->m_vQuads[QuadIndex]; + if(Prop == EQuadPointProp::PROP_POS_X || Prop == EQuadPointProp::PROP_POS_Y) + { + auto vCurrentPoints = std::vector(Quad.m_aPoints, Quad.m_aPoints + 5); + vpActions.push_back(std::make_shared(m_pEditor, GroupIndex, LayerIndex, QuadIndex, m_InitalPoints[QuadIndex], vCurrentPoints)); + } + else + { + int Value = 0; + for(int v = 0; v < 4; v++) + { + if(m_SelectedQuadPoints & (1 << v)) + { + if(Prop == EQuadPointProp::PROP_COLOR) + { + Value = PackColor(Quad.m_aColors[v]); + } + else if(Prop == EQuadPointProp::PROP_TEX_U) + { + Value = Quad.m_aTexcoords[v].x; + } + else if(Prop == EQuadPointProp::PROP_TEX_V) + { + Value = Quad.m_aTexcoords[v].y; + } + + if(Value != m_PreviousValuesPoint[QuadIndex][v][Prop]) + vpActions.push_back(std::make_shared(m_pEditor, GroupIndex, LayerIndex, QuadIndex, v, Prop, m_PreviousValuesPoint[QuadIndex][v][Prop], Value)); + } + } + } + } + + if(!vpActions.empty()) + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, vpActions)); +} + +void CQuadEditTracker::EndQuadPointPropTrackAll() +{ + std::vector> vpActions; + for(auto &Prop : m_vTrackedProps) + { + int GroupIndex = m_pEditor->m_SelectedGroup; + int LayerIndex = m_pEditor->m_vSelectedLayers[0]; + + for(auto QuadIndex : m_vSelectedQuads) + { + auto &Quad = m_pLayer->m_vQuads[QuadIndex]; + if(Prop == EQuadPointProp::PROP_POS_X || Prop == EQuadPointProp::PROP_POS_Y) + { + auto vCurrentPoints = std::vector(Quad.m_aPoints, Quad.m_aPoints + 5); + vpActions.push_back(std::make_shared(m_pEditor, GroupIndex, LayerIndex, QuadIndex, m_InitalPoints[QuadIndex], vCurrentPoints)); + } + else + { + int Value = 0; + for(int v = 0; v < 4; v++) + { + if(m_SelectedQuadPoints & (1 << v)) + { + if(Prop == EQuadPointProp::PROP_COLOR) + { + Value = PackColor(Quad.m_aColors[v]); + } + else if(Prop == EQuadPointProp::PROP_TEX_U) + { + Value = Quad.m_aTexcoords[v].x; + } + else if(Prop == EQuadPointProp::PROP_TEX_V) + { + Value = Quad.m_aTexcoords[v].y; + } + + if(Value != m_PreviousValuesPoint[QuadIndex][v][Prop]) + vpActions.push_back(std::make_shared(m_pEditor, GroupIndex, LayerIndex, QuadIndex, v, Prop, m_PreviousValuesPoint[QuadIndex][v][Prop], Value)); + } + } + } + } + } + + if(!vpActions.empty()) + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, vpActions)); + + m_vTrackedProps.clear(); +} + +void CEditorPropTracker::BeginPropTrack(int Prop, int Value) +{ + if(m_TrackedProp != -1 || m_TrackedProp == Prop) + return; + m_TrackedProp = Prop; + m_PreviousValue = Value; +} + +void CEditorPropTracker::StopPropTrack(int Prop, int Value) +{ + if(Prop != m_TrackedProp) + return; + m_TrackedProp = -1; + m_CurrentValue = Value; +} + +// ----------------------------- + +void CEnvelopeEditorOperationTracker::Begin(EEnvelopeEditorOp Operation) +{ + if(m_TrackedOp == Operation) + return; + + if(m_TrackedOp != EEnvelopeEditorOp::OP_NONE) + { + Stop(true); + } + + m_TrackedOp = Operation; + + if(Operation == EEnvelopeEditorOp::OP_DRAG_POINT || Operation == EEnvelopeEditorOp::OP_DRAG_POINT_X || Operation == EEnvelopeEditorOp::OP_DRAG_POINT_Y || Operation == EEnvelopeEditorOp::OP_SCALE) + { + HandlePointDragStart(); + } +} + +void CEnvelopeEditorOperationTracker::Stop(bool Switch) +{ + if(m_TrackedOp == EEnvelopeEditorOp::OP_NONE) + return; + + if(m_TrackedOp == EEnvelopeEditorOp::OP_DRAG_POINT || m_TrackedOp == EEnvelopeEditorOp::OP_DRAG_POINT_X || m_TrackedOp == EEnvelopeEditorOp::OP_DRAG_POINT_Y || m_TrackedOp == EEnvelopeEditorOp::OP_SCALE) + { + HandlePointDragEnd(Switch); + } + + m_TrackedOp = EEnvelopeEditorOp::OP_NONE; +} + +void CEnvelopeEditorOperationTracker::HandlePointDragStart() +{ + // Figure out which points are selected and which channels + // Save their X and Y position (time and value) + auto pEnv = m_pEditor->m_Map.m_vpEnvelopes[m_pEditor->m_SelectedEnvelope]; + + for(auto [PointIndex, Channel] : m_pEditor->m_vSelectedEnvelopePoints) + { + auto &Point = pEnv->m_vPoints[PointIndex]; + auto &Data = m_SavedValues[PointIndex]; + Data.m_Values[Channel] = Point.m_aValues[Channel]; + if(Data.m_Used) + continue; + Data.m_Time = Point.m_Time; + Data.m_Used = true; + } +} + +void CEnvelopeEditorOperationTracker::HandlePointDragEnd(bool Switch) +{ + if(Switch && m_TrackedOp != EEnvelopeEditorOp::OP_SCALE) + return; + + int EnvIndex = m_pEditor->m_SelectedEnvelope; + auto pEnv = m_pEditor->m_Map.m_vpEnvelopes[EnvIndex]; + std::vector> vpActions; + + for(auto const &Entry : m_SavedValues) + { + int PointIndex = Entry.first; + auto &Point = pEnv->m_vPoints[PointIndex]; + const auto &Data = Entry.second; + + if(Data.m_Time != Point.m_Time) + { // Save time + vpActions.push_back(std::make_shared(m_pEditor, EnvIndex, PointIndex, 0, CEditorActionEnvelopeEditPoint::EEditType::TIME, Data.m_Time, Point.m_Time)); + } + + for(auto Value : Data.m_Values) + { + // Value.second is the saved value, Value.first is the channel + int Channel = Value.first; + if(Value.second != Point.m_aValues[Channel]) + { // Save value + vpActions.push_back(std::make_shared(m_pEditor, EnvIndex, PointIndex, Channel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, Value.second, Point.m_aValues[Channel])); + } + } + } + + if(!vpActions.empty()) + { + m_pEditor->m_EnvelopeEditorHistory.RecordAction(std::make_shared(m_pEditor, vpActions, "Envelope point drag")); + } + + m_SavedValues.clear(); +} + +// ----------------------------------------------------------------------- + +void CLayerPropTracker::OnEnd(ELayerProp Prop, int Value) +{ + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], Prop, m_OriginalValue, Value)); +} + +int CLayerPropTracker::PropToValue(ELayerProp Prop) +{ + switch(Prop) + { + case ELayerProp::PROP_GROUP: return m_pEditor->m_SelectedGroup; + case ELayerProp::PROP_HQ: return m_pObject->m_Flags; + case ELayerProp::PROP_ORDER: return m_pEditor->m_vSelectedLayers[0]; + default: return 0; + } +} + +// ----------------------------------------------------------------------- + +bool CLayerTilesPropTracker::EndChecker(ETilesProp Prop, EEditState State, int Value) +{ + return (State == EEditState::END || State == EEditState::ONE_GO) && (Value != m_OriginalValue || Prop == ETilesProp::PROP_SHIFT); +} + +void CLayerTilesPropTracker::OnStart(ETilesProp Prop) +{ + if(Prop == ETilesProp::PROP_WIDTH || Prop == ETilesProp::PROP_HEIGHT) + { + m_SavedLayers[LAYERTYPE_TILES] = m_pObject->Duplicate(); + if(m_pObject->m_Game || m_pObject->m_Front || m_pObject->m_Switch || m_pObject->m_Speedup || m_pObject->m_Tune || m_pObject->m_Tele) + { // Need to save all entities layers when any entity layer + if(m_pEditor->m_Map.m_pFrontLayer && !m_pObject->m_Front) + m_SavedLayers[LAYERTYPE_FRONT] = m_pEditor->m_Map.m_pFrontLayer->Duplicate(); + if(m_pEditor->m_Map.m_pTeleLayer && !m_pObject->m_Tele) + m_SavedLayers[LAYERTYPE_TELE] = m_pEditor->m_Map.m_pTeleLayer->Duplicate(); + if(m_pEditor->m_Map.m_pSwitchLayer && !m_pObject->m_Switch) + m_SavedLayers[LAYERTYPE_SWITCH] = m_pEditor->m_Map.m_pSwitchLayer->Duplicate(); + if(m_pEditor->m_Map.m_pSpeedupLayer && !m_pObject->m_Speedup) + m_SavedLayers[LAYERTYPE_SPEEDUP] = m_pEditor->m_Map.m_pSpeedupLayer->Duplicate(); + if(m_pEditor->m_Map.m_pTuneLayer && !m_pObject->m_Tune) + m_SavedLayers[LAYERTYPE_TUNE] = m_pEditor->m_Map.m_pTuneLayer->Duplicate(); + if(!m_pObject->m_Game) + m_SavedLayers[LAYERTYPE_GAME] = m_pEditor->m_Map.m_pGameLayer->Duplicate(); + } + } + else if(Prop == ETilesProp::PROP_SHIFT) + { + m_SavedLayers[LAYERTYPE_TILES] = m_pObject->Duplicate(); + } +} + +void CLayerTilesPropTracker::OnEnd(ETilesProp Prop, int Value) +{ + auto pAction = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], Prop, m_OriginalValue, Value); + + pAction->SetSavedLayers(m_SavedLayers); + m_SavedLayers.clear(); + + m_pEditor->m_EditorHistory.RecordAction(pAction); +} + +int CLayerTilesPropTracker::PropToValue(ETilesProp Prop) +{ + switch(Prop) + { + case ETilesProp::PROP_AUTOMAPPER: return m_pObject->m_AutoMapperConfig; + case ETilesProp::PROP_COLOR: return PackColor(m_pObject->m_Color); + case ETilesProp::PROP_COLOR_ENV: return m_pObject->m_ColorEnv; + case ETilesProp::PROP_COLOR_ENV_OFFSET: return m_pObject->m_ColorEnvOffset; + case ETilesProp::PROP_HEIGHT: return m_pObject->m_Height; + case ETilesProp::PROP_WIDTH: return m_pObject->m_Width; + case ETilesProp::PROP_IMAGE: return m_pObject->m_Image; + case ETilesProp::PROP_SEED: return m_pObject->m_Seed; + case ETilesProp::PROP_SHIFT_BY: return m_pEditor->m_ShiftBy; + default: return 0; + } +} + +// ------------------------------ + +void CLayerTilesCommonPropTracker::OnStart(ETilesCommonProp Prop) +{ + for(auto &pLayer : m_vpLayers) + { + if(Prop == ETilesCommonProp::PROP_SHIFT) + { + m_SavedLayers[pLayer][LAYERTYPE_TILES] = pLayer->Duplicate(); + } + } +} + +void CLayerTilesCommonPropTracker::OnEnd(ETilesCommonProp Prop, int Value) +{ + std::vector> vpActions; + + static std::map s_PropMap{ + {ETilesCommonProp::PROP_COLOR, ETilesProp::PROP_COLOR}, + {ETilesCommonProp::PROP_SHIFT, ETilesProp::PROP_SHIFT}, + {ETilesCommonProp::PROP_SHIFT_BY, ETilesProp::PROP_SHIFT_BY}}; + + int j = 0; + for(auto &pLayer : m_vpLayers) + { + int LayerIndex = m_vLayerIndices[j++]; + auto pAction = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, LayerIndex, s_PropMap[Prop], m_OriginalValue, Value); + pAction->SetSavedLayers(m_SavedLayers[pLayer]); + vpActions.push_back(pAction); + } + + char aDisplay[256]; + static const char *s_apNames[] = { + "width", + "height", + "shift", + "shift by", + "color", + }; + + str_format(aDisplay, sizeof(aDisplay), "Edit %d layers common property: %s", (int)m_vpLayers.size(), s_apNames[(int)Prop]); + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, vpActions, aDisplay)); +} + +bool CLayerTilesCommonPropTracker::EndChecker(ETilesCommonProp Prop, EEditState State, int Value) +{ + return (State == EEditState::END || State == EEditState::ONE_GO) && (Value != m_OriginalValue || Prop == ETilesCommonProp::PROP_SHIFT); +} + +int CLayerTilesCommonPropTracker::PropToValue(ETilesCommonProp Prop) +{ + if(Prop == ETilesCommonProp::PROP_SHIFT_BY) + return m_pEditor->m_ShiftBy; + return 0; +} + +// ------------------------------ + +void CLayerGroupPropTracker::OnEnd(EGroupProp Prop, int Value) +{ + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, Prop, m_OriginalValue, Value)); +} + +int CLayerGroupPropTracker::PropToValue(EGroupProp Prop) +{ + switch(Prop) + { + case EGroupProp::PROP_ORDER: return m_pEditor->m_SelectedGroup; + case EGroupProp::PROP_POS_X: return m_pObject->m_OffsetX; + case EGroupProp::PROP_POS_Y: return m_pObject->m_OffsetY; + case EGroupProp::PROP_PARA_X: return m_pObject->m_ParallaxX; + case EGroupProp::PROP_PARA_Y: return m_pObject->m_ParallaxY; + case EGroupProp::PROP_USE_CLIPPING: return m_pObject->m_UseClipping; + case EGroupProp::PROP_CLIP_X: return m_pObject->m_ClipX; + case EGroupProp::PROP_CLIP_Y: return m_pObject->m_ClipY; + case EGroupProp::PROP_CLIP_W: return m_pObject->m_ClipW; + case EGroupProp::PROP_CLIP_H: return m_pObject->m_ClipH; + default: return 0; + } +} + +// ------------------------------------------------------------------ + +void CLayerQuadsPropTracker::OnEnd(ELayerQuadsProp Prop, int Value) +{ + auto pAction = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], Prop, m_OriginalValue, Value); + m_pEditor->m_EditorHistory.RecordAction(pAction); +} + +int CLayerQuadsPropTracker::PropToValue(ELayerQuadsProp Prop) +{ + if(Prop == ELayerQuadsProp::PROP_IMAGE) + return m_pObject->m_Image; + return 0; +} + +// ------------------------------------------------------------------- + +void CLayerSoundsPropTracker::OnEnd(ELayerSoundsProp Prop, int Value) +{ + auto pAction = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], Prop, m_OriginalValue, Value); + m_pEditor->m_EditorHistory.RecordAction(pAction); +} + +int CLayerSoundsPropTracker::PropToValue(ELayerSoundsProp Prop) +{ + if(Prop == ELayerSoundsProp::PROP_SOUND) + return m_pObject->m_Sound; + return 0; +} + +// ---- + +void CSoundSourcePropTracker::OnEnd(ESoundProp Prop, int Value) +{ + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_pEditor->m_SelectedSource, Prop, m_OriginalValue, Value)); +} + +int CSoundSourcePropTracker::PropToValue(ESoundProp Prop) +{ + switch(Prop) + { + case ESoundProp::PROP_POS_X: return m_pObject->m_Position.x; + case ESoundProp::PROP_POS_Y: return m_pObject->m_Position.y; + case ESoundProp::PROP_LOOP: return m_pObject->m_Loop; + case ESoundProp::PROP_PAN: return m_pObject->m_Pan; + case ESoundProp::PROP_TIME_DELAY: return m_pObject->m_TimeDelay; + case ESoundProp::PROP_FALLOFF: return m_pObject->m_Falloff; + case ESoundProp::PROP_POS_ENV: return m_pObject->m_PosEnv; + case ESoundProp::PROP_POS_ENV_OFFSET: return m_pObject->m_PosEnvOffset; + case ESoundProp::PROP_SOUND_ENV: return m_pObject->m_SoundEnv; + case ESoundProp::PROP_SOUND_ENV_OFFSET: return m_pObject->m_SoundEnvOffset; + default: return 0; + } +} + +// ---- + +void CSoundSourceRectShapePropTracker::OnEnd(ERectangleShapeProp Prop, int Value) +{ + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_pEditor->m_SelectedSource, Prop, m_OriginalValue, Value)); +} + +int CSoundSourceRectShapePropTracker::PropToValue(ERectangleShapeProp Prop) +{ + switch(Prop) + { + case ERectangleShapeProp::PROP_RECTANGLE_WIDTH: return m_pObject->m_Shape.m_Rectangle.m_Width; + case ERectangleShapeProp::PROP_RECTANGLE_HEIGHT: return m_pObject->m_Shape.m_Rectangle.m_Height; + default: return 0; + } +} + +void CSoundSourceCircleShapePropTracker::OnEnd(ECircleShapeProp Prop, int Value) +{ + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_pEditor->m_SelectedSource, Prop, m_OriginalValue, Value)); +} + +int CSoundSourceCircleShapePropTracker::PropToValue(ECircleShapeProp Prop) +{ + switch(Prop) + { + case ECircleShapeProp::PROP_CIRCLE_RADIUS: return m_pObject->m_Shape.m_Circle.m_Radius; + default: return 0; + } +} diff --git a/src/game/editor/editor_trackers.h b/src/game/editor/editor_trackers.h new file mode 100644 index 00000000000..156096b6af8 --- /dev/null +++ b/src/game/editor/editor_trackers.h @@ -0,0 +1,271 @@ +#ifndef GAME_EDITOR_EDITOR_TRACKERS_H +#define GAME_EDITOR_EDITOR_TRACKERS_H + +#include +#include +#include + +#include +#include +#include + +class CEditor; +class CLayerTiles; +class CLayerGroup; +class CLayerSounds; +struct CSoundSource; + +class CQuadEditTracker +{ +public: + CQuadEditTracker(); + ~CQuadEditTracker(); + + void BeginQuadTrack(const std::shared_ptr &pLayer, const std::vector &vSelectedQuads); + void EndQuadTrack(); + + void BeginQuadPropTrack(const std::shared_ptr &pLayer, const std::vector &vSelectedQuads, EQuadProp Prop); + void EndQuadPropTrack(EQuadProp Prop); + + void BeginQuadPointPropTrack(const std::shared_ptr &pLayer, const std::vector &vSelectedQuads, int SelectedQuadPoints); + void AddQuadPointPropTrack(EQuadPointProp Prop); + void EndQuadPointPropTrack(EQuadPointProp Prop); + void EndQuadPointPropTrackAll(); + + CEditor *m_pEditor; + +private: + std::vector m_vSelectedQuads; + int m_SelectedQuadPoints; + std::map> m_InitalPoints; + + bool m_Tracking = false; + std::shared_ptr m_pLayer; + + EQuadProp m_TrackedProp; + std::vector m_vTrackedProps; + std::map m_PreviousValues; + std::map>> m_PreviousValuesPoint; +}; + +class CEditorPropTracker +{ +public: + CEditorPropTracker() = default; + + void BeginPropTrack(int Prop, int Value); + void StopPropTrack(int Prop, int Value); + inline void Reset() { m_TrackedProp = -1; } + + CEditor *m_pEditor; + int m_PreviousValue; + int m_CurrentValue; + +private: + int m_TrackedProp = -1; +}; + +enum class EEnvelopeEditorOp +{ + OP_NONE = 0, + OP_SELECT, + OP_DRAG_POINT, + OP_DRAG_POINT_X, + OP_DRAG_POINT_Y, + OP_CONTEXT_MENU, + OP_BOX_SELECT, + OP_SCALE +}; + +class CEnvelopeEditorOperationTracker +{ +public: + CEnvelopeEditorOperationTracker() = default; + + void Begin(EEnvelopeEditorOp Operation); + void Stop(bool Switch = true); + inline void Reset() { m_TrackedOp = EEnvelopeEditorOp::OP_NONE; } + + CEditor *m_pEditor; + +private: + EEnvelopeEditorOp m_TrackedOp = EEnvelopeEditorOp::OP_NONE; + + struct SPointData + { + bool m_Used; + int m_Time; + std::map m_Values; + }; + + std::map m_SavedValues; + + void HandlePointDragStart(); + void HandlePointDragEnd(bool Switch); +}; + +template +class CPropTracker +{ +public: + CPropTracker(CEditor *pEditor) : + m_pEditor(pEditor), m_OriginalValue(0) {} + CEditor *m_pEditor; + + void Begin(T *pObject, E Prop, EEditState State) + { + if(Prop == static_cast(-1)) + return; + m_pObject = pObject; + int Value = PropToValue(Prop); + if(StartChecker(Prop, State, Value)) + { + m_OriginalValue = Value; + OnStart(Prop); + } + } + + void End(E Prop, EEditState State) + { + if(Prop == static_cast(-1)) + return; + int Value = PropToValue(Prop); + if(EndChecker(Prop, State, Value)) + { + OnEnd(Prop, Value); + } + } + +protected: + virtual void OnStart(E Prop) {} + virtual void OnEnd(E Prop, int Value) {} + virtual int PropToValue(E Prop) { return 0; } + virtual bool StartChecker(E Prop, EEditState State, int Value) + { + return State == EEditState::START || State == EEditState::ONE_GO; + } + virtual bool EndChecker(E Prop, EEditState State, int Value) + { + return (State == EEditState::END || State == EEditState::ONE_GO) && (Value != m_OriginalValue); + } + + int m_OriginalValue; + T *m_pObject; +}; + +class CLayerPropTracker : public CPropTracker +{ +public: + CLayerPropTracker(CEditor *pEditor) : + CPropTracker(pEditor){}; + +protected: + void OnEnd(ELayerProp Prop, int Value) override; + int PropToValue(ELayerProp Prop) override; +}; + +class CLayerTilesPropTracker : public CPropTracker +{ +public: + CLayerTilesPropTracker(CEditor *pEditor) : + CPropTracker(pEditor){}; + +protected: + void OnStart(ETilesProp Prop) override; + void OnEnd(ETilesProp Prop, int Value) override; + bool EndChecker(ETilesProp Prop, EEditState State, int Value) override; + + int PropToValue(ETilesProp Prop) override; + +private: + std::map> m_SavedLayers; +}; + +class CLayerTilesCommonPropTracker : public CPropTracker +{ +public: + CLayerTilesCommonPropTracker(CEditor *pEditor) : + CPropTracker(pEditor){}; + +protected: + void OnStart(ETilesCommonProp Prop) override; + void OnEnd(ETilesCommonProp Prop, int Value) override; + bool EndChecker(ETilesCommonProp Prop, EEditState State, int Value) override; + + int PropToValue(ETilesCommonProp Prop) override; + +private: + std::map, std::map>> m_SavedLayers; + +public: + std::vector> m_vpLayers; + std::vector m_vLayerIndices; +}; + +class CLayerGroupPropTracker : public CPropTracker +{ +public: + CLayerGroupPropTracker(CEditor *pEditor) : + CPropTracker(pEditor){}; + +protected: + void OnEnd(EGroupProp Prop, int Value) override; + int PropToValue(EGroupProp Prop) override; +}; + +class CLayerQuadsPropTracker : public CPropTracker +{ +public: + CLayerQuadsPropTracker(CEditor *pEditor) : + CPropTracker(pEditor){}; + +protected: + void OnEnd(ELayerQuadsProp Prop, int Value) override; + int PropToValue(ELayerQuadsProp Prop) override; +}; + +class CLayerSoundsPropTracker : public CPropTracker +{ +public: + CLayerSoundsPropTracker(CEditor *pEditor) : + CPropTracker(pEditor){}; + +protected: + void OnEnd(ELayerSoundsProp Prop, int Value) override; + int PropToValue(ELayerSoundsProp Prop) override; +}; + +class CSoundSourcePropTracker : public CPropTracker +{ +public: + CSoundSourcePropTracker(CEditor *pEditor) : + CPropTracker(pEditor) {} + +protected: + void OnEnd(ESoundProp Prop, int Value) override; + int PropToValue(ESoundProp Prop) override; +}; + +class CSoundSourceRectShapePropTracker : public CPropTracker +{ +public: + CSoundSourceRectShapePropTracker(CEditor *pEditor) : + CPropTracker(pEditor) {} + +protected: + void OnEnd(ERectangleShapeProp Prop, int Value) override; + int PropToValue(ERectangleShapeProp Prop) override; +}; + +class CSoundSourceCircleShapePropTracker : public CPropTracker +{ +public: + CSoundSourceCircleShapePropTracker(CEditor *pEditor) : + CPropTracker(pEditor) {} + +protected: + void OnEnd(ECircleShapeProp Prop, int Value) override; + int PropToValue(ECircleShapeProp Prop) override; +}; + +#endif diff --git a/src/game/editor/mapitems.h b/src/game/editor/mapitems.h new file mode 100644 index 00000000000..7873ee7e736 --- /dev/null +++ b/src/game/editor/mapitems.h @@ -0,0 +1,125 @@ +#ifndef GAME_EDITOR_MAPITEMS_H +#define GAME_EDITOR_MAPITEMS_H + +enum class EQuadProp +{ + PROP_NONE = -1, + PROP_ORDER, + PROP_POS_X, + PROP_POS_Y, + PROP_POS_ENV, + PROP_POS_ENV_OFFSET, + PROP_COLOR_ENV, + PROP_COLOR_ENV_OFFSET, + NUM_PROPS, +}; + +enum class EQuadPointProp +{ + PROP_NONE = -1, + PROP_POS_X, + PROP_POS_Y, + PROP_COLOR, + PROP_TEX_U, + PROP_TEX_V, + NUM_PROPS, +}; + +enum class ESoundProp +{ + PROP_NONE = -1, + PROP_POS_X, + PROP_POS_Y, + PROP_LOOP, + PROP_PAN, + PROP_TIME_DELAY, + PROP_FALLOFF, + PROP_POS_ENV, + PROP_POS_ENV_OFFSET, + PROP_SOUND_ENV, + PROP_SOUND_ENV_OFFSET, + NUM_PROPS, +}; + +enum class ERectangleShapeProp +{ + PROP_NONE = -1, + PROP_RECTANGLE_WIDTH, + PROP_RECTANGLE_HEIGHT, + NUM_RECTANGLE_PROPS, +}; + +enum class ECircleShapeProp +{ + PROP_NONE = -1, + PROP_CIRCLE_RADIUS, + NUM_CIRCLE_PROPS, +}; + +enum class ELayerProp +{ + PROP_NONE = -1, + PROP_GROUP, + PROP_ORDER, + PROP_HQ, + NUM_PROPS, +}; + +enum class ETilesProp +{ + PROP_NONE = -1, + PROP_WIDTH, + PROP_HEIGHT, + PROP_SHIFT, + PROP_SHIFT_BY, + PROP_IMAGE, + PROP_COLOR, + PROP_COLOR_ENV, + PROP_COLOR_ENV_OFFSET, + PROP_AUTOMAPPER, + PROP_SEED, + NUM_PROPS +}; + +enum class ETilesCommonProp +{ + PROP_NONE = -1, + PROP_WIDTH, + PROP_HEIGHT, + PROP_SHIFT, + PROP_SHIFT_BY, + PROP_COLOR, + NUM_PROPS, +}; + +enum class EGroupProp +{ + PROP_NONE = -1, + PROP_ORDER, + PROP_POS_X, + PROP_POS_Y, + PROP_PARA_X, + PROP_PARA_Y, + PROP_USE_CLIPPING, + PROP_CLIP_X, + PROP_CLIP_Y, + PROP_CLIP_W, + PROP_CLIP_H, + NUM_PROPS, +}; + +enum class ELayerQuadsProp +{ + PROP_NONE = -1, + PROP_IMAGE, + NUM_PROPS, +}; + +enum class ELayerSoundsProp +{ + PROP_NONE = -1, + PROP_SOUND, + NUM_PROPS, +}; + +#endif diff --git a/src/game/editor/mapitems/envelope.h b/src/game/editor/mapitems/envelope.h index 0d167e459c1..2b25c9f3620 100644 --- a/src/game/editor/mapitems/envelope.h +++ b/src/game/editor/mapitems/envelope.h @@ -25,6 +25,7 @@ class CEnvelope void AddPoint(int Time, int v0, int v1 = 0, int v2 = 0, int v3 = 0); float EndTime() const; int GetChannels() const; + EType Type() const { return m_Type; } private: void Resort(); diff --git a/src/game/editor/mapitems/layer.h b/src/game/editor/mapitems/layer.h index 298337d0591..238c9e4ccdf 100644 --- a/src/game/editor/mapitems/layer.h +++ b/src/game/editor/mapitems/layer.h @@ -62,6 +62,7 @@ class CLayer virtual void ModifySoundIndex(FIndexModifyFunction pfnFunc) {} virtual std::shared_ptr Duplicate() const = 0; + virtual const char *TypeName() const = 0; virtual void GetSize(float *pWidth, float *pHeight) { diff --git a/src/game/editor/mapitems/layer_front.cpp b/src/game/editor/mapitems/layer_front.cpp index 47d26058673..f046299c4b8 100644 --- a/src/game/editor/mapitems/layer_front.cpp +++ b/src/game/editor/mapitems/layer_front.cpp @@ -45,3 +45,8 @@ void CLayerFront::Resize(int NewW, int NewH) if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); } + +const char *CLayerFront::TypeName() const +{ + return "front"; +} diff --git a/src/game/editor/mapitems/layer_front.h b/src/game/editor/mapitems/layer_front.h index f5aeb3a5805..fbddfac1fa4 100644 --- a/src/game/editor/mapitems/layer_front.h +++ b/src/game/editor/mapitems/layer_front.h @@ -10,6 +10,7 @@ class CLayerFront : public CLayerTiles void Resize(int NewW, int NewH) override; void SetTile(int x, int y, CTile Tile) override; + const char *TypeName() const override; }; #endif diff --git a/src/game/editor/mapitems/layer_game.cpp b/src/game/editor/mapitems/layer_game.cpp index 48d0618110f..eb0ee541fcd 100644 --- a/src/game/editor/mapitems/layer_game.cpp +++ b/src/game/editor/mapitems/layer_game.cpp @@ -72,3 +72,8 @@ CUI::EPopupMenuFunctionResult CLayerGame::RenderProperties(CUIRect *pToolbox) m_Image = -1; return Result; } + +const char *CLayerGame::TypeName() const +{ + return "game"; +} diff --git a/src/game/editor/mapitems/layer_game.h b/src/game/editor/mapitems/layer_game.h index 4c00c7e7fcf..ec6f1d78fa0 100644 --- a/src/game/editor/mapitems/layer_game.h +++ b/src/game/editor/mapitems/layer_game.h @@ -11,6 +11,7 @@ class CLayerGame : public CLayerTiles CTile GetTile(int x, int y) override; void SetTile(int x, int y, CTile Tile) override; + const char *TypeName() const override; CUI::EPopupMenuFunctionResult RenderProperties(CUIRect *pToolbox) override; }; diff --git a/src/game/editor/mapitems/layer_quads.cpp b/src/game/editor/mapitems/layer_quads.cpp index e1e58f3c918..3f69708fd58 100644 --- a/src/game/editor/mapitems/layer_quads.cpp +++ b/src/game/editor/mapitems/layer_quads.cpp @@ -3,6 +3,7 @@ #include "layer_quads.h" #include +#include #include "image.h" @@ -140,6 +141,7 @@ int CLayerQuads::BrushGrab(std::shared_ptr pBrush, CUIRect Rect) void CLayerQuads::BrushPlace(std::shared_ptr pBrush, float wx, float wy) { std::shared_ptr pQuadLayer = std::static_pointer_cast(pBrush); + std::vector vAddedQuads; for(const auto &Quad : pQuadLayer->m_vQuads) { CQuad n = Quad; @@ -151,7 +153,9 @@ void CLayerQuads::BrushPlace(std::shared_ptr pBrush, float wx, float wy) } m_vQuads.push_back(n); + vAddedQuads.push_back(n); } + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], vAddedQuads)); m_pEditor->m_Map.OnModify(); } @@ -219,26 +223,23 @@ void CLayerQuads::GetSize(float *pWidth, float *pHeight) CUI::EPopupMenuFunctionResult CLayerQuads::RenderProperties(CUIRect *pToolBox) { - enum - { - PROP_IMAGE = 0, - NUM_PROPS, - }; - CProperty aProps[] = { {"Image", m_Image, PROPTYPE_IMAGE, -1, 0}, {nullptr}, }; - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)ELayerQuadsProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); - if(Prop != -1) + auto [State, Prop] = m_pEditor->DoPropertiesWithState(pToolBox, aProps, s_aIds, &NewVal); + if(Prop != ELayerQuadsProp::PROP_NONE) { m_pEditor->m_Map.OnModify(); } - if(Prop == PROP_IMAGE) + static CLayerQuadsPropTracker s_Tracker(m_pEditor); + s_Tracker.Begin(this, Prop, State); + + if(Prop == ELayerQuadsProp::PROP_IMAGE) { if(NewVal >= 0) m_Image = NewVal % m_pEditor->m_Map.m_vpImages.size(); @@ -246,6 +247,8 @@ CUI::EPopupMenuFunctionResult CLayerQuads::RenderProperties(CUIRect *pToolBox) m_Image = -1; } + s_Tracker.End(Prop, State); + return CUI::POPUP_KEEP_OPEN; } @@ -280,3 +283,8 @@ int CLayerQuads::SwapQuads(int Index0, int Index1) std::swap(m_vQuads[Index0], m_vQuads[Index1]); return Index1; } + +const char *CLayerQuads::TypeName() const +{ + return "quads"; +} diff --git a/src/game/editor/mapitems/layer_quads.h b/src/game/editor/mapitems/layer_quads.h index 7ba890e6894..dd826b385eb 100644 --- a/src/game/editor/mapitems/layer_quads.h +++ b/src/game/editor/mapitems/layer_quads.h @@ -28,6 +28,7 @@ class CLayerQuads : public CLayer void GetSize(float *pWidth, float *pHeight) override; std::shared_ptr Duplicate() const override; + const char *TypeName() const override; int m_Image; std::vector m_vQuads; diff --git a/src/game/editor/mapitems/layer_sounds.cpp b/src/game/editor/mapitems/layer_sounds.cpp index 5742368c08e..ff8250617a7 100644 --- a/src/game/editor/mapitems/layer_sounds.cpp +++ b/src/game/editor/mapitems/layer_sounds.cpp @@ -1,7 +1,7 @@ #include "layer_sounds.h" #include - +#include #include static const float s_SourceVisualSize = 32.0f; @@ -171,6 +171,7 @@ int CLayerSounds::BrushGrab(std::shared_ptr pBrush, CUIRect Rect) void CLayerSounds::BrushPlace(std::shared_ptr pBrush, float wx, float wy) { std::shared_ptr pSoundLayer = std::static_pointer_cast(pBrush); + std::vector vAddedSources; for(const auto &Source : pSoundLayer->m_vSources) { CSoundSource n = Source; @@ -179,32 +180,31 @@ void CLayerSounds::BrushPlace(std::shared_ptr pBrush, float wx, float wy n.m_Position.y += f2fx(wy); m_vSources.push_back(n); + vAddedSources.push_back(n); } + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], vAddedSources)); m_pEditor->m_Map.OnModify(); } CUI::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox) { - enum - { - PROP_SOUND = 0, - NUM_PROPS, - }; - CProperty aProps[] = { {"Sound", m_Sound, PROPTYPE_SOUND, -1, 0}, {nullptr}, }; - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)ELayerSoundsProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); - if(Prop != -1) + auto [State, Prop] = m_pEditor->DoPropertiesWithState(pToolBox, aProps, s_aIds, &NewVal); + if(Prop != ELayerSoundsProp::PROP_NONE) { m_pEditor->m_Map.OnModify(); } - if(Prop == PROP_SOUND) + static CLayerSoundsPropTracker s_Tracker(m_pEditor); + s_Tracker.Begin(this, Prop, State); + + if(Prop == ELayerSoundsProp::PROP_SOUND) { if(NewVal >= 0) m_Sound = NewVal % m_pEditor->m_Map.m_vpSounds.size(); @@ -212,6 +212,8 @@ CUI::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox) m_Sound = -1; } + s_Tracker.End(Prop, State); + return CUI::POPUP_KEEP_OPEN; } @@ -233,3 +235,8 @@ std::shared_ptr CLayerSounds::Duplicate() const { return std::make_shared(*this); } + +const char *CLayerSounds::TypeName() const +{ + return "sounds"; +} diff --git a/src/game/editor/mapitems/layer_sounds.h b/src/game/editor/mapitems/layer_sounds.h index 0d89329f650..bc71b13642f 100644 --- a/src/game/editor/mapitems/layer_sounds.h +++ b/src/game/editor/mapitems/layer_sounds.h @@ -23,6 +23,7 @@ class CLayerSounds : public CLayer void ModifySoundIndex(FIndexModifyFunction pfnFunc) override; std::shared_ptr Duplicate() const override; + const char *TypeName() const override; int m_Sound; std::vector m_vSources; diff --git a/src/game/editor/mapitems/layer_speedup.cpp b/src/game/editor/mapitems/layer_speedup.cpp index bcdd43d064d..02795a43169 100644 --- a/src/game/editor/mapitems/layer_speedup.cpp +++ b/src/game/editor/mapitems/layer_speedup.cpp @@ -12,6 +12,16 @@ CLayerSpeedup::CLayerSpeedup(CEditor *pEditor, int w, int h) : mem_zero(m_pSpeedupTile, (size_t)w * h * sizeof(CSpeedupTile)); } +CLayerSpeedup::CLayerSpeedup(const CLayerSpeedup &Other) : + CLayerTiles(Other) +{ + str_copy(m_aName, "Speedup copy"); + m_Speedup = 1; + + m_pSpeedupTile = new CSpeedupTile[m_Width * m_Height]; + mem_copy(m_pSpeedupTile, Other.m_pSpeedupTile, (size_t)m_Width * m_Height * sizeof(CSpeedupTile)); +} + CLayerSpeedup::~CLayerSpeedup() { delete[] m_pSpeedupTile; @@ -84,53 +94,78 @@ void CLayerSpeedup::BrushDraw(std::shared_ptr pBrush, float wx, float wy if(!Destructive && GetTile(fx, fy).m_Index) continue; + int Index = fy * m_Width + fx; + SSpeedupTileStateChange::SData Previous{ + m_pSpeedupTile[Index].m_Force, + m_pSpeedupTile[Index].m_Angle, + m_pSpeedupTile[Index].m_MaxSpeed, + m_pSpeedupTile[Index].m_Type, + m_pTiles[Index].m_Index}; + if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSpeedupTile(pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index)) && pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index != TILE_AIR) { if(m_pEditor->m_SpeedupAngle != pSpeedupLayer->m_SpeedupAngle || m_pEditor->m_SpeedupForce != pSpeedupLayer->m_SpeedupForce || m_pEditor->m_SpeedupMaxSpeed != pSpeedupLayer->m_SpeedupMaxSpeed) { - m_pSpeedupTile[fy * m_Width + fx].m_Force = m_pEditor->m_SpeedupForce; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = m_pEditor->m_SpeedupAngle; - m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pSpeedupTile[Index].m_Force = m_pEditor->m_SpeedupForce; + m_pSpeedupTile[Index].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; + m_pSpeedupTile[Index].m_Angle = m_pEditor->m_SpeedupAngle; + m_pSpeedupTile[Index].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pTiles[Index].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; } else if(pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force) { - m_pSpeedupTile[fy * m_Width + fx].m_Force = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Angle; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_MaxSpeed; - m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pSpeedupTile[Index].m_Force = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force; + m_pSpeedupTile[Index].m_Angle = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Angle; + m_pSpeedupTile[Index].m_MaxSpeed = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_MaxSpeed; + m_pSpeedupTile[Index].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pTiles[Index].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; } else if(m_pEditor->m_SpeedupForce) { - m_pSpeedupTile[fy * m_Width + fx].m_Force = m_pEditor->m_SpeedupForce; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = m_pEditor->m_SpeedupAngle; - m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pSpeedupTile[Index].m_Force = m_pEditor->m_SpeedupForce; + m_pSpeedupTile[Index].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; + m_pSpeedupTile[Index].m_Angle = m_pEditor->m_SpeedupAngle; + m_pSpeedupTile[Index].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pTiles[Index].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; } else { - m_pSpeedupTile[fy * m_Width + fx].m_Force = 0; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = 0; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = 0; - m_pSpeedupTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; + m_pSpeedupTile[Index].m_Force = 0; + m_pSpeedupTile[Index].m_MaxSpeed = 0; + m_pSpeedupTile[Index].m_Angle = 0; + m_pSpeedupTile[Index].m_Type = 0; + m_pTiles[Index].m_Index = 0; } } else { - m_pSpeedupTile[fy * m_Width + fx].m_Force = 0; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = 0; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = 0; - m_pSpeedupTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; + m_pSpeedupTile[Index].m_Force = 0; + m_pSpeedupTile[Index].m_MaxSpeed = 0; + m_pSpeedupTile[Index].m_Angle = 0; + m_pSpeedupTile[Index].m_Type = 0; + m_pTiles[Index].m_Index = 0; } + + SSpeedupTileStateChange::SData Current{ + m_pSpeedupTile[Index].m_Force, + m_pSpeedupTile[Index].m_Angle, + m_pSpeedupTile[Index].m_MaxSpeed, + m_pSpeedupTile[Index].m_Type, + m_pTiles[Index].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); } FlagModified(sx, sy, pSpeedupLayer->m_Width, pSpeedupLayer->m_Height); } +void CLayerSpeedup::RecordStateChange(int x, int y, SSpeedupTileStateChange::SData Previous, SSpeedupTileStateChange::SData Current) +{ + if(!m_History[y][x].m_Changed) + m_History[y][x] = SSpeedupTileStateChange{true, Previous, Current}; + else + m_History[y][x].m_Current = Current; +} + void CLayerSpeedup::BrushFlipX() { CLayerTiles::BrushFlipX(); @@ -211,6 +246,13 @@ void CLayerSpeedup::FillSelection(bool Empty, std::shared_ptr pBrush, CU const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int TgtIndex = fy * m_Width + fx; + SSpeedupTileStateChange::SData Previous{ + m_pSpeedupTile[TgtIndex].m_Force, + m_pSpeedupTile[TgtIndex].m_Angle, + m_pSpeedupTile[TgtIndex].m_MaxSpeed, + m_pSpeedupTile[TgtIndex].m_Type, + m_pTiles[TgtIndex].m_Index}; + if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSpeedupTile((pLt->m_pTiles[SrcIndex]).m_Index))) // no speed up tile chosen: reset { m_pTiles[TgtIndex].m_Index = 0; @@ -240,7 +282,26 @@ void CLayerSpeedup::FillSelection(bool Empty, std::shared_ptr pBrush, CU m_pSpeedupTile[TgtIndex].m_MaxSpeed = pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed; } } + + SSpeedupTileStateChange::SData Current{ + m_pSpeedupTile[TgtIndex].m_Force, + m_pSpeedupTile[TgtIndex].m_Angle, + m_pSpeedupTile[TgtIndex].m_MaxSpeed, + m_pSpeedupTile[TgtIndex].m_Type, + m_pTiles[TgtIndex].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); } } FlagModified(sx, sy, w, h); } + +std::shared_ptr CLayerSpeedup::Duplicate() const +{ + return std::make_shared(*this); +} + +const char *CLayerSpeedup::TypeName() const +{ + return "speedup"; +} diff --git a/src/game/editor/mapitems/layer_speedup.h b/src/game/editor/mapitems/layer_speedup.h index 74d0a14c55e..990f488e7d3 100644 --- a/src/game/editor/mapitems/layer_speedup.h +++ b/src/game/editor/mapitems/layer_speedup.h @@ -3,10 +3,24 @@ #include "layer_tiles.h" +struct SSpeedupTileStateChange +{ + bool m_Changed; + struct SData + { + int m_Force; + int m_Angle; + int m_MaxSpeed; + int m_Type; + int m_Index; + } m_Previous, m_Current; +}; + class CLayerSpeedup : public CLayerTiles { public: CLayerSpeedup(CEditor *pEditor, int w, int h); + CLayerSpeedup(const CLayerSpeedup &Other); ~CLayerSpeedup(); CSpeedupTile *m_pSpeedupTile; @@ -22,6 +36,19 @@ class CLayerSpeedup : public CLayerTiles void BrushFlipY() override; void BrushRotate(float Amount) override; void FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) override; + + EditorTileStateChangeHistory m_History; + void ClearHistory() override + { + CLayerTiles::ClearHistory(); + m_History.clear(); + } + + std::shared_ptr Duplicate() const override; + const char *TypeName() const override; + +private: + void RecordStateChange(int x, int y, SSpeedupTileStateChange::SData Previous, SSpeedupTileStateChange::SData Current); }; #endif diff --git a/src/game/editor/mapitems/layer_switch.cpp b/src/game/editor/mapitems/layer_switch.cpp index ef46a33f1d1..6d59903b6a4 100644 --- a/src/game/editor/mapitems/layer_switch.cpp +++ b/src/game/editor/mapitems/layer_switch.cpp @@ -12,6 +12,16 @@ CLayerSwitch::CLayerSwitch(CEditor *pEditor, int w, int h) : mem_zero(m_pSwitchTile, (size_t)w * h * sizeof(CSwitchTile)); } +CLayerSwitch::CLayerSwitch(const CLayerSwitch &Other) : + CLayerTiles(Other) +{ + str_copy(m_aName, "Switch copy"); + m_Switch = 1; + + m_pSwitchTile = new CSwitchTile[m_Width * m_Height]; + mem_copy(m_pSwitchTile, Other.m_pSwitchTile, (size_t)m_Width * m_Height * sizeof(CSwitchTile)); +} + CLayerSwitch::~CLayerSwitch() { delete[] m_pSwitchTile; @@ -83,54 +93,79 @@ void CLayerSwitch::BrushDraw(std::shared_ptr pBrush, float wx, float wy) if(!Destructive && GetTile(fx, fy).m_Index) continue; + int Index = fy * m_Width + fx; + SSwitchTileStateChange::SData Previous{ + m_pSwitchTile[Index].m_Number, + m_pSwitchTile[Index].m_Type, + m_pSwitchTile[Index].m_Flags, + m_pSwitchTile[Index].m_Delay, + m_pTiles[Index].m_Index}; + if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) && pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index != TILE_AIR) { if(m_pEditor->m_SwitchNum != pSwitchLayer->m_SwitchNumber || m_pEditor->m_SwitchDelay != pSwitchLayer->m_SwitchDelay) { - m_pSwitchTile[fy * m_Width + fx].m_Number = m_pEditor->m_SwitchNum; - m_pSwitchTile[fy * m_Width + fx].m_Delay = m_pEditor->m_SwitchDelay; + m_pSwitchTile[Index].m_Number = m_pEditor->m_SwitchNum; + m_pSwitchTile[Index].m_Delay = m_pEditor->m_SwitchDelay; } else if(pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number) { - m_pSwitchTile[fy * m_Width + fx].m_Number = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number; - m_pSwitchTile[fy * m_Width + fx].m_Delay = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Delay; + m_pSwitchTile[Index].m_Number = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number; + m_pSwitchTile[Index].m_Delay = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Delay; } else { - m_pSwitchTile[fy * m_Width + fx].m_Number = m_pEditor->m_SwitchNum; - m_pSwitchTile[fy * m_Width + fx].m_Delay = m_pEditor->m_SwitchDelay; + m_pSwitchTile[Index].m_Number = m_pEditor->m_SwitchNum; + m_pSwitchTile[Index].m_Delay = m_pEditor->m_SwitchDelay; } - m_pSwitchTile[fy * m_Width + fx].m_Type = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; - m_pSwitchTile[fy * m_Width + fx].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; - m_pTiles[fy * m_Width + fx].m_Index = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; + m_pSwitchTile[Index].m_Type = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; + m_pSwitchTile[Index].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; + m_pTiles[Index].m_Index = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; + m_pTiles[Index].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; if(!IsSwitchTileFlagsUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) { - m_pSwitchTile[fy * m_Width + fx].m_Flags = 0; + m_pSwitchTile[Index].m_Flags = 0; } if(!IsSwitchTileNumberUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) { - m_pSwitchTile[fy * m_Width + fx].m_Number = 0; + m_pSwitchTile[Index].m_Number = 0; } if(!IsSwitchTileDelayUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) { - m_pSwitchTile[fy * m_Width + fx].m_Delay = 0; + m_pSwitchTile[Index].m_Delay = 0; } } else { - m_pSwitchTile[fy * m_Width + fx].m_Number = 0; - m_pSwitchTile[fy * m_Width + fx].m_Type = 0; - m_pSwitchTile[fy * m_Width + fx].m_Flags = 0; - m_pSwitchTile[fy * m_Width + fx].m_Delay = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; + m_pSwitchTile[Index].m_Number = 0; + m_pSwitchTile[Index].m_Type = 0; + m_pSwitchTile[Index].m_Flags = 0; + m_pSwitchTile[Index].m_Delay = 0; + m_pTiles[Index].m_Index = 0; } + + SSwitchTileStateChange::SData Current{ + m_pSwitchTile[Index].m_Number, + m_pSwitchTile[Index].m_Type, + m_pSwitchTile[Index].m_Flags, + m_pSwitchTile[Index].m_Delay, + m_pTiles[Index].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); } FlagModified(sx, sy, pSwitchLayer->m_Width, pSwitchLayer->m_Height); } +void CLayerSwitch::RecordStateChange(int x, int y, SSwitchTileStateChange::SData Previous, SSwitchTileStateChange::SData Current) +{ + if(!m_History[y][x].m_Changed) + m_History[y][x] = SSwitchTileStateChange{true, Previous, Current}; + else + m_History[y][x].m_Current = Current; +} + void CLayerSwitch::BrushFlipX() { CLayerTiles::BrushFlipX(); @@ -217,6 +252,13 @@ void CLayerSwitch::FillSelection(bool Empty, std::shared_ptr pBrush, CUI const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int TgtIndex = fy * m_Width + fx; + SSwitchTileStateChange::SData Previous{ + m_pSwitchTile[TgtIndex].m_Number, + m_pSwitchTile[TgtIndex].m_Type, + m_pSwitchTile[TgtIndex].m_Flags, + m_pSwitchTile[TgtIndex].m_Delay, + m_pTiles[TgtIndex].m_Index}; + if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSwitchTile((pLt->m_pTiles[SrcIndex]).m_Index))) { m_pTiles[TgtIndex].m_Index = 0; @@ -250,6 +292,15 @@ void CLayerSwitch::FillSelection(bool Empty, std::shared_ptr pBrush, CUI m_pSwitchTile[TgtIndex].m_Flags = pLt->m_pSwitchTile[SrcIndex].m_Flags; } } + + SSwitchTileStateChange::SData Current{ + m_pSwitchTile[TgtIndex].m_Number, + m_pSwitchTile[TgtIndex].m_Type, + m_pSwitchTile[TgtIndex].m_Flags, + m_pSwitchTile[TgtIndex].m_Delay, + m_pTiles[TgtIndex].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); } } FlagModified(sx, sy, w, h); @@ -270,3 +321,13 @@ bool CLayerSwitch::ContainsElementWithId(int Id) return false; } + +std::shared_ptr CLayerSwitch::Duplicate() const +{ + return std::make_shared(*this); +} + +const char *CLayerSwitch::TypeName() const +{ + return "switch"; +} diff --git a/src/game/editor/mapitems/layer_switch.h b/src/game/editor/mapitems/layer_switch.h index 4011105cc40..7a8aae79b2d 100644 --- a/src/game/editor/mapitems/layer_switch.h +++ b/src/game/editor/mapitems/layer_switch.h @@ -3,10 +3,24 @@ #include "layer_tiles.h" +struct SSwitchTileStateChange +{ + bool m_Changed; + struct SData + { + int m_Number; + int m_Type; + int m_Flags; + int m_Delay; + int m_Index; + } m_Previous, m_Current; +}; + class CLayerSwitch : public CLayerTiles { public: CLayerSwitch(CEditor *pEditor, int w, int h); + CLayerSwitch(const CLayerSwitch &Other); ~CLayerSwitch(); CSwitchTile *m_pSwitchTile; @@ -22,6 +36,19 @@ class CLayerSwitch : public CLayerTiles void BrushRotate(float Amount) override; void FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) override; virtual bool ContainsElementWithId(int Id); + + EditorTileStateChangeHistory m_History; + inline void ClearHistory() override + { + CLayerTiles::ClearHistory(); + m_History.clear(); + } + + std::shared_ptr Duplicate() const override; + const char *TypeName() const override; + +private: + void RecordStateChange(int x, int y, SSwitchTileStateChange::SData Previous, SSwitchTileStateChange::SData Current); }; #endif diff --git a/src/game/editor/mapitems/layer_tele.cpp b/src/game/editor/mapitems/layer_tele.cpp index 6b397b4db66..0006a74c908 100644 --- a/src/game/editor/mapitems/layer_tele.cpp +++ b/src/game/editor/mapitems/layer_tele.cpp @@ -12,6 +12,16 @@ CLayerTele::CLayerTele(CEditor *pEditor, int w, int h) : mem_zero(m_pTeleTile, (size_t)w * h * sizeof(CTeleTile)); } +CLayerTele::CLayerTele(const CLayerTele &Other) : + CLayerTiles(Other) +{ + str_copy(m_aName, "Tele copy"); + m_Tele = 1; + + m_pTeleTile = new CTeleTile[m_Width * m_Height]; + mem_copy(m_pTeleTile, Other.m_pTeleTile, (size_t)m_Width * m_Height * sizeof(CTeleTile)); +} + CLayerTele::~CLayerTele() { delete[] m_pTeleTile; @@ -80,48 +90,80 @@ void CLayerTele::BrushDraw(std::shared_ptr pBrush, float wx, float wy) if(!Destructive && GetTile(fx, fy).m_Index) continue; + int Index = fy * m_Width + fx; + STeleTileStateChange::SData Previous{ + m_pTeleTile[Index].m_Number, + m_pTeleTile[Index].m_Type, + m_pTiles[Index].m_Index}; + if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTeleTile(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) && pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index != TILE_AIR) { if(!IsTeleTileNumberUsed(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) { // Tele tile number is unused. Set a known value which is not 0, // as tiles with number 0 would be ignored by previous versions. - m_pTeleTile[fy * m_Width + fx].m_Number = 255; + m_pTeleTile[Index].m_Number = 255; } else if(m_pEditor->m_TeleNumber != pTeleLayer->m_TeleNum) { - m_pTeleTile[fy * m_Width + fx].m_Number = m_pEditor->m_TeleNumber; + m_pTeleTile[Index].m_Number = m_pEditor->m_TeleNumber; } else if(pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number) { - m_pTeleTile[fy * m_Width + fx].m_Number = pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number; + m_pTeleTile[Index].m_Number = pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number; } else { if(!m_pEditor->m_TeleNumber) { - m_pTeleTile[fy * m_Width + fx].m_Number = 0; - m_pTeleTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; + m_pTeleTile[Index].m_Number = 0; + m_pTeleTile[Index].m_Type = 0; + m_pTiles[Index].m_Index = 0; + + STeleTileStateChange::SData Current{ + m_pTeleTile[Index].m_Number, + m_pTeleTile[Index].m_Type, + m_pTiles[Index].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); continue; } else - m_pTeleTile[fy * m_Width + fx].m_Number = m_pEditor->m_TeleNumber; + { + m_pTeleTile[Index].m_Number = m_pEditor->m_TeleNumber; + } } - m_pTeleTile[fy * m_Width + fx].m_Type = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; + m_pTeleTile[Index].m_Type = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; + m_pTiles[Index].m_Index = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; } else { - m_pTeleTile[fy * m_Width + fx].m_Number = 0; - m_pTeleTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; + m_pTeleTile[Index].m_Number = 0; + m_pTeleTile[Index].m_Type = 0; + m_pTiles[Index].m_Index = 0; } + + STeleTileStateChange::SData Current{ + m_pTeleTile[Index].m_Number, + m_pTeleTile[Index].m_Type, + m_pTiles[Index].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); } FlagModified(sx, sy, pTeleLayer->m_Width, pTeleLayer->m_Height); } +void CLayerTele::RecordStateChange(int x, int y, STeleTileStateChange::SData Previous, STeleTileStateChange::SData Current) +{ + if(!m_History[y][x].m_Changed) + m_History[y][x] = STeleTileStateChange{true, Previous, Current}; + else + { + m_History[y][x].m_Current = Current; + } +} + void CLayerTele::BrushFlipX() { CLayerTiles::BrushFlipX(); @@ -202,6 +244,11 @@ void CLayerTele::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRe const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int TgtIndex = fy * m_Width + fx; + STeleTileStateChange::SData Previous{ + m_pTeleTile[TgtIndex].m_Number, + m_pTeleTile[TgtIndex].m_Type, + m_pTiles[TgtIndex].m_Index}; + if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTeleTile((pLt->m_pTiles[SrcIndex]).m_Index))) { m_pTiles[TgtIndex].m_Index = 0; @@ -227,6 +274,13 @@ void CLayerTele::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRe m_pTeleTile[TgtIndex].m_Number = pLt->m_pTeleTile[SrcIndex].m_Number; } } + + STeleTileStateChange::SData Current{ + m_pTeleTile[TgtIndex].m_Number, + m_pTeleTile[TgtIndex].m_Type, + m_pTiles[TgtIndex].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); } } FlagModified(sx, sy, w, h); @@ -247,3 +301,13 @@ bool CLayerTele::ContainsElementWithId(int Id) return false; } + +std::shared_ptr CLayerTele::Duplicate() const +{ + return std::make_shared(*this); +} + +const char *CLayerTele::TypeName() const +{ + return "tele"; +} diff --git a/src/game/editor/mapitems/layer_tele.h b/src/game/editor/mapitems/layer_tele.h index d3375518535..569dd305e47 100644 --- a/src/game/editor/mapitems/layer_tele.h +++ b/src/game/editor/mapitems/layer_tele.h @@ -3,10 +3,22 @@ #include "layer_tiles.h" +struct STeleTileStateChange +{ + bool m_Changed; + struct SData + { + int m_Number; + int m_Type; + int m_Index; + } m_Previous, m_Current; +}; + class CLayerTele : public CLayerTiles { public: CLayerTele(CEditor *pEditor, int w, int h); + CLayerTele(const CLayerTele &Other); ~CLayerTele(); CTeleTile *m_pTeleTile; @@ -21,6 +33,21 @@ class CLayerTele : public CLayerTiles void BrushRotate(float Amount) override; void FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) override; virtual bool ContainsElementWithId(int Id); + + EditorTileStateChangeHistory m_History; + inline void ClearHistory() override + { + CLayerTiles::ClearHistory(); + m_History.clear(); + } + + std::shared_ptr Duplicate() const override; + const char *TypeName() const override; + +private: + void RecordStateChange(int x, int y, STeleTileStateChange::SData Previous, STeleTileStateChange::SData Current); + + friend class CLayerTiles; }; #endif diff --git a/src/game/editor/mapitems/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp index f2e79d5e822..c3f953a7a59 100644 --- a/src/game/editor/mapitems/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -2,10 +2,13 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include "layer_tiles.h" -#include - #include #include +#include +#include + +#include +#include #include "image.h" @@ -75,10 +78,25 @@ CTile CLayerTiles::GetTile(int x, int y) } void CLayerTiles::SetTile(int x, int y, CTile Tile) +{ + auto CurrentTile = m_pTiles[y * m_Width + x]; + SetTileIgnoreHistory(x, y, Tile); + RecordStateChange(x, y, CurrentTile, Tile); +} + +void CLayerTiles::SetTileIgnoreHistory(int x, int y, CTile Tile) { m_pTiles[y * m_Width + x] = Tile; } +void CLayerTiles::RecordStateChange(int x, int y, CTile Previous, CTile Tile) +{ + if(!m_TilesHistory[y][x].m_Changed) + m_TilesHistory[y][x] = STileStateChange{true, Previous, Tile}; + else + m_TilesHistory[y][x].m_Current = Tile; +} + void CLayerTiles::PrepareForSave() { for(int y = 0; y < m_Height; y++) @@ -579,6 +597,11 @@ std::shared_ptr CLayerTiles::Duplicate() const return std::make_shared(*this); } +const char *CLayerTiles::TypeName() const +{ + return "tiles"; +} + void CLayerTiles::Resize(int NewW, int NewH) { CTile *pNewData = new CTile[NewW * NewH]; @@ -678,7 +701,6 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) static int s_GameTilesButton = 0; if(m_pEditor->DoButton_Editor(&s_GameTilesButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer")) m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY()); - int Result = m_pEditor->PopupSelectGameTileOpResult(); switch(Result) { @@ -717,17 +739,49 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) const int OffsetX = -pGroup->m_OffsetX / 32; const int OffsetY = -pGroup->m_OffsetY / 32; + static const char *s_apGametileOpNames[] = { + "Air", + "Hookable", + "Death", + "Unhookable", + "Hookthrough", + "Freeze", + "Unfreeze", + "Deep Freeze", + "Deep Unfreeze", + "Blue Check-Tele", + "Red Check-Tele", + "Live Freeze", + "Live Unfreeze", + }; + + std::vector> vpActions; + std::shared_ptr pGLayer = m_pEditor->m_Map.m_pGameLayer; + int GameLayerIndex = std::find(m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(), m_pEditor->m_Map.m_pGameGroup->m_vpLayers.end(), pGLayer) - m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(); + if(Result != TILE_TELECHECKIN && Result != TILE_TELECHECKINEVIL) { - std::shared_ptr pGLayer = m_pEditor->m_Map.m_pGameLayer; - if(pGLayer->m_Width < m_Width + OffsetX || pGLayer->m_Height < m_Height + OffsetY) { + std::map> savedLayers; + savedLayers[LAYERTYPE_TILES] = pGLayer->Duplicate(); + savedLayers[LAYERTYPE_GAME] = savedLayers[LAYERTYPE_TILES]; + + int PrevW = pGLayer->m_Width; + int PrevH = pGLayer->m_Height; const int NewW = pGLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pGLayer->m_Width; const int NewH = pGLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pGLayer->m_Height; pGLayer->Resize(NewW, NewH); + vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); + const std::shared_ptr &Action1 = std::static_pointer_cast(vpActions[vpActions.size() - 1]); + vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH)); + const std::shared_ptr &Action2 = std::static_pointer_cast(vpActions[vpActions.size() - 1]); + + Action1->SetSavedLayers(savedLayers); + Action2->SetSavedLayers(savedLayers); } + int Changes = 0; for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++) { for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++) @@ -736,40 +790,111 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) { const CTile ResultTile = {(unsigned char)Result}; pGLayer->SetTile(x + OffsetX, y + OffsetY, ResultTile); + Changes++; } } } + + vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup)); + char aDisplay[256]; + str_format(aDisplay, sizeof(aDisplay), "Construct '%s' game tiles (x%d)", s_apGametileOpNames[Result], Changes); + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, vpActions, aDisplay, true)); } else { if(!m_pEditor->m_Map.m_pTeleLayer) { - std::shared_ptr pLayer = std::make_shared(m_pEditor, m_Width, m_Height); + std::shared_ptr pLayer = std::make_shared(m_pEditor, m_Width, m_Height); m_pEditor->m_Map.MakeTeleLayer(pLayer); m_pEditor->m_Map.m_pGameGroup->AddLayer(pLayer); + + vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_Map.m_pGameGroup->m_vpLayers.size() - 1)); + + if(m_Width != pGLayer->m_Width || m_Height > pGLayer->m_Height) + { + std::map> savedLayers; + savedLayers[LAYERTYPE_TILES] = pGLayer->Duplicate(); + savedLayers[LAYERTYPE_GAME] = savedLayers[LAYERTYPE_TILES]; + + int NewW = pGLayer->m_Width; + int NewH = pGLayer->m_Height; + if(m_Width > pGLayer->m_Width) + { + NewW = m_Width; + } + if(m_Height > pGLayer->m_Height) + { + NewH = m_Height; + } + + int PrevW = pGLayer->m_Width; + int PrevH = pGLayer->m_Height; + pLayer->Resize(NewW, NewH); + vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); + const std::shared_ptr &Action1 = std::static_pointer_cast(vpActions[vpActions.size() - 1]); + vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH)); + const std::shared_ptr &Action2 = std::static_pointer_cast(vpActions[vpActions.size() - 1]); + + Action1->SetSavedLayers(savedLayers); + Action2->SetSavedLayers(savedLayers); + } } std::shared_ptr pTLayer = m_pEditor->m_Map.m_pTeleLayer; + int TeleLayerIndex = std::find(m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(), m_pEditor->m_Map.m_pGameGroup->m_vpLayers.end(), pTLayer) - m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(); if(pTLayer->m_Width < m_Width + OffsetX || pTLayer->m_Height < m_Height + OffsetY) { + std::map> savedLayers; + savedLayers[LAYERTYPE_TILES] = pTLayer->Duplicate(); + savedLayers[LAYERTYPE_TELE] = savedLayers[LAYERTYPE_TILES]; + + int PrevW = pTLayer->m_Width; + int PrevH = pTLayer->m_Height; int NewW = pTLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pTLayer->m_Width; int NewH = pTLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pTLayer->m_Height; pTLayer->Resize(NewW, NewH); + std::shared_ptr Action1, Action2; + vpActions.push_back(Action1 = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, TeleLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); + vpActions.push_back(Action2 = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, TeleLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH)); + + Action1->SetSavedLayers(savedLayers); + Action2->SetSavedLayers(savedLayers); } + int Changes = 0; for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++) { for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++) { if(GetTile(x, y).m_Index) { - pTLayer->m_pTiles[(y + OffsetY) * pTLayer->m_Width + x + OffsetX].m_Index = TILE_AIR + Result; - pTLayer->m_pTeleTile[(y + OffsetY) * pTLayer->m_Width + x + OffsetX].m_Number = 1; - pTLayer->m_pTeleTile[(y + OffsetY) * pTLayer->m_Width + x + OffsetX].m_Type = TILE_AIR + Result; + auto TileIndex = (y + OffsetY) * pTLayer->m_Width + x + OffsetX; + Changes++; + + STeleTileStateChange::SData Previous{ + pTLayer->m_pTeleTile[TileIndex].m_Number, + pTLayer->m_pTeleTile[TileIndex].m_Type, + pTLayer->m_pTiles[TileIndex].m_Index}; + + pTLayer->m_pTiles[TileIndex].m_Index = TILE_AIR + Result; + pTLayer->m_pTeleTile[TileIndex].m_Number = 1; + pTLayer->m_pTeleTile[TileIndex].m_Type = TILE_AIR + Result; + + STeleTileStateChange::SData Current{ + pTLayer->m_pTeleTile[TileIndex].m_Number, + pTLayer->m_pTeleTile[TileIndex].m_Type, + pTLayer->m_pTiles[TileIndex].m_Index}; + + pTLayer->RecordStateChange(x, y, Previous, Current); } } } + + vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup)); + char aDisplay[256]; + str_format(aDisplay, sizeof(aDisplay), "Construct 'tele' game tiles (x%d)", Changes); + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, vpActions, aDisplay, true)); } } } @@ -790,6 +915,12 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) { m_AutoAutoMap = !m_AutoAutoMap; FlagModified(0, 0, m_Width, m_Height); + if(!m_TilesHistory.empty()) // Sometimes pressing that button causes the automap to run so we should be able to undo that + { + // record undo + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_TilesHistory)); + ClearHistory(); + } } } @@ -797,31 +928,15 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) if(m_pEditor->DoButton_Editor(&s_AutoMapperButton, "Automap", 0, &Button, 0, "Run the automapper")) { m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.Proceed(this, m_AutoMapperConfig, m_Seed); + // record undo + m_pEditor->m_EditorHistory.RecordAction(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_TilesHistory)); + ClearHistory(); return CUI::POPUP_CLOSE_CURRENT; } } } - enum - { - PROP_WIDTH = 0, - PROP_HEIGHT, - PROP_SHIFT, - PROP_SHIFT_BY, - PROP_IMAGE, - PROP_COLOR, - PROP_COLOR_ENV, - PROP_COLOR_ENV_OFFSET, - PROP_AUTOMAPPER, - PROP_SEED, - NUM_PROPS, - }; - - int Color = 0; - Color |= m_Color.r << 24; - Color |= m_Color.g << 16; - Color |= m_Color.b << 8; - Color |= m_Color.a; + int Color = PackColor(m_Color); CProperty aProps[] = { {"Width", m_Width, PROPTYPE_INT_SCROLL, 1, 100000}, @@ -839,21 +954,24 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) if(EntitiesLayer) // remove the image and color properties if this is a game layer { - aProps[PROP_IMAGE].m_pName = nullptr; - aProps[PROP_COLOR].m_pName = nullptr; - aProps[PROP_AUTOMAPPER].m_pName = nullptr; + aProps[(int)ETilesProp::PROP_IMAGE].m_pName = nullptr; + aProps[(int)ETilesProp::PROP_COLOR].m_pName = nullptr; + aProps[(int)ETilesProp::PROP_AUTOMAPPER].m_pName = nullptr; } if(m_Image == -1) { - aProps[PROP_AUTOMAPPER].m_pName = nullptr; - aProps[PROP_SEED].m_pName = nullptr; + aProps[(int)ETilesProp::PROP_AUTOMAPPER].m_pName = nullptr; + aProps[(int)ETilesProp::PROP_SEED].m_pName = nullptr; } - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)ETilesProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); + auto [State, Prop] = m_pEditor->DoPropertiesWithState(pToolBox, aProps, s_aIds, &NewVal); - if(Prop == PROP_WIDTH && NewVal > 1) + static CLayerTilesPropTracker s_Tracker(m_pEditor); + s_Tracker.Begin(this, Prop, State); + + if(Prop == ETilesProp::PROP_WIDTH && NewVal > 1) { if(NewVal > 1000 && !m_pEditor->m_LargeLayerWasWarned) { @@ -863,7 +981,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) } Resize(NewVal, m_Height); } - else if(Prop == PROP_HEIGHT && NewVal > 1) + else if(Prop == ETilesProp::PROP_HEIGHT && NewVal > 1) { if(NewVal > 1000 && !m_pEditor->m_LargeLayerWasWarned) { @@ -873,15 +991,15 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) } Resize(m_Width, NewVal); } - else if(Prop == PROP_SHIFT) + else if(Prop == ETilesProp::PROP_SHIFT) { Shift(NewVal); } - else if(Prop == PROP_SHIFT_BY) + else if(Prop == ETilesProp::PROP_SHIFT_BY) { m_pEditor->m_ShiftBy = NewVal; } - else if(Prop == PROP_IMAGE) + else if(Prop == ETilesProp::PROP_IMAGE) { m_Image = NewVal; if(NewVal == -1) @@ -901,14 +1019,14 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) } } } - else if(Prop == PROP_COLOR) + else if(Prop == ETilesProp::PROP_COLOR) { m_Color.r = (NewVal >> 24) & 0xff; m_Color.g = (NewVal >> 16) & 0xff; m_Color.b = (NewVal >> 8) & 0xff; m_Color.a = NewVal & 0xff; } - else if(Prop == PROP_COLOR_ENV) + else if(Prop == ETilesProp::PROP_COLOR_ENV) { int Index = clamp(NewVal - 1, -1, (int)m_pEditor->m_Map.m_vpEnvelopes.size() - 1); const int Step = (Index - m_ColorEnv) % 2; @@ -924,15 +1042,15 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) } } } - else if(Prop == PROP_COLOR_ENV_OFFSET) + else if(Prop == ETilesProp::PROP_COLOR_ENV_OFFSET) { m_ColorEnvOffset = NewVal; } - else if(Prop == PROP_SEED) + else if(Prop == ETilesProp::PROP_SEED) { m_Seed = NewVal; } - else if(Prop == PROP_AUTOMAPPER) + else if(Prop == ETilesProp::PROP_AUTOMAPPER) { if(m_Image >= 0 && m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.ConfigNamesNum() > 0 && NewVal >= 0) m_AutoMapperConfig = NewVal % m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.ConfigNamesNum(); @@ -940,15 +1058,17 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) m_AutoMapperConfig = -1; } - if(Prop != -1) + if(Prop != ETilesProp::PROP_NONE) { FlagModified(0, 0, m_Width, m_Height); } + s_Tracker.End(Prop, State); + return CUI::POPUP_KEEP_OPEN; } -CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropState &State, CEditor *pEditor, CUIRect *pToolbox, std::vector> &vpLayers) +CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropState &State, CEditor *pEditor, CUIRect *pToolbox, std::vector> &vpLayers, std::vector &vLayerIndices) { if(State.m_Modified) { @@ -957,22 +1077,77 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta static int s_CommitButton = 0; if(pEditor->DoButton_Editor(&s_CommitButton, "Commit", 0, &Commit, 0, "Applies the changes")) { + bool HasModifiedSize = (State.m_Modified & SCommonPropState::MODIFIED_SIZE) != 0; + bool HasModifiedColor = (State.m_Modified & SCommonPropState::MODIFIED_COLOR) != 0; + + std::vector> vpActions; + int j = 0; + int GroupIndex = pEditor->m_SelectedGroup; for(auto &pLayer : vpLayers) { - if((State.m_Modified & SCommonPropState::MODIFIED_SIZE) != 0) + int LayerIndex = vLayerIndices[j++]; + if(HasModifiedSize) + { + std::map> SavedLayers; + SavedLayers[LAYERTYPE_TILES] = pLayer->Duplicate(); + if(pLayer->m_Game || pLayer->m_Front || pLayer->m_Switch || pLayer->m_Speedup || pLayer->m_Tune || pLayer->m_Tele) + { // Need to save all entities layers when any entity layer + if(pEditor->m_Map.m_pFrontLayer && !pLayer->m_Front) + SavedLayers[LAYERTYPE_FRONT] = pEditor->m_Map.m_pFrontLayer->Duplicate(); + if(pEditor->m_Map.m_pTeleLayer && !pLayer->m_Tele) + SavedLayers[LAYERTYPE_TELE] = pEditor->m_Map.m_pTeleLayer->Duplicate(); + if(pEditor->m_Map.m_pSwitchLayer && !pLayer->m_Switch) + SavedLayers[LAYERTYPE_SWITCH] = pEditor->m_Map.m_pSwitchLayer->Duplicate(); + if(pEditor->m_Map.m_pSpeedupLayer && !pLayer->m_Speedup) + SavedLayers[LAYERTYPE_SPEEDUP] = pEditor->m_Map.m_pSpeedupLayer->Duplicate(); + if(pEditor->m_Map.m_pTuneLayer && !pLayer->m_Tune) + SavedLayers[LAYERTYPE_TUNE] = pEditor->m_Map.m_pTuneLayer->Duplicate(); + if(!pLayer->m_Game) + SavedLayers[LAYERTYPE_GAME] = pEditor->m_Map.m_pGameLayer->Duplicate(); + } + + int PrevW = pLayer->m_Width; + int PrevH = pLayer->m_Height; pLayer->Resize(State.m_Width, State.m_Height); - if((State.m_Modified & SCommonPropState::MODIFIED_COLOR) != 0) + if(PrevW != State.m_Width) + { + std::shared_ptr pAction; + vpActions.push_back(pAction = std::make_shared(pEditor, GroupIndex, LayerIndex, ETilesProp::PROP_WIDTH, PrevW, State.m_Width)); + pAction->SetSavedLayers(SavedLayers); + } + + if(PrevH != State.m_Height) + { + std::shared_ptr pAction; + vpActions.push_back(pAction = std::make_shared(pEditor, GroupIndex, LayerIndex, ETilesProp::PROP_HEIGHT, PrevH, State.m_Height)); + pAction->SetSavedLayers(SavedLayers); + } + } + + if(HasModifiedColor) { + int Color = 0; + Color |= pLayer->m_Color.r << 24; + Color |= pLayer->m_Color.g << 16; + Color |= pLayer->m_Color.b << 8; + Color |= pLayer->m_Color.a; + pLayer->m_Color.r = (State.m_Color >> 24) & 0xff; pLayer->m_Color.g = (State.m_Color >> 16) & 0xff; pLayer->m_Color.b = (State.m_Color >> 8) & 0xff; pLayer->m_Color.a = State.m_Color & 0xff; + + vpActions.push_back(std::make_shared(pEditor, GroupIndex, LayerIndex, ETilesProp::PROP_COLOR, Color, State.m_Color)); } pLayer->FlagModified(0, 0, pLayer->m_Width, pLayer->m_Height); } State.m_Modified = 0; + + char aDisplay[256]; + str_format(aDisplay, sizeof(aDisplay), "Edit %d layers common properties: %s", (int)vpLayers.size(), HasModifiedColor && HasModifiedSize ? "color, size" : (HasModifiedColor ? "color" : "size")); + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, vpActions, aDisplay)); } } else @@ -999,16 +1174,6 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta pToolbox->HSplitTop(2.0f, nullptr, pToolbox); } - enum - { - PROP_WIDTH = 0, - PROP_HEIGHT, - PROP_SHIFT, - PROP_SHIFT_BY, - PROP_COLOR, - NUM_PROPS, - }; - CProperty aProps[] = { {"Width", State.m_Width, PROPTYPE_INT_SCROLL, 1, 100000}, {"Height", State.m_Height, PROPTYPE_INT_SCROLL, 1, 100000}, @@ -1018,11 +1183,17 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta {nullptr}, }; - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)ETilesCommonProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = pEditor->DoProperties(pToolbox, aProps, s_aIds, &NewVal); + auto [PropState, Prop] = pEditor->DoPropertiesWithState(pToolbox, aProps, s_aIds, &NewVal); + + static CLayerTilesCommonPropTracker s_Tracker(pEditor); + s_Tracker.m_vpLayers = vpLayers; + s_Tracker.m_vLayerIndices = vLayerIndices; - if(Prop == PROP_WIDTH && NewVal > 1) + s_Tracker.Begin(nullptr, Prop, PropState); + + if(Prop == ETilesCommonProp::PROP_WIDTH && NewVal > 1) { if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned) { @@ -1032,7 +1203,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta } State.m_Width = NewVal; } - else if(Prop == PROP_HEIGHT && NewVal > 1) + else if(Prop == ETilesCommonProp::PROP_HEIGHT && NewVal > 1) { if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned) { @@ -1042,25 +1213,27 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta } State.m_Height = NewVal; } - else if(Prop == PROP_SHIFT) + else if(Prop == ETilesCommonProp::PROP_SHIFT) { for(auto &pLayer : vpLayers) pLayer->Shift(NewVal); } - else if(Prop == PROP_SHIFT_BY) + else if(Prop == ETilesCommonProp::PROP_SHIFT_BY) { pEditor->m_ShiftBy = NewVal; } - else if(Prop == PROP_COLOR) + else if(Prop == ETilesCommonProp::PROP_COLOR) { State.m_Color = NewVal; } - if(Prop == PROP_WIDTH || Prop == PROP_HEIGHT) + s_Tracker.End(Prop, PropState); + + if(Prop == ETilesCommonProp::PROP_WIDTH || Prop == ETilesCommonProp::PROP_HEIGHT) { State.m_Modified |= SCommonPropState::MODIFIED_SIZE; } - else if(Prop == PROP_COLOR) + else if(Prop == ETilesCommonProp::PROP_COLOR) { State.m_Modified |= SCommonPropState::MODIFIED_COLOR; } diff --git a/src/game/editor/mapitems/layer_tiles.h b/src/game/editor/mapitems/layer_tiles.h index 48717db3ce6..10e7fc25afb 100644 --- a/src/game/editor/mapitems/layer_tiles.h +++ b/src/game/editor/mapitems/layer_tiles.h @@ -1,8 +1,21 @@ #ifndef GAME_EDITOR_MAPITEMS_LAYER_TILES_H #define GAME_EDITOR_MAPITEMS_LAYER_TILES_H +#include +#include + #include "layer.h" +struct STileStateChange +{ + bool m_Changed; + CTile m_Previous; + CTile m_Current; +}; + +template +using EditorTileStateChangeHistory = std::map>; + enum { DIRECTION_LEFT = 0, @@ -89,6 +102,7 @@ class CLayerTiles : public CLayer virtual CTile GetTile(int x, int y); virtual void SetTile(int x, int y, CTile Tile); + void SetTileIgnoreHistory(int x, int y, CTile Tile); virtual void Resize(int NewW, int NewH); virtual void Shift(int Direction); @@ -114,6 +128,7 @@ class CLayerTiles : public CLayer void BrushRotate(float Amount) override; std::shared_ptr Duplicate() const override; + const char *TypeName() const override; virtual void ShowInfo(); CUI::EPopupMenuFunctionResult RenderProperties(CUIRect *pToolbox) override; @@ -130,7 +145,7 @@ class CLayerTiles : public CLayer int m_Height = -1; int m_Color = 0; }; - static CUI::EPopupMenuFunctionResult RenderCommonProperties(SCommonPropState &State, CEditor *pEditor, CUIRect *pToolbox, std::vector> &vpLayers); + static CUI::EPopupMenuFunctionResult RenderCommonProperties(SCommonPropState &State, CEditor *pEditor, CUIRect *pToolbox, std::vector> &vpLayers, std::vector &vLayerIndices); void ModifyImageIndex(FIndexModifyFunction pfnFunc) override; void ModifyEnvelopeIndex(FIndexModifyFunction pfnFunc) override; @@ -166,6 +181,14 @@ class CLayerTiles : public CLayer int m_Switch; int m_Tune; char m_aFileName[IO_MAX_PATH_LENGTH]; + + EditorTileStateChangeHistory m_TilesHistory; + inline virtual void ClearHistory() { m_TilesHistory.clear(); } + +protected: + void RecordStateChange(int x, int y, CTile Previous, CTile Tile); + + friend class CAutoMapper; }; #endif diff --git a/src/game/editor/mapitems/layer_tune.cpp b/src/game/editor/mapitems/layer_tune.cpp index 619336943d0..0a41fcc7b20 100644 --- a/src/game/editor/mapitems/layer_tune.cpp +++ b/src/game/editor/mapitems/layer_tune.cpp @@ -12,6 +12,16 @@ CLayerTune::CLayerTune(CEditor *pEditor, int w, int h) : mem_zero(m_pTuneTile, (size_t)w * h * sizeof(CTuneTile)); } +CLayerTune::CLayerTune(const CLayerTune &Other) : + CLayerTiles(Other) +{ + str_copy(m_aName, "Tune copy"); + m_Tune = 1; + + m_pTuneTile = new CTuneTile[m_Width * m_Height]; + mem_copy(m_pTuneTile, Other.m_pTuneTile, (size_t)m_Width * m_Height * sizeof(CTuneTile)); +} + CLayerTune::~CLayerTune() { delete[] m_pTuneTile; @@ -82,40 +92,61 @@ void CLayerTune::BrushDraw(std::shared_ptr pBrush, float wx, float wy) if(!Destructive && GetTile(fx, fy).m_Index) continue; + int Index = fy * m_Width + fx; + STuneTileStateChange::SData Previous{ + m_pTuneTile[Index].m_Number, + m_pTuneTile[Index].m_Type, + m_pTiles[Index].m_Index}; + if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTuneTile(pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index)) && pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index != TILE_AIR) { if(m_pEditor->m_TuningNum != pTuneLayer->m_TuningNumber) { - m_pTuneTile[fy * m_Width + fx].m_Number = m_pEditor->m_TuningNum; + m_pTuneTile[Index].m_Number = m_pEditor->m_TuningNum; } else if(pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number) - m_pTuneTile[fy * m_Width + fx].m_Number = pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number; + m_pTuneTile[Index].m_Number = pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number; else { if(!m_pEditor->m_TuningNum) { - m_pTuneTile[fy * m_Width + fx].m_Number = 0; - m_pTuneTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; + m_pTuneTile[Index].m_Number = 0; + m_pTuneTile[Index].m_Type = 0; + m_pTiles[Index].m_Index = 0; continue; } else - m_pTuneTile[fy * m_Width + fx].m_Number = m_pEditor->m_TuningNum; + m_pTuneTile[Index].m_Number = m_pEditor->m_TuningNum; } - m_pTuneTile[fy * m_Width + fx].m_Type = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; + m_pTuneTile[Index].m_Type = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; + m_pTiles[Index].m_Index = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; } else { - m_pTuneTile[fy * m_Width + fx].m_Number = 0; - m_pTuneTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; + m_pTuneTile[Index].m_Number = 0; + m_pTuneTile[Index].m_Type = 0; + m_pTiles[Index].m_Index = 0; } + + STuneTileStateChange::SData Current{ + m_pTuneTile[Index].m_Number, + m_pTuneTile[Index].m_Type, + m_pTiles[Index].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); } FlagModified(sx, sy, pTuneLayer->m_Width, pTuneLayer->m_Height); } +void CLayerTune::RecordStateChange(int x, int y, STuneTileStateChange::SData Previous, STuneTileStateChange::SData Current) +{ + if(!m_History[y][x].m_Changed) + m_History[y][x] = STuneTileStateChange{true, Previous, Current}; + else + m_History[y][x].m_Current = Current; +} + void CLayerTune::BrushFlipX() { CLayerTiles::BrushFlipX(); @@ -194,6 +225,11 @@ void CLayerTune::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRe const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int TgtIndex = fy * m_Width + fx; + STuneTileStateChange::SData Previous{ + m_pTuneTile[TgtIndex].m_Number, + m_pTuneTile[TgtIndex].m_Type, + m_pTiles[TgtIndex].m_Index}; + if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTuneTile((pLt->m_pTiles[SrcIndex]).m_Index))) { m_pTiles[TgtIndex].m_Index = 0; @@ -213,8 +249,25 @@ void CLayerTune::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRe m_pTuneTile[TgtIndex].m_Number = pLt->m_pTuneTile[SrcIndex].m_Number; } } + + STuneTileStateChange::SData Current{ + m_pTuneTile[TgtIndex].m_Number, + m_pTuneTile[TgtIndex].m_Type, + m_pTiles[TgtIndex].m_Index}; + + RecordStateChange(fx, fy, Previous, Current); } } FlagModified(sx, sy, w, h); } + +std::shared_ptr CLayerTune::Duplicate() const +{ + return std::make_shared(*this); +} + +const char *CLayerTune::TypeName() const +{ + return "tune"; +} diff --git a/src/game/editor/mapitems/layer_tune.h b/src/game/editor/mapitems/layer_tune.h index 8706778ece8..cf35b95693b 100644 --- a/src/game/editor/mapitems/layer_tune.h +++ b/src/game/editor/mapitems/layer_tune.h @@ -3,10 +3,22 @@ #include "layer_tiles.h" +struct STuneTileStateChange +{ + bool m_Changed; + struct SData + { + int m_Number; + int m_Type; + int m_Index; + } m_Previous, m_Current; +}; + class CLayerTune : public CLayerTiles { public: CLayerTune(CEditor *pEditor, int w, int h); + CLayerTune(const CLayerTune &Other); ~CLayerTune(); CTuneTile *m_pTuneTile; @@ -20,6 +32,19 @@ class CLayerTune : public CLayerTiles void BrushFlipY() override; void BrushRotate(float Amount) override; void FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) override; + + EditorTileStateChangeHistory m_History; + inline void ClearHistory() override + { + CLayerTiles::ClearHistory(); + m_History.clear(); + } + + std::shared_ptr Duplicate() const override; + const char *TypeName() const override; + +private: + void RecordStateChange(int x, int y, STuneTileStateChange::SData Previous, STuneTileStateChange::SData Current); }; #endif diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 0dd22b9b0ea..6150965fe24 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -16,6 +16,7 @@ #include #include "editor.h" +#include "editor_actions.h" CUI::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect View, bool Active) { @@ -344,6 +345,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, { if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete group", 0, &Button, 0, "Delete group")) { + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, true)); pEditor->m_Map.DeleteGroup(pEditor->m_SelectedGroup); pEditor->m_SelectedGroup = maximum(0, pEditor->m_SelectedGroup - 1); return CUI::POPUP_CLOSE_CURRENT; @@ -403,8 +405,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, std::shared_ptr pTeleLayer = std::make_shared(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); pEditor->m_Map.MakeTeleLayer(pTeleLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTeleLayer); - pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); + int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1; + pEditor->SelectLayer(LayerIndex); pEditor->m_pBrush->Clear(); + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, LayerIndex)); return CUI::POPUP_CLOSE_CURRENT; } } @@ -420,8 +424,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, std::shared_ptr pSpeedupLayer = std::make_shared(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); pEditor->m_Map.MakeSpeedupLayer(pSpeedupLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSpeedupLayer); - pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); + int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1; + pEditor->SelectLayer(LayerIndex); pEditor->m_pBrush->Clear(); + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, LayerIndex)); return CUI::POPUP_CLOSE_CURRENT; } } @@ -437,8 +443,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, std::shared_ptr pTuneLayer = std::make_shared(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); pEditor->m_Map.MakeTuneLayer(pTuneLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTuneLayer); - pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); + int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1; + pEditor->SelectLayer(LayerIndex); pEditor->m_pBrush->Clear(); + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, LayerIndex)); return CUI::POPUP_CLOSE_CURRENT; } } @@ -454,8 +462,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, std::shared_ptr pFrontLayer = std::make_shared(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); pEditor->m_Map.MakeFrontLayer(pFrontLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pFrontLayer); - pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); + int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1; + pEditor->SelectLayer(LayerIndex); pEditor->m_pBrush->Clear(); + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, LayerIndex)); return CUI::POPUP_CLOSE_CURRENT; } } @@ -471,8 +481,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, std::shared_ptr pSwitchLayer = std::make_shared(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); pEditor->m_Map.MakeSwitchLayer(pSwitchLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSwitchLayer); - pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); + int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1; + pEditor->SelectLayer(LayerIndex); pEditor->m_pBrush->Clear(); + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, LayerIndex)); return CUI::POPUP_CLOSE_CURRENT; } } @@ -485,8 +497,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, { std::shared_ptr pQuadLayer = std::make_shared(pEditor); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pQuadLayer); - pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); + int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1; + pEditor->SelectLayer(LayerIndex); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false; + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, LayerIndex)); return CUI::POPUP_CLOSE_CURRENT; } @@ -499,8 +513,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, std::shared_ptr pTileLayer = std::make_shared(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); pTileLayer->m_pEditor = pEditor; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTileLayer); - pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); + int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1; + pEditor->SelectLayer(LayerIndex); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false; + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, LayerIndex)); return CUI::POPUP_CLOSE_CURRENT; } @@ -512,8 +528,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, { std::shared_ptr pSoundLayer = std::make_shared(pEditor); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSoundLayer); - pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); + int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1; + pEditor->SelectLayer(LayerIndex); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false; + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, LayerIndex)); return CUI::POPUP_CLOSE_CURRENT; } @@ -530,21 +548,6 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, pEditor->m_Map.OnModify(); } - enum - { - PROP_ORDER = 0, - PROP_POS_X, - PROP_POS_Y, - PROP_PARA_X, - PROP_PARA_Y, - PROP_USE_CLIPPING, - PROP_CLIP_X, - PROP_CLIP_Y, - PROP_CLIP_W, - PROP_CLIP_H, - NUM_PROPS, - }; - CProperty aProps[] = { {"Order", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, (int)pEditor->m_Map.m_vpGroups.size() - 1}, {"Pos X", -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX, PROPTYPE_INT_SCROLL, -1000000, 1000000}, @@ -561,17 +564,20 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, // cut the properties that aren't needed if(pEditor->GetSelectedGroup()->m_GameGroup) - aProps[PROP_POS_X].m_pName = nullptr; + aProps[(int)EGroupProp::PROP_POS_X].m_pName = nullptr; - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)EGroupProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) + auto [State, Prop] = pEditor->DoPropertiesWithState(&View, aProps, s_aIds, &NewVal); + if(Prop != EGroupProp::PROP_NONE) { pEditor->m_Map.OnModify(); } - if(Prop == PROP_ORDER) + static CLayerGroupPropTracker s_Tracker(pEditor); + s_Tracker.Begin(pEditor->GetSelectedGroup().get(), Prop, State); + + if(Prop == EGroupProp::PROP_ORDER) { pEditor->m_SelectedGroup = pEditor->m_Map.SwapGroups(pEditor->m_SelectedGroup, NewVal); } @@ -579,44 +585,46 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, // these can not be changed on the game group if(!pEditor->GetSelectedGroup()->m_GameGroup) { - if(Prop == PROP_PARA_X) + if(Prop == EGroupProp::PROP_PARA_X) { pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal; } - else if(Prop == PROP_PARA_Y) + else if(Prop == EGroupProp::PROP_PARA_Y) { pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal; } - else if(Prop == PROP_POS_X) + else if(Prop == EGroupProp::PROP_POS_X) { - pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal; + pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = NewVal; } - else if(Prop == PROP_POS_Y) + else if(Prop == EGroupProp::PROP_POS_Y) { - pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = -NewVal; + pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = NewVal; } - else if(Prop == PROP_USE_CLIPPING) + else if(Prop == EGroupProp::PROP_USE_CLIPPING) { pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping = NewVal; } - else if(Prop == PROP_CLIP_X) + else if(Prop == EGroupProp::PROP_CLIP_X) { pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX = NewVal; } - else if(Prop == PROP_CLIP_Y) + else if(Prop == EGroupProp::PROP_CLIP_Y) { pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY = NewVal; } - else if(Prop == PROP_CLIP_W) + else if(Prop == EGroupProp::PROP_CLIP_W) { pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW = NewVal; } - else if(Prop == PROP_CLIP_H) + else if(Prop == EGroupProp::PROP_CLIP_H) { pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH = NewVal; } } + s_Tracker.End(Prop, State); + return CUI::POPUP_KEEP_OPEN; } @@ -630,7 +638,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, if(pPopup->m_vpLayers.size() > 1) { - return CLayerTiles::RenderCommonProperties(pPopup->m_CommonPropState, pEditor, &View, pPopup->m_vpLayers); + return CLayerTiles::RenderCommonProperties(pPopup->m_CommonPropState, pEditor, &View, pPopup->m_vpLayers, pPopup->m_vLayerIndices); } const bool EntitiesLayer = pCurrentLayer->IsEntitiesLayer(); @@ -643,6 +651,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, static int s_DeleteButton = 0; if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete layer", 0, &DeleteButton, 0, "Deletes the layer")) { + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0])); + if(pCurrentLayer == pEditor->m_Map.m_pFrontLayer) pEditor->m_Map.m_pFrontLayer = nullptr; if(pCurrentLayer == pEditor->m_Map.m_pTeleLayer) @@ -654,6 +664,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, if(pCurrentLayer == pEditor->m_Map.m_pTuneLayer) pEditor->m_Map.m_pTuneLayer = nullptr; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DeleteLayer(pEditor->m_vSelectedLayers[0]); + return CUI::POPUP_CLOSE_CURRENT; } } @@ -668,6 +679,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, if(pEditor->DoButton_Editor(&s_DuplicationButton, "Duplicate layer", 0, &DuplicateButton, 0, "Duplicates the layer")) { pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DuplicateLayer(pEditor->m_vSelectedLayers[0]); + pEditor->m_EditorHistory.RecordAction(std::make_shared(pEditor, pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0] + 1, true)); return CUI::POPUP_CLOSE_CURRENT; } } @@ -690,14 +702,6 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, if(!EntitiesLayer || pEditor->m_Map.m_pGameLayer != pCurrentLayer) View.HSplitBottom(10.0f, &View, nullptr); - enum - { - PROP_GROUP = 0, - PROP_ORDER, - PROP_HQ, - NUM_PROPS, - }; - CProperty aProps[] = { {"Group", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, (int)pEditor->m_Map.m_vpGroups.size() - 1}, {"Order", pEditor->m_vSelectedLayers[0], PROPTYPE_INT_STEP, 0, (int)pCurrentGroup->m_vpLayers.size() - 1}, @@ -718,19 +722,23 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, aProps[2].m_Type = PROPTYPE_NULL; } - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)ELayerProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) + auto [State, Prop] = pEditor->DoPropertiesWithState(&View, aProps, s_aIds, &NewVal); + if(Prop != ELayerProp::PROP_NONE) { pEditor->m_Map.OnModify(); } - if(Prop == PROP_ORDER) + static CLayerPropTracker s_Tracker(pEditor); + s_Tracker.Begin(pCurrentLayer.get(), Prop, State); + + if(Prop == ELayerProp::PROP_ORDER) { - pEditor->SelectLayer(pCurrentGroup->SwapLayers(pEditor->m_vSelectedLayers[0], NewVal)); + int NewIndex = pCurrentGroup->SwapLayers(pEditor->m_vSelectedLayers[0], NewVal); + pEditor->SelectLayer(NewIndex); } - else if(Prop == PROP_GROUP) + else if(Prop == ELayerProp::PROP_GROUP) { if(NewVal >= 0 && (size_t)NewVal < pEditor->m_Map.m_vpGroups.size()) { @@ -742,13 +750,15 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[NewVal]->m_vpLayers.size() - 1); } } - else if(Prop == PROP_HQ) + else if(Prop == ELayerProp::PROP_HQ) { pCurrentLayer->m_Flags &= ~LAYERFLAG_DETAIL; if(NewVal) pCurrentLayer->m_Flags |= LAYERFLAG_DETAIL; } + s_Tracker.End(Prop, State); + return pCurrentLayer->RenderProperties(&View); } @@ -782,6 +792,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b static int s_AspectRatioButton = 0; if(pEditor->DoButton_Editor(&s_AspectRatioButton, "Aspect ratio", 0, &Button, 0, "Resizes the current Quad based on the aspect ratio of the image")) { + pEditor->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads); for(auto &pQuad : vpQuads) { int Top = pQuad->m_aPoints[0].y; @@ -810,6 +821,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b pQuad->m_aPoints[3].y = Top + Height; pEditor->m_Map.OnModify(); } + pEditor->m_QuadTracker.EndQuadTrack(); + return CUI::POPUP_CLOSE_CURRENT; } } @@ -820,6 +833,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b static int s_AlignButton = 0; if(pEditor->DoButton_Editor(&s_AlignButton, "Align", 0, &Button, 0, "Aligns coordinates of the quad points")) { + pEditor->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads); for(auto &pQuad : vpQuads) { for(int k = 1; k < 4; k++) @@ -829,6 +843,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b } pEditor->m_Map.OnModify(); } + pEditor->m_QuadTracker.EndQuadTrack(); return CUI::POPUP_CLOSE_CURRENT; } @@ -838,6 +853,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b static int s_Button = 0; if(pEditor->DoButton_Editor(&s_Button, "Square", 0, &Button, 0, "Squares the current quad")) { + pEditor->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads); for(auto &pQuad : vpQuads) { int Top = pQuad->m_aPoints[0].y; @@ -867,6 +883,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b pQuad->m_aPoints[3].y = Bottom; pEditor->m_Map.OnModify(); } + pEditor->m_QuadTracker.EndQuadTrack(); return CUI::POPUP_CLOSE_CURRENT; } @@ -881,18 +898,6 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b return CUI::POPUP_CLOSE_CURRENT; } - enum - { - PROP_ORDER = 0, - PROP_POS_X, - PROP_POS_Y, - PROP_POS_ENV, - PROP_POS_ENV_OFFSET, - PROP_COLOR_ENV, - PROP_COLOR_ENV_OFFSET, - NUM_PROPS, - }; - const int NumQuads = pLayer ? (int)pLayer->m_vQuads.size() : 0; CProperty aProps[] = { {"Order", pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], PROPTYPE_INT_STEP, 0, NumQuads}, @@ -905,18 +910,23 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b {nullptr}, }; - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)EQuadProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) + auto PropRes = pEditor->DoPropertiesWithState(&View, aProps, s_aIds, &NewVal); + EQuadProp Prop = PropRes.m_Value; + if(Prop != EQuadProp::PROP_NONE) { pEditor->m_Map.OnModify(); + if(PropRes.m_State == EEditState::START || PropRes.m_State == EEditState::ONE_GO) + { + pEditor->m_QuadTracker.BeginQuadPropTrack(pLayer, pEditor->m_vSelectedQuads, Prop); + } } const float OffsetX = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].x; const float OffsetY = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].y; - if(Prop == PROP_ORDER && pLayer) + if(Prop == EQuadProp::PROP_ORDER && pLayer) { const int QuadIndex = pLayer->SwapQuads(pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], NewVal); pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex] = QuadIndex; @@ -924,17 +934,17 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b for(auto &pQuad : vpQuads) { - if(Prop == PROP_POS_X) + if(Prop == EQuadProp::PROP_POS_X) { for(auto &Point : pQuad->m_aPoints) Point.x += OffsetX; } - else if(Prop == PROP_POS_Y) + else if(Prop == EQuadProp::PROP_POS_Y) { for(auto &Point : pQuad->m_aPoints) Point.y += OffsetY; } - else if(Prop == PROP_POS_ENV) + else if(Prop == EQuadProp::PROP_POS_ENV) { int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1); int StepDirection = Index < pQuad->m_PosEnv ? -1 : 1; @@ -950,11 +960,11 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b } } } - else if(Prop == PROP_POS_ENV_OFFSET) + else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET) { pQuad->m_PosEnvOffset = NewVal; } - else if(Prop == PROP_COLOR_ENV) + else if(Prop == EQuadProp::PROP_COLOR_ENV) { int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1); int StepDirection = Index < pQuad->m_ColorEnv ? -1 : 1; @@ -970,12 +980,20 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b } } } - else if(Prop == PROP_COLOR_ENV_OFFSET) + else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET) { pQuad->m_ColorEnvOffset = NewVal; } } + if(Prop != EQuadProp::PROP_NONE) + { + if(PropRes.m_State == EEditState::END || PropRes.m_State == EEditState::ONE_GO) + { + pEditor->m_QuadTracker.EndQuadPropTrack(Prop); + } + } + return CUI::POPUP_KEEP_OPEN; } @@ -983,6 +1001,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, { CEditor *pEditor = static_cast(pContext); CSoundSource *pSource = pEditor->GetSelectedSource(); + if(!pSource) + return CUI::POPUP_CLOSE_CURRENT; CUIRect Button; @@ -994,9 +1014,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, std::shared_ptr pLayer = std::static_pointer_cast(pEditor->GetSelectedLayerType(0, LAYERTYPE_SOUNDS)); if(pLayer) { - pEditor->m_Map.OnModify(); - pLayer->m_vSources.erase(pLayer->m_vSources.begin() + pEditor->m_SelectedSource); - pEditor->m_SelectedSource--; + pEditor->m_EditorHistory.Execute(std::make_shared(pEditor, pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0], pEditor->m_SelectedSource)); } return CUI::POPUP_CLOSE_CURRENT; } @@ -1015,40 +1033,9 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, static int s_ShapeTypeButton = 0; if(pEditor->DoButton_Editor(&s_ShapeTypeButton, s_apShapeNames[pSource->m_Shape.m_Type], 0, &ShapeButton, 0, "Change shape")) { - pSource->m_Shape.m_Type = (pSource->m_Shape.m_Type + 1) % CSoundShape::NUM_SHAPES; - - // set default values - switch(pSource->m_Shape.m_Type) - { - case CSoundShape::SHAPE_CIRCLE: - { - pSource->m_Shape.m_Circle.m_Radius = 1000.0f; - break; - } - case CSoundShape::SHAPE_RECTANGLE: - { - pSource->m_Shape.m_Rectangle.m_Width = f2fx(1000.0f); - pSource->m_Shape.m_Rectangle.m_Height = f2fx(800.0f); - break; - } - } + pEditor->m_EditorHistory.Execute(std::make_shared(pEditor, pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0], pEditor->m_SelectedSource, CEditorActionEditSoundSource::EEditType::SHAPE, (pSource->m_Shape.m_Type + 1) % CSoundShape::NUM_SHAPES)); } - enum - { - PROP_POS_X = 0, - PROP_POS_Y, - PROP_LOOP, - PROP_PAN, - PROP_TIME_DELAY, - PROP_FALLOFF, - PROP_POS_ENV, - PROP_POS_ENV_OFFSET, - PROP_SOUND_ENV, - PROP_SOUND_ENV_OFFSET, - NUM_PROPS, - }; - CProperty aProps[] = { {"Pos X", pSource->m_Position.x / 1000, PROPTYPE_INT_SCROLL, -1000000, 1000000}, {"Pos Y", pSource->m_Position.y / 1000, PROPTYPE_INT_SCROLL, -1000000, 1000000}, @@ -1063,39 +1050,42 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, {nullptr}, }; - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)ESoundProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) + auto [State, Prop] = pEditor->DoPropertiesWithState(&View, aProps, s_aIds, &NewVal); + if(Prop != ESoundProp::PROP_NONE) { pEditor->m_Map.OnModify(); } - if(Prop == PROP_POS_X) + static CSoundSourcePropTracker s_Tracker(pEditor); + s_Tracker.Begin(pSource, Prop, State); + + if(Prop == ESoundProp::PROP_POS_X) { pSource->m_Position.x = NewVal * 1000; } - else if(Prop == PROP_POS_Y) + else if(Prop == ESoundProp::PROP_POS_Y) { pSource->m_Position.y = NewVal * 1000; } - else if(Prop == PROP_LOOP) + else if(Prop == ESoundProp::PROP_LOOP) { pSource->m_Loop = NewVal; } - else if(Prop == PROP_PAN) + else if(Prop == ESoundProp::PROP_PAN) { pSource->m_Pan = NewVal; } - else if(Prop == PROP_TIME_DELAY) + else if(Prop == ESoundProp::PROP_TIME_DELAY) { pSource->m_TimeDelay = NewVal; } - else if(Prop == PROP_FALLOFF) + else if(Prop == ESoundProp::PROP_FALLOFF) { pSource->m_Falloff = NewVal; } - else if(Prop == PROP_POS_ENV) + else if(Prop == ESoundProp::PROP_POS_ENV) { int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1); const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1; @@ -1108,11 +1098,11 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, } } } - else if(Prop == PROP_POS_ENV_OFFSET) + else if(Prop == ESoundProp::PROP_POS_ENV_OFFSET) { pSource->m_PosEnvOffset = NewVal; } - else if(Prop == PROP_SOUND_ENV) + else if(Prop == ESoundProp::PROP_SOUND_ENV) { int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1); const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1; @@ -1125,75 +1115,72 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, } } } - else if(Prop == PROP_SOUND_ENV_OFFSET) + else if(Prop == ESoundProp::PROP_SOUND_ENV_OFFSET) { pSource->m_SoundEnvOffset = NewVal; } + s_Tracker.End(Prop, State); + // source shape properties switch(pSource->m_Shape.m_Type) { case CSoundShape::SHAPE_CIRCLE: { - enum - { - PROP_CIRCLE_RADIUS = 0, - NUM_CIRCLE_PROPS, - }; - CProperty aCircleProps[] = { {"Radius", pSource->m_Shape.m_Circle.m_Radius, PROPTYPE_INT_SCROLL, 0, 1000000}, {nullptr}, }; - static int s_aCircleIds[NUM_CIRCLE_PROPS] = {0}; + static int s_aCircleIds[(int)ECircleShapeProp::NUM_CIRCLE_PROPS] = {0}; NewVal = 0; - Prop = pEditor->DoProperties(&View, aCircleProps, s_aCircleIds, &NewVal); - if(Prop != -1) + auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState(&View, aCircleProps, s_aCircleIds, &NewVal); + if(LocalProp != ECircleShapeProp::PROP_NONE) { pEditor->m_Map.OnModify(); } - if(Prop == PROP_CIRCLE_RADIUS) + static CSoundSourceCircleShapePropTracker s_ShapeTracker(pEditor); + s_ShapeTracker.Begin(pSource, LocalProp, LocalState); + + if(LocalProp == ECircleShapeProp::PROP_CIRCLE_RADIUS) { pSource->m_Shape.m_Circle.m_Radius = NewVal; } + s_ShapeTracker.End(LocalProp, LocalState); break; } case CSoundShape::SHAPE_RECTANGLE: { - enum - { - PROP_RECTANGLE_WIDTH = 0, - PROP_RECTANGLE_HEIGHT, - NUM_RECTANGLE_PROPS, - }; - CProperty aRectangleProps[] = { {"Width", pSource->m_Shape.m_Rectangle.m_Width / 1024, PROPTYPE_INT_SCROLL, 0, 1000000}, {"Height", pSource->m_Shape.m_Rectangle.m_Height / 1024, PROPTYPE_INT_SCROLL, 0, 1000000}, {nullptr}, }; - static int s_aRectangleIds[NUM_RECTANGLE_PROPS] = {0}; + static int s_aRectangleIds[(int)ERectangleShapeProp::NUM_RECTANGLE_PROPS] = {0}; NewVal = 0; - Prop = pEditor->DoProperties(&View, aRectangleProps, s_aRectangleIds, &NewVal); - if(Prop != -1) + auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState(&View, aRectangleProps, s_aRectangleIds, &NewVal); + if(LocalProp != ERectangleShapeProp::PROP_NONE) { pEditor->m_Map.OnModify(); } - if(Prop == PROP_RECTANGLE_WIDTH) + static CSoundSourceRectShapePropTracker s_ShapeTracker(pEditor); + s_ShapeTracker.Begin(pSource, LocalProp, LocalState); + + if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_WIDTH) { pSource->m_Shape.m_Rectangle.m_Width = NewVal * 1024; } - else if(Prop == PROP_RECTANGLE_HEIGHT) + else if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_HEIGHT) { pSource->m_Shape.m_Rectangle.m_Height = NewVal * 1024; } + s_ShapeTracker.End(LocalProp, LocalState); break; } } @@ -1208,22 +1195,9 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, if(!in_range(pEditor->m_SelectedQuadIndex, 0, vpQuads.size() - 1)) return CUI::POPUP_CLOSE_CURRENT; CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex]; + std::shared_ptr pLayer = std::static_pointer_cast(pEditor->GetSelectedLayerType(0, LAYERTYPE_QUADS)); - enum - { - PROP_POS_X = 0, - PROP_POS_Y, - PROP_COLOR, - PROP_TEX_U, - PROP_TEX_V, - NUM_PROPS, - }; - - int Color = 0; - Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].r << 24; - Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].g << 16; - Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].b << 8; - Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].a; + int Color = PackColor(pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint]); const int X = fx2i(pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].x); const int Y = fx2i(pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].y); @@ -1239,29 +1213,35 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, {nullptr}, }; - static int s_aIds[NUM_PROPS] = {0}; + static int s_aIds[(int)EQuadPointProp::NUM_PROPS] = {0}; int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); - if(Prop != -1) + auto PropRes = pEditor->DoPropertiesWithState(&View, aProps, s_aIds, &NewVal); + EQuadPointProp Prop = PropRes.m_Value; + if(Prop != EQuadPointProp::PROP_NONE) { pEditor->m_Map.OnModify(); + if(PropRes.m_State == EEditState::START || PropRes.m_State == EEditState::ONE_GO) + { + pEditor->m_QuadTracker.BeginQuadPointPropTrack(pLayer, pEditor->m_vSelectedQuads, pEditor->m_SelectedQuadPoints); + pEditor->m_QuadTracker.AddQuadPointPropTrack(Prop); + } } for(CQuad *pQuad : vpQuads) { - if(Prop == PROP_POS_X) + if(Prop == EQuadPointProp::PROP_POS_X) { for(int v = 0; v < 4; v++) if(pEditor->IsQuadCornerSelected(v)) pQuad->m_aPoints[v].x = i2fx(fx2i(pQuad->m_aPoints[v].x) + NewVal - X); } - else if(Prop == PROP_POS_Y) + else if(Prop == EQuadPointProp::PROP_POS_Y) { for(int v = 0; v < 4; v++) if(pEditor->IsQuadCornerSelected(v)) pQuad->m_aPoints[v].y = i2fx(fx2i(pQuad->m_aPoints[v].y) + NewVal - Y); } - else if(Prop == PROP_COLOR) + else if(Prop == EQuadPointProp::PROP_COLOR) { for(int v = 0; v < 4; v++) { @@ -1274,13 +1254,13 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, } } } - else if(Prop == PROP_TEX_U) + else if(Prop == EQuadPointProp::PROP_TEX_U) { for(int v = 0; v < 4; v++) if(pEditor->IsQuadCornerSelected(v)) pQuad->m_aTexcoords[v].x = f2fx(fx2f(pQuad->m_aTexcoords[v].x) + (NewVal - TextureU) / 1024.0f); } - else if(Prop == PROP_TEX_V) + else if(Prop == EQuadPointProp::PROP_TEX_V) { for(int v = 0; v < 4; v++) if(pEditor->IsQuadCornerSelected(v)) @@ -1288,12 +1268,24 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, } } + if(Prop != EQuadPointProp::PROP_NONE) + { + pEditor->m_Map.OnModify(); + if(PropRes.m_State == EEditState::END || PropRes.m_State == EEditState::ONE_GO) + { + pEditor->m_QuadTracker.EndQuadPointPropTrack(Prop); + } + } + return CUI::POPUP_KEEP_OPEN; } CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast(pContext); + if(pEditor->m_SelectedEnvelope < 0 || pEditor->m_SelectedEnvelope >= (int)pEditor->m_Map.m_vpEnvelopes.size()) + return CUI::POPUP_CLOSE_CURRENT; + const float RowHeight = 12.0f; CUIRect Row, Label, EditBox; @@ -1309,14 +1301,41 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie Row.VSplitLeft(10.0f, nullptr, &EditBox); pEditor->UI()->DoLabel(&Label, "Color:", RowHeight - 2.0f, TEXTALIGN_ML); - const auto [SelectedIndex, _] = pEditor->m_vSelectedEnvelopePoints.front(); + const auto SelectedPoint = pEditor->m_vSelectedEnvelopePoints.front(); + const int SelectedIndex = SelectedPoint.first; auto *pValues = pEnvelope->m_vPoints[SelectedIndex].m_aValues; const ColorRGBA Color = ColorRGBA(fx2f(pValues[0]), fx2f(pValues[1]), fx2f(pValues[2]), fx2f(pValues[3])); const auto &&SetColor = [&](ColorRGBA NewColor) { - if(Color == NewColor) + if(Color == NewColor && pEditor->m_ColorPickerPopupContext.m_State == EEditState::EDITING) return; + + static int s_Values[4]; + + if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::START || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO) + { + for(int Channel = 0; Channel < 4; ++Channel) + s_Values[Channel] = pValues[Channel]; + } + for(int Channel = 0; Channel < 4; ++Channel) + { pValues[Channel] = f2fx(NewColor[Channel]); + } + + if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::END || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO) + { + std::vector> vpActions(4); + + for(int Channel = 0; Channel < 4; ++Channel) + { + vpActions[Channel] = std::make_shared(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, Channel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, s_Values[Channel], f2fx(NewColor[Channel])); + } + + char aDisplay[256]; + str_format(aDisplay, sizeof(aDisplay), "Edit color of point %d of envelope %d", SelectedIndex, pEditor->m_SelectedEnvelope); + pEditor->m_EnvelopeEditorHistory.RecordAction(std::make_shared(pEditor, vpActions, aDisplay)); + } + pEditor->m_UpdateEnvPointInfo = true; pEditor->m_Map.OnModify(); }; @@ -1327,37 +1346,23 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie static CLineInputNumber s_CurValueInput; static CLineInputNumber s_CurTimeInput; + static float s_CurrentTime = 0; + static float s_CurrentValue = 0; + if(pEditor->m_UpdateEnvPointInfo) { pEditor->m_UpdateEnvPointInfo = false; - int CurrentTime; - int CurrentValue; - if(pEditor->IsTangentInSelected()) - { - auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint; - - CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]; - CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel]; - } - else if(pEditor->IsTangentOutSelected()) - { - auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint; - - CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]; - CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel]; - } - else - { - auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front(); - - CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time; - CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel]; - } + auto TimeAndValue = pEditor->EnvGetSelectedTimeAndValue(); + int CurrentTime = TimeAndValue.first; + int CurrentValue = TimeAndValue.second; // update displayed text s_CurValueInput.SetFloat(fx2f(CurrentValue)); s_CurTimeInput.SetFloat(CurrentTime / 1000.0f); + + s_CurrentTime = s_CurTimeInput.GetFloat(); + s_CurrentValue = s_CurValueInput.GetFloat(); } View.HSplitTop(RowHeight, &Row, &View); @@ -1377,53 +1382,46 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie { float CurrentTime = s_CurTimeInput.GetFloat(); float CurrentValue = s_CurValueInput.GetFloat(); - if(pEditor->IsTangentInSelected()) - { - auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint; - - pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel] = minimum(CurrentTime * 1000.0f - pEnvelope->m_vPoints[SelectedIndex].m_Time, 0); - CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]) / 1000.0f; - - pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel] = f2fx(CurrentValue) - pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel]; - } - else if(pEditor->IsTangentOutSelected()) + if(!(absolute(CurrentTime - s_CurrentTime) < 0.0001f && absolute(CurrentValue - s_CurrentValue) < 0.0001f)) { - auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint; - - pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel] = maximum(CurrentTime * 1000.0f - pEnvelope->m_vPoints[SelectedIndex].m_Time, 0); - CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]) / 1000.0f; - - pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel] = f2fx(CurrentValue) - pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel]; - } - else - { - auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front(); - if(pEnvelope->GetChannels() == 4) - CurrentValue = clamp(CurrentValue, 0.0f, 1.0f); - pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] = f2fx(CurrentValue); + auto [OldTime, OldValue] = pEditor->EnvGetSelectedTimeAndValue(); - if(SelectedIndex != 0) + if(pEditor->IsTangentInSelected()) { - pEnvelope->m_vPoints[SelectedIndex].m_Time = CurrentTime * 1000.0f; + auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint; - if(pEnvelope->m_vPoints[SelectedIndex].m_Time < pEnvelope->m_vPoints[SelectedIndex - 1].m_Time) - pEnvelope->m_vPoints[SelectedIndex].m_Time = pEnvelope->m_vPoints[SelectedIndex - 1].m_Time + 1; - if(static_cast(SelectedIndex) + 1 != pEnvelope->m_vPoints.size() && pEnvelope->m_vPoints[SelectedIndex].m_Time > pEnvelope->m_vPoints[SelectedIndex + 1].m_Time) - pEnvelope->m_vPoints[SelectedIndex].m_Time = pEnvelope->m_vPoints[SelectedIndex + 1].m_Time - 1; + pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::TANGENT_IN, static_cast(OldTime * 1000.0f), f2fx(OldValue), static_cast(CurrentTime * 1000.0f), f2fx(CurrentValue))); + CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]) / 1000.0f; + } + else if(pEditor->IsTangentOutSelected()) + { + auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint; - CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time / 1000.0f; + pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::TANGENT_OUT, static_cast(OldTime * 1000.0f), f2fx(OldValue), static_cast(CurrentTime * 1000.0f), f2fx(CurrentValue))); + CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]) / 1000.0f; } else { - CurrentTime = 0.0f; - pEnvelope->m_vPoints[SelectedIndex].m_Time = 0.0f; + auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front(); + pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::POINT, static_cast(OldTime * 1000.0f), f2fx(OldValue), static_cast(CurrentTime * 1000.0f), f2fx(CurrentValue))); + + if(SelectedIndex != 0) + { + CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time / 1000.0f; + } + else + { + CurrentTime = 0.0f; + pEnvelope->m_vPoints[SelectedIndex].m_Time = 0.0f; + } } - } - s_CurTimeInput.SetFloat(static_cast(CurrentTime * 1000.0f) / 1000.0f); - s_CurValueInput.SetFloat(fx2f(f2fx(CurrentValue))); + s_CurTimeInput.SetFloat(static_cast(CurrentTime * 1000.0f) / 1000.0f); + s_CurValueInput.SetFloat(fx2f(f2fx(CurrentValue))); - pEditor->m_Map.OnModify(); + s_CurrentTime = s_CurTimeInput.GetFloat(); + s_CurrentValue = s_CurValueInput.GetFloat(); + } } View.HSplitTop(6.0f, nullptr, &View); @@ -1436,24 +1434,18 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie if(pEditor->IsTangentInSelected()) { auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint; - - pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel] = 0.0f; - pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel] = 0.0f; + pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, true)); } else if(pEditor->IsTangentOutSelected()) { auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint; - - pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel] = 0.0f; - pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel] = 0.0f; + pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, false)); } else { auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front(); - - pEnvelope->m_vPoints.erase(pEnvelope->m_vPoints.begin() + SelectedIndex); + pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex)); } - pEditor->m_Map.OnModify(); return CUI::POPUP_CLOSE_CURRENT; } @@ -1515,6 +1507,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPointCurveType(void *pContext, CU if(pEditor->DoButton_MenuItem(&s_ButtonSmoothID, "Smooth", 0, &ButtonSmooth)) CurveType = CURVETYPE_SMOOTH; + std::vector> vpActions; + if(CurveType >= 0) { std::shared_ptr pEnvelope = pEditor->m_Map.m_vpEnvelopes.at(pEditor->m_SelectedEnvelope); @@ -1551,13 +1545,20 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPointCurveType(void *pContext, CU CEnvPoint &CurrentPoint = pEnvelope->m_vPoints[SelectedIndex]; ColorRGBA Channels; HelperEnvelope.Eval(CurrentPoint.m_Time / 1000.0f, Channels); + int PrevValue = CurrentPoint.m_aValues[c]; CurrentPoint.m_aValues[c] = f2fx(Channels.r); + vpActions.push_back(std::make_shared(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, PrevValue, CurrentPoint.m_aValues[c])); } } } } } + if(!vpActions.empty()) + { + pEditor->m_EnvelopeEditorHistory.RecordAction(std::make_shared(pEditor, vpActions, "Project points")); + } + pEditor->m_Map.OnModify(); return CUI::POPUP_CLOSE_CURRENT; } diff --git a/src/game/editor/tileart.cpp b/src/game/editor/tileart.cpp index da96fdcff53..5524dce026e 100644 --- a/src/game/editor/tileart.cpp +++ b/src/game/editor/tileart.cpp @@ -1,4 +1,5 @@ #include "editor.h" +#include "editor_actions.h" #include @@ -184,10 +185,15 @@ static void SetTilelayerIndices(const std::shared_ptr &pLayer, cons } } -void CEditor::AddTileart() +void CEditor::AddTileart(bool IgnoreHistory) { + char aTileArtFileName[IO_MAX_PATH_LENGTH]; + IStorage::StripPathAndExtension(m_aTileartFilename, aTileArtFileName, sizeof(aTileArtFileName)); + std::shared_ptr pGroup = m_Map.NewGroup(); - str_copy(pGroup->m_aName, m_aTileartFilename); + str_copy(pGroup->m_aName, aTileArtFileName); + + int ImageCount = m_Map.m_vpImages.size(); auto vUniqueColors = GetUniqueColors(m_TileartImageInfo); auto vaColorGroups = GroupColors(vUniqueColors); @@ -195,11 +201,16 @@ void CEditor::AddTileart() char aImageName[IO_MAX_PATH_LENGTH]; for(size_t i = 0; i < vColorImages.size(); i++) { - str_format(aImageName, sizeof(aImageName), "%s %" PRIzu, m_aTileartFilename, i + 1); + str_format(aImageName, sizeof(aImageName), "%s %" PRIzu, aTileArtFileName, i + 1); std::shared_ptr pLayer = AddLayerWithImage(this, pGroup, m_TileartImageInfo.m_Width, m_TileartImageInfo.m_Height, vColorImages[i], aImageName); SetTilelayerIndices(pLayer, vaColorGroups[i], m_TileartImageInfo); } - SortImages(); + auto IndexMap = SortImages(); + + if(!IgnoreHistory) + { + m_EditorHistory.RecordAction(std::make_shared(this, ImageCount, m_aTileartFilename, IndexMap)); + } free(m_TileartImageInfo.m_pData); m_TileartImageInfo.m_pData = nullptr; @@ -237,7 +248,7 @@ bool CEditor::CallbackAddTileart(const char *pFilepath, int StorageType, void *p return false; } - IStorage::StripPathAndExtension(pFilepath, pEditor->m_aTileartFilename, sizeof(pEditor->m_aTileartFilename)); + str_copy(pEditor->m_aTileartFilename, pFilepath); if(pEditor->m_TileartImageInfo.m_Width * pEditor->m_TileartImageInfo.m_Height > 10'000) { pEditor->m_PopupEventType = CEditor::POPEVENT_PIXELART_BIG_IMAGE; diff --git a/src/game/mapitems.cpp b/src/game/mapitems.cpp index a9620bc98eb..2ccab306703 100644 --- a/src/game/mapitems.cpp +++ b/src/game/mapitems.cpp @@ -150,3 +150,13 @@ bool IsCreditsTile(int TileIndex) (TILE_CREDITS_7 == TileIndex) || (TILE_CREDITS_8 == TileIndex)); } + +int PackColor(CColor Color) +{ + int Res = 0; + Res |= Color.r << 24; + Res |= Color.g << 16; + Res |= Color.b << 8; + Res |= Color.a; + return Res; +} diff --git a/src/game/mapitems.h b/src/game/mapitems.h index c65fcfa88ee..d89cde0cc29 100644 --- a/src/game/mapitems.h +++ b/src/game/mapitems.h @@ -578,5 +578,6 @@ bool IsValidTuneTile(int Index); bool IsValidEntity(int Index); bool IsRotatableTile(int Index); bool IsCreditsTile(int TileIndex); +int PackColor(CColor Color); #endif From 4ad0a48a48ed44b408f0bc0bfc83195abe081579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 7 Aug 2023 20:27:22 +0200 Subject: [PATCH 078/198] Add missing unpacker error checks for server and client packets Ensure all packets are unpacked correctly before using the unpacked data. Ensure strings are valid UTF-8 in `CUnpacker::GetString`. --- src/engine/client/client.cpp | 194 ++++++++++++++++++--------------- src/engine/server/server.cpp | 206 +++++++++++++++++++---------------- src/engine/shared/packer.cpp | 6 + 3 files changed, 225 insertions(+), 181 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 3e4e1dfe77a..90324c02cee 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -1328,7 +1328,6 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) SHA256_DIGEST *pMapSha256 = (SHA256_DIGEST *)Unpacker.GetRaw(sizeof(*pMapSha256)); int MapCrc = Unpacker.GetInt(); int MapSize = Unpacker.GetInt(); - if(Unpacker.Error()) { return; @@ -1355,7 +1354,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) } int Version = Unpacker.GetInt(); int Flags = Unpacker.GetInt(); - if(Version <= 0) + if(Unpacker.Error() || Version <= 0) { return; } @@ -1376,85 +1375,82 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES); int MapCrc = Unpacker.GetInt(); int MapSize = Unpacker.GetInt(); - const char *pError = 0; - - if(Unpacker.Error()) + if(Unpacker.Error() || MapSize < 0) + { return; - - if(m_DummyConnected) - DummyDisconnect(0); + } for(int i = 0; pMap[i]; i++) // protect the player from nasty map names { if(pMap[i] == '/' || pMap[i] == '\\') - pError = "strange character in map name"; + { + return; + } } - if(MapSize < 0) - pError = "invalid map size"; + if(m_DummyConnected) + { + DummyDisconnect(0); + } - if(pError) - DisconnectWithReason(pError); - else + SHA256_DIGEST *pMapSha256 = nullptr; + const char *pMapUrl = nullptr; + if(MapDetailsWerePresent && str_comp(m_aMapDetailsName, pMap) == 0 && m_MapDetailsCrc == MapCrc) { - SHA256_DIGEST *pMapSha256 = 0; - const char *pMapUrl = nullptr; - if(MapDetailsWerePresent && str_comp(m_aMapDetailsName, pMap) == 0 && m_MapDetailsCrc == MapCrc) - { - pMapSha256 = &m_MapDetailsSha256; - pMapUrl = m_aMapDetailsUrl[0] ? m_aMapDetailsUrl : nullptr; - } - pError = LoadMapSearch(pMap, pMapSha256, MapCrc); + pMapSha256 = &m_MapDetailsSha256; + pMapUrl = m_aMapDetailsUrl[0] ? m_aMapDetailsUrl : nullptr; + } - if(!pError) + if(LoadMapSearch(pMap, pMapSha256, MapCrc) != nullptr) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); + SetLoadingStateDetail(IClient::LOADING_STATE_DETAIL_SENDING_READY); + SendReady(CONN_MAIN); + } + else + { + if(m_MapdownloadFileTemp) { - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); - SetLoadingStateDetail(IClient::LOADING_STATE_DETAIL_SENDING_READY); - SendReady(CONN_MAIN); + io_close(m_MapdownloadFileTemp); + Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE); } - else - { - if(m_MapdownloadFileTemp) - { - io_close(m_MapdownloadFileTemp); - Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE); - } - // start map download - FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, false, m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename)); - FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, true, m_aMapdownloadFilenameTemp, sizeof(m_aMapdownloadFilenameTemp)); + // start map download + FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, false, m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename)); + FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, true, m_aMapdownloadFilenameTemp, sizeof(m_aMapdownloadFilenameTemp)); - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilenameTemp); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf); + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilenameTemp); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf); - m_MapdownloadChunk = 0; - str_copy(m_aMapdownloadName, pMap); + m_MapdownloadChunk = 0; + str_copy(m_aMapdownloadName, pMap); - m_MapdownloadSha256Present = (bool)pMapSha256; - m_MapdownloadSha256 = pMapSha256 ? *pMapSha256 : SHA256_ZEROED; - m_MapdownloadCrc = MapCrc; - m_MapdownloadTotalsize = MapSize; - m_MapdownloadAmount = 0; + m_MapdownloadSha256Present = (bool)pMapSha256; + m_MapdownloadSha256 = pMapSha256 ? *pMapSha256 : SHA256_ZEROED; + m_MapdownloadCrc = MapCrc; + m_MapdownloadTotalsize = MapSize; + m_MapdownloadAmount = 0; - ResetMapDownload(); + ResetMapDownload(); - if(pMapSha256) - { - char aUrl[256]; - char aEscaped[256]; - EscapeUrl(aEscaped, sizeof(aEscaped), m_aMapdownloadFilename + 15); // cut off downloadedmaps/ - bool UseConfigUrl = str_comp(g_Config.m_ClMapDownloadUrl, "https://maps.ddnet.org") != 0 || m_aMapDownloadUrl[0] == '\0'; - str_format(aUrl, sizeof(aUrl), "%s/%s", UseConfigUrl ? g_Config.m_ClMapDownloadUrl : m_aMapDownloadUrl, aEscaped); - - m_pMapdownloadTask = HttpGetFile(pMapUrl ? pMapUrl : aUrl, Storage(), m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE); - m_pMapdownloadTask->Timeout(CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, 0, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime}); - m_pMapdownloadTask->MaxResponseSize(1024 * 1024 * 1024); // 1 GiB - m_pMapdownloadTask->ExpectSha256(*pMapSha256); - Engine()->AddJob(m_pMapdownloadTask); - } - else - SendMapRequest(); + if(pMapSha256) + { + char aUrl[256]; + char aEscaped[256]; + EscapeUrl(aEscaped, sizeof(aEscaped), m_aMapdownloadFilename + 15); // cut off downloadedmaps/ + bool UseConfigUrl = str_comp(g_Config.m_ClMapDownloadUrl, "https://maps.ddnet.org") != 0 || m_aMapDownloadUrl[0] == '\0'; + str_format(aUrl, sizeof(aUrl), "%s/%s", UseConfigUrl ? g_Config.m_ClMapDownloadUrl : m_aMapDownloadUrl, aEscaped); + + m_pMapdownloadTask = HttpGetFile(pMapUrl ? pMapUrl : aUrl, Storage(), m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE); + m_pMapdownloadTask->Timeout(CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, 0, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime}); + m_pMapdownloadTask->MaxResponseSize(1024 * 1024 * 1024); // 1 GiB + m_pMapdownloadTask->ExpectSha256(*pMapSha256); + Engine()->AddJob(m_pMapdownloadTask); + } + else + { + SendMapRequest(); } } } @@ -1465,10 +1461,10 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) int Chunk = Unpacker.GetInt(); int Size = Unpacker.GetInt(); const unsigned char *pData = Unpacker.GetRaw(Size); - - // check for errors if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk || !m_MapdownloadFileTemp) + { return; + } io_write(m_MapdownloadFileTemp, pData, Size); @@ -1565,6 +1561,10 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) else if(Msg == NETMSG_REDIRECT) { int RedirectPort = Unpacker.GetInt(); + if(Unpacker.Error()) + { + return; + } char aAddr[128]; char aIP[64]; NETADDR ServerAddr = ServerAddress(); @@ -1577,35 +1577,47 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC); const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(Unpacker.Error() == 0) + if(!Unpacker.Error()) + { m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp); + } } else if(Conn == CONN_MAIN && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_REM) { const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(Unpacker.Error() == 0) + if(!Unpacker.Error()) + { m_pConsole->DeregisterTemp(pName); + } } else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_STATUS) { int ResultInt = Unpacker.GetInt(); - if(Unpacker.Error() == 0) + if(!Unpacker.Error()) + { m_aRconAuthed[Conn] = ResultInt; + } if(Conn == CONN_MAIN) { int Old = m_UseTempRconCommands; m_UseTempRconCommands = Unpacker.GetInt(); - if(Unpacker.Error() != 0) + if(Unpacker.Error()) + { m_UseTempRconCommands = 0; + } if(Old != 0 && m_UseTempRconCommands == 0) + { m_pConsole->DeregisterTempAll(); + } } } else if(!Dummy && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_LINE) { const char *pLine = Unpacker.GetString(); - if(Unpacker.Error() == 0) + if(!Unpacker.Error()) + { GameClient()->OnRconLine(pLine); + } } else if(Conn == CONN_MAIN && Msg == NETMSG_PING_REPLY) { @@ -1617,6 +1629,11 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) { int InputPredTick = Unpacker.GetInt(); int TimeLeft = Unpacker.GetInt(); + if(Unpacker.Error()) + { + return; + } + int64_t Now = time_get(); // adjust our prediction time @@ -1636,16 +1653,20 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) } else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY) { - int GameTick = Unpacker.GetInt(); - int DeltaTick = GameTick - Unpacker.GetInt(); - // only allow packets from the server we actually want if(net_addr_comp(&pPacket->m_Address, &ServerAddress())) + { return; + } // we are not allowed to process snapshot yet if(State() < IClient::STATE_LOADING) + { return; + } + + int GameTick = Unpacker.GetInt(); + int DeltaTick = GameTick - Unpacker.GetInt(); int NumParts = 1; int Part = 0; @@ -1664,9 +1685,10 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) } const char *pData = (const char *)Unpacker.GetRaw(PartSize); - if(Unpacker.Error() || NumParts < 1 || NumParts > CSnapshot::MAX_PARTS || Part < 0 || Part >= NumParts || PartSize < 0 || PartSize > MAX_SNAPSHOT_PACKSIZE) + { return; + } // Check m_aAckGameTick to see if we already got a snapshot for that tick if(GameTick >= m_aCurrentRecvTick[Conn] && GameTick > m_aAckGameTick[Conn]) @@ -1916,23 +1938,23 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) else if(Conn == CONN_MAIN && Msg == NETMSG_RCONTYPE) { bool UsernameReq = Unpacker.GetInt() & 1; - GameClient()->OnRconType(UsernameReq); + if(!Unpacker.Error()) + { + GameClient()->OnRconType(UsernameReq); + } } } - else + else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0) { - if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0) + // game message + if(!Dummy) { - // game message - if(!Dummy) - { - for(auto &DemoRecorder : m_aDemoRecorder) - if(DemoRecorder.IsRecording()) - DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize); - } - - GameClient()->OnMessage(Msg, &Unpacker, Conn, Dummy); + for(auto &DemoRecorder : m_aDemoRecorder) + if(DemoRecorder.IsRecording()) + DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize); } + + GameClient()->OnMessage(Msg, &Unpacker, Conn, Dummy); } } diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 80cb9672612..f7d6becb578 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1540,7 +1540,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) CUuid *pConnectionID = (CUuid *)Unpacker.GetRaw(sizeof(*pConnectionID)); int DDNetVersion = Unpacker.GetInt(); const char *pDDNetVersionStr = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(Unpacker.Error() || !str_utf8_check(pDDNetVersionStr) || DDNetVersion < 0) + if(Unpacker.Error() || DDNetVersion < 0) { return; } @@ -1557,7 +1557,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && (m_aClients[ClientID].m_State == CClient::STATE_PREAUTH || m_aClients[ClientID].m_State == CClient::STATE_AUTH)) { const char *pVersion = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(!str_utf8_check(pVersion)) + if(Unpacker.Error()) { return; } @@ -1571,7 +1571,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } const char *pPassword = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(!str_utf8_check(pPassword)) + if(Unpacker.Error()) { return; } @@ -1610,6 +1610,10 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } int Chunk = Unpacker.GetInt(); + if(Unpacker.Error()) + { + return; + } if(Chunk != m_aClients[ClientID].m_NextMapChunk || !Config()->m_SvFastDownload) { SendMapData(ClientID, Chunk); @@ -1675,14 +1679,15 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } else if(Msg == NETMSG_INPUT) { - m_aClients[ClientID].m_LastAckedSnapshot = Unpacker.GetInt(); + const int LastAckedSnapshot = Unpacker.GetInt(); int IntendedTick = Unpacker.GetInt(); int Size = Unpacker.GetInt(); - - // check for errors if(Unpacker.Error() || Size / 4 > MAX_INPUT_SIZE || IntendedTick < MIN_TICK || IntendedTick >= MAX_TICK) + { return; + } + m_aClients[ClientID].m_LastAckedSnapshot = LastAckedSnapshot; if(m_aClients[ClientID].m_LastAckedSnapshot > 0) m_aClients[ClientID].m_SnapRate = CClient::SNAPRATE_FULL; @@ -1712,7 +1717,13 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) pInput->m_GameTick = IntendedTick; for(int i = 0; i < Size / 4; i++) + { pInput->m_aData[i] = Unpacker.GetInt(); + } + if(Unpacker.Error()) + { + return; + } GameServer()->OnClientPrepareInput(ClientID, pInput->m_aData); mem_copy(m_aClients[ClientID].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE * sizeof(int)); @@ -1727,11 +1738,11 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) else if(Msg == NETMSG_RCON_CMD) { const char *pCmd = Unpacker.GetString(); - if(!str_utf8_check(pCmd)) + if(Unpacker.Error()) { return; } - if(Unpacker.Error() == 0 && !str_comp(pCmd, "crashmeplx")) + if(!str_comp(pCmd, "crashmeplx")) { int Version = m_aClients[ClientID].m_DDNetVersion; if(GameServer()->PlayerExists(ClientID) && Version < VERSION_DDNET_OLD) @@ -1739,7 +1750,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) m_aClients[ClientID].m_DDNetVersion = VERSION_DDNET_OLD; } } - else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0 && m_aClients[ClientID].m_Authed) + else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_Authed) { if(GameServer()->PlayerExists(ClientID)) { @@ -1762,108 +1773,113 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } else if(Msg == NETMSG_RCON_AUTH) { + if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) == 0) + { + return; + } const char *pName = ""; if(!IsSixup(ClientID)) + { pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); // login name, now used + } const char *pPw = Unpacker.GetString(CUnpacker::SANITIZE_CC); - if(!str_utf8_check(pPw) || !str_utf8_check(pName)) + if(Unpacker.Error()) { return; } - if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0) - { - int AuthLevel = -1; - int KeySlot = -1; + int AuthLevel = -1; + int KeySlot = -1; - if(!pName[0]) - { - if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_ADMIN)), pPw)) - AuthLevel = AUTHED_ADMIN; - else if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_MOD)), pPw)) - AuthLevel = AUTHED_MOD; - else if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_HELPER)), pPw)) - AuthLevel = AUTHED_HELPER; - } - else - { - KeySlot = m_AuthManager.FindKey(pName); - if(m_AuthManager.CheckKey(KeySlot, pPw)) - AuthLevel = m_AuthManager.KeyLevel(KeySlot); - } + if(!pName[0]) + { + if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_ADMIN)), pPw)) + AuthLevel = AUTHED_ADMIN; + else if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_MOD)), pPw)) + AuthLevel = AUTHED_MOD; + else if(m_AuthManager.CheckKey((KeySlot = m_AuthManager.DefaultKey(AUTHED_HELPER)), pPw)) + AuthLevel = AUTHED_HELPER; + } + else + { + KeySlot = m_AuthManager.FindKey(pName); + if(m_AuthManager.CheckKey(KeySlot, pPw)) + AuthLevel = m_AuthManager.KeyLevel(KeySlot); + } - if(AuthLevel != -1) + if(AuthLevel != -1) + { + if(m_aClients[ClientID].m_Authed != AuthLevel) { - if(m_aClients[ClientID].m_Authed != AuthLevel) + if(!IsSixup(ClientID)) { - if(!IsSixup(ClientID)) - { - CMsgPacker Msgp(NETMSG_RCON_AUTH_STATUS, true); - Msgp.AddInt(1); //authed - Msgp.AddInt(1); //cmdlist - SendMsg(&Msgp, MSGFLAG_VITAL, ClientID); - } - else - { - CMsgPacker Msgp(protocol7::NETMSG_RCON_AUTH_ON, true, true); - SendMsg(&Msgp, MSGFLAG_VITAL, ClientID); - } - - m_aClients[ClientID].m_Authed = AuthLevel; // Keeping m_Authed around is unwise... - m_aClients[ClientID].m_AuthKey = KeySlot; - int SendRconCmds = IsSixup(ClientID) ? true : Unpacker.GetInt(); - if(Unpacker.Error() == 0 && SendRconCmds) - // AUTHED_ADMIN - AuthLevel gets the proper IConsole::ACCESS_LEVEL_ - m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(AUTHED_ADMIN - AuthLevel, CFGFLAG_SERVER); + CMsgPacker Msgp(NETMSG_RCON_AUTH_STATUS, true); + Msgp.AddInt(1); //authed + Msgp.AddInt(1); //cmdlist + SendMsg(&Msgp, MSGFLAG_VITAL, ClientID); + } + else + { + CMsgPacker Msgp(protocol7::NETMSG_RCON_AUTH_ON, true, true); + SendMsg(&Msgp, MSGFLAG_VITAL, ClientID); + } - char aBuf[256]; - const char *pIdent = m_AuthManager.KeyIdent(KeySlot); - switch(AuthLevel) - { - case AUTHED_ADMIN: - { - SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted."); - str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (admin)", ClientID, pIdent); - break; - } - case AUTHED_MOD: - { - SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted."); - str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (moderator)", ClientID, pIdent); - break; - } - case AUTHED_HELPER: - { - SendRconLine(ClientID, "Helper authentication successful. Limited remote console access granted."); - str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (helper)", ClientID, pIdent); - break; - } - } - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); + m_aClients[ClientID].m_Authed = AuthLevel; // Keeping m_Authed around is unwise... + m_aClients[ClientID].m_AuthKey = KeySlot; + int SendRconCmds = IsSixup(ClientID) ? true : Unpacker.GetInt(); + if(!Unpacker.Error() && SendRconCmds) + { + // AUTHED_ADMIN - AuthLevel gets the proper IConsole::ACCESS_LEVEL_ + m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(AUTHED_ADMIN - AuthLevel, CFGFLAG_SERVER); + } - // DDRace - GameServer()->OnSetAuthed(ClientID, AuthLevel); + char aBuf[256]; + const char *pIdent = m_AuthManager.KeyIdent(KeySlot); + switch(AuthLevel) + { + case AUTHED_ADMIN: + { + SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted."); + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (admin)", ClientID, pIdent); + break; } - } - else if(Config()->m_SvRconMaxTries) - { - m_aClients[ClientID].m_AuthTries++; - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, Config()->m_SvRconMaxTries); - SendRconLine(ClientID, aBuf); - if(m_aClients[ClientID].m_AuthTries >= Config()->m_SvRconMaxTries) + case AUTHED_MOD: { - if(!Config()->m_SvRconBantime) - m_NetServer.Drop(ClientID, "Too many remote console authentication tries"); - else - m_ServerBan.BanAddr(m_NetServer.ClientAddr(ClientID), Config()->m_SvRconBantime * 60, "Too many remote console authentication tries"); + SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted."); + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (moderator)", ClientID, pIdent); + break; + } + case AUTHED_HELPER: + { + SendRconLine(ClientID, "Helper authentication successful. Limited remote console access granted."); + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed with key=%s (helper)", ClientID, pIdent); + break; + } } + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); + + // DDRace + GameServer()->OnSetAuthed(ClientID, AuthLevel); } - else + } + else if(Config()->m_SvRconMaxTries) + { + m_aClients[ClientID].m_AuthTries++; + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, Config()->m_SvRconMaxTries); + SendRconLine(ClientID, aBuf); + if(m_aClients[ClientID].m_AuthTries >= Config()->m_SvRconMaxTries) { - SendRconLine(ClientID, "Wrong password."); + if(!Config()->m_SvRconBantime) + m_NetServer.Drop(ClientID, "Too many remote console authentication tries"); + else + m_ServerBan.BanAddr(m_NetServer.ClientAddr(ClientID), Config()->m_SvRconBantime * 60, "Too many remote console authentication tries"); } } + else + { + SendRconLine(ClientID, "Wrong password."); + } } else if(Msg == NETMSG_PING) { @@ -1896,11 +1912,10 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } } } - else + else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State >= CClient::STATE_READY) { // game message - if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State >= CClient::STATE_READY) - GameServer()->OnMessage(Msg, &Unpacker, ClientID); + GameServer()->OnMessage(Msg, &Unpacker, ClientID); } } @@ -2491,13 +2506,14 @@ void CServer::PumpNetwork(bool PacketWaiting) Unpacker.Reset((unsigned char *)Packet.m_pData + sizeof(SERVERBROWSE_GETINFO), Packet.m_DataSize - sizeof(SERVERBROWSE_GETINFO)); int SrvBrwsToken = Unpacker.GetInt(); if(Unpacker.Error()) + { continue; + } CPacker Packer; - CNetChunk Response; - GetServerInfoSixup(&Packer, SrvBrwsToken, RateLimitServerInfoConnless()); + CNetChunk Response; Response.m_ClientID = -1; Response.m_Address = Packet.m_Address; Response.m_Flags = NETSENDFLAG_CONNLESS; diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp index 2b183113b16..3593929ea68 100644 --- a/src/engine/shared/packer.cpp +++ b/src/engine/shared/packer.cpp @@ -172,6 +172,12 @@ const char *CUnpacker::GetString(int SanitizeType) } m_pCurrent++; + if(!str_utf8_check(pPtr)) + { + m_Error = true; + return ""; + } + // sanitize all strings if(SanitizeType & SANITIZE) str_sanitize(pPtr); From b738f5f9cea463ce6f842621426cfe63f33ee50e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 3 Dec 2023 20:19:56 +0100 Subject: [PATCH 079/198] Fix votes with timeout over 60 seconds not being shown in client Setting a vote timeout longer than 60 seconds with `sv_vote_time` caused the vote network messages to be discarded with the error message `weird message 'Sv_VoteSet' (15), failed on 'm_Timeout'` by the client, as the protocol did not allow longer vote timeouts. This changes the protocol so the vote timeout can be any positive integer although for now the maximum `sv_vote_time` value is changed back to 60 again to preserve compatibility with old clients. Closes #7583. --- datasrc/network.py | 2 +- src/engine/shared/config_variables.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datasrc/network.py b/datasrc/network.py index 89855665182..7b1b291c61e 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -439,7 +439,7 @@ ]), NetMessage("Sv_VoteSet", [ - NetIntRange("m_Timeout", 0, 60), + NetIntRange("m_Timeout", 0, 'max_int'), NetStringStrict("m_pDescription"), NetStringStrict("m_pReason"), ]), diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 536ed952dfc..36704680a14 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -449,7 +449,7 @@ MACRO_CONFIG_INT(SvEyeEmoteChangeDelay, sv_eye_emote_change_delay, 1, 0, 9999, C MACRO_CONFIG_INT(SvChatDelay, sv_chat_delay, 1, 0, 9999, CFGFLAG_SERVER, "The time in seconds between chat messages") MACRO_CONFIG_INT(SvTeamChangeDelay, sv_team_change_delay, 3, 0, 9999, CFGFLAG_SERVER, "The time in seconds between team changes (spectator/in game)") MACRO_CONFIG_INT(SvInfoChangeDelay, sv_info_change_delay, 5, 0, 9999, CFGFLAG_SERVER, "The time in seconds between info changes (name/skin/color), to avoid ranbow mod set this to a very high time") -MACRO_CONFIG_INT(SvVoteTime, sv_vote_time, 25, 1, 9999, CFGFLAG_SERVER, "The time in seconds a vote lasts") +MACRO_CONFIG_INT(SvVoteTime, sv_vote_time, 25, 1, 60, CFGFLAG_SERVER, "The time in seconds a vote lasts") MACRO_CONFIG_INT(SvVoteMapTimeDelay, sv_vote_map_delay, 0, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between map votes") MACRO_CONFIG_INT(SvVoteDelay, sv_vote_delay, 3, 0, 9999, CFGFLAG_SERVER, "The time in seconds between any vote") MACRO_CONFIG_INT(SvVoteKickDelay, sv_vote_kick_delay, 0, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between kick votes") From 40bfbf2dd0b085b1f2b44a69225aaa6ff3c481b6 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Tue, 5 Dec 2023 17:01:40 -0300 Subject: [PATCH 080/198] Update brazilian_portuguese.txt --- data/languages/brazilian_portuguese.txt | 37 +++++++++++++------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/data/languages/brazilian_portuguese.txt b/data/languages/brazilian_portuguese.txt index 9f0a663caf5..5f34f8d9947 100644 --- a/data/languages/brazilian_portuguese.txt +++ b/data/languages/brazilian_portuguese.txt @@ -29,6 +29,7 @@ # Rafael Fontenelle 2023-07-07 12:11:00 # Rafael Fontenelle 2023-08-14 14:17:00 # Rafael Fontenelle 2023-09-25 14:23:00 +# Rafael Fontenelle 2023-12-05 17:01:00 ##### /authors ##### ##### translated strings ##### @@ -1723,56 +1724,56 @@ Render cut to video == Renderizar corte do vídeo Error playing demo -== +== Erro ao reproduzir demo Some map images could not be loaded. Check the local console for details. -== +== Algumas imagens de mapa não puderam ser carregadas. Verifique o console local para detalhes. Some map sounds could not be loaded. Check the local console for details. -== +== Alguns sons de mapa não puderam ser carregadas. Verifique o console local para detalhes. Loading menu themes -== +== Carregando temas de menu Render complete -== +== Renderização concluída Videos directory -== +== Diretório de vídeos Video was saved to '%s' -== +== O vídeo foi salvo em "%s" No demo selected -== +== Nenhuma demo selecionada Created -== +== Criada Netversion -== +== Netversion [Demo details] map not included -== +== mapa não incluído Ghosts directory -== +== Diretório de fantasmas Activate all -== +== Ativar tudo Deactivate all -== +== Desativar tudo Enable ghost -== +== Habilitar fantasma Only save improvements -== +== Salvar somente melhorias Regular background color -== +== Cor de fundo regular Entities background color -== +== Cor de fundo de entidades From 6b547b1388e0c98d69efcaf1ea91e9b106dcf13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 7 Dec 2023 17:46:55 +0100 Subject: [PATCH 081/198] Fix incorrect map being loaded/downloaded The `LoadMapSearch` function returns an error message or `nullptr` on success but the condition was incorrectly changed in #7580 so the opposite was checked instead. Closes #7597. --- src/engine/client/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 939abf42dea..fdd46fee4eb 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -1402,7 +1402,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) pMapUrl = m_aMapDetailsUrl[0] ? m_aMapDetailsUrl : nullptr; } - if(LoadMapSearch(pMap, pMapSha256, MapCrc) != nullptr) + if(LoadMapSearch(pMap, pMapSha256, MapCrc) == nullptr) { m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); SetLoadingStateDetail(IClient::LOADING_STATE_DETAIL_SENDING_READY); From f8ca16c50e07a0cecda22cf04ad812563b6b8676 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Thu, 7 Dec 2023 17:59:56 +0100 Subject: [PATCH 082/198] Use real ClientID to lookup authed state and score The variable `Id` is translated for old clients and is not guranteed to be the real id. While `m_ClientID` is the real ID. That can also be used to index the CServer::m_aClients[] array to get the authed state. closed #7599 Fixes ``` valgrind ./DDNet-Server "dbg_dummies 1" [..] 2023-12-07 18:01:33 I chat: *** 'Debug dummy 1' entered and joined the game ==75634== Conditional jump or move depends on uninitialised value(s) ==75634== at 0x2DAA83: CVariableInt::Pack(unsigned char*, int, int) (compression.cpp:17) ==75634== by 0x2DAD79: CVariableInt::Compress(void const*, int, void*, int) (compression.cpp:98) ==75634== by 0x23EC50: CServer::DoSnapshot() (server.cpp:1046) ==75634== by 0x247D2D: CServer::Run() (server.cpp:2994) ==75634== by 0x230173: main (main.cpp:193) ==75634== ==75634== Conditional jump or move depends on uninitialised value(s) ==75634== at 0x2DAAF7: CVariableInt::Pack(unsigned char*, int, int) (compression.cpp:25) ==75634== by 0x2DAD79: CVariableInt::Compress(void const*, int, void*, int) (compression.cpp:98) ==75634== by 0x23EC50: CServer::DoSnapshot() (server.cpp:1046) ==75634== by 0x247D2D: CServer::Run() (server.cpp:2994) ==75634== by 0x230173: main (main.cpp:193) ==75634== ==75634== Conditional jump or move depends on uninitialised value(s) ==75634== at 0x2DAA83: CVariableInt::Pack(unsigned char*, int, int) (compression.cpp:17) ==75634== by 0x32E812: CPacker::AddInt(int) (packer.cpp:20) ==75634== by 0x23ED51: CServer::DoSnapshot() (server.cpp:1059) ==75634== by 0x247D2D: CServer::Run() (server.cpp:2994) ==75634== by 0x230173: main (main.cpp:193) ==75634== ==75634== Conditional jump or move depends on uninitialised value(s) ==75634== at 0x2DAAF7: CVariableInt::Pack(unsigned char*, int, int) (compression.cpp:25) ==75634== by 0x32E812: CPacker::AddInt(int) (packer.cpp:20) ==75634== by 0x23ED51: CServer::DoSnapshot() (server.cpp:1059) ==75634== by 0x247D2D: CServer::Run() (server.cpp:2994) ==75634== by 0x230173: main (main.cpp:193) ==75634== ``` Which is using ID 0 as index in the m_aClients array but only ID 63 was ever initialized. --- src/game/server/player.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index de3ae840eb5..a98192c20b6 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -379,7 +379,7 @@ void CPlayer::Snap(int SnappingClient) pPlayerInfo->m_PlayerFlags |= protocol7::PLAYERFLAG_ADMIN; // Times are in milliseconds for 0.7 - pPlayerInfo->m_Score = m_Score.has_value() ? GameServer()->Score()->PlayerData(id)->m_BestTime * 1000 : -1; + pPlayerInfo->m_Score = m_Score.has_value() ? GameServer()->Score()->PlayerData(m_ClientID)->m_BestTime * 1000 : -1; pPlayerInfo->m_Latency = Latency; } @@ -412,7 +412,7 @@ void CPlayer::Snap(int SnappingClient) if(!pDDNetPlayer) return; - pDDNetPlayer->m_AuthLevel = Server()->GetAuthedState(id); + pDDNetPlayer->m_AuthLevel = Server()->GetAuthedState(m_ClientID); pDDNetPlayer->m_Flags = 0; if(m_Afk) pDDNetPlayer->m_Flags |= EXPLAYERFLAG_AFK; From 2dd917f4c0dd286565ba7c263ab47460c80a0c1b Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 6 Dec 2023 12:50:10 +0100 Subject: [PATCH 083/198] Update preview lines instead of initializing them only once --- src/game/client/components/menus_settings.cpp | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index df1b8b1a7f7..4b3370ebf76 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2691,9 +2691,17 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) PREVIEW_SPAMMER, PREVIEW_CLIENT }; - auto &&AddPreviewLine = [](int Index, int ClientID, const char *pName, const char *pText, int Flag, int Repeats) { - s_vLines.emplace_back(); - SPreviewLine *pLine = &s_vLines[s_vLines.size() - 1]; + auto &&SetPreviewLine = [](int Index, int ClientID, const char *pName, const char *pText, int Flag, int Repeats) { + SPreviewLine *pLine; + if((int)s_vLines.size() <= Index) + { + s_vLines.emplace_back(); + pLine = &s_vLines.back(); + } + else + { + pLine = &s_vLines[Index]; + } pLine->m_ClientID = ClientID; pLine->m_Team = Flag & FLAG_TEAM; pLine->m_Friend = Flag & FLAG_FRIEND; @@ -2807,21 +2815,20 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) return vec2{LocalCursor.m_LongestLineWidth + AppendCursor.m_LongestLineWidth, AppendCursor.Height() + RealMsgPaddingY}; }; - // Init lines - if(s_vLines.empty()) + // Set preview lines { char aLineBuilder[128]; str_format(aLineBuilder, sizeof(aLineBuilder), "'%s' entered and joined the game", aBuf); - AddPreviewLine(PREVIEW_SYS, -1, "*** ", aLineBuilder, 0, 0); + SetPreviewLine(PREVIEW_SYS, -1, "*** ", aLineBuilder, 0, 0); str_format(aLineBuilder, sizeof(aLineBuilder), "Hey, how are you %s?", aBuf); - AddPreviewLine(PREVIEW_HIGHLIGHT, 7, "Random Tee", aLineBuilder, FLAG_HIGHLIGHT, 0); + SetPreviewLine(PREVIEW_HIGHLIGHT, 7, "Random Tee", aLineBuilder, FLAG_HIGHLIGHT, 0); - AddPreviewLine(PREVIEW_TEAM, 11, "Your Teammate", "Let's speedrun this!", FLAG_TEAM, 0); - AddPreviewLine(PREVIEW_FRIEND, 8, "Friend", "Hello there", FLAG_FRIEND, 0); - AddPreviewLine(PREVIEW_SPAMMER, 9, "Spammer", "Hey fools, I'm spamming here!", 0, 5); - AddPreviewLine(PREVIEW_CLIENT, -1, "— ", "Echo command executed", FLAG_CLIENT, 0); + SetPreviewLine(PREVIEW_TEAM, 11, "Your Teammate", "Let's speedrun this!", FLAG_TEAM, 0); + SetPreviewLine(PREVIEW_FRIEND, 8, "Friend", "Hello there", FLAG_FRIEND, 0); + SetPreviewLine(PREVIEW_SPAMMER, 9, "Spammer", "Hey fools, I'm spamming here!", 0, 5); + SetPreviewLine(PREVIEW_CLIENT, -1, "— ", "Echo command executed", FLAG_CLIENT, 0); } SetLineSkin(1, GameClient()->m_Skins.FindOrNullptr("pinky")); From a927eee53e8510f4bcf53ec08386fc30ad43455a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 7 Dec 2023 21:24:03 +0100 Subject: [PATCH 084/198] Fix incorrect error code logged in socket warnings On Windows the function `setsockopt` does not set `errno` on errors but instead `WSAGetLastError` must be used. This is encapsulated in the `net_errno` function. --- src/base/system.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/base/system.cpp b/src/base/system.cpp index 4255afa228d..6aac1a1649f 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -1551,14 +1551,17 @@ NETSOCKET net_udp_create(NETADDR bindaddr) /* set broadcast */ if(setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)) != 0) - dbg_msg("socket", "Setting BROADCAST on ipv4 failed: %d", errno); + { + dbg_msg("socket", "Setting BROADCAST on ipv4 failed: %d", net_errno()); + } { /* set DSCP/TOS */ int iptos = 0x10 /* IPTOS_LOWDELAY */; - //int iptos = 46; /* High Priority */ if(setsockopt(socket, IPPROTO_IP, IP_TOS, (char *)&iptos, sizeof(iptos)) != 0) - dbg_msg("socket", "Setting TOS on ipv4 failed: %d", errno); + { + dbg_msg("socket", "Setting TOS on ipv4 failed: %d", net_errno()); + } } } } @@ -1596,14 +1599,17 @@ NETSOCKET net_udp_create(NETADDR bindaddr) /* set broadcast */ if(setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)) != 0) - dbg_msg("socket", "Setting BROADCAST on ipv6 failed: %d", errno); + { + dbg_msg("socket", "Setting BROADCAST on ipv6 failed: %d", net_errno()); + } { /* set DSCP/TOS */ int iptos = 0x10 /* IPTOS_LOWDELAY */; - //int iptos = 46; /* High Priority */ if(setsockopt(socket, IPPROTO_IP, IP_TOS, (char *)&iptos, sizeof(iptos)) != 0) - dbg_msg("socket", "Setting TOS on ipv6 failed: %d", errno); + { + dbg_msg("socket", "Setting TOS on ipv6 failed: %d", net_errno()); + } } } } From 14c95a65f376f6a6d7a7e709c6b515a844c99840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 7 Dec 2023 21:32:17 +0100 Subject: [PATCH 085/198] Avoid setting `IP_TOS` on IPv6 sockets on Windows As this does not work and always causes the error message to be printed. See #7605. --- src/base/system.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/base/system.cpp b/src/base/system.cpp index 6aac1a1649f..16082cca145 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -1603,6 +1603,8 @@ NETSOCKET net_udp_create(NETADDR bindaddr) dbg_msg("socket", "Setting BROADCAST on ipv6 failed: %d", net_errno()); } + // TODO: setting IP_TOS on ipv6 with setsockopt is not supported on Windows, see https://github.com/ddnet/ddnet/issues/7605 +#if !defined(CONF_FAMILY_WINDOWS) { /* set DSCP/TOS */ int iptos = 0x10 /* IPTOS_LOWDELAY */; @@ -1611,6 +1613,7 @@ NETSOCKET net_udp_create(NETADDR bindaddr) dbg_msg("socket", "Setting TOS on ipv6 failed: %d", net_errno()); } } +#endif } } From 30df38f926d20e69a4724afdd73157669616f32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 7 Dec 2023 20:36:30 +0100 Subject: [PATCH 086/198] Add compile time errors for unsupported endianness We support little and big endian but not PDP endian (Middle-endian). Define endianness as string `CONF_ARCH_ENDIAN_STRING` to avoid conditional compilation when printing endianness. --- src/base/detect.h | 8 ++++++++ src/engine/shared/engine.cpp | 9 +-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/base/detect.h b/src/base/detect.h index 90eeeae787a..8bceed08616 100644 --- a/src/base/detect.h +++ b/src/base/detect.h @@ -195,4 +195,12 @@ #define CONF_ARCH_STRING "unknown" #endif +#if defined(CONF_ARCH_ENDIAN_LITTLE) +#define CONF_ARCH_ENDIAN_STRING "little endian" +#elif defined(CONF_ARCH_ENDIAN_BIG) +#define CONF_ARCH_ENDIAN_STRING "big endian" +#else +#error "Unsupported endianness" +#endif + #endif diff --git a/src/engine/shared/engine.cpp b/src/engine/shared/engine.cpp index 259c28523fc..7ab141ebbd7 100644 --- a/src/engine/shared/engine.cpp +++ b/src/engine/shared/engine.cpp @@ -49,15 +49,8 @@ class CEngine : public IEngine str_copy(m_aAppName, pAppname); if(!Test) { - // dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); -#ifdef CONF_ARCH_ENDIAN_LITTLE - dbg_msg("engine", "arch is little endian"); -#elif defined(CONF_ARCH_ENDIAN_BIG) - dbg_msg("engine", "arch is big endian"); -#else - dbg_msg("engine", "unknown endian"); -#endif + dbg_msg("engine", "arch is %s", CONF_ARCH_ENDIAN_STRING); char aVersionStr[128]; if(os_version_str(aVersionStr, sizeof(aVersionStr))) From bac36702964f37dbade73f87fe4a6aa59dd146f5 Mon Sep 17 00:00:00 2001 From: furo Date: Sat, 9 Dec 2023 01:44:00 +0100 Subject: [PATCH 087/198] Don't scale TargetXY when in spectator --- src/game/client/components/controls.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index 81e415d5e3c..f72cce98f70 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -267,8 +267,11 @@ int CControls::SnapInput(int *pData) m_aInputData[g_Config.m_ClDummy].m_Direction = 1; // scale TargetX, TargetY by zoom. - m_aInputData[g_Config.m_ClDummy].m_TargetX *= m_pClient->m_Camera.m_Zoom; - m_aInputData[g_Config.m_ClDummy].m_TargetY *= m_pClient->m_Camera.m_Zoom; + if(!m_pClient->m_Snap.m_SpecInfo.m_Active) + { + m_aInputData[g_Config.m_ClDummy].m_TargetX *= m_pClient->m_Camera.m_Zoom; + m_aInputData[g_Config.m_ClDummy].m_TargetY *= m_pClient->m_Camera.m_Zoom; + } // dummy copy moves if(g_Config.m_ClDummyCopyMoves) From e0b51c2aec8b475e8c2279678b4931d962317024 Mon Sep 17 00:00:00 2001 From: furo Date: Fri, 13 Oct 2023 19:36:00 +0200 Subject: [PATCH 088/198] Add ability to scroll in console. Remove pages. --- src/engine/client/text.cpp | 19 +- src/engine/textrender.h | 5 +- src/game/client/components/chat.cpp | 2 +- src/game/client/components/console.cpp | 311 +++++++++++++++++-------- src/game/client/components/console.h | 11 +- src/game/client/lineinput.cpp | 9 +- src/game/client/lineinput.h | 2 +- src/game/client/ui.cpp | 2 +- 8 files changed, 240 insertions(+), 121 deletions(-) diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index 15a734aa310..a1e6718329c 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -1268,6 +1268,7 @@ class CTextRender : public IEngineTextRender pCursor->m_GlyphCount = 0; pCursor->m_CharCount = 0; pCursor->m_MaxLines = 0; + pCursor->m_LineSpacing = 0; pCursor->m_StartX = x; pCursor->m_StartY = y; @@ -1330,11 +1331,12 @@ class CTextRender : public IEngineTextRender return Cursor.m_LongestLineWidth; } - STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0) override + STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, float LineSpacing = 0.0f, int Flags = 0) override { CTextCursor Cursor; SetCursor(&Cursor, 0, 0, Size, Flags); Cursor.m_LineWidth = LineWidth; + Cursor.m_LineSpacing = LineSpacing; TextEx(&Cursor, pText, StrLength); return Cursor.BoundingBox(); } @@ -1477,6 +1479,7 @@ class CTextRender : public IEngineTextRender const float CursorY = round_to_int(pCursor->m_Y * FakeToScreen.y) / FakeToScreen.y; const int ActualSize = round_truncate(pCursor->m_FontSize * FakeToScreen.y); pCursor->m_AlignedFontSize = ActualSize / FakeToScreen.y; + const float LineSpacing = pCursor->m_LineSpacing; // string length if(Length < 0) @@ -1534,10 +1537,10 @@ class CTextRender : public IEngineTextRender const auto &&CheckInsideChar = [&](bool CheckOuter, vec2 CursorPos, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool { return (LastCharX - LastCharWidth / 2 <= CursorPos.x && CharX + CharWidth / 2 > CursorPos.x && - CharY - pCursor->m_AlignedFontSize <= CursorPos.y && - CharY > CursorPos.y) || + CharY - pCursor->m_AlignedFontSize - LineSpacing <= CursorPos.y && + CharY + LineSpacing > CursorPos.y) || (CheckOuter && - CharY - pCursor->m_AlignedFontSize > CursorPos.y); + CharY - pCursor->m_AlignedFontSize + LineSpacing > CursorPos.y); }; const auto &&CheckSelectionStart = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) { if(!SelectionStarted && !SelectionUsedCase) @@ -1552,10 +1555,10 @@ class CTextRender : public IEngineTextRender }; const auto &&CheckOutsideChar = [&](bool CheckOuter, vec2 CursorPos, float CharX, float CharWidth, float CharY) -> bool { return (CharX + CharWidth / 2 > CursorPos.x && - CharY - pCursor->m_AlignedFontSize <= CursorPos.y && - CharY > CursorPos.y) || + CharY - pCursor->m_AlignedFontSize - LineSpacing <= CursorPos.y && + CharY + LineSpacing > CursorPos.y) || (CheckOuter && - CharY <= CursorPos.y); + CharY - LineSpacing <= CursorPos.y); }; const auto &&CheckSelectionEnd = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) { if(SelectionStarted && !SelectionUsedCase) @@ -1580,7 +1583,7 @@ class CTextRender : public IEngineTextRender return false; DrawX = pCursor->m_StartX; - DrawY += pCursor->m_AlignedFontSize; + DrawY += pCursor->m_AlignedFontSize + pCursor->m_LineSpacing; if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { DrawX = round_to_int(DrawX * FakeToScreen.x) / FakeToScreen.x; // realign diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 9862d7a88b6..e6f0fbddfc8 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -197,6 +197,7 @@ class CTextCursor float m_FontSize; float m_AlignedFontSize; + float m_LineSpacing; ETextCursorSelectionMode m_CalculateSelectionMode; float m_SelectionHeightFactor; @@ -219,7 +220,7 @@ class CTextCursor float Height() const { - return m_LineCount * m_AlignedFontSize; + return m_LineCount * m_AlignedFontSize + std::max(0, m_LineCount - 1) * m_LineSpacing; } STextBoundingBox BoundingBox() const @@ -303,7 +304,7 @@ class ITextRender : public IInterface virtual void TextSelectionColor(ColorRGBA rgb) = 0; virtual void Text(float x, float y, float Size, const char *pText, float LineWidth = -1.0f) = 0; virtual float TextWidth(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0, const STextSizeProperties &TextSizeProps = {}) = 0; - virtual STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0) = 0; + virtual STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, float LineSpacing = 0.0f, int Flags = 0) = 0; virtual ColorRGBA GetTextColor() const = 0; virtual ColorRGBA GetTextOutlineColor() const = 0; diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 9aec741f99d..9e62ee139fb 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -1175,7 +1175,7 @@ void CChat::OnRender() m_Input.Activate(EInputPriority::CHAT); // Ensure that the input is active const CUIRect InputCursorRect = {Cursor.m_X, Cursor.m_Y - ScrollOffset, 0.0f, 0.0f}; - const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, m_Input.WasChanged(), MessageMaxWidth); + const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, m_Input.WasChanged(), MessageMaxWidth, 0.0f); Graphics()->ClipDisable(); diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 7c2ae047078..060c18c4ba3 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -26,6 +26,9 @@ #include "console.h" +static constexpr float FONT_SIZE = 10.0f; +static constexpr float LINE_SPACING = 1.0f; + class CConsoleLogger : public ILogger { CGameConsole *m_pConsole; @@ -142,31 +145,42 @@ void CGameConsole::CInstance::ClearBacklog() } m_Backlog.Init(); - m_BacklogCurPage = 0; + m_BacklogCurLine = 0; } -void CGameConsole::CInstance::ClearBacklogYOffsets() +void CGameConsole::CInstance::UpdateBacklogTextAttributes() { - // Pending backlog entries are not handled because they don't have a Y offset yet. + // Pending backlog entries are not handled because they don't have text attributes yet. for(CInstance::CBacklogEntry *pEntry = m_Backlog.First(); pEntry; pEntry = m_Backlog.Next(pEntry)) { - pEntry->m_YOffset = -1.0f; + UpdateEntryTextAttributes(pEntry); } } void CGameConsole::CInstance::PumpBacklogPending() { - // We must ensure that no log messages are printed while owning - // m_BacklogPendingLock or this will result in a dead lock. - const CLockScope LockScopePending(m_BacklogPendingLock); - for(CInstance::CBacklogEntry *pPendingEntry = m_BacklogPending.First(); pPendingEntry; pPendingEntry = m_BacklogPending.Next(pPendingEntry)) + std::vector vpEntries; { - const size_t EntrySize = sizeof(CBacklogEntry) + pPendingEntry->m_Length; - CBacklogEntry *pEntry = m_Backlog.Allocate(EntrySize); - mem_copy(pEntry, pPendingEntry, EntrySize); - ++m_NewLineCounter; + // We must ensure that no log messages are printed while owning + // m_BacklogPendingLock or this will result in a dead lock. + const CLockScope LockScopePending(m_BacklogPendingLock); + for(CInstance::CBacklogEntry *pPendingEntry = m_BacklogPending.First(); pPendingEntry; pPendingEntry = m_BacklogPending.Next(pPendingEntry)) + { + const size_t EntrySize = sizeof(CBacklogEntry) + pPendingEntry->m_Length; + CBacklogEntry *pEntry = m_Backlog.Allocate(EntrySize); + mem_copy(pEntry, pPendingEntry, EntrySize); + vpEntries.push_back(pEntry); + } + + m_BacklogPending.Init(); + } + + m_pGameConsole->UI()->MapScreen(); + for(CInstance::CBacklogEntry *pEntry : vpEntries) + { + UpdateEntryTextAttributes(pEntry); + m_NewLineCounter += pEntry->m_LineCount; } - m_BacklogPending.Init(); } void CGameConsole::CInstance::ClearHistory() @@ -343,26 +357,41 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) } else if(Event.m_Key == KEY_PAGEUP) { - ++m_BacklogCurPage; + m_BacklogCurLine += GetLinesToScroll(-1, m_LinesRendered); m_HasSelection = false; } else if(Event.m_Key == KEY_PAGEDOWN) { m_HasSelection = false; - --m_BacklogCurPage; - if(m_BacklogCurPage < 0) - m_BacklogCurPage = 0; + m_BacklogCurLine -= GetLinesToScroll(1, m_LinesRendered); + + if(m_BacklogCurLine < 0) + m_BacklogCurLine = 0; + } + else if(Event.m_Key == KEY_MOUSE_WHEEL_UP) + { + m_BacklogCurLine += GetLinesToScroll(-1, 1); + m_HasSelection = false; + } + else if(Event.m_Key == KEY_MOUSE_WHEEL_DOWN) + { + m_HasSelection = false; + --m_BacklogCurLine; + if(m_BacklogCurLine < 0) + m_BacklogCurLine = 0; } // in order not to conflict with CLineInput's handling of Home/End only // react to it when the input is empty else if(Event.m_Key == KEY_HOME && m_Input.IsEmpty()) { - m_BacklogCurPage = INT_MAX; + int Lines = GetLinesToScroll(-1, -1); + m_BacklogCurLine += Lines; + m_BacklogLastActiveLine = m_BacklogCurLine; m_HasSelection = false; } else if(Event.m_Key == KEY_END && m_Input.IsEmpty()) { - m_BacklogCurPage = 0; + m_BacklogCurLine = 0; m_HasSelection = false; } } @@ -428,9 +457,47 @@ void CGameConsole::CInstance::PrintLine(const char *pLine, int Len, ColorRGBA Pr pEntry->m_YOffset = -1.0f; pEntry->m_PrintColor = PrintColor; pEntry->m_Length = Len; + pEntry->m_LineCount = -1; str_copy(pEntry->m_aText, pLine, Len + 1); } +int CGameConsole::CInstance::GetLinesToScroll(int Direction, int LinesToScroll) +{ + auto *pEntry = m_Backlog.Last(); + int Line = 0; + int LinesToSkip = (Direction == -1 ? m_BacklogCurLine + m_LinesRendered : m_BacklogCurLine - 1); + while(Line < LinesToSkip && pEntry) + { + if(pEntry->m_LineCount == -1) + UpdateEntryTextAttributes(pEntry); + Line += pEntry->m_LineCount; + pEntry = m_Backlog.Prev(pEntry); + } + + int Amount = maximum(0, Line - LinesToSkip); + while(pEntry && (LinesToScroll > 0 ? Amount < LinesToScroll : true)) + { + if(pEntry->m_LineCount == -1) + UpdateEntryTextAttributes(pEntry); + Amount += pEntry->m_LineCount; + pEntry = Direction == -1 ? m_Backlog.Prev(pEntry) : m_Backlog.Next(pEntry); + } + + return LinesToScroll > 0 ? minimum(Amount, LinesToScroll) : Amount; +} + +void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry) +{ + CTextCursor Cursor; + m_pGameConsole->TextRender()->SetCursor(&Cursor, 0.0f, 0.0f, FONT_SIZE, 0); + Cursor.m_LineWidth = m_pGameConsole->UI()->Screen()->w - 10; + Cursor.m_MaxLines = 10; + Cursor.m_LineSpacing = LINE_SPACING; + m_pGameConsole->TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); + pEntry->m_YOffset = Cursor.Height() + LINE_SPACING; + pEntry->m_LineCount = Cursor.m_LineCount; +} + CGameConsole::CGameConsole() : m_LocalConsole(CONSOLETYPE_LOCAL), m_RemoteConsole(CONSOLETYPE_REMOTE) { @@ -539,7 +606,7 @@ void CGameConsole::OnRender() { m_ConsoleState = CONSOLE_CLOSED; pConsole->m_Input.Deactivate(); - pConsole->m_BacklogLastActivePage = -1; + pConsole->m_BacklogLastActiveLine = -1; } else if(m_ConsoleState == CONSOLE_OPENING) { @@ -620,8 +687,10 @@ void CGameConsole::OnRender() ConsoleHeight -= 22.0f; { - float FontSize = 10.0f; - float RowHeight = FontSize * 1.25f; + // Get height of 1 line + float LineHeight = TextRender()->TextBoundingBox(FONT_SIZE, " ", -1, -1, LINE_SPACING).m_H + LINE_SPACING; + + float RowHeight = FONT_SIZE * 1.25f; float x = 3; float y = ConsoleHeight - RowHeight - 5.0f; @@ -630,7 +699,7 @@ void CGameConsole::OnRender() // render prompt CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, x, y, FontSize, TEXTFLAG_RENDER); + TextRender()->SetCursor(&Cursor, x, y, FONT_SIZE, TEXTFLAG_RENDER); const char *pPrompt = "> "; if(m_ConsoleType == CONSOLETYPE_REMOTE) { @@ -697,16 +766,17 @@ void CGameConsole::OnRender() // render console input (wrap line) pConsole->m_Input.SetHidden(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->State() == IClient::STATE_ONLINE && !Client()->RconAuthed() && (pConsole->m_UserGot || !pConsole->m_UsernameReq)); pConsole->m_Input.Activate(EInputPriority::CONSOLE); // Ensure that the input is active - const CUIRect InputCursorRect = {x, y + FontSize, 0.0f, 0.0f}; - pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FontSize, TEXTALIGN_BL, pConsole->m_Input.WasChanged(), Screen.w - 10.0f - x); + const CUIRect InputCursorRect = {x, y + FONT_SIZE, 0.0f, 0.0f}; + pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FONT_SIZE, TEXTALIGN_BL, pConsole->m_Input.WasChanged(), Screen.w - 10.0f - x, LINE_SPACING); if(pConsole->m_LastInputHeight == 0.0f && pConsole->m_BoundingBox.m_H != 0.0f) pConsole->m_LastInputHeight = pConsole->m_BoundingBox.m_H; if(pConsole->m_Input.HasSelection()) pConsole->m_HasSelection = false; // Clear console selection if we have a line input selection - y -= pConsole->m_BoundingBox.m_H - FontSize; - TextRender()->SetCursor(&Cursor, x, y, FontSize, TEXTFLAG_RENDER); + y -= pConsole->m_BoundingBox.m_H - FONT_SIZE; + TextRender()->SetCursor(&Cursor, x, y, FONT_SIZE, TEXTFLAG_RENDER); Cursor.m_LineWidth = Screen.w - 10.0f - x; + Cursor.m_LineSpacing = LINE_SPACING; if(pConsole->m_LastInputHeight != pConsole->m_BoundingBox.m_H) { @@ -726,7 +796,7 @@ void CGameConsole::OnRender() Info.m_Width = Screen.w; Info.m_TotalWidth = 0.0f; Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer; - TextRender()->SetCursor(&Info.m_Cursor, InitialX - Info.m_Offset, InitialY + RowHeight + 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); + TextRender()->SetCursor(&Info.m_Cursor, InitialX - Info.m_Offset, InitialY + RowHeight + 2.0f, FONT_SIZE, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); Info.m_Cursor.m_LineWidth = std::numeric_limits::max(); const int NumCommands = m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info); pConsole->m_CompletionRenderOffset = Info.m_Offset; @@ -764,86 +834,122 @@ void CGameConsole::OnRender() pConsole->PumpBacklogPending(); - // render log (current page, wrap lines) + // render console log (current entry, status, wrap lines) CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.Last(); float OffsetY = 0.0f; - float LineOffset = 1.0f; std::string SelectionString; - if(pConsole->m_BacklogLastActivePage < 0) - pConsole->m_BacklogLastActivePage = pConsole->m_BacklogCurPage; - int TotalPages = 1; - for(int Page = 0; Page <= maximum(pConsole->m_BacklogLastActivePage, pConsole->m_BacklogCurPage); ++Page, OffsetY = 0.0f) + if(pConsole->m_BacklogLastActiveLine < 0) + pConsole->m_BacklogLastActiveLine = pConsole->m_BacklogCurLine; + + int LineNum = -1; + pConsole->m_LinesRendered = 0; + + int SkippedLines = 0; + bool First = true; + + const float XScale = Graphics()->ScreenWidth() / Screen.w; + const float YScale = Graphics()->ScreenHeight() / Screen.h; + float CalcOffsetY = 0; + while(y - (CalcOffsetY + LineHeight) > RowHeight) + CalcOffsetY += LineHeight; + float ClipStartY = y - CalcOffsetY; + Graphics()->ClipEnable(0, ClipStartY * YScale, Screen.w * XScale, y * YScale - ClipStartY * YScale); + + while(pEntry) { - while(pEntry) + if(pEntry->m_LineCount == -1) + pConsole->UpdateEntryTextAttributes(pEntry); + + LineNum += pEntry->m_LineCount; + while(pConsole->m_NewLineCounter > 0) { - TextRender()->TextColor(pEntry->m_PrintColor); + --pConsole->m_NewLineCounter; - // get y offset (calculate it if we haven't yet) - if(pEntry->m_YOffset < 0.0f) + // keep scroll position when new entries are printed. + if(pConsole->m_BacklogCurLine != 0) { - TextRender()->SetCursor(&Cursor, 0.0f, 0.0f, FontSize, 0); - Cursor.m_LineWidth = Screen.w - 10; - Cursor.m_MaxLines = 10; - TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); - pEntry->m_YOffset = Cursor.Height() + LineOffset; + pConsole->m_BacklogCurLine++; + pConsole->m_BacklogLastActiveLine++; } - OffsetY += pEntry->m_YOffset; + } + if(LineNum < pConsole->m_BacklogLastActiveLine) + { + SkippedLines += pEntry->m_LineCount; + pEntry = pConsole->m_Backlog.Prev(pEntry); + continue; + } + TextRender()->TextColor(pEntry->m_PrintColor); - if((pConsole->m_HasSelection || pConsole->m_MouseIsPress) && pConsole->m_NewLineCounter > 0) - { - float MouseExtraOff = pEntry->m_YOffset; - pConsole->m_MousePress.y -= MouseExtraOff; - if(!pConsole->m_MouseIsPress) - pConsole->m_MouseRelease.y -= MouseExtraOff; - } + if(First) + { + int Diff = pConsole->m_BacklogLastActiveLine - SkippedLines; + OffsetY -= Diff * LineHeight - LINE_SPACING; + } + + float LocalOffsetY = OffsetY + pEntry->m_YOffset / (float)pEntry->m_LineCount; + OffsetY += pEntry->m_YOffset; + + if((pConsole->m_HasSelection || pConsole->m_MouseIsPress) && pConsole->m_NewLineCounter > 0) + { + float MouseExtraOff = pEntry->m_YOffset; + pConsole->m_MousePress.y -= MouseExtraOff; + if(!pConsole->m_MouseIsPress) + pConsole->m_MouseRelease.y -= MouseExtraOff; + } - // next page when lines reach the top - if(y - OffsetY <= RowHeight) - break; + // stop rendering when lines reach the top + bool Outside = y - OffsetY <= RowHeight; + int CanRenderOneLine = y - LocalOffsetY > RowHeight; + if(Outside && !CanRenderOneLine) + break; + + int LinesNotRendered = pEntry->m_LineCount - minimum((int)std::floor((y - LocalOffsetY) / RowHeight), pEntry->m_LineCount); + pConsole->m_LinesRendered -= LinesNotRendered; + + TextRender()->SetCursor(&Cursor, 0.0f, y - OffsetY, FONT_SIZE, TEXTFLAG_RENDER); + Cursor.m_LineWidth = Screen.w - 10.0f; + Cursor.m_MaxLines = pEntry->m_LineCount; + Cursor.m_LineSpacing = LINE_SPACING; + Cursor.m_CalculateSelectionMode = (m_ConsoleState == CONSOLE_OPEN && pConsole->m_MousePress.y < pConsole->m_BoundingBox.m_Y && (pConsole->m_MouseIsPress || (pConsole->m_CurSelStart != pConsole->m_CurSelEnd) || pConsole->m_HasSelection)) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; + Cursor.m_PressMouse = pConsole->m_MousePress; + Cursor.m_ReleaseMouse = pConsole->m_MouseRelease; + TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); + if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) + { + pConsole->m_CurSelStart = minimum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd); + pConsole->m_CurSelEnd = maximum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd); + } + pConsole->m_LinesRendered += First ? pEntry->m_LineCount - (pConsole->m_BacklogLastActiveLine - SkippedLines) : pEntry->m_LineCount; - // just render output from current backlog page (render bottom up) - if(Page == pConsole->m_BacklogLastActivePage) + if(pConsole->m_CurSelStart != pConsole->m_CurSelEnd) + { + if(m_WantsSelectionCopy) { - TextRender()->SetCursor(&Cursor, 0.0f, y - OffsetY, FontSize, TEXTFLAG_RENDER); - Cursor.m_LineWidth = Screen.w - 10.0f; - Cursor.m_MaxLines = 10; - Cursor.m_CalculateSelectionMode = (m_ConsoleState == CONSOLE_OPEN && pConsole->m_MousePress.y < pConsole->m_BoundingBox.m_Y && (pConsole->m_MouseIsPress || (pConsole->m_CurSelStart != pConsole->m_CurSelEnd) || pConsole->m_HasSelection)) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; - Cursor.m_PressMouse = pConsole->m_MousePress; - Cursor.m_ReleaseMouse = pConsole->m_MouseRelease; - TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); - if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) - { - pConsole->m_CurSelStart = minimum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd); - pConsole->m_CurSelEnd = maximum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd); - } - if(pConsole->m_CurSelStart != pConsole->m_CurSelEnd) - { - if(m_WantsSelectionCopy) - { - const bool HasNewLine = !SelectionString.empty(); - const size_t OffUTF8Start = str_utf8_offset_chars_to_bytes(pEntry->m_aText, pConsole->m_CurSelStart); - const size_t OffUTF8End = str_utf8_offset_chars_to_bytes(pEntry->m_aText, pConsole->m_CurSelEnd); - SelectionString.insert(0, (std::string(&pEntry->m_aText[OffUTF8Start], OffUTF8End - OffUTF8Start) + (HasNewLine ? "\n" : ""))); - } - pConsole->m_HasSelection = true; - } + const bool HasNewLine = !SelectionString.empty(); + const size_t OffUTF8Start = str_utf8_offset_chars_to_bytes(pEntry->m_aText, pConsole->m_CurSelStart); + const size_t OffUTF8End = str_utf8_offset_chars_to_bytes(pEntry->m_aText, pConsole->m_CurSelEnd); + SelectionString.insert(0, (std::string(&pEntry->m_aText[OffUTF8Start], OffUTF8End - OffUTF8Start) + (HasNewLine ? "\n" : ""))); } - pEntry = pConsole->m_Backlog.Prev(pEntry); - - // reset color - TextRender()->TextColor(1, 1, 1, 1); - if(pConsole->m_NewLineCounter > 0) - --pConsole->m_NewLineCounter; + pConsole->m_HasSelection = true; } + pEntry = pConsole->m_Backlog.Prev(pEntry); + + // reset color + TextRender()->TextColor(TextRender()->DefaultTextColor()); + First = false; + if(!pEntry) break; - TotalPages++; } - pConsole->m_BacklogCurPage = clamp(pConsole->m_BacklogCurPage, 0, TotalPages - 1); - pConsole->m_BacklogLastActivePage = pConsole->m_BacklogCurPage; + + Graphics()->ClipDisable(); + + if(LineNum >= 0) + pConsole->m_BacklogCurLine = clamp(pConsole->m_BacklogCurLine, 0, LineNum); + pConsole->m_BacklogLastActiveLine = pConsole->m_BacklogCurLine; if(m_WantsSelectionCopy && !SelectionString.empty()) { @@ -854,16 +960,17 @@ void CGameConsole::OnRender() m_WantsSelectionCopy = false; } - // render page + TextRender()->TextColor(TextRender()->DefaultTextColor()); + + // render current lines and status (locked, following) char aBuf[128]; - TextRender()->TextColor(1, 1, 1, 1); - str_format(aBuf, sizeof(aBuf), Localize("-Page %d-"), pConsole->m_BacklogCurPage + 1); - TextRender()->Text(10.0f, FontSize / 2.f, FontSize, aBuf, -1.0f); + str_format(aBuf, sizeof(aBuf), Localize("Lines %d - %d (%s)"), pConsole->m_BacklogCurLine + 1, pConsole->m_BacklogCurLine + pConsole->m_LinesRendered, pConsole->m_BacklogCurLine != 0 ? Localize("Locked") : Localize("Following")); + TextRender()->Text(10.0f, FONT_SIZE / 2.f, FONT_SIZE, aBuf); // render version str_copy(aBuf, "v" GAME_VERSION " on " CONF_PLATFORM_STRING " " CONF_ARCH_STRING); - float Width = TextRender()->TextWidth(FontSize, aBuf, -1, -1.0f); - TextRender()->Text(Screen.w - Width - 10.0f, FontSize / 2.f, FontSize, aBuf, -1.0f); + float Width = TextRender()->TextWidth(FONT_SIZE, aBuf, -1, -1.0f); + TextRender()->Text(Screen.w - Width - 10.0f, FONT_SIZE / 2.f, FONT_SIZE, aBuf); } } @@ -985,15 +1092,15 @@ void CGameConsole::ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserD void CGameConsole::ConConsolePageUp(IConsole::IResult *pResult, void *pUserData) { CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole(); - pConsole->m_BacklogCurPage++; + pConsole->m_BacklogCurLine += pConsole->GetLinesToScroll(-1, pConsole->m_LinesRendered); } void CGameConsole::ConConsolePageDown(IConsole::IResult *pResult, void *pUserData) { CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole(); - --pConsole->m_BacklogCurPage; - if(pConsole->m_BacklogCurPage < 0) - pConsole->m_BacklogCurPage = 0; + pConsole->m_BacklogCurLine -= pConsole->GetLinesToScroll(1, pConsole->m_LinesRendered); + if(pConsole->m_BacklogCurLine < 0) + pConsole->m_BacklogCurLine = 0; } void CGameConsole::ConchainConsoleOutputLevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) @@ -1018,9 +1125,9 @@ void CGameConsole::RequireUsername(bool UsernameReq) void CGameConsole::PrintLine(int Type, const char *pLine) { if(Type == CONSOLETYPE_LOCAL) - m_LocalConsole.PrintLine(pLine, str_length(pLine), ColorRGBA{1, 1, 1, 1}); + m_LocalConsole.PrintLine(pLine, str_length(pLine), TextRender()->DefaultTextColor()); else if(Type == CONSOLETYPE_REMOTE) - m_RemoteConsole.PrintLine(pLine, str_length(pLine), ColorRGBA{1, 1, 1, 1}); + m_RemoteConsole.PrintLine(pLine, str_length(pLine), TextRender()->DefaultTextColor()); } void CGameConsole::OnConsoleInit() @@ -1048,9 +1155,9 @@ void CGameConsole::OnInit() Engine()->SetAdditionalLogger(std::unique_ptr(m_pConsoleLogger)); // add resize event Graphics()->AddWindowResizeListener([this]() { - m_LocalConsole.ClearBacklogYOffsets(); + m_LocalConsole.UpdateBacklogTextAttributes(); m_LocalConsole.m_HasSelection = false; - m_RemoteConsole.ClearBacklogYOffsets(); + m_RemoteConsole.UpdateBacklogTextAttributes(); m_RemoteConsole.m_HasSelection = false; }); } diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index cadaefd95aa..0a361a8af34 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -30,6 +30,7 @@ class CGameConsole : public CComponent struct CBacklogEntry { float m_YOffset; + int m_LineCount; ColorRGBA m_PrintColor; size_t m_Length; char m_aText[1]; @@ -43,8 +44,9 @@ class CGameConsole : public CComponent CLineInputBuffered m_Input; const char *m_pName; int m_Type; - int m_BacklogCurPage; - int m_BacklogLastActivePage = -1; + int m_BacklogCurLine; + int m_BacklogLastActiveLine = -1; + int m_LinesRendered; STextBoundingBox m_BoundingBox = {0.0f, 0.0f, 0.0f, 0.0f}; float m_LastInputHeight = 0.0f; @@ -80,7 +82,7 @@ class CGameConsole : public CComponent void Init(CGameConsole *pGameConsole); void ClearBacklog() REQUIRES(!m_BacklogPendingLock); - void ClearBacklogYOffsets(); + void UpdateBacklogTextAttributes(); void PumpBacklogPending() REQUIRES(!m_BacklogPendingLock); void ClearHistory(); void Reset(); @@ -89,10 +91,13 @@ class CGameConsole : public CComponent bool OnInput(const IInput::CEvent &Event); void PrintLine(const char *pLine, int Len, ColorRGBA PrintColor) REQUIRES(!m_BacklogPendingLock); + int GetLinesToScroll(int Direction, int LinesToScroll); const char *GetString() const { return m_Input.GetString(); } static void PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser); static void PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser); + + void UpdateEntryTextAttributes(CBacklogEntry *pEntry); }; class IConsole *m_pConsole; diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index e6cbf781068..b894946fa97 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -388,7 +388,7 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event) return m_WasChanged || KeyHandled; } -STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth) +STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing) { // update derived attributes to handle external changes to the buffer UpdateStrData(); @@ -422,12 +422,13 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al pDisplayStr = DisplayStrBuffer.c_str(); } - const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth); + const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth, LineSpacing); const vec2 CursorPos = CUI::CalcAlignedCursorPos(pRect, BoundingBox.Size(), Align); TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = LineWidth; Cursor.m_ForceCursorRendering = Changed; + Cursor.m_LineSpacing = LineSpacing; Cursor.m_PressMouse.x = m_MouseSelection.m_PressMouse.x; Cursor.m_ReleaseMouse.x = m_MouseSelection.m_ReleaseMouse.x; if(LineWidth < 0.0f) @@ -497,6 +498,7 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al CTextCursor CaretCursor; TextRender()->SetCursor(&CaretCursor, CursorPos.x, CursorPos.y, FontSize, 0); CaretCursor.m_LineWidth = LineWidth; + CaretCursor.m_LineSpacing = LineSpacing; CaretCursor.m_CursorMode = TEXT_CURSOR_CURSOR_MODE_SET; CaretCursor.m_CursorCharacter = str_utf8_offset_bytes_to_chars(pDisplayStr, DisplayCursorOffset); TextRender()->TextEx(&CaretCursor, pDisplayStr); @@ -504,10 +506,11 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al } else { - const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth); + const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth, LineSpacing); const vec2 CursorPos = CUI::CalcAlignedCursorPos(pRect, BoundingBox.Size(), Align); TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = LineWidth; + Cursor.m_LineSpacing = LineSpacing; TextRender()->TextEx(&Cursor, pDisplayStr); } diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h index 54f196e6121..6b51155a116 100644 --- a/src/game/client/lineinput.h +++ b/src/game/client/lineinput.h @@ -180,7 +180,7 @@ class CLineInput return Changed; } - STextBoundingBox Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth); + STextBoundingBox Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing); SMouseSelection *GetMouseSelection() { return &m_MouseSelection; } const void *GetClearButtonId() const { return &m_ClearButtonId; } diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index ae47c3b674c..506d6e8029c 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -824,7 +824,7 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize pRect->Draw(ms_LightButtonColorFunction.GetColor(Active, HotItem() == pLineInput), Corners, 3.0f); ClipEnable(pRect); Textbox.x -= ScrollOffset; - const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed, -1.0f); + const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed, -1.0f, 0.0f); ClipDisable(); // Scroll left or right if necessary From efc451324de0bf3af7dc538ff61c538145021d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 12 Nov 2023 23:45:19 +0100 Subject: [PATCH 089/198] Separate snapshot storage holder and snap data allocations Previously, only one buffer was allocated to store each snapshot holder structure, the snapshot data and alternative snapshot data. This prevents tools like ASAN from detecting some invalid accesses, e.g. to the snapshot holder structure by underflowing the snapshot data pointer. Now three separate allocations are used instead. This hopefully helps debugging #2677. --- src/engine/shared/snapshot.cpp | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index ca7d4900a53..aedd8e373f7 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -464,17 +464,14 @@ void CSnapshotStorage::Init() void CSnapshotStorage::PurgeAll() { - CHolder *pHolder = m_pFirst; - - while(pHolder) + while(m_pFirst) { - CHolder *pNext = pHolder->m_pNext; - free(pHolder); - pHolder = pNext; + CHolder *pNext = m_pFirst->m_pNext; + free(m_pFirst->m_pSnap); + free(m_pFirst->m_pAltSnap); + free(m_pFirst); + m_pFirst = pNext; } - - // no more snapshots in storage - m_pFirst = nullptr; m_pLast = nullptr; } @@ -487,6 +484,8 @@ void CSnapshotStorage::PurgeUntil(int Tick) CHolder *pNext = pHolder->m_pNext; if(pHolder->m_Tick >= Tick) return; // no more to remove + free(pHolder->m_pSnap); + free(pHolder->m_pAltSnap); free(pHolder); // did we come to the end of the list? @@ -495,7 +494,6 @@ void CSnapshotStorage::PurgeUntil(int Tick) m_pFirst = pNext; pNext->m_pPrev = nullptr; - pHolder = pNext; } @@ -509,20 +507,17 @@ void CSnapshotStorage::Add(int Tick, int64_t Tagtime, size_t DataSize, const voi dbg_assert(DataSize <= (size_t)CSnapshot::MAX_SIZE, "Snapshot data size invalid"); dbg_assert(AltDataSize <= (size_t)CSnapshot::MAX_SIZE, "Alt snapshot data size invalid"); - // allocate memory for holder + snapshot data - const size_t TotalSize = sizeof(CHolder) + DataSize + AltDataSize; - CHolder *pHolder = (CHolder *)malloc(TotalSize); - - // set data + CHolder *pHolder = static_cast(malloc(sizeof(CHolder))); pHolder->m_Tick = Tick; pHolder->m_Tagtime = Tagtime; - pHolder->m_SnapSize = DataSize; - pHolder->m_pSnap = (CSnapshot *)(pHolder + 1); + + pHolder->m_pSnap = static_cast(malloc(DataSize)); mem_copy(pHolder->m_pSnap, pData, DataSize); + pHolder->m_SnapSize = DataSize; if(AltDataSize) // create alternative if wanted { - pHolder->m_pAltSnap = (CSnapshot *)(((char *)pHolder->m_pSnap) + DataSize); + pHolder->m_pAltSnap = static_cast(malloc(AltDataSize)); mem_copy(pHolder->m_pAltSnap, pAltData, AltDataSize); pHolder->m_AltSnapSize = AltDataSize; } From 140fcea667ffb4a27e3a68c7d5c6c0401bcee3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 10 Dec 2023 11:23:17 +0100 Subject: [PATCH 090/198] Avoid using console to reset game settings Prevent potential dead lock when using `/map` chat command in combination with Teehistorian. Closes #7619. I could not reproduce the issue on Windows though. This also means resetting game settings should actually be efficient now, because it only involves iterating over all game settings and directly resetting their value. --- src/engine/shared/config.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp index b262c493a42..f87100c6c9b 100644 --- a/src/engine/shared/config.cpp +++ b/src/engine/shared/config.cpp @@ -156,7 +156,7 @@ struct SIntConfigVariable : public SConfigVariable void ResetToOld() override { - SetValue(m_OldValue); + *m_pVariable = m_OldValue; } }; @@ -257,7 +257,7 @@ struct SColorConfigVariable : public SConfigVariable void ResetToOld() override { - SetValue(m_OldValue); + *m_pVariable = m_OldValue; } }; @@ -362,7 +362,7 @@ struct SStringConfigVariable : public SConfigVariable void ResetToOld() override { - SetValue(m_pOldValue); + str_copy(m_pStr, m_pOldValue, m_MaxSize); } }; From 20d9920d006e7d190c69017144ab0d54a7dfc6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 10 Dec 2023 11:41:18 +0100 Subject: [PATCH 091/198] Consistent rounding of editor value/color picker buttons Fix too large rounding for some editor color picker buttons. --- src/game/editor/editor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 7297a613a75..d970e1cc621 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -442,7 +442,7 @@ SEditResult CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const cha str_format(aBuf, sizeof(aBuf), "#%06X", Current); else str_from_int(Current, aBuf); - pRect->Draw(pColor ? *pColor : GetButtonColor(pID, 0), Corners, 5.0f); + pRect->Draw(pColor ? *pColor : GetButtonColor(pID, 0), Corners, 3.0f); UI()->DoLabel(pRect, aBuf, 10, TEXTALIGN_MC); } @@ -3106,9 +3106,9 @@ void CEditor::SetHotQuadPoint(const std::shared_ptr &pLayer) void CEditor::DoColorPickerButton(const void *pID, const CUIRect *pRect, ColorRGBA Color, const std::function &SetColor) { CUIRect ColorRect; - pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(pID)), IGraphics::CORNER_ALL, 5.0f); + pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(pID)), IGraphics::CORNER_ALL, 3.0f); pRect->Margin(1.0f, &ColorRect); - ColorRect.Draw(Color, IGraphics::CORNER_ALL, 5.0f); + ColorRect.Draw(Color, IGraphics::CORNER_ALL, 3.0f); const int ButtonResult = DoButton_Editor_Common(pID, nullptr, 0, pRect, 0, "Click to show the color picker. Shift+rightclick to copy color to clipboard. Shift+leftclick to paste color from clipboard."); if(Input()->ShiftIsPressed()) From 1b036374369dbd5fcd291fde46a8b5e97a0bcd70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 6 Dec 2023 22:33:14 +0100 Subject: [PATCH 092/198] Remove unused color argument, use `ColorRGBA` instead of `vec4` --- src/game/client/components/menus.cpp | 6 ++-- src/game/client/components/menus.h | 2 +- src/game/client/components/menus_ingame.cpp | 2 +- src/game/client/components/menus_settings.cpp | 4 +-- .../components/menus_settings_assets.cpp | 2 +- src/game/client/components/menus_start.cpp | 30 +++++++++---------- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 2160c327d7d..96e7b56902f 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -120,7 +120,7 @@ int CMenus::DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, return Active ? UI()->DoButtonLogic(pID, Checked, pRect) : 0; } -int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName, int Corners, float r, float FontFactor, vec4 ColorHot, vec4 Color) +int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName, int Corners, float Rounding, float FontFactor, ColorRGBA Color) { CUIRect Text = *pRect; @@ -128,7 +128,7 @@ int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, Color = ColorRGBA(0.6f, 0.6f, 0.6f, 0.5f); Color.a *= UI()->ButtonColorMul(pButtonContainer); - pRect->Draw(Color, Corners, r); + pRect->Draw(Color, Corners, Rounding); if(pImageName) { @@ -385,7 +385,7 @@ ColorHSLA CMenus::DoLine_ColorPicker(CButtonContainer *pResetID, const float Lin ColorHSLA PickedColor = DoButton_ColorPicker(&ColorPickerButton, pColorValue, Alpha); ResetButton.HMargin(2.0f, &ResetButton); - if(DoButton_Menu(pResetID, Localize("Reset"), 0, &ResetButton, nullptr, IGraphics::CORNER_ALL, 4.0f, 0.1f, vec4(1, 1, 1, 0.5f), vec4(1, 1, 1, 0.25f))) + if(DoButton_Menu(pResetID, Localize("Reset"), 0, &ResetButton, nullptr, IGraphics::CORNER_ALL, 4.0f, 0.1f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f))) { *pColorValue = color_cast(DefaultColor).Pack(Alpha); } diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index e2fadf14822..954693769e9 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -59,7 +59,7 @@ class CMenus : public CComponent int DoButton_FontIcon(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners = IGraphics::CORNER_ALL, bool Enabled = true); int DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active); - int DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName = nullptr, int Corners = IGraphics::CORNER_ALL, float r = 5.0f, float FontFactor = 0.0f, vec4 ColorHot = vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4 Color = vec4(1, 1, 1, 0.5f)); + int DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName = nullptr, int Corners = IGraphics::CORNER_ALL, float Rounding = 5.0f, float FontFactor = 0.0f, ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f)); int DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator = nullptr, const ColorRGBA *pDefaultColor = nullptr, const ColorRGBA *pActiveColor = nullptr, const ColorRGBA *pHoverColor = nullptr, float EdgeRounding = 10); int DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect); diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 106d6511007..b5b28983fbb 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -75,7 +75,7 @@ void CMenus::RenderGame(CUIRect MainView) static CButtonContainer s_DummyButton; if(!Client()->DummyAllowed()) { - DoButton_Menu(&s_DummyButton, Localize("Connect Dummy"), 1, &Button, nullptr, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(1.0f, 0.5f, 0.5f, 0.75f), vec4(1, 0.5f, 0.5f, 0.5f)); + DoButton_Menu(&s_DummyButton, Localize("Connect Dummy"), 1, &Button); } else if(DummyConnecting) { diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 4b3370ebf76..9bb0e9ef5fd 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -625,7 +625,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) EyesLabel.HSplitTop(10.0f, 0, &EyesLabel); } float Highlight = (m_Dummy ? g_Config.m_ClDummyDefaultEyes == CurrentEyeEmote : g_Config.m_ClPlayerDefaultEyes == CurrentEyeEmote) ? 1.0f : 0.0f; - if(DoButton_Menu(&s_aEyeButtons[CurrentEyeEmote], "", 0, &EyesTee, 0, IGraphics::CORNER_ALL, 10.0f, 0.0f, vec4(1, 1, 1, 0.5f + Highlight * 0.25f), vec4(1, 1, 1, 0.25f + Highlight * 0.25f))) + if(DoButton_Menu(&s_aEyeButtons[CurrentEyeEmote], "", 0, &EyesTee, 0, IGraphics::CORNER_ALL, 10.0f, 0.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f + Highlight * 0.25f))) { if(m_Dummy) { @@ -924,7 +924,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); static CButtonContainer s_SkinRefreshButtonID; - if(DoButton_Menu(&s_SkinRefreshButtonID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton, nullptr, IGraphics::CORNER_ALL, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f))) + if(DoButton_Menu(&s_SkinRefreshButtonID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton)) { // reset render flags for possible loading screen TextRender()->SetRenderFlags(0); diff --git a/src/game/client/components/menus_settings_assets.cpp b/src/game/client/components/menus_settings_assets.cpp index e1db2fe072c..9dc405de118 100644 --- a/src/game/client/components/menus_settings_assets.cpp +++ b/src/game/client/components/menus_settings_assets.cpp @@ -659,7 +659,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); static CButtonContainer s_AssetsReloadBtnID; - if(DoButton_Menu(&s_AssetsReloadBtnID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &ReloadButton, nullptr, IGraphics::CORNER_ALL, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f))) + if(DoButton_Menu(&s_AssetsReloadBtnID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &ReloadButton)) { ClearCustomItems(s_CurCustomTab); } diff --git a/src/game/client/components/menus_start.cpp b/src/game/client/components/menus_start.cpp index b0a4c143dfb..3e4c4a7fd97 100644 --- a/src/game/client/components/menus_start.cpp +++ b/src/game/client/components/menus_start.cpp @@ -39,7 +39,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button); static CButtonContainer s_DiscordButton; - if(DoButton_Menu(&s_DiscordButton, Localize("Discord"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f))) + if(DoButton_Menu(&s_DiscordButton, Localize("Discord"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) { const char *pLink = Localize("https://ddnet.org/discord"); if(!open_link(pLink)) @@ -51,7 +51,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button); static CButtonContainer s_LearnButton; - if(DoButton_Menu(&s_LearnButton, Localize("Learn"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f))) + if(DoButton_Menu(&s_LearnButton, Localize("Learn"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) { const char *pLink = Localize("https://wiki.ddnet.org/"); if(!open_link(pLink)) @@ -64,7 +64,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button); static CButtonContainer s_TutorialButton; static float s_JoinTutorialTime = 0.0f; - if(DoButton_Menu(&s_TutorialButton, Localize("Tutorial"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || + if(DoButton_Menu(&s_TutorialButton, Localize("Tutorial"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (s_JoinTutorialTime != 0.0f && Client()->LocalTime() >= s_JoinTutorialTime)) { const char *pAddr = ServerBrowser()->GetTutorialServer(); @@ -88,7 +88,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button); static CButtonContainer s_WebsiteButton; - if(DoButton_Menu(&s_WebsiteButton, Localize("Website"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f))) + if(DoButton_Menu(&s_WebsiteButton, Localize("Website"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) { const char *pLink = "https://ddnet.org/"; if(!open_link(pLink)) @@ -100,7 +100,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button); static CButtonContainer s_NewsButton; - if(DoButton_Menu(&s_NewsButton, Localize("News"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), g_Config.m_UiUnreadNews ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_N)) + if(DoButton_Menu(&s_NewsButton, Localize("News"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, g_Config.m_UiUnreadNews ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_N)) NewPage = PAGE_NEWS; CUIRect Menu; @@ -110,7 +110,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) Menu.HSplitBottom(40.0f, &Menu, &Button); static CButtonContainer s_QuitButton; bool UsedEscape = false; - if(DoButton_Menu(&s_QuitButton, Localize("Quit"), 0, &Button, 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || (UsedEscape = UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE)) || CheckHotKey(KEY_Q)) + if(DoButton_Menu(&s_QuitButton, Localize("Quit"), 0, &Button, 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (UsedEscape = UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE)) || CheckHotKey(KEY_Q)) { if(UsedEscape || m_pClient->Editor()->HasUnsavedData() || (Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0)) { @@ -125,7 +125,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) Menu.HSplitBottom(100.0f, &Menu, 0); Menu.HSplitBottom(40.0f, &Menu, &Button); static CButtonContainer s_SettingsButton; - if(DoButton_Menu(&s_SettingsButton, Localize("Settings"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "settings" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_S)) + if(DoButton_Menu(&s_SettingsButton, Localize("Settings"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "settings" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_S)) NewPage = PAGE_SETTINGS; Menu.HSplitBottom(5.0f, &Menu, 0); // little space @@ -135,7 +135,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) if(!is_process_alive(m_ServerProcess.m_Process)) KillServer(); - if(DoButton_Menu(&s_LocalServerButton, m_ServerProcess.m_Process ? Localize("Stop server") : Localize("Run server"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "local_server" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), m_ServerProcess.m_Process ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(0.0f, 0.0f, 0.0f, 0.25f)) || (CheckHotKey(KEY_R) && Input()->KeyPress(KEY_R))) + if(DoButton_Menu(&s_LocalServerButton, m_ServerProcess.m_Process ? Localize("Stop server") : Localize("Run server"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "local_server" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, m_ServerProcess.m_Process ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (CheckHotKey(KEY_R) && Input()->KeyPress(KEY_R))) { if(m_ServerProcess.m_Process) { @@ -166,7 +166,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) Menu.HSplitBottom(5.0f, &Menu, 0); // little space Menu.HSplitBottom(40.0f, &Menu, &Button); static CButtonContainer s_MapEditorButton; - if(DoButton_Menu(&s_MapEditorButton, Localize("Editor"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "editor" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), m_pClient->Editor()->HasUnsavedData() ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(0.0f, 0.0f, 0.0f, 0.25f)) || (!EditorHotkeyWasPressed && Client()->LocalTime() - EditorHotKeyChecktime < 0.1f && CheckHotKey(KEY_E))) + if(DoButton_Menu(&s_MapEditorButton, Localize("Editor"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "editor" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, m_pClient->Editor()->HasUnsavedData() ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || (!EditorHotkeyWasPressed && Client()->LocalTime() - EditorHotKeyChecktime < 0.1f && CheckHotKey(KEY_E))) { g_Config.m_ClEditor = 1; Input()->MouseModeRelative(); @@ -181,7 +181,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) Menu.HSplitBottom(5.0f, &Menu, 0); // little space Menu.HSplitBottom(40.0f, &Menu, &Button); static CButtonContainer s_DemoButton; - if(DoButton_Menu(&s_DemoButton, Localize("Demos"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "demos" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_D)) + if(DoButton_Menu(&s_DemoButton, Localize("Demos"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "demos" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || CheckHotKey(KEY_D)) { NewPage = PAGE_DEMOS; } @@ -189,7 +189,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) Menu.HSplitBottom(5.0f, &Menu, 0); // little space Menu.HSplitBottom(40.0f, &Menu, &Button); static CButtonContainer s_PlayButton; - if(DoButton_Menu(&s_PlayButton, Localize("Play", "Start menu"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "play_game" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f)) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || CheckHotKey(KEY_P)) + if(DoButton_Menu(&s_PlayButton, Localize("Play", "Start menu"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "play_game" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || CheckHotKey(KEY_P)) { NewPage = g_Config.m_UiPage >= PAGE_INTERNET && g_Config.m_UiPage <= PAGE_FAVORITES ? g_Config.m_UiPage : PAGE_INTERNET; } @@ -243,7 +243,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) Part.VSplitLeft(100.0f, &Update, NULL); static CButtonContainer s_VersionUpdate; - if(DoButton_Menu(&s_VersionUpdate, Localize("Update now"), 0, &Update, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f))) + if(DoButton_Menu(&s_VersionUpdate, Localize("Update now"), 0, &Update, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) { Updater()->InitiateUpdate(); } @@ -254,7 +254,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) Part.VSplitLeft(50.0f, &Restart, &Part); static CButtonContainer s_VersionUpdate; - if(DoButton_Menu(&s_VersionUpdate, Localize("Restart"), 0, &Restart, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f))) + if(DoButton_Menu(&s_VersionUpdate, Localize("Restart"), 0, &Restart, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f))) { Client()->Restart(); } @@ -265,9 +265,9 @@ void CMenus::RenderStartMenu(CUIRect MainView) Part.VSplitLeft(100.0f, &ProgressBar, &Percent); ProgressBar.y += 2.0f; ProgressBar.HMargin(1.0f, &ProgressBar); - ProgressBar.Draw(vec4(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 5.0f); + ProgressBar.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 5.0f); ProgressBar.w = clamp((float)Updater()->GetCurrentPercent(), 10.0f, 100.0f); - ProgressBar.Draw(vec4(1.0f, 1.0f, 1.0f, 0.5f), IGraphics::CORNER_ALL, 5.0f); + ProgressBar.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), IGraphics::CORNER_ALL, 5.0f); } #elif defined(CONF_INFORM_UPDATE) if(str_comp(Client()->LatestVersion(), "0") != 0) From 83af5e47f3cb16df28aaa9ddb3356f220cc4a993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 6 Dec 2023 22:55:37 +0100 Subject: [PATCH 093/198] Improve key reader button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show localized text "Press a key…" instead of "???" while waiting for a key to be pressed. Render button with green background while waiting for a key to be pressed. Inline unnecessary `DoButton_KeySelect` function. Fix names of static variables. --- src/game/client/components/menus.cpp | 38 ++++++++++++---------------- src/game/client/components/menus.h | 1 - 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 96e7b56902f..985ba4bfe0f 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -157,14 +157,6 @@ int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, return UI()->DoButtonLogic(pButtonContainer, Checked, pRect); } -void CMenus::DoButton_KeySelect(const void *pID, const char *pText, const CUIRect *pRect) -{ - pRect->Draw(ColorRGBA(1, 1, 1, 0.5f * UI()->ButtonColorMul(pID)), IGraphics::CORNER_ALL, 5.0f); - CUIRect Temp; - pRect->HMargin(1.0f, &Temp); - UI()->DoLabel(&Temp, pText, Temp.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); -} - int CMenus::DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator, const ColorRGBA *pDefaultColor, const ColorRGBA *pActiveColor, const ColorRGBA *pHoverColor, float EdgeRounding) { const bool MouseInside = UI()->HotItem() == pButtonContainer; @@ -452,15 +444,15 @@ int CMenus::DoButton_CheckBox_Number(const void *pID, const char *pText, int Che int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination) { // process - static const void *pGrabbedID = 0; - static bool MouseReleased = true; + static const void *s_pGrabbedID = nullptr; + static bool s_MouseReleased = true; static int s_ButtonUsed = 0; const bool Inside = UI()->MouseHovered(pRect); int NewKey = Key; *pNewModifierCombination = ModifierCombination; - if(!UI()->MouseButton(0) && !UI()->MouseButton(1) && pGrabbedID == pID) - MouseReleased = true; + if(!UI()->MouseButton(0) && !UI()->MouseButton(1) && s_pGrabbedID == pID) + s_MouseReleased = true; if(UI()->CheckActiveItem(pID)) { @@ -474,8 +466,8 @@ int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int Modi } m_Binder.m_GotKey = false; UI()->SetActiveItem(nullptr); - MouseReleased = false; - pGrabbedID = pID; + s_MouseReleased = false; + s_pGrabbedID = pID; } if(s_ButtonUsed == 1 && !UI()->MouseButton(1)) @@ -487,7 +479,7 @@ int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int Modi } else if(UI()->HotItem() == pID) { - if(MouseReleased) + if(s_MouseReleased) { if(UI()->MouseButton(0)) { @@ -508,17 +500,19 @@ int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int Modi if(Inside) UI()->SetHotItem(pID); - // draw + char aBuf[64]; if(UI()->CheckActiveItem(pID) && s_ButtonUsed == 0) - DoButton_KeySelect(pID, "???", pRect); + str_copy(aBuf, Localize("Press a key…")); else if(NewKey == 0) - DoButton_KeySelect(pID, "", pRect); + aBuf[0] = '\0'; else - { - char aBuf[64]; str_format(aBuf, sizeof(aBuf), "%s%s", CBinds::GetKeyBindModifiersName(*pNewModifierCombination), Input()->KeyName(NewKey)); - DoButton_KeySelect(pID, aBuf, pRect); - } + + const ColorRGBA Color = UI()->CheckActiveItem(pID) && m_Binder.m_TakeKey ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.4f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(pID)); + pRect->Draw(Color, IGraphics::CORNER_ALL, 5.0f); + CUIRect Temp; + pRect->HMargin(1.0f, &Temp); + UI()->DoLabel(&Temp, aBuf, Temp.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); return NewKey; } diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 954693769e9..86485bb6aac 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -72,7 +72,6 @@ class CMenus : public CComponent void DoLaserPreview(const CUIRect *pRect, ColorHSLA OutlineColor, ColorHSLA InnerColor, const int LaserType); int DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect); - void DoButton_KeySelect(const void *pID, const char *pText, const CUIRect *pRect); int DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination); void DoSettingsControlsButtons(int Start, int Stop, CUIRect View); From 5b7c064ea250357c2bb0033c194570a5cbd225c5 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Sat, 9 Dec 2023 18:07:35 +0100 Subject: [PATCH 094/198] Reset history entry along with chat input when using Escape --- src/game/client/components/chat.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 9aec741f99d..7f68d9c549b 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -227,7 +227,10 @@ bool CChat::OnInput(const IInput::CEvent &Event) DisableMode(); m_pClient->OnRelease(); if(g_Config.m_ClChatReset) + { m_Input.Clear(); + m_pHistoryEntry = nullptr; + } } else if(Event.m_Flags & IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER)) { @@ -253,7 +256,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) mem_copy(pEntry->m_aText, m_Input.GetString(), m_Input.GetLength() + 1); } } - m_pHistoryEntry = 0x0; + m_pHistoryEntry = nullptr; DisableMode(); m_pClient->OnRelease(); m_Input.Clear(); From 51220b1dfc92aa67af732d03b887755e74c4e157 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Mon, 11 Dec 2023 00:03:46 +0100 Subject: [PATCH 095/198] Fix editor modified state not being updated properly --- src/game/editor/editor.cpp | 5 +++++ src/game/editor/mapitems/layer_tiles.cpp | 2 +- src/game/editor/popups.cpp | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index d970e1cc621..05f7487b842 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -5674,6 +5674,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) if(m_SelectedEnvelope >= (int)m_Map.m_vpEnvelopes.size()) m_SelectedEnvelope = m_Map.m_vpEnvelopes.size() - 1; pEnvelope = m_SelectedEnvelope >= 0 ? m_Map.m_vpEnvelopes[m_SelectedEnvelope] : nullptr; + m_Map.OnModify(); } // Move right button @@ -5686,6 +5687,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) m_Map.SwapEnvelopes(m_SelectedEnvelope, m_SelectedEnvelope + 1); m_SelectedEnvelope = clamp(m_SelectedEnvelope + 1, 0, m_Map.m_vpEnvelopes.size() - 1); pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope]; + m_Map.OnModify(); } // Move left button @@ -5697,6 +5699,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) m_Map.SwapEnvelopes(m_SelectedEnvelope - 1, m_SelectedEnvelope); m_SelectedEnvelope = clamp(m_SelectedEnvelope - 1, 0, m_Map.m_vpEnvelopes.size() - 1); pEnvelope = m_Map.m_vpEnvelopes[m_SelectedEnvelope]; + m_Map.OnModify(); } if(pEnvelope) @@ -5875,6 +5878,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope, CEditorActionEnvelopeEdit::EEditType::SYNC, pEnvelope->m_Synchronized, !pEnvelope->m_Synchronized)); pEnvelope->m_Synchronized = !pEnvelope->m_Synchronized; + m_Map.OnModify(); } ToolBar.VSplitLeft(4.0f, nullptr, &ToolBar); @@ -6172,6 +6176,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) m_EnvelopeEditorHistory.RecordAction(std::make_shared(this, m_SelectedEnvelope, i, 0, CEditorActionEnvelopeEditPoint::EEditType::CURVE_TYPE, PrevCurve, pEnvelope->m_vPoints[i].m_Curvetype)); + m_Map.OnModify(); } } } diff --git a/src/game/editor/mapitems/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp index c3f953a7a59..56a8f33d3cc 100644 --- a/src/game/editor/mapitems/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -1058,7 +1058,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) m_AutoMapperConfig = -1; } - if(Prop != ETilesProp::PROP_NONE) + if(Prop != ETilesProp::PROP_NONE && Prop != ETilesProp::PROP_SHIFT_BY) { FlagModified(0, 0, m_Width, m_Height); } diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 6150965fe24..2e5210db4ec 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -1903,6 +1903,14 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMapInfo(void *pContext, CUIRect View static int s_ConfirmButton = 0; if(pEditor->DoButton_Editor(&s_ConfirmButton, "Confirm", 0, &Label, 0, nullptr) || (Active && pEditor->UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))) { + bool AuthorDifferent = str_comp(pEditor->m_Map.m_MapInfoTmp.m_aAuthor, pEditor->m_Map.m_MapInfo.m_aAuthor) != 0; + bool VersionDifferent = str_comp(pEditor->m_Map.m_MapInfoTmp.m_aVersion, pEditor->m_Map.m_MapInfo.m_aVersion) != 0; + bool CreditsDifferent = str_comp(pEditor->m_Map.m_MapInfoTmp.m_aCredits, pEditor->m_Map.m_MapInfo.m_aCredits) != 0; + bool LicenseDifferent = str_comp(pEditor->m_Map.m_MapInfoTmp.m_aLicense, pEditor->m_Map.m_MapInfo.m_aLicense) != 0; + + if(AuthorDifferent || VersionDifferent || CreditsDifferent || LicenseDifferent) + pEditor->m_Map.OnModify(); + pEditor->m_Map.m_MapInfo.Copy(pEditor->m_Map.m_MapInfoTmp); return CUI::POPUP_CLOSE_CURRENT; } From 7b20bf71e693667e839201a716292abee97b1c05 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Mon, 11 Dec 2023 00:05:13 +0100 Subject: [PATCH 096/198] Fix modified state being set when lineinput is selected. Differentiate between text and cursor changes by adding an additional `m_WasCursorChanged`. --- src/game/client/components/chat.cpp | 2 +- src/game/client/components/console.cpp | 2 +- src/game/client/lineinput.cpp | 9 +++++---- src/game/client/lineinput.h | 7 +++++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 008ac9ba1fa..73913aa8b15 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -1178,7 +1178,7 @@ void CChat::OnRender() m_Input.Activate(EInputPriority::CHAT); // Ensure that the input is active const CUIRect InputCursorRect = {Cursor.m_X, Cursor.m_Y - ScrollOffset, 0.0f, 0.0f}; - const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, m_Input.WasChanged(), MessageMaxWidth, 0.0f); + const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, m_Input.WasCursorChanged(), MessageMaxWidth, 0.0f); Graphics()->ClipDisable(); diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 060c18c4ba3..e01b6393b1f 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -767,7 +767,7 @@ void CGameConsole::OnRender() pConsole->m_Input.SetHidden(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->State() == IClient::STATE_ONLINE && !Client()->RconAuthed() && (pConsole->m_UserGot || !pConsole->m_UsernameReq)); pConsole->m_Input.Activate(EInputPriority::CONSOLE); // Ensure that the input is active const CUIRect InputCursorRect = {x, y + FONT_SIZE, 0.0f, 0.0f}; - pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FONT_SIZE, TEXTALIGN_BL, pConsole->m_Input.WasChanged(), Screen.w - 10.0f - x, LINE_SPACING); + pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FONT_SIZE, TEXTALIGN_BL, pConsole->m_Input.WasCursorChanged(), Screen.w - 10.0f - x, LINE_SPACING); if(pConsole->m_LastInputHeight == 0.0f && pConsole->m_BoundingBox.m_H != 0.0f) pConsole->m_LastInputHeight = pConsole->m_BoundingBox.m_H; if(pConsole->m_Input.HasSelection()) diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index b894946fa97..e1e2ab7b75e 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -86,6 +86,7 @@ void CLineInput::SetRange(const char *pString, size_t Begin, size_t End) m_Len += AddedCharSize - RemovedCharSize; m_NumChars += AddedCharCount - RemovedCharCount; m_WasChanged = true; + m_WasCursorChanged = true; m_pStr[m_Len] = '\0'; m_SelectionStart = m_SelectionEnd = m_CursorPos; } @@ -158,7 +159,7 @@ void CLineInput::MoveCursor(EMoveDirection Direction, bool MoveWord, const char void CLineInput::SetCursorOffset(size_t Offset) { m_SelectionStart = m_SelectionEnd = m_LastCompositionCursorPos = m_CursorPos = clamp(Offset, 0, m_Len); - m_WasChanged = true; + m_WasCursorChanged = true; } void CLineInput::SetSelection(size_t Start, size_t End) @@ -168,7 +169,7 @@ void CLineInput::SetSelection(size_t Start, size_t End) std::swap(Start, End); m_SelectionStart = clamp(Start, 0, m_Len); m_SelectionEnd = clamp(End, 0, m_Len); - m_WasChanged = true; + m_WasCursorChanged = true; } size_t CLineInput::OffsetFromActualToDisplay(size_t ActualOffset) @@ -383,9 +384,9 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event) } } - m_WasChanged |= OldCursorPos != m_CursorPos; + m_WasCursorChanged |= OldCursorPos != m_CursorPos; m_WasChanged |= SelectionLength != GetSelectionLength(); - return m_WasChanged || KeyHandled; + return m_WasChanged || m_WasCursorChanged || KeyHandled; } STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing) diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h index 6b51155a116..a0991780ee6 100644 --- a/src/game/client/lineinput.h +++ b/src/game/client/lineinput.h @@ -77,6 +77,7 @@ class CLineInput FDisplayTextCallback m_pfnDisplayTextCallback; FCalculateOffsetCallback m_pfnCalculateOffsetCallback; bool m_WasChanged; + bool m_WasCursorChanged; bool m_WasRendered; char m_ClearButtonId; @@ -179,6 +180,12 @@ class CLineInput m_WasChanged = false; return Changed; } + bool WasCursorChanged() + { + const bool Changed = m_WasCursorChanged; + m_WasCursorChanged = false; + return Changed; + } STextBoundingBox Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing); SMouseSelection *GetMouseSelection() { return &m_MouseSelection; } From 0c69378fcda7791532993ea47d944c1fc77958c2 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Mon, 11 Dec 2023 13:36:42 +0100 Subject: [PATCH 097/198] Prevent editing the color of entities layers --- src/game/editor/mapitems/layer_tiles.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/editor/mapitems/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp index 56a8f33d3cc..e65e65fa655 100644 --- a/src/game/editor/mapitems/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -1125,7 +1125,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta } } - if(HasModifiedColor) + if(HasModifiedColor && !pLayer->IsEntitiesLayer()) { int Color = 0; Color |= pLayer->m_Color.r << 24; From 8c5b8a88c8ea96271a435bdcfccdcab1bb032cf9 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Mon, 11 Dec 2023 13:37:23 +0100 Subject: [PATCH 098/198] Ensure white color for entities layers when loading map layers --- src/game/layers.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/game/layers.cpp b/src/game/layers.cpp index d36b1cbbc15..b00d0038c35 100644 --- a/src/game/layers.cpp +++ b/src/game/layers.cpp @@ -45,6 +45,7 @@ void CLayers::Init(class IKernel *pKernel) if(pLayer->m_Type == LAYERTYPE_TILES) { CMapItemLayerTilemap *pTilemap = reinterpret_cast(pLayer); + bool IsEntities = false; if(pTilemap->m_Flags & TILESLAYERFLAG_GAME) { @@ -66,6 +67,7 @@ void CLayers::Init(class IKernel *pKernel) m_pGameGroup->m_ClipH = 0; } + IsEntities = true; //break; } if(pTilemap->m_Flags & TILESLAYERFLAG_TELE) @@ -75,6 +77,7 @@ void CLayers::Init(class IKernel *pKernel) pTilemap->m_Tele = *((int *)(pTilemap) + 15); } m_pTeleLayer = pTilemap; + IsEntities = true; } if(pTilemap->m_Flags & TILESLAYERFLAG_SPEEDUP) { @@ -83,6 +86,7 @@ void CLayers::Init(class IKernel *pKernel) pTilemap->m_Speedup = *((int *)(pTilemap) + 16); } m_pSpeedupLayer = pTilemap; + IsEntities = true; } if(pTilemap->m_Flags & TILESLAYERFLAG_FRONT) { @@ -91,6 +95,7 @@ void CLayers::Init(class IKernel *pKernel) pTilemap->m_Front = *((int *)(pTilemap) + 17); } m_pFrontLayer = pTilemap; + IsEntities = true; } if(pTilemap->m_Flags & TILESLAYERFLAG_SWITCH) { @@ -99,6 +104,7 @@ void CLayers::Init(class IKernel *pKernel) pTilemap->m_Switch = *((int *)(pTilemap) + 18); } m_pSwitchLayer = pTilemap; + IsEntities = true; } if(pTilemap->m_Flags & TILESLAYERFLAG_TUNE) { @@ -107,6 +113,12 @@ void CLayers::Init(class IKernel *pKernel) pTilemap->m_Tune = *((int *)(pTilemap) + 19); } m_pTuneLayer = pTilemap; + IsEntities = true; + } + + if(IsEntities) + { // Ensure default color for entities layers + pTilemap->m_Color = CColor(255, 255, 255, 255); } } } From 466dca87a6f3e0bb2507bac894684c098234fcad Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 11 Dec 2023 16:52:21 +0100 Subject: [PATCH 099/198] Support `F5` and `Modifer` + `R` for refresh where possible. --- src/game/client/components/menus_ingame.cpp | 2 +- src/game/client/components/menus_settings.cpp | 2 +- src/game/client/components/menus_settings_assets.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index b5b28983fbb..7fa2a1a5b06 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -1115,7 +1115,7 @@ void CMenus::RenderGhost(CUIRect MainView) static CButtonContainer s_DirectoryButton; static CButtonContainer s_ActivateAll; - if(DoButton_FontIcon(&s_ReloadButton, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &Button) || Input()->KeyPress(KEY_F5)) + if(DoButton_FontIcon(&s_ReloadButton, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &Button) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed())) { m_pClient->m_Ghost.UnloadAll(); GhostlistPopulate(); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 9bb0e9ef5fd..588e284592a 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -924,7 +924,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); static CButtonContainer s_SkinRefreshButtonID; - if(DoButton_Menu(&s_SkinRefreshButtonID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton)) + if(DoButton_Menu(&s_SkinRefreshButtonID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed())) { // reset render flags for possible loading screen TextRender()->SetRenderFlags(0); diff --git a/src/game/client/components/menus_settings_assets.cpp b/src/game/client/components/menus_settings_assets.cpp index 9dc405de118..e0ff5a40819 100644 --- a/src/game/client/components/menus_settings_assets.cpp +++ b/src/game/client/components/menus_settings_assets.cpp @@ -659,7 +659,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); static CButtonContainer s_AssetsReloadBtnID; - if(DoButton_Menu(&s_AssetsReloadBtnID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &ReloadButton)) + if(DoButton_Menu(&s_AssetsReloadBtnID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &ReloadButton) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed())) { ClearCustomItems(s_CurCustomTab); } From ab5a7b88bdc55b8dc059933bc587b37a174472a1 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Mon, 11 Dec 2023 20:21:01 +0100 Subject: [PATCH 100/198] Add ui setting for cl_authed_player_color --- src/game/client/components/menus_settings.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 588e284592a..a5e2fd73647 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2976,6 +2976,11 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) break; } } + + Section.HSplitTop(LineSize, &Button, &Section); + ColorRGBA GreenDefault(0.7f, 1.0f, 0.7f, 1); + static CButtonContainer s_AuthedColor; + DoLine_ColorPicker(&s_AuthedColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Authed name color in scoreboard"), &g_Config.m_ClAuthedPlayerColor, GreenDefault, false); } else if(s_CurTab == APPEARANCE_TAB_HOOK_COLLISION) { From b42f43ce99649ef5635f1ada9154731b758f371f Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 11 Dec 2023 21:17:24 +0100 Subject: [PATCH 101/198] Fix envelope scaling being triggered in name input. --- src/game/editor/editor.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 05f7487b842..b63c3e676dd 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -5599,6 +5599,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static float s_MouseXStart = 0.0f; static float s_MouseYStart = 0.0f; + static CLineInput s_NameInput; + CUIRect ToolBar, CurveBar, ColorBar, DragBar; View.HSplitTop(30.0f, &DragBar, nullptr); DragBar.y -= 2.0f; @@ -5800,7 +5802,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ToolBar.VSplitLeft(3.0f, nullptr, &ToolBar); ToolBar.VSplitLeft(ToolBar.w > ToolBar.h * 40 ? 80.0f : 60.0f, &Button, &ToolBar); - static CLineInput s_NameInput; s_NameInput.SetBuffer(pEnvelope->m_aName, sizeof(pEnvelope->m_aName)); if(DoEditBox(&s_NameInput, &Button, 10.0f, IGraphics::CORNER_ALL, "The name of the selected envelope")) { @@ -6737,7 +6738,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static float s_MidpointY = 0.0f; static std::vector s_vInitialPositionsX; static std::vector s_vInitialPositionsY; - if(s_Operation == EEnvelopeEditorOp::OP_NONE && Input()->KeyIsPressed(KEY_S) && !Input()->ModifierIsPressed() && !m_vSelectedEnvelopePoints.empty()) + if(s_Operation == EEnvelopeEditorOp::OP_NONE && !s_NameInput.IsActive() && Input()->KeyIsPressed(KEY_S) && !Input()->ModifierIsPressed() && !m_vSelectedEnvelopePoints.empty()) { s_Operation = EEnvelopeEditorOp::OP_SCALE; s_ScaleFactorX = 1.0f; From 0aca09093f93d680ca207b0a3505ad25c530279c Mon Sep 17 00:00:00 2001 From: archimede67 Date: Mon, 11 Dec 2023 22:26:19 +0100 Subject: [PATCH 102/198] Use `WasCursorChanged()` when rendering lineinput in `CUI::DoEditBox` --- src/game/client/ui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 1a322686e67..b1e02070220 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -842,7 +842,7 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize pRect->Draw(ms_LightButtonColorFunction.GetColor(Active, HotItem() == pLineInput), Corners, 3.0f); ClipEnable(pRect); Textbox.x -= ScrollOffset; - const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed, -1.0f, 0.0f); + const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, pLineInput->WasCursorChanged(), -1.0f, 0.0f); ClipDisable(); // Scroll left or right if necessary From 05af24a05235e751f7d42c527e2665252cbf9882 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Mon, 11 Dec 2023 15:40:09 +0100 Subject: [PATCH 103/198] Make less headers depend on `` Move a couple of trivial type definitions to `` instead. --- CMakeLists.txt | 1 + datasrc/network.py | 1 + src/antibot/antibot_data.h | 1 - src/base/hash_ctxt.h | 1 - src/base/system.h | 56 +------------------ src/base/types.h | 54 ++++++++++++++++++ src/engine/client/backend/backend_base.cpp | 1 + src/engine/client/favorites.cpp | 1 + src/engine/client/ghost.h | 2 + src/engine/client/graphics_threaded.h | 1 + src/engine/client/serverbrowser_http.h | 2 +- .../client/serverbrowser_ping_cache.cpp | 1 + src/engine/client/serverbrowser_ping_cache.h | 2 +- src/engine/client/steam.cpp | 1 + src/engine/client/video.h | 1 - src/engine/client/warning.cpp | 14 +++++ src/engine/demo.h | 12 +--- src/engine/discord.h | 1 + src/engine/graphics.h | 1 + src/engine/input.h | 1 + src/engine/kernel.h | 2 - src/engine/map.h | 1 + src/engine/server/databases/connection.cpp | 5 ++ src/engine/server/databases/connection.h | 6 +- src/engine/server/upnp.h | 2 +- src/engine/serverbrowser.h | 2 +- src/engine/shared/console.h | 1 + src/engine/shared/csv.cpp | 2 + src/engine/shared/csv.h | 2 +- src/engine/shared/datafile.h | 2 +- src/engine/shared/demo.cpp | 10 ++++ src/engine/shared/fifo.h | 1 + src/engine/shared/filecollection.cpp | 1 + src/engine/shared/filecollection.h | 2 +- src/engine/shared/jsonwriter.cpp | 2 + src/engine/shared/jsonwriter.h | 3 +- src/engine/shared/linereader.cpp | 2 + src/engine/shared/linereader.h | 2 +- src/engine/shared/map.h | 2 +- src/engine/shared/netban.h | 3 +- src/engine/shared/protocol_ex.cpp | 1 + src/engine/shared/serverinfo.cpp | 1 + src/engine/shared/uuid_manager.cpp | 6 ++ src/engine/shared/uuid_manager.h | 4 +- src/engine/steam.h | 2 + src/engine/storage.h | 1 + src/engine/warning.h | 15 ++--- src/game/client/components/voting.h | 2 - src/game/editor/mapitems/sound.h | 3 +- src/game/gamecore.h | 6 +- src/game/teamscore.cpp | 1 + src/test/csv.cpp | 1 + src/test/jsonwriter.cpp | 1 + src/test/linereader.cpp | 1 + src/tools/uuid.cpp | 1 + 55 files changed, 149 insertions(+), 105 deletions(-) create mode 100644 src/engine/client/warning.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 72954eeddea..bf2c7bb09e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2157,6 +2157,7 @@ if(CLIENT) updater.h video.cpp video.h + warning.cpp ) set_src(GAME_CLIENT GLOB_RECURSE src/game/client animstate.cpp diff --git a/datasrc/network.py b/datasrc/network.py index 7b1b291c61e..f35c8a64e19 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -51,6 +51,7 @@ EntityClasses = ["PROJECTILE", "DOOR", "DRAGGER_WEAK", "DRAGGER_NORMAL", "DRAGGER_STRONG", "GUN_NORMAL", "GUN_EXPLOSIVE", "GUN_FREEZE", "GUN_UNFREEZE", "LIGHT", "PICKUP"] RawHeader = ''' +#include #include enum diff --git a/src/antibot/antibot_data.h b/src/antibot/antibot_data.h index ea244aa194a..f65f6262878 100644 --- a/src/antibot/antibot_data.h +++ b/src/antibot/antibot_data.h @@ -1,7 +1,6 @@ #ifndef ANTIBOT_ANTIBOT_DATA_H #define ANTIBOT_ANTIBOT_DATA_H -#include #include enum diff --git a/src/base/hash_ctxt.h b/src/base/hash_ctxt.h index 2d382a75f38..e407f04c3f9 100644 --- a/src/base/hash_ctxt.h +++ b/src/base/hash_ctxt.h @@ -2,7 +2,6 @@ #define BASE_HASH_CTXT_H #include "hash.h" -#include "system.h" #include #if defined(CONF_OPENSSL) diff --git a/src/base/system.h b/src/base/system.h index 447c8ef7fdc..c575949e623 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -9,6 +9,7 @@ #define BASE_SYSTEM_H #include "detect.h" +#include "types.h" #ifndef __USE_GNU #define __USE_GNU @@ -226,12 +227,8 @@ enum IOSEEK_START = 0, IOSEEK_CUR = 1, IOSEEK_END = 2, - - IO_MAX_PATH_LENGTH = 512, }; -typedef void *IOHANDLE; - /** * Opens a file. * @@ -741,43 +738,11 @@ ETimeSeason time_season(); * @defgroup Network-General */ -/** - * @ingroup Network-General - */ -typedef struct NETSOCKET_INTERNAL *NETSOCKET; - -/** - * @ingroup Network-General - */ -enum -{ - NETADDR_MAXSTRSIZE = 1 + (8 * 4 + 7) + 1 + 1 + 5 + 1, // [XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX]:XXXXX - - NETTYPE_LINK_BROADCAST = 4, - - NETTYPE_INVALID = 0, - NETTYPE_IPV4 = 1, - NETTYPE_IPV6 = 2, - NETTYPE_WEBSOCKET_IPV4 = 8, - - NETTYPE_ALL = NETTYPE_IPV4 | NETTYPE_IPV6 | NETTYPE_WEBSOCKET_IPV4, - NETTYPE_MASK = NETTYPE_ALL | NETTYPE_LINK_BROADCAST, -}; +extern const NETADDR NETADDR_ZEROED; /** * @ingroup Network-General */ -typedef struct NETADDR -{ - unsigned int type; - unsigned char ip[16]; - unsigned short port; - - bool operator==(const NETADDR &other) const; - bool operator!=(const NETADDR &other) const { return !(*this == other); } -} NETADDR; - -extern const NETADDR NETADDR_ZEROED; #ifdef CONF_FAMILY_UNIX /** @@ -1810,16 +1775,8 @@ void str_escape(char **dst, const char *src, const char *end); * * @remark The strings are treated as zero-terminated strings. */ -typedef int (*FS_LISTDIR_CALLBACK)(const char *name, int is_dir, int dir_type, void *user); void fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user); -typedef struct -{ - const char *m_pName; - time_t m_TimeCreated; // seconds since UNIX Epoch - time_t m_TimeModified; // seconds since UNIX Epoch -} CFsFileInfo; - /** * Lists the files and folders in a directory and gets additional file information. * @@ -1832,7 +1789,6 @@ typedef struct * * @remark The strings are treated as zero-terminated strings. */ -typedef int (*FS_LISTDIR_CALLBACK_FILEINFO)(const CFsFileInfo *info, int is_dir, int dir_type, void *user); void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int type, void *user); /** @@ -2183,14 +2139,6 @@ int str_isallnum_hex(const char *str); unsigned str_quickhash(const char *str); -enum -{ - /** - * The maximum bytes necessary to encode one Unicode codepoint with UTF-8. - */ - UTF8_BYTE_LENGTH = 4, -}; - int str_utf8_to_skeleton(const char *str, int *buf, int buf_len); /* diff --git a/src/base/types.h b/src/base/types.h index ace9dc2f7c8..ffa1b47ed2c 100644 --- a/src/base/types.h +++ b/src/base/types.h @@ -1,6 +1,8 @@ #ifndef BASE_TYPES_H #define BASE_TYPES_H +#include + enum class TRISTATE { NONE, @@ -8,4 +10,56 @@ enum class TRISTATE ALL, }; +typedef void *IOHANDLE; + +typedef int (*FS_LISTDIR_CALLBACK)(const char *name, int is_dir, int dir_type, void *user); + +typedef struct +{ + const char *m_pName; + time_t m_TimeCreated; // seconds since UNIX Epoch + time_t m_TimeModified; // seconds since UNIX Epoch +} CFsFileInfo; + +typedef int (*FS_LISTDIR_CALLBACK_FILEINFO)(const CFsFileInfo *info, int is_dir, int dir_type, void *user); + +/** + * @ingroup Network-General + */ +typedef struct NETSOCKET_INTERNAL *NETSOCKET; + +enum +{ + /** + * The maximum bytes necessary to encode one Unicode codepoint with UTF-8. + */ + UTF8_BYTE_LENGTH = 4, + + IO_MAX_PATH_LENGTH = 512, + + NETADDR_MAXSTRSIZE = 1 + (8 * 4 + 7) + 1 + 1 + 5 + 1, // [XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX]:XXXXX + + NETTYPE_LINK_BROADCAST = 4, + + NETTYPE_INVALID = 0, + NETTYPE_IPV4 = 1, + NETTYPE_IPV6 = 2, + NETTYPE_WEBSOCKET_IPV4 = 8, + + NETTYPE_ALL = NETTYPE_IPV4 | NETTYPE_IPV6 | NETTYPE_WEBSOCKET_IPV4, + NETTYPE_MASK = NETTYPE_ALL | NETTYPE_LINK_BROADCAST, +}; + +/** + * @ingroup Network-General + */ +typedef struct NETADDR +{ + unsigned int type; + unsigned char ip[16]; + unsigned short port; + + bool operator==(const NETADDR &other) const; + bool operator!=(const NETADDR &other) const { return !(*this == other); } +} NETADDR; #endif // BASE_TYPES_H diff --git a/src/engine/client/backend/backend_base.cpp b/src/engine/client/backend/backend_base.cpp index 9e2ad5c4d3b..168598064fb 100644 --- a/src/engine/client/backend/backend_base.cpp +++ b/src/engine/client/backend/backend_base.cpp @@ -1,4 +1,5 @@ #include "backend_base.h" +#include #include void *CCommandProcessorFragment_GLBase::Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP) diff --git a/src/engine/client/favorites.cpp b/src/engine/client/favorites.cpp index 39d015a75c6..9df4e42aff6 100644 --- a/src/engine/client/favorites.cpp +++ b/src/engine/client/favorites.cpp @@ -1,3 +1,4 @@ +#include #include #include #include diff --git a/src/engine/client/ghost.h b/src/engine/client/ghost.h index f555e1f2de5..544b7d04445 100644 --- a/src/engine/client/ghost.h +++ b/src/engine/client/ghost.h @@ -3,6 +3,8 @@ #include +#include + enum { MAX_ITEM_SIZE = 128, diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h index 514680749d9..eaf0570d66e 100644 --- a/src/engine/client/graphics_threaded.h +++ b/src/engine/client/graphics_threaded.h @@ -1,6 +1,7 @@ #ifndef ENGINE_CLIENT_GRAPHICS_THREADED_H #define ENGINE_CLIENT_GRAPHICS_THREADED_H +#include #include #include diff --git a/src/engine/client/serverbrowser_http.h b/src/engine/client/serverbrowser_http.h index d75b3d157df..b6884f869a3 100644 --- a/src/engine/client/serverbrowser_http.h +++ b/src/engine/client/serverbrowser_http.h @@ -1,6 +1,6 @@ #ifndef ENGINE_CLIENT_SERVERBROWSER_HTTP_H #define ENGINE_CLIENT_SERVERBROWSER_HTTP_H -#include +#include class CServerInfo; class IConsole; diff --git a/src/engine/client/serverbrowser_ping_cache.cpp b/src/engine/client/serverbrowser_ping_cache.cpp index ff46adf3141..d59f1bd8347 100644 --- a/src/engine/client/serverbrowser_ping_cache.cpp +++ b/src/engine/client/serverbrowser_ping_cache.cpp @@ -1,5 +1,6 @@ #include "serverbrowser_ping_cache.h" +#include #include #include diff --git a/src/engine/client/serverbrowser_ping_cache.h b/src/engine/client/serverbrowser_ping_cache.h index bacd8378220..5c81e939f8e 100644 --- a/src/engine/client/serverbrowser_ping_cache.h +++ b/src/engine/client/serverbrowser_ping_cache.h @@ -1,6 +1,6 @@ #ifndef ENGINE_CLIENT_SERVERBROWSER_PING_CACHE_H #define ENGINE_CLIENT_SERVERBROWSER_PING_CACHE_H -#include +#include class IConsole; class IStorage; diff --git a/src/engine/client/steam.cpp b/src/engine/client/steam.cpp index f5b3609cd0e..6c1cd20ea1d 100644 --- a/src/engine/client/steam.cpp +++ b/src/engine/client/steam.cpp @@ -1,5 +1,6 @@ #include +#include #include #include diff --git a/src/engine/client/video.h b/src/engine/client/video.h index 849a0b72535..58f2186edfe 100644 --- a/src/engine/client/video.h +++ b/src/engine/client/video.h @@ -2,7 +2,6 @@ #define ENGINE_CLIENT_VIDEO_H #include -#include extern "C" { #include diff --git a/src/engine/client/warning.cpp b/src/engine/client/warning.cpp new file mode 100644 index 00000000000..0ed4a9daae7 --- /dev/null +++ b/src/engine/client/warning.cpp @@ -0,0 +1,14 @@ +#include + +#include + +SWarning::SWarning(const char *pMsg) +{ + str_copy(m_aWarningTitle, ""); + str_copy(m_aWarningMsg, pMsg); +} +SWarning::SWarning(const char *pTitle, const char *pMsg) +{ + str_copy(m_aWarningTitle, pTitle); + str_copy(m_aWarningMsg, pMsg); +} diff --git a/src/engine/demo.h b/src/engine/demo.h index 240551de26d..f42193e9b03 100644 --- a/src/engine/demo.h +++ b/src/engine/demo.h @@ -8,6 +8,8 @@ #include #include +#include + enum { MAX_TIMELINE_MARKERS = 64 @@ -37,15 +39,7 @@ struct CDemoHeader unsigned char m_aLength[sizeof(int32_t)]; char m_aTimestamp[20]; - bool Valid() const - { - // Check marker and ensure that strings are zero-terminated and valid UTF-8. - return mem_comp(m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) == 0 && - mem_has_null(m_aNetversion, sizeof(m_aNetversion)) && str_utf8_check(m_aNetversion) && - mem_has_null(m_aMapName, sizeof(m_aMapName)) && str_utf8_check(m_aMapName) && - mem_has_null(m_aType, sizeof(m_aType)) && str_utf8_check(m_aType) && - mem_has_null(m_aTimestamp, sizeof(m_aTimestamp)) && str_utf8_check(m_aTimestamp); - } + bool Valid() const; }; struct CTimelineMarkers diff --git a/src/engine/discord.h b/src/engine/discord.h index 92e4401fad2..5b5f0a82698 100644 --- a/src/engine/discord.h +++ b/src/engine/discord.h @@ -2,6 +2,7 @@ #define ENGINE_DISCORD_H #include "kernel.h" +#include class IDiscord : public IInterface { diff --git a/src/engine/graphics.h b/src/engine/graphics.h index 5021422618b..951748b1170 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -7,6 +7,7 @@ #include "warning.h" #include +#include #include #include diff --git a/src/engine/input.h b/src/engine/input.h index cbf693faa13..87740c2fa2a 100644 --- a/src/engine/input.h +++ b/src/engine/input.h @@ -4,6 +4,7 @@ #define ENGINE_INPUT_H #include "kernel.h" +#include const int g_MaxKeys = 512; extern const char g_aaKeyStrings[g_MaxKeys][20]; diff --git a/src/engine/kernel.h b/src/engine/kernel.h index 95cab9f7122..7412142a0d0 100644 --- a/src/engine/kernel.h +++ b/src/engine/kernel.h @@ -3,8 +3,6 @@ #ifndef ENGINE_KERNEL_H #define ENGINE_KERNEL_H -#include - class IKernel; class IInterface; diff --git a/src/engine/map.h b/src/engine/map.h index 5f5f4c67a6c..47c2c5ed8a1 100644 --- a/src/engine/map.h +++ b/src/engine/map.h @@ -5,6 +5,7 @@ #include "kernel.h" #include +#include enum { diff --git a/src/engine/server/databases/connection.cpp b/src/engine/server/databases/connection.cpp index 1609b85a9ff..de77f31ad5b 100644 --- a/src/engine/server/databases/connection.cpp +++ b/src/engine/server/databases/connection.cpp @@ -2,6 +2,11 @@ #include +IDbConnection::IDbConnection(const char *pPrefix) +{ + str_copy(m_aPrefix, pPrefix); +} + void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup) { str_format(aBuf, BufferSize, diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 332e278a4b4..0060c64284e 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -2,7 +2,6 @@ #define ENGINE_SERVER_DATABASES_CONNECTION_H #include "connection_pool.h" -#include #include @@ -12,10 +11,7 @@ class IConsole; class IDbConnection { public: - IDbConnection(const char *pPrefix) - { - str_copy(m_aPrefix, pPrefix); - } + IDbConnection(const char *pPrefix); virtual ~IDbConnection() {} IDbConnection &operator=(const IDbConnection &) = delete; virtual void Print(IConsole *pConsole, const char *pMode) = 0; diff --git a/src/engine/server/upnp.h b/src/engine/server/upnp.h index f5c6a7597ed..6b35db268c4 100644 --- a/src/engine/server/upnp.h +++ b/src/engine/server/upnp.h @@ -1,7 +1,7 @@ #ifndef ENGINE_SERVER_UPNP_H #define ENGINE_SERVER_UPNP_H -#include +#include class CUPnP { NETADDR m_Addr; diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h index 1d01d3e8fb8..c3e83a20032 100644 --- a/src/engine/serverbrowser.h +++ b/src/engine/serverbrowser.h @@ -4,7 +4,7 @@ #define ENGINE_SERVERBROWSER_H #include -#include +#include #include #include diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index f5c4c97eae1..bc22e4d1ac7 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -5,6 +5,7 @@ #include "memheap.h" #include +#include #include #include diff --git a/src/engine/shared/csv.cpp b/src/engine/shared/csv.cpp index 0fc90a87948..056091e8c86 100644 --- a/src/engine/shared/csv.cpp +++ b/src/engine/shared/csv.cpp @@ -1,5 +1,7 @@ #include "csv.h" +#include + void CsvWrite(IOHANDLE File, int NumColumns, const char *const *ppColumns) { for(int i = 0; i < NumColumns; i++) diff --git a/src/engine/shared/csv.h b/src/engine/shared/csv.h index 9d3de0e047e..0417e454883 100644 --- a/src/engine/shared/csv.h +++ b/src/engine/shared/csv.h @@ -1,7 +1,7 @@ #ifndef ENGINE_SHARED_CSV_H #define ENGINE_SHARED_CSV_H -#include +#include void CsvWrite(IOHANDLE File, int NumColumns, const char *const *ppColumns); diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h index 0d8dd6a39c9..db77ca41e46 100644 --- a/src/engine/shared/datafile.h +++ b/src/engine/shared/datafile.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include diff --git a/src/engine/shared/demo.cpp b/src/engine/shared/demo.cpp index 2b10b555495..2eefe2b824d 100644 --- a/src/engine/shared/demo.cpp +++ b/src/engine/shared/demo.cpp @@ -31,6 +31,16 @@ static const int gs_NumMarkersOffset = 176; static const ColorRGBA gs_DemoPrintColor{0.75f, 0.7f, 0.7f, 1.0f}; +bool CDemoHeader::Valid() const +{ + // Check marker and ensure that strings are zero-terminated and valid UTF-8. + return mem_comp(m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) == 0 && + mem_has_null(m_aNetversion, sizeof(m_aNetversion)) && str_utf8_check(m_aNetversion) && + mem_has_null(m_aMapName, sizeof(m_aMapName)) && str_utf8_check(m_aMapName) && + mem_has_null(m_aType, sizeof(m_aType)) && str_utf8_check(m_aType) && + mem_has_null(m_aTimestamp, sizeof(m_aTimestamp)) && str_utf8_check(m_aTimestamp); +} + CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData) { m_File = 0; diff --git a/src/engine/shared/fifo.h b/src/engine/shared/fifo.h index b844de0738f..ad431b9bbab 100644 --- a/src/engine/shared/fifo.h +++ b/src/engine/shared/fifo.h @@ -1,6 +1,7 @@ #ifndef ENGINE_SHARED_FIFO_H #define ENGINE_SHARED_FIFO_H +#include #include class CFifo diff --git a/src/engine/shared/filecollection.cpp b/src/engine/shared/filecollection.cpp index 8125e8ab91e..0dab1c258f4 100644 --- a/src/engine/shared/filecollection.cpp +++ b/src/engine/shared/filecollection.cpp @@ -2,6 +2,7 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include +#include #include diff --git a/src/engine/shared/filecollection.h b/src/engine/shared/filecollection.h index d284685ac80..9a4b561c65a 100644 --- a/src/engine/shared/filecollection.h +++ b/src/engine/shared/filecollection.h @@ -3,7 +3,7 @@ #ifndef ENGINE_SHARED_FILECOLLECTION_H #define ENGINE_SHARED_FILECOLLECTION_H -#include +#include #include diff --git a/src/engine/shared/jsonwriter.cpp b/src/engine/shared/jsonwriter.cpp index 5c870e4f5aa..ca703bf49c9 100644 --- a/src/engine/shared/jsonwriter.cpp +++ b/src/engine/shared/jsonwriter.cpp @@ -3,6 +3,8 @@ #include "jsonwriter.h" +#include + static char EscapeJsonChar(char c) { switch(c) diff --git a/src/engine/shared/jsonwriter.h b/src/engine/shared/jsonwriter.h index 2365afe2f96..83086055a0c 100644 --- a/src/engine/shared/jsonwriter.h +++ b/src/engine/shared/jsonwriter.h @@ -3,9 +3,10 @@ #ifndef ENGINE_SHARED_JSONWRITER_H #define ENGINE_SHARED_JSONWRITER_H -#include +#include #include +#include /** * JSON writer with abstract writing function. diff --git a/src/engine/shared/linereader.cpp b/src/engine/shared/linereader.cpp index 8cc02a59ce8..d322ee27926 100644 --- a/src/engine/shared/linereader.cpp +++ b/src/engine/shared/linereader.cpp @@ -2,6 +2,8 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include "linereader.h" +#include + void CLineReader::Init(IOHANDLE File) { m_BufferMaxSize = sizeof(m_aBuffer) - 1; diff --git a/src/engine/shared/linereader.h b/src/engine/shared/linereader.h index 6d4aa07e202..7d5f1c5ab45 100644 --- a/src/engine/shared/linereader.h +++ b/src/engine/shared/linereader.h @@ -2,7 +2,7 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #ifndef ENGINE_SHARED_LINEREADER_H #define ENGINE_SHARED_LINEREADER_H -#include +#include // buffered stream for reading lines, should perhaps be something smaller class CLineReader diff --git a/src/engine/shared/map.h b/src/engine/shared/map.h index ebb3a8591cb..c104c6c0a6a 100644 --- a/src/engine/shared/map.h +++ b/src/engine/shared/map.h @@ -3,7 +3,7 @@ #ifndef ENGINE_SHARED_MAP_H #define ENGINE_SHARED_MAP_H -#include +#include #include "datafile.h" #include diff --git a/src/engine/shared/netban.h b/src/engine/shared/netban.h index fd7dbb8a857..6de37e51d90 100644 --- a/src/engine/shared/netban.h +++ b/src/engine/shared/netban.h @@ -1,9 +1,8 @@ #ifndef ENGINE_SHARED_NETBAN_H #define ENGINE_SHARED_NETBAN_H -#include - #include +#include inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2) { diff --git a/src/engine/shared/protocol_ex.cpp b/src/engine/shared/protocol_ex.cpp index 4d44ca7bed7..51f4b286ada 100644 --- a/src/engine/shared/protocol_ex.cpp +++ b/src/engine/shared/protocol_ex.cpp @@ -3,6 +3,7 @@ #include "config.h" #include "uuid_manager.h" +#include #include #include diff --git a/src/engine/shared/serverinfo.cpp b/src/engine/shared/serverinfo.cpp index 9f0f4a1b42f..ecccfa5994e 100644 --- a/src/engine/shared/serverinfo.cpp +++ b/src/engine/shared/serverinfo.cpp @@ -2,6 +2,7 @@ #include "json.h" #include +#include #include #include diff --git a/src/engine/shared/uuid_manager.cpp b/src/engine/shared/uuid_manager.cpp index 4dfb9010060..3cff589d608 100644 --- a/src/engine/shared/uuid_manager.cpp +++ b/src/engine/shared/uuid_manager.cpp @@ -1,6 +1,7 @@ #include "uuid_manager.h" #include +#include #include #include @@ -100,6 +101,11 @@ bool CUuid::operator!=(const CUuid &Other) const return !(*this == Other); } +bool CUuid::operator<(const CUuid &Other) const +{ + return mem_comp(this, &Other, sizeof(*this)) < 0; +} + static int GetIndex(int ID) { return ID - OFFSET_UUID; diff --git a/src/engine/shared/uuid_manager.h b/src/engine/shared/uuid_manager.h index c1c607e750f..00d1a4822f5 100644 --- a/src/engine/shared/uuid_manager.h +++ b/src/engine/shared/uuid_manager.h @@ -3,8 +3,6 @@ #include -#include - enum { UUID_MAXSTRSIZE = 37, // 12345678-0123-5678-0123-567890123456 @@ -21,7 +19,7 @@ struct CUuid bool operator==(const CUuid &Other) const; bool operator!=(const CUuid &Other) const; - bool operator<(const CUuid &Other) const { return mem_comp(m_aData, Other.m_aData, sizeof(m_aData)) < 0; } + bool operator<(const CUuid &Other) const; }; extern const CUuid UUID_ZEROED; diff --git a/src/engine/steam.h b/src/engine/steam.h index baf9661175b..20341e2b048 100644 --- a/src/engine/steam.h +++ b/src/engine/steam.h @@ -1,6 +1,8 @@ #ifndef ENGINE_STEAM_H #define ENGINE_STEAM_H +#include + #include "kernel.h" class ISteam : public IInterface diff --git a/src/engine/storage.h b/src/engine/storage.h index 38d441f3955..9380d0dd5e2 100644 --- a/src/engine/storage.h +++ b/src/engine/storage.h @@ -4,6 +4,7 @@ #define ENGINE_STORAGE_H #include +#include #include "kernel.h" diff --git a/src/engine/warning.h b/src/engine/warning.h index 0214a6b0fbe..bd1e45e7528 100644 --- a/src/engine/warning.h +++ b/src/engine/warning.h @@ -3,17 +3,10 @@ struct SWarning { - SWarning() {} - SWarning(const char *pMsg) - { - str_copy(m_aWarningTitle, ""); - str_copy(m_aWarningMsg, pMsg); - } - SWarning(const char *pTitle, const char *pMsg) - { - str_copy(m_aWarningTitle, pTitle); - str_copy(m_aWarningMsg, pMsg); - } + SWarning() = default; + SWarning(const char *pMsg); + SWarning(const char *pTitle, const char *pMsg); + char m_aWarningTitle[128]; char m_aWarningMsg[256]; bool m_WasShown = false; diff --git a/src/game/client/components/voting.h b/src/game/client/components/voting.h index 077edcb8162..f81959a68ed 100644 --- a/src/game/client/components/voting.h +++ b/src/game/client/components/voting.h @@ -3,8 +3,6 @@ #ifndef GAME_CLIENT_COMPONENTS_VOTING_H #define GAME_CLIENT_COMPONENTS_VOTING_H -#include - #include #include diff --git a/src/game/editor/mapitems/sound.h b/src/game/editor/mapitems/sound.h index 681f9ad30b1..28b222972cb 100644 --- a/src/game/editor/mapitems/sound.h +++ b/src/game/editor/mapitems/sound.h @@ -1,8 +1,7 @@ #ifndef GAME_EDITOR_MAPITEMS_SOUND_H #define GAME_EDITOR_MAPITEMS_SOUND_H -#include - +#include #include class CEditorSound : public CEditorComponent diff --git a/src/game/gamecore.h b/src/game/gamecore.h index 8c81c00bda9..07c6d188fd0 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -3,7 +3,6 @@ #ifndef GAME_GAMECORE_H #define GAME_GAMECORE_H -#include #include #include @@ -191,7 +190,10 @@ class CWorldCore public: CWorldCore() { - mem_zero(m_apCharacters, sizeof(m_apCharacters)); + for(auto &pCharacter : m_apCharacters) + { + pCharacter = nullptr; + } m_pPrng = nullptr; } diff --git a/src/game/teamscore.cpp b/src/game/teamscore.cpp index 968eb8ef960..0835a9814bd 100644 --- a/src/game/teamscore.cpp +++ b/src/game/teamscore.cpp @@ -1,5 +1,6 @@ /* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ #include "teamscore.h" +#include #include CTeamsCore::CTeamsCore() diff --git a/src/test/csv.cpp b/src/test/csv.cpp index 905983fdf32..2022f98ca28 100644 --- a/src/test/csv.cpp +++ b/src/test/csv.cpp @@ -1,6 +1,7 @@ #include "test.h" #include +#include #include static void Expect(int NumColumns, const char *const *ppColumns, const char *pExpected) diff --git a/src/test/jsonwriter.cpp b/src/test/jsonwriter.cpp index f61137c39dd..c8bcede6104 100644 --- a/src/test/jsonwriter.cpp +++ b/src/test/jsonwriter.cpp @@ -3,6 +3,7 @@ #include "test.h" #include +#include #include #include diff --git a/src/test/linereader.cpp b/src/test/linereader.cpp index cd53b38ca8b..05d93627b75 100644 --- a/src/test/linereader.cpp +++ b/src/test/linereader.cpp @@ -1,6 +1,7 @@ #include "test.h" #include +#include #include void TestFileLineReader(const char *pWritten, bool SkipBom, std::initializer_list pReads) diff --git a/src/tools/uuid.cpp b/src/tools/uuid.cpp index 0cb35aa8d10..6a6a011c214 100644 --- a/src/tools/uuid.cpp +++ b/src/tools/uuid.cpp @@ -1,4 +1,5 @@ #include +#include #include int main(int argc, const char **argv) { From be53d83019a41220fce4fc0bb90eae1da9bdbdac Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Tue, 12 Dec 2023 00:44:46 +0100 Subject: [PATCH 104/198] Remove `#include ` from one more header --- datasrc/compile.py | 1 + datasrc/network.py | 5 ----- datasrc/seven/compile.py | 1 + datasrc/seven/network.py | 4 ---- src/engine/server.h | 1 + src/engine/server/authmanager.cpp | 1 + src/game/gamecore.cpp | 1 + src/game/server/teehistorian.cpp | 1 + 8 files changed, 6 insertions(+), 9 deletions(-) diff --git a/datasrc/compile.py b/datasrc/compile.py index 5e9a93b6c9b..80cd109242f 100644 --- a/datasrc/compile.py +++ b/datasrc/compile.py @@ -126,6 +126,7 @@ def gen_network_source(): print("""\ #include "protocol.h" +#include #include #include #include diff --git a/datasrc/network.py b/datasrc/network.py index f35c8a64e19..d434524f896 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -51,7 +51,6 @@ EntityClasses = ["PROJECTILE", "DOOR", "DRAGGER_WEAK", "DRAGGER_NORMAL", "DRAGGER_STRONG", "GUN_NORMAL", "GUN_EXPLOSIVE", "GUN_FREEZE", "GUN_UNFREEZE", "LIGHT", "PICKUP"] RawHeader = ''' -#include #include enum @@ -79,10 +78,6 @@ }; ''' -RawSource = ''' -#include "protocol.h" -''' - Enums = [ Enum("EMOTE", Emotes), Enum("POWERUP", Powerups), diff --git a/datasrc/seven/compile.py b/datasrc/seven/compile.py index 551901a1cd9..b24e2cdee5d 100644 --- a/datasrc/seven/compile.py +++ b/datasrc/seven/compile.py @@ -171,6 +171,7 @@ class CNetObjHandler lines = [] lines += ['#include "protocol7.h"'] + lines += ['#include '] lines += ['#include '] lines += ['#include '] diff --git a/datasrc/seven/network.py b/datasrc/seven/network.py index 8a041afba67..0a23dc6bf20 100644 --- a/datasrc/seven/network.py +++ b/datasrc/seven/network.py @@ -55,10 +55,6 @@ }; ''' -RawSource = ''' -#include "protocol.h" -''' - Enums = [ Pickups, Emotes, diff --git a/src/engine/server.h b/src/engine/server.h index 7f8e1306b04..1e81f77397b 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -8,6 +8,7 @@ #include #include +#include #include "kernel.h" #include "message.h" diff --git a/src/engine/server/authmanager.cpp b/src/engine/server/authmanager.cpp index 6014af70fe1..59f1bc3c5a8 100644 --- a/src/engine/server/authmanager.cpp +++ b/src/engine/server/authmanager.cpp @@ -1,5 +1,6 @@ #include "authmanager.h" #include +#include #include #include diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index 409fa0780c0..3858aa6a5e0 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -6,6 +6,7 @@ #include "mapitems.h" #include "teamscore.h" +#include #include const char *CTuningParams::ms_apNames[] = diff --git a/src/game/server/teehistorian.cpp b/src/game/server/teehistorian.cpp index 355916b0254..9fc9545c536 100644 --- a/src/game/server/teehistorian.cpp +++ b/src/game/server/teehistorian.cpp @@ -1,5 +1,6 @@ #include "teehistorian.h" +#include #include #include #include From 10b486a6b063c8d806d14a5f8806c01b0a79eebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 7 Dec 2023 18:51:57 +0100 Subject: [PATCH 105/198] Remove unnecessary use of `GetCompletePath` The result `aFullPath` is unused. --- src/engine/shared/engine.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/engine/shared/engine.cpp b/src/engine/shared/engine.cpp index 7ab141ebbd7..ce356eb533d 100644 --- a/src/engine/shared/engine.cpp +++ b/src/engine/shared/engine.cpp @@ -81,8 +81,6 @@ class CEngine : public IEngine if(!m_pConsole || !m_pStorage) return; - char aFullPath[IO_MAX_PATH_LENGTH]; - m_pStorage->GetCompletePath(IStorage::TYPE_SAVE, "dumps/", aFullPath, sizeof(aFullPath)); m_pConsole->Register("dbg_lognetwork", "", CFGFLAG_SERVER | CFGFLAG_CLIENT, Con_DbgLognetwork, this, "Log the network"); } From 187120dd317d6c4779fcd05ff8c8b277bcd0c96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 7 Dec 2023 18:53:53 +0100 Subject: [PATCH 106/198] Ensure network log files are closed when shutting down engine Previously, the network log files were not explicitly closed unless the `dbg_lognetwork` command is used again manually. --- src/engine/shared/engine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/shared/engine.cpp b/src/engine/shared/engine.cpp index ce356eb533d..9f0469e98de 100644 --- a/src/engine/shared/engine.cpp +++ b/src/engine/shared/engine.cpp @@ -71,6 +71,7 @@ class CEngine : public IEngine ~CEngine() override { m_JobPool.Destroy(); + CNetBase::CloseLog(); } void Init() override From 9a1644982e120750210bc9cd6b84a4ce8d057181 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Wed, 13 Dec 2023 11:58:29 +0100 Subject: [PATCH 107/198] Remove ddnet.appdata.xml It was outdated already. Fixes #7607. --- CMakeLists.txt | 1 - other/ddnet.appdata.xml | 91 ----------------------------------------- 2 files changed, 92 deletions(-) delete mode 100644 other/ddnet.appdata.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index bf2c7bb09e9..8e0b2860c66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3084,7 +3084,6 @@ if(NOT DEV) endif() install(TARGETS ${TARGETS_TOOLS} DESTINATION ${CMAKE_INSTALL_LIBDIR}/ddnet COMPONENT tools) install(FILES other/ddnet.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications COMPONENT client) - install(FILES other/ddnet.appdata.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo COMPONENT client) foreach(SIZE 16 32 48 256) install(FILES other/icons/DDNet_${SIZE}x${SIZE}x32.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME ddnet.png COMPONENT client) install(FILES other/icons/DDNet-Server_${SIZE}x${SIZE}x32.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME ddnet-server.png COMPONENT server) diff --git a/other/ddnet.appdata.xml b/other/ddnet.appdata.xml deleted file mode 100644 index 1cf1e0810f6..00000000000 --- a/other/ddnet.appdata.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - ddnet.desktop - CC0-1.0 - CC-BY-SA-3.0 and Apache-2.0 - DDNet - - DDraceNetwork, a cooperative racing mod of Teeworlds - - -

- DDraceNetwork is an actively maintained version of DDRace, - a Teeworlds modification with a unique cooperative gameplay. - Help each other play through custom maps with up to 64 players, - compete against the best in international tournaments, design your - own maps, or run your own server. -

-
- - - https://ddnet.org/screenshots/1.png - - - https://ddnet.org/screenshots/2.png - - - https://ddnet.org/screenshots/3.png - - - - multiplayer - teeworlds - game - - - moderate - intense - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - https://ddnet.org/ - https://github.com/ddnet/ddnet/issues - https://www.paypal.me/ddnet - - DDNet - -
From 09e222c39f8b3388566567bb34ea839951fc9a39 Mon Sep 17 00:00:00 2001 From: furo Date: Wed, 13 Dec 2023 13:23:33 +0100 Subject: [PATCH 108/198] Set correct colour for authed colour reset button --- src/game/client/components/menus_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index a5e2fd73647..7a54d9797ac 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2978,7 +2978,7 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) } Section.HSplitTop(LineSize, &Button, &Section); - ColorRGBA GreenDefault(0.7f, 1.0f, 0.7f, 1); + ColorRGBA GreenDefault(0.78f, 1.0f, 0.8f, 1.0f); static CButtonContainer s_AuthedColor; DoLine_ColorPicker(&s_AuthedColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Authed name color in scoreboard"), &g_Config.m_ClAuthedPlayerColor, GreenDefault, false); } From 157e8b7302c05927ffa2f6b0f0576b884c6f6b46 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Tue, 12 Dec 2023 23:20:30 +0100 Subject: [PATCH 109/198] Pass error message as buffer to CanJoinTeam() Now `CGameContext` no longer assumes the `IGameController` declined the team join due to slots. This enables custom gametypes to disallow joining the game if the player died, an active tournament is running or the player is not logged in yet. And then the controller can print the correct error message accordingly. --- src/game/server/gamecontext.cpp | 24 ++++++++---------------- src/game/server/gamecontroller.cpp | 20 ++++++++++++++++---- src/game/server/gamecontroller.h | 2 +- src/game/server/player.cpp | 2 +- src/game/server/player.h | 2 +- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 56b7f3e4440..cf13e81add5 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -2389,24 +2389,16 @@ void CGameContext::OnSetTeamNetMessage(const CNetMsg_Cl_SetTeam *pMsg, int Clien } // Switch team on given client and kill/respawn them - if(m_pController->CanJoinTeam(pMsg->m_Team, ClientID)) + char aTeamJoinError[512]; + if(m_pController->CanJoinTeam(pMsg->m_Team, ClientID, aTeamJoinError, sizeof(aTeamJoinError))) { - if(pPlayer->IsPaused()) - SendChatTarget(ClientID, "Use /pause first then you can kill"); - else - { - if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS) - m_VoteUpdate = true; - m_pController->DoTeamChange(pPlayer, pMsg->m_Team); - pPlayer->m_TeamChangeTick = Server()->Tick(); - } - } - else - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "Only %d active players are allowed", Server()->MaxClients() - g_Config.m_SvSpectatorSlots); - SendBroadcast(aBuf, ClientID); + if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS) + m_VoteUpdate = true; + m_pController->DoTeamChange(pPlayer, pMsg->m_Team); + pPlayer->m_TeamChangeTick = Server()->Tick(); } + if(aTeamJoinError[0]) + SendBroadcast(aTeamJoinError, ClientID); } void CGameContext::OnIsDDNetLegacyNetMessage(const CNetMsg_Cl_IsDDNetLegacy *pMsg, int ClientID, CUnpacker *pUnpacker) diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 270dc6a0edf..138c36e18a0 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -710,14 +710,21 @@ int IGameController::GetAutoTeam(int NotThisID) int Team = 0; - if(CanJoinTeam(Team, NotThisID)) + if(CanJoinTeam(Team, NotThisID, nullptr, 0)) return Team; return -1; } -bool IGameController::CanJoinTeam(int Team, int NotThisID) +bool IGameController::CanJoinTeam(int Team, int NotThisID, char *pErrorReason, int ErrorReasonSize) { - if(Team == TEAM_SPECTATORS || (GameServer()->m_apPlayers[NotThisID] && GameServer()->m_apPlayers[NotThisID]->GetTeam() != TEAM_SPECTATORS)) + const CPlayer *pPlayer = GameServer()->m_apPlayers[NotThisID]; + if(pPlayer && pPlayer->IsPaused()) + { + if(pErrorReason) + str_copy(pErrorReason, "Use /pause first then you can kill", ErrorReasonSize); + return false; + } + if(Team == TEAM_SPECTATORS || (pPlayer && pPlayer->GetTeam() != TEAM_SPECTATORS)) return true; int aNumplayers[2] = {0, 0}; @@ -730,7 +737,12 @@ bool IGameController::CanJoinTeam(int Team, int NotThisID) } } - return (aNumplayers[0] + aNumplayers[1]) < Server()->MaxClients() - g_Config.m_SvSpectatorSlots; + if((aNumplayers[0] + aNumplayers[1]) < Server()->MaxClients() - g_Config.m_SvSpectatorSlots) + return true; + + if(pErrorReason) + str_format(pErrorReason, ErrorReasonSize, "Only %d active players are allowed", Server()->MaxClients() - g_Config.m_SvSpectatorSlots); + return false; } int IGameController::ClampTeam(int Team) diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h index 81c3fa33b89..a3044d151a2 100644 --- a/src/game/server/gamecontroller.h +++ b/src/game/server/gamecontroller.h @@ -145,7 +145,7 @@ class IGameController */ virtual const char *GetTeamName(int Team); virtual int GetAutoTeam(int NotThisID); - virtual bool CanJoinTeam(int Team, int NotThisID); + virtual bool CanJoinTeam(int Team, int NotThisID, char *pErrorReason, int ErrorReasonSize); int ClampTeam(int Team); CClientMask GetMaskForPlayerWorldEvent(int Asker, int ExceptID = -1); diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index a98192c20b6..11dab99af65 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -832,7 +832,7 @@ int CPlayer::ForcePause(int Time) return Pause(PAUSE_SPEC, true); } -int CPlayer::IsPaused() +int CPlayer::IsPaused() const { return m_ForcePauseTime ? m_ForcePauseTime : -1 * m_Paused; } diff --git a/src/game/server/player.h b/src/game/server/player.h index 1f050cb7802..4c9224c215c 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -176,7 +176,7 @@ class CPlayer void ProcessPause(); int Pause(int State, bool Force); int ForcePause(int Time); - int IsPaused(); + int IsPaused() const; bool IsPlaying(); int64_t m_Last_KickVote; From 6521344ecaa811db595284b96845b9c2d5ea3e58 Mon Sep 17 00:00:00 2001 From: BlaiZephyr Date: Wed, 13 Dec 2023 15:46:49 +0100 Subject: [PATCH 110/198] =?UTF-8?q?changed=20GFX=20=E2=86=92=20Gfx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/engine/client/backend/backend_base.h | 22 +-- .../client/backend/vulkan/backend_vulkan.cpp | 126 +++++++++--------- src/engine/client/backend_sdl.cpp | 22 +-- src/engine/client/backend_sdl.h | 16 +-- 4 files changed, 93 insertions(+), 93 deletions(-) diff --git a/src/engine/client/backend/backend_base.h b/src/engine/client/backend/backend_base.h index 25af6a68412..19b7eba6f27 100644 --- a/src/engine/client/backend/backend_base.h +++ b/src/engine/client/backend/backend_base.h @@ -15,7 +15,7 @@ struct SBackendCapabilites; -enum EDebugGFXModes +enum EDebugGfxModes { DEBUG_GFX_MODE_NONE = 0, DEBUG_GFX_MODE_MINIMUM, @@ -32,7 +32,7 @@ enum ERunCommandReturnTypes RUN_COMMAND_COMMAND_ERROR, }; -enum EGFXErrorType +enum EGfxErrorType { GFX_ERROR_TYPE_NONE = 0, GFX_ERROR_TYPE_INIT, @@ -46,7 +46,7 @@ enum EGFXErrorType GFX_ERROR_TYPE_UNKNOWN, }; -enum EGFXWarningType +enum EGfxWarningType { GFX_WARNING_TYPE_NONE = 0, GFX_WARNING_TYPE_INIT_FAILED, @@ -56,7 +56,7 @@ enum EGFXWarningType GFX_WARNING_TYPE_UNKNOWN, }; -struct SGFXErrorContainer +struct SGfxErrorContainer { struct SError { @@ -68,21 +68,21 @@ struct SGFXErrorContainer return m_RequiresTranslation == Other.m_RequiresTranslation && m_Err == Other.m_Err; } }; - EGFXErrorType m_ErrorType = EGFXErrorType::GFX_ERROR_TYPE_NONE; + EGfxErrorType m_ErrorType = EGfxErrorType::GFX_ERROR_TYPE_NONE; std::vector m_vErrors; }; -struct SGFXWarningContainer +struct SGfxWarningContainer { - EGFXWarningType m_WarningType = EGFXWarningType::GFX_WARNING_TYPE_NONE; + EGfxWarningType m_WarningType = EGfxWarningType::GFX_WARNING_TYPE_NONE; std::vector m_vWarnings; }; class CCommandProcessorFragment_GLBase { protected: - SGFXErrorContainer m_Error; - SGFXWarningContainer m_Warning; + SGfxErrorContainer m_Error; + SGfxWarningContainer m_Warning; static void *Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP); @@ -97,10 +97,10 @@ class CCommandProcessorFragment_GLBase virtual void StartCommands(size_t CommandCount, size_t EstimatedRenderCallCount) {} virtual void EndCommands() {} - const SGFXErrorContainer &GetError() { return m_Error; } + const SGfxErrorContainer &GetError() { return m_Error; } virtual void ErroneousCleanup() {} - const SGFXWarningContainer &GetWarning() { return m_Warning; } + const SGfxWarningContainer &GetWarning() { return m_Warning; } enum { diff --git a/src/engine/client/backend/vulkan/backend_vulkan.cpp b/src/engine/client/backend/vulkan/backend_vulkan.cpp index 63b32af2264..b7b8b41cce4 100644 --- a/src/engine/client/backend/vulkan/backend_vulkan.cpp +++ b/src/engine/client/backend/vulkan/backend_vulkan.cpp @@ -1099,15 +1099,15 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase * After an error occurred, the rendering stop as soon as possible * Always stop the current code execution after a call to this function (e.g. return false) */ - void SetError(EGFXErrorType ErrType, const char *pErr, const char *pErrStrExtra = nullptr) + void SetError(EGfxErrorType ErrType, const char *pErr, const char *pErrStrExtra = nullptr) { std::unique_lock Lock(m_ErrWarnMutex); - SGFXErrorContainer::SError Err = {false, pErr}; + SGfxErrorContainer::SError Err = {false, pErr}; if(std::find(m_Error.m_vErrors.begin(), m_Error.m_vErrors.end(), Err) == m_Error.m_vErrors.end()) m_Error.m_vErrors.emplace_back(Err); if(pErrStrExtra != nullptr) { - SGFXErrorContainer::SError ErrExtra = {false, pErrStrExtra}; + SGfxErrorContainer::SError ErrExtra = {false, pErrStrExtra}; if(std::find(m_Error.m_vErrors.begin(), m_Error.m_vErrors.end(), ErrExtra) == m_Error.m_vErrors.end()) m_Error.m_vErrors.emplace_back(ErrExtra); } @@ -1125,7 +1125,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase Lock.unlock(); // during initialization vulkan should not throw any errors but warnings instead // since most code in the swapchain is shared with runtime code, add this extra code path - SetWarning(EGFXWarningType::GFX_WARNING_TYPE_INIT_FAILED, pErr); + SetWarning(EGfxWarningType::GFX_WARNING_TYPE_INIT_FAILED, pErr); } } @@ -1136,7 +1136,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase m_Warning.m_vWarnings.emplace(m_Warning.m_vWarnings.begin(), pWarningPre); } - void SetWarning(EGFXWarningType WarningType, const char *pWarning) + void SetWarning(EGfxWarningType WarningType, const char *pWarning) { std::unique_lock Lock(m_ErrWarnMutex); dbg_msg("vulkan", "vulkan warning: %s", pWarning); @@ -1200,10 +1200,10 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase dbg_msg("vulkan", "%s", pCriticalError); break; case VK_ERROR_LAYER_NOT_PRESENT: - SetWarning(EGFXWarningType::GFX_WARNING_MISSING_EXTENSION, "One Vulkan layer was not present. (try to disable them)"); + SetWarning(EGfxWarningType::GFX_WARNING_MISSING_EXTENSION, "One Vulkan layer was not present. (try to disable them)"); break; case VK_ERROR_EXTENSION_NOT_PRESENT: - SetWarning(EGFXWarningType::GFX_WARNING_MISSING_EXTENSION, "One Vulkan extension was not present. (try to disable them)"); + SetWarning(EGfxWarningType::GFX_WARNING_MISSING_EXTENSION, "One Vulkan extension was not present. (try to disable them)"); break; case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: dbg_msg("vulkan", "native window in use"); @@ -1626,7 +1626,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase { if(vkMapMemory(m_VKDevice, TmpBufferMemory.m_Mem, 0, VK_WHOLE_SIZE, 0, &pMapData) != VK_SUCCESS) { - SetError(RequiresMapping ? EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Failed to map buffer block memory.", + SetError(RequiresMapping ? EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Failed to map buffer block memory.", GetMemoryUsageShort()); delete pNewHeap; return false; @@ -1643,7 +1643,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase Heaps.back()->m_Heap.Init(MemoryBlockSize * BlockCount, 0); if(!Heaps.back()->m_Heap.Allocate(RequiredSize, TargetAlignment, AllocatedMem)) { - SetError(RequiresMapping ? EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Heap allocation failed directly after creating fresh heap.", + SetError(RequiresMapping ? EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Heap allocation failed directly after creating fresh heap.", GetMemoryUsageShort()); return false; } @@ -1800,7 +1800,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(!AllocateVulkanMemory(&MemAllocInfo, &BufferMemory.m_Mem)) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE, "Allocation for image memory failed.", + SetError(EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE, "Allocation for image memory failed.", GetMemoryUsageShort()); return false; } @@ -2256,7 +2256,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkEndCommandBuffer(CommandBuffer) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be ended anymore."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be ended anymore."); return false; } @@ -2301,7 +2301,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase const char *pCritErrorMsg = CheckVulkanCriticalError(QueueSubmitRes); if(pCritErrorMsg != nullptr) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED, "Submitting to graphics queue failed.", pCritErrorMsg); + SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED, "Submitting to graphics queue failed.", pCritErrorMsg); return false; } } @@ -2328,7 +2328,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase const char *pCritErrorMsg = CheckVulkanCriticalError(QueuePresentRes); if(pCritErrorMsg != nullptr) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_SWAP_FAILED, "Presenting graphics queue failed.", pCritErrorMsg); + SetError(EGfxErrorType::GFX_ERROR_TYPE_SWAP_FAILED, "Presenting graphics queue failed.", pCritErrorMsg); return false; } } @@ -2370,7 +2370,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase const char *pCritErrorMsg = CheckVulkanCriticalError(AcqResult); if(pCritErrorMsg != nullptr) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_SWAP_FAILED, "Acquiring next image failed.", pCritErrorMsg); + SetError(EGfxErrorType::GFX_ERROR_TYPE_SWAP_FAILED, "Acquiring next image failed.", pCritErrorMsg); return false; } else if(AcqResult == VK_ERROR_SURFACE_LOST_KHR) @@ -2421,7 +2421,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkBeginCommandBuffer(CommandBuffer, &BeginInfo) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be filled anymore."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be filled anymore."); return false; } @@ -3485,14 +3485,14 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase unsigned int ExtCount = 0; if(!SDL_Vulkan_GetInstanceExtensions(pWindow, &ExtCount, nullptr)) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get instance extensions from SDL."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get instance extensions from SDL."); return false; } std::vector vExtensionList(ExtCount); if(!SDL_Vulkan_GetInstanceExtensions(pWindow, &ExtCount, vExtensionList.data())) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get instance extensions from SDL."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get instance extensions from SDL."); return false; } @@ -3541,7 +3541,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase VkResult Res = vkEnumerateInstanceLayerProperties(&LayerCount, NULL); if(Res != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get vulkan layers."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get vulkan layers."); return false; } @@ -3549,7 +3549,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase Res = vkEnumerateInstanceLayerProperties(&LayerCount, vVKInstanceLayers.data()); if(Res != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get vulkan layers."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get vulkan layers."); return false; } @@ -3626,7 +3626,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase const char *pCritErrorMsg = CheckVulkanCriticalError(Res); if(pCritErrorMsg != nullptr) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating instance failed.", pCritErrorMsg); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating instance failed.", pCritErrorMsg); return false; } else if(Res == VK_ERROR_LAYER_NOT_PRESENT || Res == VK_ERROR_EXTENSION_NOT_PRESENT) @@ -3693,12 +3693,12 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase auto Res = vkEnumeratePhysicalDevices(m_VKInstance, &DevicesCount, nullptr); if(Res != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, CheckVulkanCriticalError(Res)); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, CheckVulkanCriticalError(Res)); return false; } if(DevicesCount == 0) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "No vulkan compatible devices found."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "No vulkan compatible devices found."); return false; } @@ -3706,12 +3706,12 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase Res = vkEnumeratePhysicalDevices(m_VKInstance, &DevicesCount, vDeviceList.data()); if(Res != VK_SUCCESS && Res != VK_INCOMPLETE) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, CheckVulkanCriticalError(Res)); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, CheckVulkanCriticalError(Res)); return false; } if(DevicesCount == 0) { - SetWarning(EGFXWarningType::GFX_WARNING_TYPE_INIT_FAILED_MISSING_INTEGRATED_GPU_DRIVER, "No vulkan compatible devices found."); + SetWarning(EGfxWarningType::GFX_WARNING_TYPE_INIT_FAILED_MISSING_INTEGRATED_GPU_DRIVER, "No vulkan compatible devices found."); return false; } // make sure to use the correct amount of devices available @@ -3833,7 +3833,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase vkGetPhysicalDeviceQueueFamilyProperties(CurDevice, &FamQueueCount, nullptr); if(FamQueueCount == 0) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "No vulkan queue family properties found."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "No vulkan queue family properties found."); return false; } @@ -3855,7 +3855,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(QueueNodeIndex == std::numeric_limits::max()) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "No vulkan queue found that matches the requirements: graphics queue."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "No vulkan queue found that matches the requirements: graphics queue."); return false; } @@ -3874,14 +3874,14 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase uint32_t DevPropCount = 0; if(vkEnumerateDeviceExtensionProperties(m_VKGPU, NULL, &DevPropCount, NULL) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Querying logical device extension properties failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Querying logical device extension properties failed."); return false; } std::vector vDevPropList(DevPropCount); if(vkEnumerateDeviceExtensionProperties(m_VKGPU, NULL, &DevPropCount, vDevPropList.data()) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Querying logical device extension properties failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Querying logical device extension properties failed."); return false; } @@ -3921,7 +3921,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase VkResult res = vkCreateDevice(m_VKGPU, &VKCreateInfo, nullptr, &m_VKDevice); if(res != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Logical device could not be created."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Logical device could not be created."); return false; } @@ -3933,7 +3933,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(!SDL_Vulkan_CreateSurface(pWindow, m_VKInstance, &m_VKPresentSurface)) { dbg_msg("vulkan", "error from sdl: %s", SDL_GetError()); - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating a vulkan surface for the SDL window failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating a vulkan surface for the SDL window failed."); return false; } @@ -3941,7 +3941,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase vkGetPhysicalDeviceSurfaceSupportKHR(m_VKGPU, m_VKGraphicsQueueIndex, m_VKPresentSurface, &IsSupported); if(!IsSupported) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface does not support presenting the framebuffer to a screen. (maybe the wrong GPU was selected?)"); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface does not support presenting the framebuffer to a screen. (maybe the wrong GPU was selected?)"); return false; } @@ -3958,14 +3958,14 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase uint32_t PresentModeCount = 0; if(vkGetPhysicalDeviceSurfacePresentModesKHR(m_VKGPU, m_VKPresentSurface, &PresentModeCount, NULL) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface presentation modes could not be fetched."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface presentation modes could not be fetched."); return false; } std::vector vPresentModeList(PresentModeCount); if(vkGetPhysicalDeviceSurfacePresentModesKHR(m_VKGPU, m_VKPresentSurface, &PresentModeCount, vPresentModeList.data()) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface presentation modes could not be fetched."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface presentation modes could not be fetched."); return false; } @@ -3995,7 +3995,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase { if(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_VKGPU, m_VKPresentSurface, &VKSurfCapabilities) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface capabilities could not be fetched."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface capabilities could not be fetched."); return false; } return true; @@ -4047,7 +4047,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase std::vector vOurImgUsages = OurImageUsages(); if(vOurImgUsages.empty()) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Framebuffer image attachment types not supported."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Framebuffer image attachment types not supported."); return false; } @@ -4058,7 +4058,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase VkImageUsageFlags ImgUsageFlags = ImgUsage & VKCapabilities.supportedUsageFlags; if(ImgUsageFlags != ImgUsage) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Framebuffer image attachment types not supported."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Framebuffer image attachment types not supported."); return false; } @@ -4081,7 +4081,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase VkResult Res = vkGetPhysicalDeviceSurfaceFormatsKHR(m_VKGPU, m_VKPresentSurface, &SurfFormats, nullptr); if(Res != VK_SUCCESS && Res != VK_INCOMPLETE) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface format fetching failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface format fetching failed."); return false; } @@ -4089,7 +4089,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase Res = vkGetPhysicalDeviceSurfaceFormatsKHR(m_VKGPU, m_VKPresentSurface, &SurfFormats, vSurfFormatList.data()); if(Res != VK_SUCCESS && Res != VK_INCOMPLETE) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "The device surface format fetching failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "The device surface format fetching failed."); return false; } @@ -4175,7 +4175,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase const char *pCritErrorMsg = CheckVulkanCriticalError(SwapchainCreateRes); if(pCritErrorMsg != nullptr) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the swap chain failed.", pCritErrorMsg); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the swap chain failed.", pCritErrorMsg); return false; } else if(SwapchainCreateRes == VK_ERROR_NATIVE_WINDOW_IN_USE_KHR) @@ -4199,7 +4199,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase VkResult res = vkGetSwapchainImagesKHR(m_VKDevice, m_VKSwapChain, &ImgCount, nullptr); if(res != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get swap chain images."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get swap chain images."); return false; } @@ -4208,7 +4208,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase m_vSwapChainImages.resize(ImgCount); if(vkGetSwapchainImagesKHR(m_VKDevice, m_VKSwapChain, &ImgCount, m_vSwapChainImages.data()) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not get swap chain images."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not get swap chain images."); return false; } @@ -4316,7 +4316,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateImageView(m_VKDevice, &CreateInfo, nullptr, &m_vSwapChainImageViewList[i]) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Could not create image views for the swap chain framebuffers."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Could not create image views for the swap chain framebuffers."); return false; } } @@ -4425,7 +4425,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateRenderPass(m_VKDevice, &CreateRenderPassInfo, nullptr, &m_VKRenderPass) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the render pass failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the render pass failed."); return false; } @@ -4460,7 +4460,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateFramebuffer(m_VKDevice, &FramebufferInfo, nullptr, &m_vFramebufferList[i]) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the framebuffers failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the framebuffers failed."); return false; } } @@ -4487,7 +4487,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateShaderModule(m_VKDevice, &CreateInfo, nullptr, &ShaderModule) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Shader module was not created."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Shader module was not created."); return false; } @@ -4511,13 +4511,13 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateDescriptorSetLayout(m_VKDevice, &LayoutInfo, nullptr, &m_StandardTexturedDescriptorSetLayout) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed."); return false; } if(vkCreateDescriptorSetLayout(m_VKDevice, &LayoutInfo, nullptr, &m_Standard3DTexturedDescriptorSetLayout) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed."); return false; } return true; @@ -4565,7 +4565,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(!ShaderLoaded) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "A shader file could not load correctly."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "A shader file could not load correctly."); return false; } @@ -4705,7 +4705,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreatePipelineLayout(m_VKDevice, &PipelineLayoutInfo, nullptr, &PipeLayout) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating pipeline layout failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating pipeline layout failed."); return false; } @@ -4741,7 +4741,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateGraphicsPipelines(m_VKDevice, VK_NULL_HANDLE, 1, &PipelineInfo, nullptr, &Pipeline) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the graphic pipeline failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the graphic pipeline failed."); return false; } @@ -4834,7 +4834,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateDescriptorSetLayout(m_VKDevice, &LayoutInfo, nullptr, &m_TextDescriptorSetLayout) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed."); return false; } @@ -4977,7 +4977,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateDescriptorSetLayout(m_VKDevice, &LayoutInfo, nullptr, &SetLayout) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating descriptor layout failed."); return false; } return true; @@ -5217,7 +5217,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase { if(vkCreateCommandPool(m_VKDevice, &CreatePoolInfo, nullptr, &m_vCommandPools[i]) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the command pool failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the command pool failed."); return false; } } @@ -5260,7 +5260,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkAllocateCommandBuffers(m_VKDevice, &AllocInfo, m_vMainDrawCommandBuffers.data()) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Allocating command buffers failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Allocating command buffers failed."); return false; } @@ -5268,7 +5268,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkAllocateCommandBuffers(m_VKDevice, &AllocInfo, m_vMemoryCommandBuffers.data()) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Allocating memory command buffers failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Allocating memory command buffers failed."); return false; } @@ -5283,7 +5283,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase AllocInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; if(vkAllocateCommandBuffers(m_VKDevice, &AllocInfo, ThreadDrawCommandBuffers.data()) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Allocating thread command buffers failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Allocating thread command buffers failed."); return false; } } @@ -5340,7 +5340,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase vkCreateSemaphore(m_VKDevice, &CreateSemaphoreInfo, nullptr, &m_vMemorySemaphores[i]) != VK_SUCCESS || vkCreateFence(m_VKDevice, &FenceInfo, nullptr, &m_vFrameFences[i]) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating swap chain sync objects(fences, semaphores) failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating swap chain sync objects(fences, semaphores) failed."); return false; } } @@ -5635,7 +5635,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateBuffer(m_VKDevice, &BufferInfo, nullptr, &VKBuffer) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Buffer creation failed.", + SetError(EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Buffer creation failed.", GetMemoryUsageShort()); return false; } @@ -5664,7 +5664,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(!AllocateVulkanMemory(&MemAllocInfo, &VKBufferMemory.m_Mem)) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Allocation for buffer object failed.", + SetError(EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Allocation for buffer object failed.", GetMemoryUsageShort()); return false; } @@ -5673,7 +5673,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkBindBufferMemory(m_VKDevice, VKBuffer, VKBufferMemory.m_Mem, 0) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Binding memory to buffer failed.", + SetError(EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, "Binding memory to buffer failed.", GetMemoryUsageShort()); return false; } @@ -5702,7 +5702,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkCreateDescriptorPool(m_VKDevice, &PoolInfo, nullptr, &NewPool.m_Pool) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_INIT, "Creating the descriptor pool failed."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_INIT, "Creating the descriptor pool failed."); return false; } @@ -6205,7 +6205,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase BeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; if(vkBeginCommandBuffer(MemCommandBuffer, &BeginInfo) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be filled anymore."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Command buffer cannot be filled anymore."); return false; } } @@ -6244,7 +6244,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase if(vkBeginCommandBuffer(DrawCommandBuffer, &BeginInfo) != VK_SUCCESS) { - SetError(EGFXErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Thread draw command buffer cannot be filled anymore."); + SetError(EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, "Thread draw command buffer cannot be filled anymore."); return false; } } diff --git a/src/engine/client/backend_sdl.cpp b/src/engine/client/backend_sdl.cpp index 142c7b82113..ab5c402d951 100644 --- a/src/engine/client/backend_sdl.cpp +++ b/src/engine/client/backend_sdl.cpp @@ -108,7 +108,7 @@ void CGraphicsBackend_Threaded::StopProcessor() void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer) { - SGFXErrorContainer Error; + SGfxErrorContainer Error; #ifdef CONF_WEBASM // run everything single threaded for now, context binding in a thread seems to not work as of now Error = m_pProcessor->GetError(); @@ -153,7 +153,7 @@ void CGraphicsBackend_Threaded::WaitForIdle() m_BufferSwapCond.wait(Lock, [this]() { return m_pBuffer == nullptr; }); } -void CGraphicsBackend_Threaded::ProcessError(const SGFXErrorContainer &Error) +void CGraphicsBackend_Threaded::ProcessError(const SGfxErrorContainer &Error) { std::string VerboseStr = "Graphics Assertion:"; for(const auto &ErrStr : Error.m_vErrors) @@ -274,31 +274,31 @@ void CCommandProcessor_SDL_GL::HandleError() switch(m_Error.m_ErrorType) { case GFX_ERROR_TYPE_INIT: - m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", "Graphics error")}); + m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", "Graphics error")}); break; case GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE: [[fallthrough]]; case GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER: [[fallthrough]]; case GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING: - m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution.", "Graphics error")}); + m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution.", "Graphics error")}); break; case GFX_ERROR_TYPE_RENDER_RECORDING: - m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("An error during command recording occurred. Try to update your GPU drivers.", "Graphics error")}); + m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("An error during command recording occurred. Try to update your GPU drivers.", "Graphics error")}); break; case GFX_ERROR_TYPE_RENDER_CMD_FAILED: - m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("A render command failed. Try to update your GPU drivers.", "Graphics error")}); + m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("A render command failed. Try to update your GPU drivers.", "Graphics error")}); break; case GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED: - m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Submitting the render commands failed. Try to update your GPU drivers.", "Graphics error")}); + m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Submitting the render commands failed. Try to update your GPU drivers.", "Graphics error")}); break; case GFX_ERROR_TYPE_SWAP_FAILED: - m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Failed to swap framebuffers. Try to update your GPU drivers.", "Graphics error")}); + m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Failed to swap framebuffers. Try to update your GPU drivers.", "Graphics error")}); break; case GFX_ERROR_TYPE_UNKNOWN: [[fallthrough]]; default: - m_Error.m_vErrors.emplace_back(SGFXErrorContainer::SError{true, Localizable("Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", "Graphics error")}); + m_Error.m_vErrors.emplace_back(SGfxErrorContainer::SError{true, Localizable("Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", "Graphics error")}); break; } } @@ -416,7 +416,7 @@ CCommandProcessor_SDL_GL::~CCommandProcessor_SDL_GL() delete m_pGLBackend; } -const SGFXErrorContainer &CCommandProcessor_SDL_GL::GetError() const +const SGfxErrorContainer &CCommandProcessor_SDL_GL::GetError() const { return m_Error; } @@ -426,7 +426,7 @@ void CCommandProcessor_SDL_GL::ErroneousCleanup() return m_pGLBackend->ErroneousCleanup(); } -const SGFXWarningContainer &CCommandProcessor_SDL_GL::GetWarning() const +const SGfxWarningContainer &CCommandProcessor_SDL_GL::GetWarning() const { return m_Warning; } diff --git a/src/engine/client/backend_sdl.h b/src/engine/client/backend_sdl.h index 8a28dc496f1..952ba052c9c 100644 --- a/src/engine/client/backend_sdl.h +++ b/src/engine/client/backend_sdl.h @@ -48,7 +48,7 @@ class CGraphicsBackend_Threaded : public IGraphicsBackend { private: TTranslateFunc m_TranslateFunc; - SGFXWarningContainer m_Warning; + SGfxWarningContainer m_Warning; public: // constructed on the main thread, the rest of the functions is run on the render thread @@ -58,10 +58,10 @@ class CGraphicsBackend_Threaded : public IGraphicsBackend virtual ~ICommandProcessor() = default; virtual void RunBuffer(CCommandBuffer *pBuffer) = 0; - virtual const SGFXErrorContainer &GetError() const = 0; + virtual const SGfxErrorContainer &GetError() const = 0; virtual void ErroneousCleanup() = 0; - virtual const SGFXWarningContainer &GetWarning() const = 0; + virtual const SGfxWarningContainer &GetWarning() const = 0; }; CGraphicsBackend_Threaded(TTranslateFunc &&TranslateFunc); @@ -71,7 +71,7 @@ class CGraphicsBackend_Threaded : public IGraphicsBackend bool IsIdle() const override; void WaitForIdle() override; - void ProcessError(const SGFXErrorContainer &Error); + void ProcessError(const SGfxErrorContainer &Error); protected: void StartProcessor(ICommandProcessor *pProcessor); @@ -181,18 +181,18 @@ class CCommandProcessor_SDL_GL : public CGraphicsBackend_Threaded::ICommandProce EBackendType m_BackendType; - SGFXErrorContainer m_Error; - SGFXWarningContainer m_Warning; + SGfxErrorContainer m_Error; + SGfxWarningContainer m_Warning; public: CCommandProcessor_SDL_GL(EBackendType BackendType, int GLMajor, int GLMinor, int GLPatch); virtual ~CCommandProcessor_SDL_GL(); void RunBuffer(CCommandBuffer *pBuffer) override; - const SGFXErrorContainer &GetError() const override; + const SGfxErrorContainer &GetError() const override; void ErroneousCleanup() override; - const SGFXWarningContainer &GetWarning() const override; + const SGfxWarningContainer &GetWarning() const override; void HandleError(); void HandleWarning(); From 172355428891e461ac049d4c31904357da58712f Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Wed, 13 Dec 2023 16:05:13 +0100 Subject: [PATCH 111/198] Move switcher snapping code to own method --- src/game/server/gamecontext.cpp | 54 ++++++++++++++++++++++++++++++ src/game/server/gamecontext.h | 1 + src/game/server/gamecontroller.cpp | 50 +-------------------------- 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 56b7f3e4440..d1d79041fa4 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -380,6 +380,60 @@ void CGameContext::CreateSoundGlobal(int Sound, int Target) } } +void CGameContext::SnapSwitchers(int SnappingClient) +{ + if(Switchers().empty()) + return; + + CPlayer *pPlayer = SnappingClient != SERVER_DEMO_CLIENT ? m_apPlayers[SnappingClient] : 0; + int Team = pPlayer && pPlayer->GetCharacter() ? pPlayer->GetCharacter()->Team() : 0; + + if(pPlayer && (pPlayer->GetTeam() == TEAM_SPECTATORS || pPlayer->IsPaused()) && pPlayer->m_SpectatorID != SPEC_FREEVIEW && m_apPlayers[pPlayer->m_SpectatorID] && m_apPlayers[pPlayer->m_SpectatorID]->GetCharacter()) + Team = m_apPlayers[pPlayer->m_SpectatorID]->GetCharacter()->Team(); + + if(Team == TEAM_SUPER) + return; + + int SentTeam = Team; + if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) + SentTeam = 0; + + CNetObj_SwitchState *pSwitchState = Server()->SnapNewItem(SentTeam); + if(!pSwitchState) + return; + + pSwitchState->m_HighestSwitchNumber = clamp((int)Switchers().size() - 1, 0, 255); + mem_zero(pSwitchState->m_aStatus, sizeof(pSwitchState->m_aStatus)); + + std::vector> vEndTicks; // + + for(int i = 0; i <= pSwitchState->m_HighestSwitchNumber; i++) + { + int Status = (int)Switchers()[i].m_aStatus[Team]; + pSwitchState->m_aStatus[i / 32] |= (Status << (i % 32)); + + int EndTick = Switchers()[i].m_aEndTick[Team]; + if(EndTick > 0 && EndTick < Server()->Tick() + 3 * Server()->TickSpeed() && Switchers()[i].m_aLastUpdateTick[Team] < Server()->Tick()) + { + // only keep track of EndTicks that have less than three second left and are not currently being updated by a player being present on a switch tile, to limit how often these are sent + vEndTicks.emplace_back(Switchers()[i].m_aEndTick[Team], i); + } + } + + // send the endtick of switchers that are about to toggle back (up to four, prioritizing those with the earliest endticks) + mem_zero(pSwitchState->m_aSwitchNumbers, sizeof(pSwitchState->m_aSwitchNumbers)); + mem_zero(pSwitchState->m_aEndTicks, sizeof(pSwitchState->m_aEndTicks)); + + std::sort(vEndTicks.begin(), vEndTicks.end()); + const int NumTimedSwitchers = minimum((int)vEndTicks.size(), (int)std::size(pSwitchState->m_aEndTicks)); + + for(int i = 0; i < NumTimedSwitchers; i++) + { + pSwitchState->m_aSwitchNumbers[i] = vEndTicks[i].second; + pSwitchState->m_aEndTicks[i] = vEndTicks[i].first; + } +} + bool CGameContext::SnapLaserObject(const CSnapContext &Context, int SnapID, const vec2 &To, const vec2 &From, int StartTick, int Owner, int LaserType, int Subtype, int SwitchNumber) { if(Context.GetClientVersion() >= VERSION_DDNET_MULTI_LASER) diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index d521bfbcdd9..caf2919b8b2 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -239,6 +239,7 @@ class CGameContext : public IGameServer void CreateSound(vec2 Pos, int Sound, CClientMask Mask = CClientMask().set()); void CreateSoundGlobal(int Sound, int Target = -1); + void SnapSwitchers(int SnappingClient); bool SnapLaserObject(const CSnapContext &Context, int SnapID, const vec2 &To, const vec2 &From, int StartTick, int Owner = -1, int LaserType = -1, int Subtype = -1, int SwitchNumber = -1); bool SnapPickup(const CSnapContext &Context, int SnapID, const vec2 &Pos, int Type, int SubType, int SwitchNumber); diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 270dc6a0edf..d0fa66db9cd 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -645,55 +645,7 @@ void IGameController::Snap(int SnappingClient) pRaceData->m_RaceFlags = protocol7::RACEFLAG_HIDE_KILLMSG | protocol7::RACEFLAG_KEEP_WANTED_WEAPON; } - if(!GameServer()->Switchers().empty()) - { - int Team = pPlayer && pPlayer->GetCharacter() ? pPlayer->GetCharacter()->Team() : 0; - - if(pPlayer && (pPlayer->GetTeam() == TEAM_SPECTATORS || pPlayer->IsPaused()) && pPlayer->m_SpectatorID != SPEC_FREEVIEW && GameServer()->m_apPlayers[pPlayer->m_SpectatorID] && GameServer()->m_apPlayers[pPlayer->m_SpectatorID]->GetCharacter()) - Team = GameServer()->m_apPlayers[pPlayer->m_SpectatorID]->GetCharacter()->Team(); - - if(Team == TEAM_SUPER) - return; - - int SentTeam = Team; - if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) - SentTeam = 0; - - CNetObj_SwitchState *pSwitchState = Server()->SnapNewItem(SentTeam); - if(!pSwitchState) - return; - - pSwitchState->m_HighestSwitchNumber = clamp((int)GameServer()->Switchers().size() - 1, 0, 255); - mem_zero(pSwitchState->m_aStatus, sizeof(pSwitchState->m_aStatus)); - - std::vector> vEndTicks; // - - for(int i = 0; i <= pSwitchState->m_HighestSwitchNumber; i++) - { - int Status = (int)GameServer()->Switchers()[i].m_aStatus[Team]; - pSwitchState->m_aStatus[i / 32] |= (Status << (i % 32)); - - int EndTick = GameServer()->Switchers()[i].m_aEndTick[Team]; - if(EndTick > 0 && EndTick < Server()->Tick() + 3 * Server()->TickSpeed() && GameServer()->Switchers()[i].m_aLastUpdateTick[Team] < Server()->Tick()) - { - // only keep track of EndTicks that have less than three second left and are not currently being updated by a player being present on a switch tile, to limit how often these are sent - vEndTicks.emplace_back(GameServer()->Switchers()[i].m_aEndTick[Team], i); - } - } - - // send the endtick of switchers that are about to toggle back (up to four, prioritizing those with the earliest endticks) - mem_zero(pSwitchState->m_aSwitchNumbers, sizeof(pSwitchState->m_aSwitchNumbers)); - mem_zero(pSwitchState->m_aEndTicks, sizeof(pSwitchState->m_aEndTicks)); - - std::sort(vEndTicks.begin(), vEndTicks.end()); - const int NumTimedSwitchers = minimum((int)vEndTicks.size(), (int)std::size(pSwitchState->m_aEndTicks)); - - for(int i = 0; i < NumTimedSwitchers; i++) - { - pSwitchState->m_aSwitchNumbers[i] = vEndTicks[i].second; - pSwitchState->m_aEndTicks[i] = vEndTicks[i].first; - } - } + GameServer()->SnapSwitchers(SnappingClient); } int IGameController::GetAutoTeam(int NotThisID) From 68508e1b816a255f62307fc4582af165333172e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 13 Dec 2023 17:47:10 +0100 Subject: [PATCH 112/198] Add small margin to callvote label --- src/game/client/components/menus_ingame.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 7fa2a1a5b06..1834c3d5aa3 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -554,7 +554,9 @@ bool CMenus::RenderServerControlServer(CUIRect MainView) if(!Item.m_Visible) continue; - UI()->DoLabel(&Item.m_Rect, pOption->m_aDescription, 13.0f, TEXTALIGN_ML); + CUIRect Label; + Item.m_Rect.VMargin(2.0f, &Label); + UI()->DoLabel(&Label, pOption->m_aDescription, 13.0f, TEXTALIGN_ML); } s_CurVoteOption = s_ListBox.DoEnd(); From a440e0e917af12a85ccf0213aae81f398ad24ed4 Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 14 Dec 2023 02:33:09 +0100 Subject: [PATCH 113/198] Don't broadcast error if `CanJoinTeam` returned true --- src/game/server/gamecontext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index cf13e81add5..24c48d13e4f 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -2397,7 +2397,7 @@ void CGameContext::OnSetTeamNetMessage(const CNetMsg_Cl_SetTeam *pMsg, int Clien m_pController->DoTeamChange(pPlayer, pMsg->m_Team); pPlayer->m_TeamChangeTick = Server()->Tick(); } - if(aTeamJoinError[0]) + else if(aTeamJoinError[0]) SendBroadcast(aTeamJoinError, ClientID); } From 035e7a1068c4cd82174ffdc6e8e0231f6bd131ff Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 14 Dec 2023 00:04:23 +0100 Subject: [PATCH 114/198] Add `Sv_CommandInfo` netmsg for autocompletion of chat commands. --- datasrc/network.py | 10 +++++ src/game/client/components/chat.cpp | 57 ++++++++++++++++++++++++----- src/game/client/components/chat.h | 25 +++++++++---- src/game/server/gamecontext.cpp | 22 ++++++++--- 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/datasrc/network.py b/datasrc/network.py index d434524f896..7ad2c0244ea 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -562,4 +562,14 @@ NetBool("m_RecordPersonal"), NetBool("m_RecordServer", default=False), ]), + + NetMessageEx("Sv_CommandInfo", "commandinfo@netmsg.ddnet.org", [ + NetStringStrict("m_pName"), + NetStringStrict("m_pArgsFormat"), + NetStringStrict("m_pHelpText") + ]), + + NetMessageEx("Sv_CommandInfoRemove", "commandinfo-remove@netmsg.ddnet.org", [ + NetStringStrict("m_pName") + ]), ] diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 73913aa8b15..22fabed7c52 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -30,11 +30,11 @@ CChat::CChat() Line.m_QuadContainerIndex = -1; } -#define CHAT_COMMAND(name, params, flags, callback, userdata, help) RegisterCommand(name, params, flags, help); +#define CHAT_COMMAND(name, params, flags, callback, userdata, help) m_vDefaultCommands.emplace_back(name, params, help); #include #undef CHAT_COMMAND - std::sort(m_vCommands.begin(), m_vCommands.end()); + std::sort(m_vDefaultCommands.begin(), m_vDefaultCommands.end()); m_Mode = MODE_NONE; m_Input.SetClipboardLineCallback([this](const char *pStr) { SayChat(pStr); }); @@ -71,9 +71,20 @@ CChat::CChat() }); } -void CChat::RegisterCommand(const char *pName, const char *pParams, int flags, const char *pHelp) +void CChat::RegisterCommand(const char *pName, const char *pParams, const char *pHelpText) { - m_vCommands.emplace_back(pName, pParams); + // Don't allow duplicate commands. + for(const auto &Command : m_vCommands) + if(str_comp(Command.m_aName, pName) == 0) + return; + + m_vCommands.emplace_back(pName, pParams, pHelpText); + m_CommandsNeedSorting = true; +} + +void CChat::UnregisterCommand(const char *pName) +{ + m_vCommands.erase(std::remove_if(m_vCommands.begin(), m_vCommands.end(), [pName](const CCommand &Command) { return str_comp(Command.m_aName, pName) == 0; }), m_vCommands.end()); } void CChat::RebuildChat() @@ -121,6 +132,8 @@ void CChat::Reset() m_CurrentLine = 0; m_IsInputCensored = false; m_EditingNewLine = true; + m_ServerSupportsCommandInfo = false; + m_CommandsNeedSorting = false; mem_zero(m_aCurrentInputText, sizeof(m_aCurrentInputText)); DisableMode(); @@ -217,6 +230,11 @@ void CChat::OnInit() Console()->Chain("cl_chat_width", ConchainChatWidth, this); } +void CChat::OnMapLoad() +{ + m_vCommands = m_vDefaultCommands; +} + bool CChat::OnInput(const IInput::CEvent &Event) { if(m_Mode == MODE_NONE) @@ -234,6 +252,12 @@ bool CChat::OnInput(const IInput::CEvent &Event) } else if(Event.m_Flags & IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER)) { + if(m_CommandsNeedSorting) + { + std::sort(m_vCommands.begin(), m_vCommands.end()); + m_CommandsNeedSorting = false; + } + if(m_Input.GetString()[0]) { bool AddEntry = false; @@ -305,7 +329,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) }); } - if(m_aCompletionBuffer[0] == '/') + if(m_aCompletionBuffer[0] == '/' && !m_vCommands.empty()) { CCommand *pCompletionCommand = 0; @@ -338,7 +362,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) auto &Command = m_vCommands[Index]; - if(str_startswith(Command.m_pName, pCommandStart)) + if(str_startswith(Command.m_aName, pCommandStart)) { pCompletionCommand = &Command; m_CompletionChosen = Index + SearchType * NumCommands; @@ -355,10 +379,10 @@ bool CChat::OnInput(const IInput::CEvent &Event) // add the command str_append(aBuf, "/"); - str_append(aBuf, pCompletionCommand->m_pName); + str_append(aBuf, pCompletionCommand->m_aName); // add separator - const char *pSeparator = pCompletionCommand->m_pParams[0] == '\0' ? "" : " "; + const char *pSeparator = pCompletionCommand->m_aParams[0] == '\0' ? "" : " "; str_append(aBuf, pSeparator); if(*pSeparator) str_append(aBuf, pSeparator); @@ -366,7 +390,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) // add part after the name str_append(aBuf, m_Input.GetString() + m_PlaceholderOffset + m_PlaceholderLength); - m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionCommand->m_pName) + 1; + m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionCommand->m_aName) + 1; m_Input.Set(aBuf); m_Input.SetCursorOffset(m_PlaceholderOffset + m_PlaceholderLength); } @@ -538,6 +562,21 @@ void CChat::OnMessage(int MsgType, void *pRawMsg) CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; AddLine(pMsg->m_ClientID, pMsg->m_Team, pMsg->m_pMessage); } + else if(MsgType == NETMSGTYPE_SV_COMMANDINFO) + { + CNetMsg_Sv_CommandInfo *pMsg = (CNetMsg_Sv_CommandInfo *)pRawMsg; + if(!m_ServerSupportsCommandInfo) + { + m_vCommands.clear(); + m_ServerSupportsCommandInfo = true; + } + RegisterCommand(pMsg->m_pName, pMsg->m_pArgsFormat, pMsg->m_pHelpText); + } + else if(MsgType == NETMSGTYPE_SV_COMMANDINFOREMOVE) + { + CNetMsg_Sv_CommandInfoRemove *pMsg = (CNetMsg_Sv_CommandInfoRemove *)pRawMsg; + UnregisterCommand(pMsg->m_pName); + } } bool CChat::LineShouldHighlight(const char *pLine, const char *pName) diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h index 62ee924390e..cf23efc935a 100644 --- a/src/game/client/components/chat.h +++ b/src/game/client/components/chat.h @@ -96,21 +96,26 @@ class CChat : public CComponent struct CCommand { - const char *m_pName; - const char *m_pParams; + char m_aName[IConsole::TEMPCMD_NAME_LENGTH]; + char m_aParams[IConsole::TEMPCMD_PARAMS_LENGTH]; + char m_aHelpText[IConsole::TEMPCMD_HELP_LENGTH]; CCommand() = default; - CCommand(const char *pName, const char *pParams) : - m_pName(pName), m_pParams(pParams) + CCommand(const char *pName, const char *pParams, const char *pHelpText) { + str_copy(m_aName, pName); + str_copy(m_aParams, pParams); + str_copy(m_aHelpText, pHelpText); } - bool operator<(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) < 0; } - bool operator<=(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) <= 0; } - bool operator==(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) == 0; } + bool operator<(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) < 0; } + bool operator<=(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) <= 0; } + bool operator==(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) == 0; } }; std::vector m_vCommands; + std::vector m_vDefaultCommands; + bool m_CommandsNeedSorting; struct CHistoryEntry { @@ -126,6 +131,8 @@ class CChat : public CComponent char m_aCurrentInputText[MAX_LINE_LENGTH]; bool m_EditingNewLine; + bool m_ServerSupportsCommandInfo; + static void ConSay(IConsole::IResult *pResult, void *pUserData); static void ConSayTeam(IConsole::IResult *pResult, void *pUserData); static void ConChat(IConsole::IResult *pResult, void *pUserData); @@ -151,7 +158,8 @@ class CChat : public CComponent void DisableMode(); void Say(int Team, const char *pLine); void SayChat(const char *pLine); - void RegisterCommand(const char *pName, const char *pParams, int flags, const char *pHelp); + void RegisterCommand(const char *pName, const char *pParams, const char *pHelpText); + void UnregisterCommand(const char *pName); void Echo(const char *pString); void OnWindowResize() override; @@ -165,6 +173,7 @@ class CChat : public CComponent void OnMessage(int MsgType, void *pRawMsg) override; bool OnInput(const IInput::CEvent &Event) override; void OnInit() override; + void OnMapLoad() override; void RebuildChat(); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 56b7f3e4440..be838a13636 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -1410,15 +1410,19 @@ void CGameContext::OnClientEnter(int ClientID) Msg.m_pName = "team"; Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientID); } + } + + for(const IConsole::CCommandInfo *pCmd = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_USER, CFGFLAG_CHAT); + pCmd; pCmd = pCmd->NextCommandInfo(IConsole::ACCESS_LEVEL_USER, CFGFLAG_CHAT)) + { + const char *pName = pCmd->m_pName; - for(const IConsole::CCommandInfo *pCmd = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_USER, CFGFLAG_CHAT); - pCmd; pCmd = pCmd->NextCommandInfo(IConsole::ACCESS_LEVEL_USER, CFGFLAG_CHAT)) + if(Server()->IsSixup(ClientID)) { - if(!str_comp_nocase(pCmd->m_pName, "w") || !str_comp_nocase(pCmd->m_pName, "whisper")) + if(!str_comp_nocase(pName, "w") || !str_comp_nocase(pName, "whisper")) continue; - const char *pName = pCmd->m_pName; - if(!str_comp_nocase(pCmd->m_pName, "r")) + if(!str_comp_nocase(pName, "r")) pName = "rescue"; protocol7::CNetMsg_Sv_CommandInfo Msg; @@ -1427,6 +1431,14 @@ void CGameContext::OnClientEnter(int ClientID) Msg.m_pHelpText = pCmd->m_pHelp; Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientID); } + else + { + CNetMsg_Sv_CommandInfo Msg; + Msg.m_pName = pName; + Msg.m_pArgsFormat = pCmd->m_pParams; + Msg.m_pHelpText = pCmd->m_pHelp; + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientID); + } } { From af77ebac54853f0f8bc4518ff3e2dc74715708ba Mon Sep 17 00:00:00 2001 From: Corantin H Date: Sat, 2 Dec 2023 19:29:31 +0100 Subject: [PATCH 115/198] Added console search function --- src/engine/client/text.cpp | 50 ++- src/engine/textrender.h | 45 +++ src/game/client/components/console.cpp | 418 +++++++++++++++++++------ src/game/client/components/console.h | 26 ++ src/game/client/ui.cpp | 2 +- 5 files changed, 435 insertions(+), 106 deletions(-) diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index a1e6718329c..7aa3b666ad1 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -1292,6 +1292,8 @@ class CTextRender : public IEngineTextRender pCursor->m_ForceCursorRendering = false; pCursor->m_CursorCharacter = -1; pCursor->m_CursorRenderedPosition = vec2(-1.0f, -1.0f); + + pCursor->m_vColorSplits = {}; } void MoveCursor(CTextCursor *pCursor, float x, float y) const override @@ -1614,6 +1616,8 @@ class CTextRender : public IEngineTextRender bool GotNewLine = false; bool GotNewLineLast = false; + int ColorOption = 0; + while(pCurrent < pEnd && pCurrent != pEllipsis) { bool NewLine = false; @@ -1665,6 +1669,7 @@ class CTextRender : public IEngineTextRender while(pCurrent < pBatchEnd && pCurrent != pEllipsis) { + const int PrevCharCount = pCursor->m_CharCount; pCursor->m_CharCount += pTmp - pCurrent; pCurrent = pTmp; int Character = NextCharacter; @@ -1745,8 +1750,19 @@ class CTextRender : public IEngineTextRender const float CharX = (DrawX + CharKerning) + BearingX; const float CharY = TmpY - BearingY; + // Check if we have any color split + ColorRGBA Color = m_Color; + if(ColorOption < (int)pCursor->m_vColorSplits.size()) + { + STextColorSplit &Split = pCursor->m_vColorSplits.at(ColorOption); + if(PrevCharCount >= Split.m_CharIndex && PrevCharCount < Split.m_CharIndex + Split.m_Length) + Color = Split.m_Color; + if(PrevCharCount >= (Split.m_CharIndex + Split.m_Length - 1)) + ColorOption++; + } + // don't add text that isn't drawn, the color overwrite is used for that - if(m_Color.a != 0.f && IsRendered) + if(Color.a != 0.f && IsRendered) { TextContainer.m_StringInfo.m_vCharacterQuads.emplace_back(); STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_vCharacterQuads.back(); @@ -1755,37 +1771,37 @@ class CTextRender : public IEngineTextRender TextCharQuad.m_aVertices[0].m_Y = CharY; TextCharQuad.m_aVertices[0].m_U = pGlyph->m_aUVs[0]; TextCharQuad.m_aVertices[0].m_V = pGlyph->m_aUVs[3]; - TextCharQuad.m_aVertices[0].m_Color.r = (unsigned char)(m_Color.r * 255.f); - TextCharQuad.m_aVertices[0].m_Color.g = (unsigned char)(m_Color.g * 255.f); - TextCharQuad.m_aVertices[0].m_Color.b = (unsigned char)(m_Color.b * 255.f); - TextCharQuad.m_aVertices[0].m_Color.a = (unsigned char)(m_Color.a * 255.f); + TextCharQuad.m_aVertices[0].m_Color.r = (unsigned char)(Color.r * 255.f); + TextCharQuad.m_aVertices[0].m_Color.g = (unsigned char)(Color.g * 255.f); + TextCharQuad.m_aVertices[0].m_Color.b = (unsigned char)(Color.b * 255.f); + TextCharQuad.m_aVertices[0].m_Color.a = (unsigned char)(Color.a * 255.f); TextCharQuad.m_aVertices[1].m_X = CharX + CharWidth; TextCharQuad.m_aVertices[1].m_Y = CharY; TextCharQuad.m_aVertices[1].m_U = pGlyph->m_aUVs[2]; TextCharQuad.m_aVertices[1].m_V = pGlyph->m_aUVs[3]; - TextCharQuad.m_aVertices[1].m_Color.r = (unsigned char)(m_Color.r * 255.f); - TextCharQuad.m_aVertices[1].m_Color.g = (unsigned char)(m_Color.g * 255.f); - TextCharQuad.m_aVertices[1].m_Color.b = (unsigned char)(m_Color.b * 255.f); - TextCharQuad.m_aVertices[1].m_Color.a = (unsigned char)(m_Color.a * 255.f); + TextCharQuad.m_aVertices[1].m_Color.r = (unsigned char)(Color.r * 255.f); + TextCharQuad.m_aVertices[1].m_Color.g = (unsigned char)(Color.g * 255.f); + TextCharQuad.m_aVertices[1].m_Color.b = (unsigned char)(Color.b * 255.f); + TextCharQuad.m_aVertices[1].m_Color.a = (unsigned char)(Color.a * 255.f); TextCharQuad.m_aVertices[2].m_X = CharX + CharWidth; TextCharQuad.m_aVertices[2].m_Y = CharY - CharHeight; TextCharQuad.m_aVertices[2].m_U = pGlyph->m_aUVs[2]; TextCharQuad.m_aVertices[2].m_V = pGlyph->m_aUVs[1]; - TextCharQuad.m_aVertices[2].m_Color.r = (unsigned char)(m_Color.r * 255.f); - TextCharQuad.m_aVertices[2].m_Color.g = (unsigned char)(m_Color.g * 255.f); - TextCharQuad.m_aVertices[2].m_Color.b = (unsigned char)(m_Color.b * 255.f); - TextCharQuad.m_aVertices[2].m_Color.a = (unsigned char)(m_Color.a * 255.f); + TextCharQuad.m_aVertices[2].m_Color.r = (unsigned char)(Color.r * 255.f); + TextCharQuad.m_aVertices[2].m_Color.g = (unsigned char)(Color.g * 255.f); + TextCharQuad.m_aVertices[2].m_Color.b = (unsigned char)(Color.b * 255.f); + TextCharQuad.m_aVertices[2].m_Color.a = (unsigned char)(Color.a * 255.f); TextCharQuad.m_aVertices[3].m_X = CharX; TextCharQuad.m_aVertices[3].m_Y = CharY - CharHeight; TextCharQuad.m_aVertices[3].m_U = pGlyph->m_aUVs[0]; TextCharQuad.m_aVertices[3].m_V = pGlyph->m_aUVs[1]; - TextCharQuad.m_aVertices[3].m_Color.r = (unsigned char)(m_Color.r * 255.f); - TextCharQuad.m_aVertices[3].m_Color.g = (unsigned char)(m_Color.g * 255.f); - TextCharQuad.m_aVertices[3].m_Color.b = (unsigned char)(m_Color.b * 255.f); - TextCharQuad.m_aVertices[3].m_Color.a = (unsigned char)(m_Color.a * 255.f); + TextCharQuad.m_aVertices[3].m_Color.r = (unsigned char)(Color.r * 255.f); + TextCharQuad.m_aVertices[3].m_Color.g = (unsigned char)(Color.g * 255.f); + TextCharQuad.m_aVertices[3].m_Color.b = (unsigned char)(Color.b * 255.f); + TextCharQuad.m_aVertices[3].m_Color.a = (unsigned char)(Color.a * 255.f); } // calculate the full width from the last selection point to the end of this selection draw on screen diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 636b5daea66..0d1287c3d4e 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -181,6 +181,18 @@ struct STextBoundingBox } }; +// Allow to render multi colored text in one go without having to call TextEx() multiple times. +// Needed to allow multi colored multi line texts +struct STextColorSplit +{ + int m_CharIndex; // Which index within the text should the split occur + int m_Length; // How long is the split + ColorRGBA m_Color; // The color the text should be starting from m_CharIndex + + STextColorSplit(int CharIndex, int Length, const ColorRGBA &Color) : + m_CharIndex(CharIndex), m_Length(Length), m_Color(Color) {} +}; + class CTextCursor { public: @@ -220,6 +232,9 @@ class CTextCursor int m_CursorCharacter; vec2 m_CursorRenderedPosition; + // Color splits of the cursor to allow multicolored text + std::vector m_vColorSplits; + float Height() const { return m_LineCount * m_AlignedFontSize + std::max(0, m_LineCount - 1) * m_LineSpacing; @@ -229,6 +244,36 @@ class CTextCursor { return {m_StartX, m_StartY, m_LongestLineWidth, Height()}; } + + void Reset() + { + m_Flags = 0; + m_LineCount = 0; + m_GlyphCount = 0; + m_CharCount = 0; + m_MaxLines = 0; + m_StartX = 0; + m_StartY = 0; + m_LineWidth = 0; + m_X = 0; + m_Y = 0; + m_MaxCharacterHeight = 0; + m_LongestLineWidth = 0; + m_FontSize = 0; + m_AlignedFontSize = 0; + m_LineSpacing = 0; + m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE; + m_SelectionHeightFactor = 0; + m_PressMouse = vec2(); + m_ReleaseMouse = vec2(); + m_SelectionStart = 0; + m_SelectionEnd = 0; + m_CursorMode = TEXT_CURSOR_CURSOR_MODE_NONE; + m_ForceCursorRendering = false; + m_CursorCharacter = 0; + m_CursorRenderedPosition = vec2(); + m_vColorSplits.clear(); + } }; struct STextContainerUsages diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index e01b6393b1f..6973ab1b116 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -24,6 +24,8 @@ #include #include +#include + #include "console.h" static constexpr float FONT_SIZE = 10.0f; @@ -98,6 +100,9 @@ static bool IsSettingCommandPrefix(const char *pStr) return std::any_of(std::begin(gs_apSettingCommands), std::end(gs_apSettingCommands), [pStr](auto *pCmd) { return str_startswith_nocase(pStr, pCmd); }); } +const ColorRGBA CGameConsole::ms_SearchHighlightColor = ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f); +const ColorRGBA CGameConsole::ms_SearchSelectedColor = ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f); + CGameConsole::CInstance::CInstance(int Type) { m_pHistoryEntry = 0x0; @@ -128,6 +133,9 @@ CGameConsole::CInstance::CInstance(int Type) m_IsCommand = false; m_Input.SetClipboardLineCallback([this](const char *pStr) { ExecuteLine(pStr); }); + + m_CurrentMatchIndex = -1; + m_aCurrentSearchString[0] = '\0'; } void CGameConsole::CInstance::Init(CGameConsole *pGameConsole) @@ -146,6 +154,7 @@ void CGameConsole::CInstance::ClearBacklog() m_Backlog.Init(); m_BacklogCurLine = 0; + UpdateSearch(); } void CGameConsole::CInstance::UpdateBacklogTextAttributes() @@ -255,104 +264,147 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) { bool Handled = false; + auto &&SelectNextSearchMatch = [&](int Direction) { + if(!m_vSearchMatches.empty()) + { + m_CurrentMatchIndex += Direction; + if(m_CurrentMatchIndex >= (int)m_vSearchMatches.size()) + m_CurrentMatchIndex = 0; + if(m_CurrentMatchIndex < 0) + m_CurrentMatchIndex = (int)m_vSearchMatches.size() - 1; + m_HasSelection = false; + // Also scroll to the correct line + ScrollToCenter(m_vSearchMatches[m_CurrentMatchIndex].m_StartLine, m_vSearchMatches[m_CurrentMatchIndex].m_EndLine); + } + }; + if(Event.m_Flags & IInput::FLAG_PRESS) { if(Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER) { - if(!m_Input.IsEmpty() || (m_UsernameReq && !m_pGameConsole->Client()->RconAuthed() && !m_UserGot)) + if(!m_Searching) { - if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) + if(!m_Input.IsEmpty() || (m_UsernameReq && !m_pGameConsole->Client()->RconAuthed() && !m_UserGot)) { - const char *pPrevEntry = m_History.Last(); - if(pPrevEntry == nullptr || str_comp(pPrevEntry, m_Input.GetString()) != 0) + if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) { - char *pEntry = m_History.Allocate(m_Input.GetLength() + 1); - str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1); + const char *pPrevEntry = m_History.Last(); + if(pPrevEntry == nullptr || str_comp(pPrevEntry, m_Input.GetString()) != 0) + { + char *pEntry = m_History.Allocate(m_Input.GetLength() + 1); + str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1); + } + // print out the user's commands before they get run + char aBuf[IConsole::CMDLINE_LENGTH + 3]; + str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString()); + m_pGameConsole->PrintLine(m_Type, aBuf); } - // print out the user's commands before they get run - char aBuf[IConsole::CMDLINE_LENGTH + 3]; - str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString()); - m_pGameConsole->PrintLine(m_Type, aBuf); + ExecuteLine(m_Input.GetString()); + m_Input.Clear(); + m_pHistoryEntry = 0x0; } - ExecuteLine(m_Input.GetString()); - m_Input.Clear(); - m_pHistoryEntry = 0x0; + } + else + { + SelectNextSearchMatch(m_pGameConsole->m_pClient->Input()->ShiftIsPressed() ? -1 : 1); } Handled = true; } else if(Event.m_Key == KEY_UP) { - if(m_pHistoryEntry) + if(!m_Searching) { - char *pTest = m_History.Prev(m_pHistoryEntry); + if(m_pHistoryEntry) + { + char *pTest = m_History.Prev(m_pHistoryEntry); + + if(pTest) + m_pHistoryEntry = pTest; + } + else + m_pHistoryEntry = m_History.Last(); - if(pTest) - m_pHistoryEntry = pTest; + if(m_pHistoryEntry) + m_Input.Set(m_pHistoryEntry); } else - m_pHistoryEntry = m_History.Last(); - - if(m_pHistoryEntry) - m_Input.Set(m_pHistoryEntry); + { + SelectNextSearchMatch(-1); + } Handled = true; } else if(Event.m_Key == KEY_DOWN) { - if(m_pHistoryEntry) - m_pHistoryEntry = m_History.Next(m_pHistoryEntry); + if(!m_Searching) + { + if(m_pHistoryEntry) + m_pHistoryEntry = m_History.Next(m_pHistoryEntry); - if(m_pHistoryEntry) - m_Input.Set(m_pHistoryEntry); + if(m_pHistoryEntry) + m_Input.Set(m_pHistoryEntry); + else + m_Input.Clear(); + } else - m_Input.Clear(); + { + SelectNextSearchMatch(1); + } Handled = true; } else if(Event.m_Key == KEY_TAB) { const int Direction = m_pGameConsole->m_pClient->Input()->ShiftIsPressed() ? -1 : 1; - // command completion - const bool UseTempCommands = m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(); - int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands); - if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) + if(!m_Searching) { + // command completion + const bool UseTempCommands = m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(); + int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands); + if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) + { + if(CompletionEnumerationCount) + { + if(m_CompletionChosen == -1 && Direction < 0) + m_CompletionChosen = 0; + m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; + m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this); + } + else if(m_CompletionChosen != -1) + { + m_CompletionChosen = -1; + Reset(); + } + } + + // Argument completion + const bool TuningCompletion = IsTuningCommandPrefix(GetString()); + const bool SettingCompletion = IsSettingCommandPrefix(GetString()); + if(TuningCompletion) + CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument); + else if(SettingCompletion) + CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands); + if(CompletionEnumerationCount) { - if(m_CompletionChosen == -1 && Direction < 0) - m_CompletionChosen = 0; - m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; - m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this); + if(m_CompletionChosenArgument == -1 && Direction < 0) + m_CompletionChosenArgument = 0; + m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; + if(TuningCompletion && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE) + PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this); + else if(SettingCompletion) + m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this); } - else if(m_CompletionChosen != -1) + else if(m_CompletionChosenArgument != -1) { - m_CompletionChosen = -1; + m_CompletionChosenArgument = -1; Reset(); } } - - // Argument completion - const bool TuningCompletion = IsTuningCommandPrefix(GetString()); - const bool SettingCompletion = IsSettingCommandPrefix(GetString()); - if(TuningCompletion) - CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument); - else if(SettingCompletion) - CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands); - - if(CompletionEnumerationCount) - { - if(m_CompletionChosenArgument == -1 && Direction < 0) - m_CompletionChosenArgument = 0; - m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; - if(TuningCompletion && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE) - PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this); - else if(SettingCompletion) - m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this); - } - else if(m_CompletionChosenArgument != -1) + else { - m_CompletionChosenArgument = -1; - Reset(); + // Use Tab / Shift-Tab to cycle through search matches + SelectNextSearchMatch(Direction); } } else if(Event.m_Key == KEY_PAGEUP) @@ -394,10 +446,21 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) m_BacklogCurLine = 0; m_HasSelection = false; } + else if(Event.m_Key == KEY_F && m_pGameConsole->Input()->ModifierIsPressed() && Event.m_Flags & IInput::FLAG_PRESS) + { + m_Searching = !m_Searching; + ClearSearch(); + + Handled = true; + } } if(!Handled) + { Handled = m_Input.ProcessInput(Event); + if(Handled) + UpdateSearch(); + } if(Event.m_Flags & (IInput::FLAG_PRESS | IInput::FLAG_TEXT)) { @@ -486,6 +549,26 @@ int CGameConsole::CInstance::GetLinesToScroll(int Direction, int LinesToScroll) return LinesToScroll > 0 ? minimum(Amount, LinesToScroll) : Amount; } +void CGameConsole::CInstance::ScrollToCenter(int StartLine, int EndLine) +{ + // This method is used to scroll lines from `StartLine` to `EndLine` to the center of the screen, if possible. + + // Find target line + int Target = maximum(0, (int)ceil(StartLine - minimum(StartLine - EndLine, m_LinesRendered) / 2) - m_LinesRendered / 2); + if(m_BacklogCurLine == Target) + return; + + // Compute acutal amount of lines to scroll to make sure lines fit in viewport and we don't have empty space + int Direction = m_BacklogCurLine - Target < 0 ? -1 : 1; + int LinesToScroll = absolute(Target - m_BacklogCurLine); + int ComputedLines = GetLinesToScroll(Direction, LinesToScroll); + + if(Direction == -1) + m_BacklogCurLine += ComputedLines; + else + m_BacklogCurLine -= ComputedLines; +} + void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry) { CTextCursor Cursor; @@ -498,6 +581,119 @@ void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry) pEntry->m_LineCount = Cursor.m_LineCount; } +void CGameConsole::CInstance::ClearSearch() +{ + m_vSearchMatches.clear(); + m_CurrentMatchIndex = -1; + m_Input.Clear(); + m_aCurrentSearchString[0] = '\0'; +} + +void CGameConsole::CInstance::UpdateSearch() +{ + if(!m_Searching) + return; + + const char *pSearchText = m_Input.GetString(); + bool SearchChanged = str_utf8_comp_nocase(pSearchText, m_aCurrentSearchString) != 0; + + int SearchLength = m_Input.GetLength(); + str_copy(m_aCurrentSearchString, pSearchText); + + m_vSearchMatches.clear(); + if(pSearchText[0] == '\0') + { + m_CurrentMatchIndex = -1; + return; + } + + if(SearchChanged) + { + m_CurrentMatchIndex = -1; + m_HasSelection = false; + } + + ITextRender *pTextRender = m_pGameConsole->UI()->TextRender(); + const int LineWidth = m_pGameConsole->UI()->Screen()->w - 10.0f; + + CBacklogEntry *pEntry = m_Backlog.Last(); + int EntryLine = 0, LineToScrollStart = 0, LineToScrollEnd = 0; + + for(; pEntry; EntryLine += pEntry->m_LineCount, pEntry = m_Backlog.Prev(pEntry)) + { + const char *pSearchPos = str_utf8_find_nocase(pEntry->m_aText, pSearchText); + if(!pSearchPos) + continue; + + int EntryLineCount = pEntry->m_LineCount; + + // Find all occurences of the search string and save their positions + while(pSearchPos) + { + int Pos = pSearchPos - pEntry->m_aText; + + if(EntryLineCount == 1) + { + m_vSearchMatches.emplace_back(Pos, EntryLine, EntryLine, EntryLine); + if(EntryLine > LineToScrollStart) + { + LineToScrollStart = EntryLine; + LineToScrollEnd = EntryLine; + } + } + else + { + // A match can span multiple lines in case of a multiline entry, so we need to know which line the match starts at + // and which line it ends at in order to put it in viewport properly + STextSizeProperties Props; + int LineCount; + Props.m_pLineCount = &LineCount; + + // Compute line of end match + pTextRender->TextWidth(FONT_SIZE, pEntry->m_aText, Pos + SearchLength, LineWidth, 0, Props); + int EndLine = (EntryLineCount - LineCount); + int MatchEndLine = EntryLine + EndLine; + + // Compute line of start of match + int MatchStartLine = MatchEndLine; + if(LineCount > 1) + { + pTextRender->TextWidth(FONT_SIZE, pEntry->m_aText, Pos, LineWidth, 0, Props); + int StartLine = (EntryLineCount - LineCount); + MatchStartLine = EntryLine + StartLine; + } + + if(MatchStartLine > LineToScrollStart) + { + LineToScrollStart = MatchStartLine; + LineToScrollEnd = MatchEndLine; + } + + m_vSearchMatches.emplace_back(Pos, MatchStartLine, MatchEndLine, EntryLine); + } + + pSearchPos = str_utf8_find_nocase(pEntry->m_aText + Pos + SearchLength, pSearchText); + } + } + + if(!m_vSearchMatches.empty() && SearchChanged) + m_CurrentMatchIndex = 0; + else + m_CurrentMatchIndex = std::clamp(m_CurrentMatchIndex, -1, (int)m_vSearchMatches.size() - 1); + + // Reverse order of lines by sorting so we have matches from top to bottom instead of bottom to top + std::sort(m_vSearchMatches.begin(), m_vSearchMatches.end(), [](const SSearchMatch &MatchA, const SSearchMatch &MatchB) { + if(MatchA.m_StartLine == MatchB.m_StartLine) + return MatchA.m_Pos < MatchB.m_Pos; // Make sure to keep position order + return MatchA.m_StartLine > MatchB.m_StartLine; + }); + + if(!m_vSearchMatches.empty() && SearchChanged) + { + ScrollToCenter(LineToScrollStart, LineToScrollEnd); + } +} + CGameConsole::CGameConsole() : m_LocalConsole(CONSOLETYPE_LOCAL), m_RemoteConsole(CONSOLETYPE_REMOTE) { @@ -590,12 +786,39 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v pInfo->m_TotalWidth = pInfo->m_Cursor.m_X + pInfo->m_Offset; } +void CGameConsole::Prompt(char (&aPrompt)[32]) +{ + CInstance *pConsole = CurrentConsole(); + if(pConsole->m_Searching) + { + str_format(aPrompt, sizeof(aPrompt), "%s: ", Localize("Searching")); + } + else if(m_ConsoleType == CONSOLETYPE_REMOTE) + { + if(Client()->State() == IClient::STATE_LOADING || Client()->State() == IClient::STATE_ONLINE) + { + if(Client()->RconAuthed()) + str_copy(aPrompt, "rcon> "); + else if(pConsole->m_UsernameReq && !pConsole->m_UserGot) + str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("Enter Username")); + else + str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("Enter Password")); + } + else + str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("NOT CONNECTED")); + } + else + { + str_copy(aPrompt, "> "); + } +} + void CGameConsole::OnRender() { CUIRect Screen = *UI()->Screen(); CInstance *pConsole = CurrentConsole(); - float ConsoleMaxHeight = Screen.h * 3 / 5.0f; + float MaxConsoleHeight = Screen.h * 3 / 5.0f; float ConsoleHeight; float Progress = (Client()->GlobalTime() - (m_StateChangeEnd - m_StateChangeDuration)) / m_StateChangeDuration; @@ -632,10 +855,10 @@ void CGameConsole::OnRender() ConsoleHeightScale = ConsoleScaleFunc(Progress); else if(m_ConsoleState == CONSOLE_CLOSING) ConsoleHeightScale = ConsoleScaleFunc(1.0f - Progress); - else //if (console_state == CONSOLE_OPEN) + else // if (console_state == CONSOLE_OPEN) ConsoleHeightScale = ConsoleScaleFunc(1.0f); - ConsoleHeight = ConsoleHeightScale * ConsoleMaxHeight; + ConsoleHeight = ConsoleHeightScale * MaxConsoleHeight; UI()->MapScreen(); @@ -700,30 +923,10 @@ void CGameConsole::OnRender() // render prompt CTextCursor Cursor; TextRender()->SetCursor(&Cursor, x, y, FONT_SIZE, TEXTFLAG_RENDER); - const char *pPrompt = "> "; - if(m_ConsoleType == CONSOLETYPE_REMOTE) - { - if(Client()->State() == IClient::STATE_LOADING || Client()->State() == IClient::STATE_ONLINE) - { - if(Client()->RconAuthed()) - pPrompt = "rcon> "; - else - { - if(pConsole->m_UsernameReq) - { - if(!pConsole->m_UserGot) - pPrompt = "Enter Username> "; - else - pPrompt = "Enter Password> "; - } - else - pPrompt = "Enter Password> "; - } - } - else - pPrompt = "NOT CONNECTED> "; - } - TextRender()->TextEx(&Cursor, pPrompt, -1); + + char aPrompt[32]; + Prompt(aPrompt); + TextRender()->TextEx(&Cursor, aPrompt); // check if mouse is pressed if(!pConsole->m_MouseIsPress && Input()->NativeMousePressed(1)) @@ -786,7 +989,7 @@ void CGameConsole::OnRender() } // render possible commands - if((m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && !pConsole->m_Input.IsEmpty()) + if(!pConsole->m_Searching && (m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && !pConsole->m_Input.IsEmpty()) { CCompletionOptionRenderInfo Info; Info.m_pSelf = this; @@ -831,8 +1034,26 @@ void CGameConsole::OnRender() UI()->DoSmoothScrollLogic(&pConsole->m_CompletionRenderOffset, &pConsole->m_CompletionRenderOffsetChange, Info.m_Width, Info.m_TotalWidth); } + else if(pConsole->m_Searching && !pConsole->m_Input.IsEmpty()) + { // Render current match and match count + CTextCursor MatchInfoCursor; + TextRender()->SetCursor(&MatchInfoCursor, InitialX, InitialY + RowHeight + 2.0f, FONT_SIZE, TEXTFLAG_RENDER); + TextRender()->TextColor(0.8f, 0.8f, 0.8f, 1.0f); + if(!pConsole->m_vSearchMatches.empty()) + { + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), Localize("Match %d of %d"), pConsole->m_CurrentMatchIndex + 1, (int)pConsole->m_vSearchMatches.size()); + TextRender()->TextEx(&MatchInfoCursor, aBuf, -1); + } + else + { + TextRender()->TextEx(&MatchInfoCursor, Localize("No results"), -1); + } + } pConsole->PumpBacklogPending(); + if(pConsole->m_NewLineCounter > 0) + pConsole->UpdateSearch(); // render console log (current entry, status, wrap lines) CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.Last(); @@ -915,7 +1136,28 @@ void CGameConsole::OnRender() Cursor.m_CalculateSelectionMode = (m_ConsoleState == CONSOLE_OPEN && pConsole->m_MousePress.y < pConsole->m_BoundingBox.m_Y && (pConsole->m_MouseIsPress || (pConsole->m_CurSelStart != pConsole->m_CurSelEnd) || pConsole->m_HasSelection)) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; Cursor.m_PressMouse = pConsole->m_MousePress; Cursor.m_ReleaseMouse = pConsole->m_MouseRelease; + + if(pConsole->m_Searching && pConsole->m_CurrentMatchIndex != -1) + { + std::vector vMatches; + std::copy_if(pConsole->m_vSearchMatches.begin(), pConsole->m_vSearchMatches.end(), std::back_inserter(vMatches), [&](const CInstance::SSearchMatch &Match) { return Match.m_EntryLine == LineNum + 1 - pEntry->m_LineCount; }); + + auto CurrentSelectedOccurrence = pConsole->m_vSearchMatches[pConsole->m_CurrentMatchIndex]; + + std::vector vColorSplits; + for(const auto &Match : vMatches) + { + bool IsSelected = CurrentSelectedOccurrence.m_EntryLine == Match.m_EntryLine && CurrentSelectedOccurrence.m_Pos == Match.m_Pos; + Cursor.m_vColorSplits.emplace_back( + Match.m_Pos, + pConsole->m_Input.GetLength(), + IsSelected ? ms_SearchSelectedColor : ms_SearchHighlightColor); + } + } + TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); + Cursor.m_vColorSplits = {}; + if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) { pConsole->m_CurSelStart = minimum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd); @@ -947,8 +1189,6 @@ void CGameConsole::OnRender() Graphics()->ClipDisable(); - if(LineNum >= 0) - pConsole->m_BacklogCurLine = clamp(pConsole->m_BacklogCurLine, 0, LineNum); pConsole->m_BacklogLastActiveLine = pConsole->m_BacklogCurLine; if(m_WantsSelectionCopy && !SelectionString.empty()) @@ -1093,12 +1333,14 @@ void CGameConsole::ConConsolePageUp(IConsole::IResult *pResult, void *pUserData) { CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole(); pConsole->m_BacklogCurLine += pConsole->GetLinesToScroll(-1, pConsole->m_LinesRendered); + pConsole->m_HasSelection = false; } void CGameConsole::ConConsolePageDown(IConsole::IResult *pResult, void *pUserData) { CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole(); pConsole->m_BacklogCurLine -= pConsole->GetLinesToScroll(1, pConsole->m_LinesRendered); + pConsole->m_HasSelection = false; if(pConsole->m_BacklogCurLine < 0) pConsole->m_BacklogCurLine = 0; } diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 0a361a8af34..78628e75d04 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -78,6 +78,21 @@ class CGameConsole : public CComponent const char *m_pCommandHelp; const char *m_pCommandParams; + bool m_Searching = false; + struct SSearchMatch + { + int m_Pos; + int m_StartLine; + int m_EndLine; + int m_EntryLine; + + SSearchMatch(int Pos, int StartLine, int EndLine, int EntryLine) : + m_Pos(Pos), m_StartLine(StartLine), m_EndLine(EndLine), m_EntryLine(EntryLine) {} + }; + int m_CurrentMatchIndex; + char m_aCurrentSearchString[IConsole::CMDLINE_LENGTH]; + std::vector m_vSearchMatches; + CInstance(int t); void Init(CGameConsole *pGameConsole); @@ -92,12 +107,19 @@ class CGameConsole : public CComponent bool OnInput(const IInput::CEvent &Event); void PrintLine(const char *pLine, int Len, ColorRGBA PrintColor) REQUIRES(!m_BacklogPendingLock); int GetLinesToScroll(int Direction, int LinesToScroll); + void ScrollToCenter(int StartLine, int EndLine); + void ClearSearch(); const char *GetString() const { return m_Input.GetString(); } static void PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser); static void PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser); void UpdateEntryTextAttributes(CBacklogEntry *pEntry); + + private: + void UpdateSearch(); + + friend class CGameConsole; }; class IConsole *m_pConsole; @@ -115,6 +137,9 @@ class CGameConsole : public CComponent bool m_WantsSelectionCopy = false; + static const ColorRGBA ms_SearchHighlightColor; + static const ColorRGBA ms_SearchSelectedColor; + void Toggle(int Type); void Dump(int Type); @@ -150,6 +175,7 @@ class CGameConsole : public CComponent virtual void OnRender() override; virtual void OnMessage(int MsgType, void *pRawMsg) override; virtual bool OnInput(const IInput::CEvent &Event) override; + void Prompt(char (&aPrompt)[32]); bool IsClosed() { return m_ConsoleState == CONSOLE_CLOSED; } }; diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index b1e02070220..9cae1a04bf1 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -47,7 +47,7 @@ void CUIElement::SUIElementRect::Reset() m_Rounding = -1.0f; m_Corners = -1; m_Text.clear(); - mem_zero(&m_Cursor, sizeof(m_Cursor)); + m_Cursor.Reset(); m_TextColor = ColorRGBA(-1, -1, -1, -1); m_TextOutlineColor = ColorRGBA(-1, -1, -1, -1); m_QuadColor = ColorRGBA(-1, -1, -1, -1); From 6a12e209b6d1b597dc2367eddaace550897760db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 6 Oct 2023 13:02:56 +0200 Subject: [PATCH 116/198] Fix `readability-make-member-function-const` --- .clang-tidy | 1 - src/engine/client.h | 6 +-- .../client/backend/opengl/opengl_sl.cpp | 4 +- src/engine/client/backend/opengl/opengl_sl.h | 4 +- .../backend/opengl/opengl_sl_program.cpp | 14 ++--- .../client/backend/opengl/opengl_sl_program.h | 14 ++--- .../client/backend/vulkan/backend_vulkan.cpp | 18 +++---- src/engine/client/serverbrowser.cpp | 6 +-- src/engine/client/serverbrowser.h | 2 +- src/engine/client/sound.cpp | 6 +-- src/engine/client/sound.h | 6 +-- src/engine/client/video.cpp | 2 +- src/engine/client/video.h | 2 +- src/engine/server/databases/connection.cpp | 10 ++-- src/engine/server/databases/connection.h | 10 ++-- src/engine/shared/config.cpp | 4 +- src/engine/shared/console.cpp | 2 +- src/engine/shared/console.h | 2 +- src/engine/shared/network.cpp | 2 +- src/engine/shared/network.h | 2 +- src/engine/shared/snapshot.cpp | 6 +-- src/engine/shared/snapshot.h | 6 +-- src/game/client/components/console.cpp | 2 +- src/game/client/components/console.h | 2 +- src/game/client/components/ghost.cpp | 2 +- src/game/client/components/ghost.h | 2 +- src/game/client/components/mapimages.cpp | 2 +- src/game/client/components/mapimages.h | 2 +- src/game/client/components/menus.cpp | 2 +- src/game/client/components/menus.h | 4 +- src/game/client/components/statboard.cpp | 2 +- src/game/client/components/statboard.h | 2 +- src/game/client/gameclient.cpp | 14 ++--- src/game/client/gameclient.h | 14 ++--- src/game/client/lineinput.cpp | 2 +- src/game/client/lineinput.h | 2 +- .../client/prediction/entities/character.cpp | 2 +- .../client/prediction/entities/character.h | 2 +- src/game/client/prediction/gameworld.cpp | 4 +- src/game/client/prediction/gameworld.h | 4 +- src/game/client/render.cpp | 26 ++++----- src/game/client/render.h | 54 +++++++++---------- src/game/client/render_map.cpp | 24 ++++----- src/game/client/ui.cpp | 12 ++--- src/game/client/ui.h | 12 ++--- src/game/collision.cpp | 2 +- src/game/collision.h | 2 +- src/game/editor/editor.cpp | 2 +- src/game/editor/editor.h | 2 +- src/game/editor/map_grid.cpp | 2 +- src/game/editor/map_grid.h | 2 +- src/game/editor/map_view.cpp | 2 +- src/game/editor/map_view.h | 2 +- src/game/editor/mapitems/layer_group.cpp | 6 +-- src/game/editor/mapitems/layer_group.h | 6 +-- src/game/editor/mapitems/layer_tiles.cpp | 12 ++--- src/game/editor/mapitems/layer_tiles.h | 12 ++--- src/game/gamecore.cpp | 2 +- src/game/gamecore.h | 2 +- src/game/server/ddracechat.cpp | 2 +- src/game/server/gamecontext.cpp | 30 +++++------ src/game/server/gamecontext.h | 28 +++++----- src/game/server/player.cpp | 2 +- src/game/server/player.h | 2 +- src/game/server/save.cpp | 4 +- src/game/server/save.h | 4 +- src/test/score.cpp | 2 +- 67 files changed, 226 insertions(+), 227 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 536ea1ca463..d41c71378bc 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -81,7 +81,6 @@ Checks: > -readability-implicit-bool-conversion, -readability-isolate-declaration, -readability-magic-numbers, - -readability-make-member-function-const, -readability-named-parameter, -readability-non-const-parameter, -readability-simplify-boolean-expr, diff --git a/src/engine/client.h b/src/engine/client.h index bc265e000d5..d4c9f3fd43e 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -324,7 +324,7 @@ class IGameClient : public IInterface virtual int OnSnapInput(int *pData, bool Dummy, bool Force) = 0; virtual void OnDummySwap() = 0; virtual void SendDummyInfo(bool Start) = 0; - virtual int GetLastRaceTick() = 0; + virtual int GetLastRaceTick() const = 0; virtual const char *GetItemName(int Type) const = 0; virtual const char *Version() const = 0; @@ -335,8 +335,8 @@ class IGameClient : public IInterface virtual void OnDummyDisconnect() = 0; virtual void DummyResetInput() = 0; virtual void Echo(const char *pString) = 0; - virtual bool CanDisplayWarning() = 0; - virtual bool IsDisplayingWarning() = 0; + virtual bool CanDisplayWarning() const = 0; + virtual bool IsDisplayingWarning() const = 0; virtual CNetObjHandler *GetNetObjHandler() = 0; }; diff --git a/src/engine/client/backend/opengl/opengl_sl.cpp b/src/engine/client/backend/opengl/opengl_sl.cpp index a17335a29d3..046e4168d73 100644 --- a/src/engine/client/backend/opengl/opengl_sl.cpp +++ b/src/engine/client/backend/opengl/opengl_sl.cpp @@ -142,12 +142,12 @@ void CGLSL::DeleteShader() glDeleteShader(m_ShaderID); } -bool CGLSL::IsLoaded() +bool CGLSL::IsLoaded() const { return m_IsLoaded; } -TWGLuint CGLSL::GetShaderID() +TWGLuint CGLSL::GetShaderID() const { return m_ShaderID; } diff --git a/src/engine/client/backend/opengl/opengl_sl.h b/src/engine/client/backend/opengl/opengl_sl.h index 258c8cd3d38..3c2a6400237 100644 --- a/src/engine/client/backend/opengl/opengl_sl.h +++ b/src/engine/client/backend/opengl/opengl_sl.h @@ -20,8 +20,8 @@ class CGLSL bool LoadShader(CGLSLCompiler *pCompiler, class IStorage *pStorage, const char *pFile, int Type); void DeleteShader(); - bool IsLoaded(); - TWGLuint GetShaderID(); + bool IsLoaded() const; + TWGLuint GetShaderID() const; CGLSL(); virtual ~CGLSL(); diff --git a/src/engine/client/backend/opengl/opengl_sl_program.cpp b/src/engine/client/backend/opengl/opengl_sl_program.cpp index e14215a1d9b..2ba94325b15 100644 --- a/src/engine/client/backend/opengl/opengl_sl_program.cpp +++ b/src/engine/client/backend/opengl/opengl_sl_program.cpp @@ -25,7 +25,7 @@ void CGLSLProgram::DeleteProgram() glDeleteProgram(m_ProgramID); } -bool CGLSLProgram::AddShader(CGLSL *pShader) +bool CGLSLProgram::AddShader(CGLSL *pShader) const { if(pShader->IsLoaded()) { @@ -35,7 +35,7 @@ bool CGLSLProgram::AddShader(CGLSL *pShader) return false; } -void CGLSLProgram::DetachShader(CGLSL *pShader) +void CGLSLProgram::DetachShader(CGLSL *pShader) const { if(pShader->IsLoaded()) { @@ -43,7 +43,7 @@ void CGLSLProgram::DetachShader(CGLSL *pShader) } } -void CGLSLProgram::DetachShaderByID(TWGLuint ShaderID) +void CGLSLProgram::DetachShaderByID(TWGLuint ShaderID) const { glDetachShader(m_ProgramID, ShaderID); } @@ -68,7 +68,7 @@ void CGLSLProgram::LinkProgram() DetachAllShaders(); } -void CGLSLProgram::DetachAllShaders() +void CGLSLProgram::DetachAllShaders() const { TWGLuint aShaders[100]; GLsizei ReturnedCount = 0; @@ -119,18 +119,18 @@ void CGLSLProgram::SetUniform(int Loc, const bool Value) glUniform1i(Loc, (int)Value); } -int CGLSLProgram::GetUniformLoc(const char *pName) +int CGLSLProgram::GetUniformLoc(const char *pName) const { return glGetUniformLocation(m_ProgramID, pName); } -void CGLSLProgram::UseProgram() +void CGLSLProgram::UseProgram() const { if(m_IsLinked) glUseProgram(m_ProgramID); } -TWGLuint CGLSLProgram::GetProgramID() +TWGLuint CGLSLProgram::GetProgramID() const { return m_ProgramID; } diff --git a/src/engine/client/backend/opengl/opengl_sl_program.h b/src/engine/client/backend/opengl/opengl_sl_program.h index 75243b6e771..f278731c860 100644 --- a/src/engine/client/backend/opengl/opengl_sl_program.h +++ b/src/engine/client/backend/opengl/opengl_sl_program.h @@ -22,15 +22,15 @@ class CGLSLProgram void CreateProgram(); void DeleteProgram(); - bool AddShader(CGLSL *pShader); + bool AddShader(CGLSL *pShader) const; void LinkProgram(); - void UseProgram(); - TWGLuint GetProgramID(); + void UseProgram() const; + TWGLuint GetProgramID() const; - void DetachShader(CGLSL *pShader); - void DetachShaderByID(TWGLuint ShaderID); - void DetachAllShaders(); + void DetachShader(CGLSL *pShader) const; + void DetachShaderByID(TWGLuint ShaderID) const; + void DetachAllShaders() const; //Support various types void SetUniformVec2(int Loc, int Count, const float *pValue); @@ -41,7 +41,7 @@ class CGLSLProgram void SetUniform(int Loc, int Count, const float *pValues); //for performance reason we do not use SetUniform with using strings... save the Locations of the variables instead - int GetUniformLoc(const char *pName); + int GetUniformLoc(const char *pName) const; CGLSLProgram(); virtual ~CGLSLProgram(); diff --git a/src/engine/client/backend/vulkan/backend_vulkan.cpp b/src/engine/client/backend/vulkan/backend_vulkan.cpp index b7b8b41cce4..84a8b48d869 100644 --- a/src/engine/client/backend/vulkan/backend_vulkan.cpp +++ b/src/engine/client/backend/vulkan/backend_vulkan.cpp @@ -68,7 +68,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase return g_Config.m_DbgGfx == DEBUG_GFX_MODE_VERBOSE || g_Config.m_DbgGfx == DEBUG_GFX_MODE_ALL; } - void VerboseAllocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage) + void VerboseAllocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage) const { const char *pUsage = "unknown"; switch(MemUsage) @@ -90,7 +90,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase dbg_msg("vulkan", "allocated chunk of memory with size: %" PRIzu " for frame %" PRIzu " (%s)", (size_t)Size, (size_t)m_CurImageIndex, pUsage); } - void VerboseDeallocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage) + void VerboseDeallocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage) const { const char *pUsage = "unknown"; switch(MemUsage) @@ -300,7 +300,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase } } - [[nodiscard]] bool IsUnused() + [[nodiscard]] bool IsUnused() const { return !m_Root.m_InUse; } @@ -829,7 +829,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase // the viewport of the resulting presented image on the screen // if there is a forced viewport the resulting image is smaller // than the full swap image size - VkExtent2D GetPresentedImageViewport() + VkExtent2D GetPresentedImageViewport() const { uint32_t ViewportWidth = m_SwapImageViewport.width; uint32_t ViewportHeight = m_SwapImageViewport.height; @@ -3168,7 +3168,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase return State.m_BlendMode == CCommandBuffer::BLEND_ADDITIVE ? VULKAN_BACKEND_BLEND_MODE_ADDITATIVE : (State.m_BlendMode == CCommandBuffer::BLEND_NONE ? VULKAN_BACKEND_BLEND_MODE_NONE : VULKAN_BACKEND_BLEND_MODE_ALPHA); } - size_t GetDynamicModeIndexFromState(const CCommandBuffer::SState &State) + size_t GetDynamicModeIndexFromState(const CCommandBuffer::SState &State) const { return (State.m_ClipEnable || m_HasDynamicViewport || m_VKSwapImgAndViewportExtent.m_HasForcedViewport) ? VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT : VULKAN_BACKEND_CLIP_MODE_NONE; } @@ -4598,7 +4598,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase VkPipelineRasterizationStateCreateInfo &Rasterizer, VkPipelineMultisampleStateCreateInfo &Multisampling, VkPipelineColorBlendAttachmentState &ColorBlendAttachment, - VkPipelineColorBlendStateCreateInfo &ColorBlending) + VkPipelineColorBlendStateCreateInfo &ColorBlending) const { InputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; InputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; @@ -5967,12 +5967,12 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase FreeDescriptorSetFromPool(DescrSet); } - [[nodiscard]] bool HasMultiSampling() + [[nodiscard]] bool HasMultiSampling() const { return GetSampleCount() != VK_SAMPLE_COUNT_1_BIT; } - VkSampleCountFlagBits GetMaxSampleCount() + VkSampleCountFlagBits GetMaxSampleCount() const { if(m_MaxMultiSample & VK_SAMPLE_COUNT_64_BIT) return VK_SAMPLE_COUNT_64_BIT; @@ -5990,7 +5990,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase return VK_SAMPLE_COUNT_1_BIT; } - VkSampleCountFlagBits GetSampleCount() + VkSampleCountFlagBits GetSampleCount() const { auto MaxSampleCount = GetMaxSampleCount(); if(m_MultiSamplingCount >= 64 && MaxSampleCount >= VK_SAMPLE_COUNT_64_BIT) diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index 332bb7aff4b..22939fe1fad 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -122,7 +122,7 @@ void CServerBrowser::Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserDa { public: CServerBrowser *m_pThis; - bool operator()(int i, int j) + bool operator()(int i, int j) const { NETADDR Addr1 = m_pThis->m_ppServerlist[i]->m_Info.m_aAddresses[0]; NETADDR Addr2 = m_pThis->m_ppServerlist[j]->m_Info.m_aAddresses[0]; @@ -541,7 +541,7 @@ void ServerBrowserFormatAddresses(char *pBuffer, int BufferSize, NETADDR *pAddrs } } -void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info) +void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info) const { const CServerInfo TmpInfo = pEntry->m_Info; pEntry->m_Info = Info; @@ -575,7 +575,7 @@ void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info) { } - bool operator()(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1) + bool operator()(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1) const { // Sort players before non players if(p0.m_Player && !p1.m_Player) diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h index 854907c910d..1920b6c7444 100644 --- a/src/engine/client/serverbrowser.h +++ b/src/engine/client/serverbrowser.h @@ -217,7 +217,7 @@ class CServerBrowser : public IServerBrowser void RegisterCommands(); static void Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData); - void SetInfo(CServerEntry *pEntry, const CServerInfo &Info); + void SetInfo(CServerEntry *pEntry, const CServerInfo &Info) const; void SetLatency(NETADDR Addr, int Latency); static bool ParseCommunityFinishes(CCommunity *pCommunity, const json_value &Finishes); diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 7784e162b56..86540773051 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -286,7 +286,7 @@ int CSound::AllocID() return -1; } -void CSound::RateConvert(CSample &Sample) +void CSound::RateConvert(CSample &Sample) const { // make sure that we need to convert this sound if(!Sample.m_pData || Sample.m_Rate == m_MixingRate) @@ -321,7 +321,7 @@ void CSound::RateConvert(CSample &Sample) Sample.m_Rate = m_MixingRate; } -bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) +bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const { OggOpusFile *pOpusFile = op_open_memory((const unsigned char *)pData, DataSize, nullptr); if(pOpusFile) @@ -414,7 +414,7 @@ static int PushBackByte(void *pId, int Char) } #endif -bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) +bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) const { char aError[100]; diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index 3780e192202..ea5eb5766f3 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -77,10 +77,10 @@ class CSound : public IEngineSound int *m_pMixBuffer = nullptr; int AllocID(); - void RateConvert(CSample &Sample); + void RateConvert(CSample &Sample) const; - bool DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize); - bool DecodeWV(CSample &Sample, const void *pData, unsigned DataSize); + bool DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const; + bool DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) const; void UpdateVolume(); diff --git a/src/engine/client/video.cpp b/src/engine/client/video.cpp index c8efc788049..62eeaf85980 100644 --- a/src/engine/client/video.cpp +++ b/src/engine/client/video.cpp @@ -809,7 +809,7 @@ bool CVideo::OpenAudio() } /* Add an output stream. */ -bool CVideo::AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId) +bool CVideo::AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId) const { AVCodecContext *pContext; diff --git a/src/engine/client/video.h b/src/engine/client/video.h index 58f2186edfe..0575cdee7ee 100644 --- a/src/engine/client/video.h +++ b/src/engine/client/video.h @@ -79,7 +79,7 @@ class CVideo : public IVideo void FinishFrames(OutputStream *pStream); void CloseStream(OutputStream *pStream); - bool AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId); + bool AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId) const; CGraphics_Threaded *m_pGraphics; IStorage *m_pStorage; diff --git a/src/engine/server/databases/connection.cpp b/src/engine/server/databases/connection.cpp index de77f31ad5b..ac124baa49f 100644 --- a/src/engine/server/databases/connection.cpp +++ b/src/engine/server/databases/connection.cpp @@ -7,7 +7,7 @@ IDbConnection::IDbConnection(const char *pPrefix) str_copy(m_aPrefix, pPrefix); } -void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup) +void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup) const { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_race%s (" @@ -33,7 +33,7 @@ void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize, bool B BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate()); } -void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup) +void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup) const { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_teamrace%s (" @@ -50,7 +50,7 @@ void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, co BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType); } -void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize) +void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize) const { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_maps (" @@ -65,7 +65,7 @@ void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize) GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate()); } -void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup) +void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup) const { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_saves%s (" @@ -82,7 +82,7 @@ void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool BinaryCollate(), BinaryCollate(), BinaryCollate()); } -void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize) +void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize) const { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_points (" diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 0060c64284e..a2da8e0dafd 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -84,11 +84,11 @@ class IDbConnection char m_aPrefix[64]; protected: - void FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup); - void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup); - void FormatCreateMaps(char *aBuf, unsigned int BufferSize); - void FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup); - void FormatCreatePoints(char *aBuf, unsigned int BufferSize); + void FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup) const; + void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup) const; + void FormatCreateMaps(char *aBuf, unsigned int BufferSize) const; + void FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup) const; + void FormatCreatePoints(char *aBuf, unsigned int BufferSize) const; }; bool MysqlAvailable(); diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp index f87100c6c9b..3c0f61e23b3 100644 --- a/src/engine/shared/config.cpp +++ b/src/engine/shared/config.cpp @@ -51,12 +51,12 @@ struct SConfigVariable virtual void ResetToOld() = 0; protected: - void ExecuteLine(const char *pLine) + void ExecuteLine(const char *pLine) const { m_pConsole->ExecuteLine(pLine, (m_Flags & CFGFLAG_GAME) != 0 ? IConsole::CLIENT_ID_GAME : -1); } - bool CheckReadOnly() + bool CheckReadOnly() const { if(!m_ReadOnly) return false; diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index f89bf6810aa..e20e2dae282 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -1034,7 +1034,7 @@ void CConsole::CResult::ResetVictim() m_Victim = VICTIM_NONE; } -bool CConsole::CResult::HasVictim() +bool CConsole::CResult::HasVictim() const { return m_Victim != VICTIM_NONE; } diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index bc22e4d1ac7..339b521ce6b 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -137,7 +137,7 @@ class CConsole : public IConsole int m_Victim; void ResetVictim(); - bool HasVictim(); + bool HasVictim() const; void SetVictim(int Victim); void SetVictim(const char *pVictim); int GetVictim() const override; diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp index d7afd49b384..92e87ce8a08 100644 --- a/src/engine/shared/network.cpp +++ b/src/engine/shared/network.cpp @@ -317,7 +317,7 @@ void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int Con CNetBase::SendPacket(Socket, pAddr, &Construct, SecurityToken, Sixup, true); } -unsigned char *CNetChunkHeader::Pack(unsigned char *pData, int Split) +unsigned char *CNetChunkHeader::Pack(unsigned char *pData, int Split) const { pData[0] = ((m_Flags & 3) << 6) | ((m_Size >> Split) & 0x3f); pData[1] = (m_Size & ((1 << Split) - 1)); diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index 8dc323f2690..7ab0de5b5f4 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -133,7 +133,7 @@ class CNetChunkHeader int m_Size; int m_Sequence; - unsigned char *Pack(unsigned char *pData, int Split = 4); + unsigned char *Pack(unsigned char *pData, int Split = 4) const; unsigned char *Unpack(unsigned char *pData, int Split = 4); }; diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index aedd8e373f7..7481eb63e4a 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -98,7 +98,7 @@ const void *CSnapshot::FindItem(int Type, int ID) const return Index < 0 ? nullptr : GetItem(Index)->Data(); } -unsigned CSnapshot::Crc() +unsigned CSnapshot::Crc() const { unsigned int Crc = 0; @@ -113,7 +113,7 @@ unsigned CSnapshot::Crc() return Crc; } -void CSnapshot::DebugDump() +void CSnapshot::DebugDump() const { dbg_msg("snapshot", "data_size=%d num_items=%d", m_DataSize, m_NumItems); for(int i = 0; i < m_NumItems; i++) @@ -537,7 +537,7 @@ void CSnapshotStorage::Add(int Tick, int64_t Tagtime, size_t DataSize, const voi m_pLast = pHolder; } -int CSnapshotStorage::Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData) +int CSnapshotStorage::Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData) const { CHolder *pHolder = m_pFirst; diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h index 07eaf7b353d..6947db1bce3 100644 --- a/src/engine/shared/snapshot.h +++ b/src/engine/shared/snapshot.h @@ -56,8 +56,8 @@ class CSnapshot int GetExternalItemType(int InternalType) const; const void *FindItem(int Type, int ID) const; - unsigned Crc(); - void DebugDump(); + unsigned Crc() const; + void DebugDump() const; bool IsValid(size_t ActualSize) const; static const CSnapshot *EmptySnapshot() { return &ms_EmptySnapshot; } @@ -131,7 +131,7 @@ class CSnapshotStorage void PurgeAll(); void PurgeUntil(int Tick); void Add(int Tick, int64_t Tagtime, size_t DataSize, const void *pData, size_t AltDataSize, const void *pAltData); - int Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData); + int Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData) const; }; class CSnapshotBuilder diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 6973ab1b116..2855b26fa6b 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -569,7 +569,7 @@ void CGameConsole::CInstance::ScrollToCenter(int StartLine, int EndLine) m_BacklogCurLine -= ComputedLines; } -void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry) +void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry) const { CTextCursor Cursor; m_pGameConsole->TextRender()->SetCursor(&Cursor, 0.0f, 0.0f, FONT_SIZE, 0); diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 78628e75d04..66e3c90e0bf 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -114,7 +114,7 @@ class CGameConsole : public CComponent static void PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser); static void PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser); - void UpdateEntryTextAttributes(CBacklogEntry *pEntry); + void UpdateEntryTextAttributes(CBacklogEntry *pEntry) const; private: void UpdateSearch(); diff --git a/src/game/client/components/ghost.cpp b/src/game/client/components/ghost.cpp index d15fa52a22b..2a55688f15d 100644 --- a/src/game/client/components/ghost.cpp +++ b/src/game/client/components/ghost.cpp @@ -679,7 +679,7 @@ void CGhost::OnMapLoad() m_AllowRestart = false; } -int CGhost::GetLastRaceTick() +int CGhost::GetLastRaceTick() const { return m_LastRaceTick; } diff --git a/src/game/client/components/ghost.h b/src/game/client/components/ghost.h index 9a6d3867791..c7582523e8c 100644 --- a/src/game/client/components/ghost.h +++ b/src/game/client/components/ghost.h @@ -174,7 +174,7 @@ class CGhost : public CComponent class IGhostLoader *GhostLoader() const { return m_pGhostLoader; } class IGhostRecorder *GhostRecorder() const { return m_pGhostRecorder; } - int GetLastRaceTick(); + int GetLastRaceTick() const; void RefindSkins(); }; diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp index 089303bf544..9a4733f1612 100644 --- a/src/game/client/components/mapimages.cpp +++ b/src/game/client/components/mapimages.cpp @@ -427,7 +427,7 @@ void CMapImages::SetTextureScale(int Scale) } } -int CMapImages::GetTextureScale() +int CMapImages::GetTextureScale() const { return m_TextureScale; } diff --git a/src/game/client/components/mapimages.h b/src/game/client/components/mapimages.h index c511b04113b..66bd83df566 100644 --- a/src/game/client/components/mapimages.h +++ b/src/game/client/components/mapimages.h @@ -70,7 +70,7 @@ class CMapImages : public CComponent IGraphics::CTextureHandle GetOverlayCenter(); void SetTextureScale(int Scale); - int GetTextureScale(); + int GetTextureScale() const; void ChangeEntitiesPath(const char *pPath); diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 985ba4bfe0f..9474f7cfc9f 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -942,7 +942,7 @@ void CMenus::PopupWarning(const char *pTopic, const char *pBody, const char *pBu m_PopupWarningLastTime = time_get_nanoseconds(); } -bool CMenus::CanDisplayWarning() +bool CMenus::CanDisplayWarning() const { return m_Popup == POPUP_NONE; } diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 86485bb6aac..b9dd4999f72 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -720,8 +720,8 @@ class CMenus : public CComponent void UpdateOwnGhost(CGhostItem Item); void DeleteGhostItem(int Index); - int GetCurPopup() { return m_Popup; } - bool CanDisplayWarning(); + int GetCurPopup() const { return m_Popup; } + bool CanDisplayWarning() const; void PopupWarning(const char *pTopic, const char *pBody, const char *pButton, std::chrono::nanoseconds Duration); diff --git a/src/game/client/components/statboard.cpp b/src/game/client/components/statboard.cpp index 71b35a1b42a..be9bed21950 100644 --- a/src/game/client/components/statboard.cpp +++ b/src/game/client/components/statboard.cpp @@ -41,7 +41,7 @@ void CStatboard::OnConsoleInit() Console()->Register("+statboard", "", CFGFLAG_CLIENT, ConKeyStats, this, "Show stats"); } -bool CStatboard::IsActive() +bool CStatboard::IsActive() const { return m_Active; } diff --git a/src/game/client/components/statboard.h b/src/game/client/components/statboard.h index 8026c56a08c..e5d2a88067d 100644 --- a/src/game/client/components/statboard.h +++ b/src/game/client/components/statboard.h @@ -28,7 +28,7 @@ class CStatboard : public CComponent virtual void OnRender() override; virtual void OnRelease() override; virtual void OnMessage(int MsgType, void *pRawMsg) override; - bool IsActive(); + bool IsActive() const; }; #endif // GAME_CLIENT_COMPONENTS_STATBOARD_H diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 17e20f8bbd4..29063df98ec 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -747,7 +747,7 @@ void CGameClient::OnDummyDisconnect() m_PredictedDummyID = -1; } -int CGameClient::GetLastRaceTick() +int CGameClient::GetLastRaceTick() const { return m_Ghost.GetLastRaceTick(); } @@ -2339,7 +2339,7 @@ void CGameClient::SendDummyInfo(bool Start) } } -void CGameClient::SendKill(int ClientID) +void CGameClient::SendKill(int ClientID) const { CNetMsg_Cl_Kill Msg; Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL); @@ -2812,7 +2812,7 @@ void CGameClient::Echo(const char *pString) m_Chat.Echo(pString); } -bool CGameClient::IsOtherTeam(int ClientID) +bool CGameClient::IsOtherTeam(int ClientID) const { bool Local = m_Snap.m_LocalClientID == ClientID; @@ -2835,7 +2835,7 @@ bool CGameClient::IsOtherTeam(int ClientID) return m_Teams.Team(ClientID) != m_Teams.Team(m_Snap.m_LocalClientID); } -int CGameClient::SwitchStateTeam() +int CGameClient::SwitchStateTeam() const { if(m_aSwitchStateTeam[g_Config.m_ClDummy] >= 0) return m_aSwitchStateTeam[g_Config.m_ClDummy]; @@ -2846,7 +2846,7 @@ int CGameClient::SwitchStateTeam() return m_Teams.Team(m_Snap.m_LocalClientID); } -bool CGameClient::IsLocalCharSuper() +bool CGameClient::IsLocalCharSuper() const { if(m_Snap.m_LocalClientID < 0) return false; @@ -3485,12 +3485,12 @@ void CGameClient::DummyResetInput() m_DummyInput = m_Controls.m_aInputData[!g_Config.m_ClDummy]; } -bool CGameClient::CanDisplayWarning() +bool CGameClient::CanDisplayWarning() const { return m_Menus.CanDisplayWarning(); } -bool CGameClient::IsDisplayingWarning() +bool CGameClient::IsDisplayingWarning() const { return m_Menus.GetCurPopup() == CMenus::POPUP_WARNING; } diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 30856f74dc5..1e76b4cf4b4 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -507,7 +507,7 @@ class CGameClient : public IGameClient void SendSwitchTeam(int Team); void SendInfo(bool Start); void SendDummyInfo(bool Start) override; - void SendKill(int ClientID); + void SendKill(int ClientID) const; // DDRace @@ -521,7 +521,7 @@ class CGameClient : public IGameClient int IntersectCharacter(vec2 HookPos, vec2 NewPos, vec2 &NewPos2, int ownID); - int GetLastRaceTick() override; + int GetLastRaceTick() const override; bool IsTeamPlay() { return m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS; } @@ -543,11 +543,11 @@ class CGameClient : public IGameClient void DummyResetInput() override; void Echo(const char *pString) override; - bool IsOtherTeam(int ClientID); - int SwitchStateTeam(); - bool IsLocalCharSuper(); - bool CanDisplayWarning() override; - bool IsDisplayingWarning() override; + bool IsOtherTeam(int ClientID) const; + int SwitchStateTeam() const; + bool IsLocalCharSuper() const; + bool CanDisplayWarning() const override; + bool IsDisplayingWarning() const override; CNetObjHandler *GetNetObjHandler() override; void LoadGameSkin(const char *pPath, bool AsDir = false); diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index e1e2ab7b75e..212c353aff4 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -633,7 +633,7 @@ void CLineInput::Activate(EInputPriority Priority) ms_ActiveInputPriority = Priority; } -void CLineInput::Deactivate() +void CLineInput::Deactivate() const { if(!IsActive()) return; diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h index a0991780ee6..0bd405a0412 100644 --- a/src/game/client/lineinput.h +++ b/src/game/client/lineinput.h @@ -194,7 +194,7 @@ class CLineInput bool IsActive() const { return GetActiveInput() == this; } void Activate(EInputPriority Priority); - void Deactivate(); + void Deactivate() const; }; template diff --git a/src/game/client/prediction/entities/character.cpp b/src/game/client/prediction/entities/character.cpp index d73e9940355..d1c1570f136 100644 --- a/src/game/client/prediction/entities/character.cpp +++ b/src/game/client/prediction/entities/character.cpp @@ -1358,7 +1358,7 @@ void CCharacter::SetCoreWorld(CGameWorld *pGameWorld) m_Core.SetCoreWorld(&pGameWorld->m_Core, pGameWorld->Collision(), pGameWorld->Teams()); } -bool CCharacter::Match(CCharacter *pChar) +bool CCharacter::Match(CCharacter *pChar) const { return distance(pChar->m_Core.m_Pos, m_Core.m_Pos) <= 32.f; } diff --git a/src/game/client/prediction/entities/character.h b/src/game/client/prediction/entities/character.h index 206212413a4..b424a8c9b73 100644 --- a/src/game/client/prediction/entities/character.h +++ b/src/game/client/prediction/entities/character.h @@ -123,7 +123,7 @@ class CCharacter : public CEntity int m_GameTeam; bool m_CanMoveInFreeze; - bool Match(CCharacter *pChar); + bool Match(CCharacter *pChar) const; void ResetPrediction(); void SetTuneZone(int Zone); diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp index 58f9eeb6979..a2fb7012367 100644 --- a/src/game/client/prediction/gameworld.cpp +++ b/src/game/client/prediction/gameworld.cpp @@ -372,7 +372,7 @@ void CGameWorld::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage, } } -bool CGameWorld::IsLocalTeam(int OwnerID) +bool CGameWorld::IsLocalTeam(int OwnerID) const { return OwnerID < 0 || m_Teams.CanCollide(m_LocalClientID, OwnerID); } @@ -698,7 +698,7 @@ CEntity *CGameWorld::FindMatch(int ObjID, int ObjType, const void *pObjData) return 0; } -void CGameWorld::OnModified() +void CGameWorld::OnModified() const { if(m_pChild) m_pChild->m_IsValidCopy = false; diff --git a/src/game/client/prediction/gameworld.h b/src/game/client/prediction/gameworld.h index fbb63ad9b05..18be1fc2586 100644 --- a/src/game/client/prediction/gameworld.h +++ b/src/game/client/prediction/gameworld.h @@ -89,8 +89,8 @@ class CGameWorld int m_LocalClientID; - bool IsLocalTeam(int OwnerID); - void OnModified(); + bool IsLocalTeam(int OwnerID) const; + void OnModified() const; void NetObjBegin(CTeamsCore Teams, int LocalClientID); void NetCharAdd(int ObjID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal); void NetObjAdd(int ObjID, int ObjType, const void *pObjData, const CNetObj_EntityEx *pDataEx); diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp index 948fdc800da..0e081c27812 100644 --- a/src/game/client/render.cpp +++ b/src/game/client/render.cpp @@ -57,7 +57,7 @@ void CRenderTools::Init(IGraphics *pGraphics, ITextRender *pTextRender) Graphics()->QuadContainerUpload(m_TeeQuadContainerIndex); } -void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy) +void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy) const { int x = pSpr->m_X + sx; int y = pSpr->m_Y + sy; @@ -82,45 +82,45 @@ void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy) Graphics()->QuadsSetSubset(x1, y1, x2, y2); } -void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy) +void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy) const { if(Id < 0 || Id >= g_pData->m_NumSprites) return; SelectSprite(&g_pData->m_aSprites[Id], Flags, sx, sy); } -void CRenderTools::GetSpriteScale(struct CDataSprite *pSprite, float &ScaleX, float &ScaleY) +void CRenderTools::GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const { int w = pSprite->m_W; int h = pSprite->m_H; GetSpriteScaleImpl(w, h, ScaleX, ScaleY); } -void CRenderTools::GetSpriteScale(int Id, float &ScaleX, float &ScaleY) +void CRenderTools::GetSpriteScale(int Id, float &ScaleX, float &ScaleY) const { GetSpriteScale(&g_pData->m_aSprites[Id], ScaleX, ScaleY); } -void CRenderTools::GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY) +void CRenderTools::GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY) const { const float f = length(vec2(Width, Height)); ScaleX = Width / f; ScaleY = Height / f; } -void CRenderTools::DrawSprite(float x, float y, float Size) +void CRenderTools::DrawSprite(float x, float y, float Size) const { IGraphics::CQuadItem QuadItem(x, y, Size * gs_SpriteWScale, Size * gs_SpriteHScale); Graphics()->QuadsDraw(&QuadItem, 1); } -void CRenderTools::DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight) +void CRenderTools::DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight) const { IGraphics::CQuadItem QuadItem(x, y, ScaledWidth, ScaledHeight); Graphics()->QuadsDraw(&QuadItem, 1); } -void CRenderTools::RenderCursor(vec2 Center, float Size) +void CRenderTools::RenderCursor(vec2 Center, float Size) const { Graphics()->WrapClamp(); Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id); @@ -132,7 +132,7 @@ void CRenderTools::RenderCursor(vec2 Center, float Size) Graphics()->WrapNormal(); } -void CRenderTools::RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor) +void CRenderTools::RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor) const { Graphics()->TextureSet(g_pData->m_aImages[ImageId].m_Id); Graphics()->QuadsBegin(); @@ -144,25 +144,25 @@ void CRenderTools::RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, c Graphics()->QuadsEnd(); } -int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float Size) +int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float Size) const { IGraphics::CQuadItem QuadItem(x, y, Size, Size); return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1); } -int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Size) +int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Size) const { IGraphics::CQuadItem QuadItem(-(Size) / 2.f, -(Size) / 2.f, (Size), (Size)); return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1); } -int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height) +int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height) const { IGraphics::CQuadItem QuadItem(-(Width) / 2.f, -(Height) / 2.f, (Width), (Height)); return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1); } -int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height) +int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height) const { IGraphics::CQuadItem QuadItem(X, Y, Width, Height); return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1); diff --git a/src/game/client/render.h b/src/game/client/render.h index 0321cfec12a..65028b7dbb3 100644 --- a/src/game/client/render.h +++ b/src/game/client/render.h @@ -130,21 +130,21 @@ class CRenderTools void Init(class IGraphics *pGraphics, class ITextRender *pTextRender); - void SelectSprite(CDataSprite *pSprite, int Flags = 0, int sx = 0, int sy = 0); - void SelectSprite(int Id, int Flags = 0, int sx = 0, int sy = 0); - - void GetSpriteScale(CDataSprite *pSprite, float &ScaleX, float &ScaleY); - void GetSpriteScale(int Id, float &ScaleX, float &ScaleY); - void GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY); - - void DrawSprite(float x, float y, float size); - void DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight); - void RenderCursor(vec2 Center, float Size); - void RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor = nullptr); - int QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float size); - int QuadContainerAddSprite(int QuadContainerIndex, float size); - int QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height); - int QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height); + void SelectSprite(CDataSprite *pSprite, int Flags = 0, int sx = 0, int sy = 0) const; + void SelectSprite(int Id, int Flags = 0, int sx = 0, int sy = 0) const; + + void GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const; + void GetSpriteScale(int Id, float &ScaleX, float &ScaleY) const; + void GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY) const; + + void DrawSprite(float x, float y, float Size) const; + void DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight) const; + void RenderCursor(vec2 Center, float Size) const; + void RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor = nullptr) const; + int QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float Size) const; + int QuadContainerAddSprite(int QuadContainerIndex, float Size) const; + int QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height) const; + int QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height) const; // larger rendering methods void GetRenderTeeBodySize(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &BodyOffset, float &Width, float &Height); @@ -158,13 +158,13 @@ class CRenderTools // map render methods (render_map.cpp) static void RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result); - void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser); - void ForceRenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha = 1.0f); - void RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset); + void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser) const; + void ForceRenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha = 1.0f) const; + void RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const; // render a rectangle made of IndexIn tiles, over a background made of IndexOut tiles // the rectangle include all tiles in [RectX, RectX+RectW-1] x [RectY, RectY+RectH-1] - void RenderTileRectangle(int RectX, int RectY, int RectW, int RectH, unsigned char IndexIn, unsigned char IndexOut, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset); + void RenderTileRectangle(int RectX, int RectY, int RectW, int RectH, unsigned char IndexIn, unsigned char IndexOut, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const; // helpers void CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight); @@ -175,14 +175,14 @@ class CRenderTools // DDRace - void RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha = 1.0f); - void RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, float Alpha = 1.0f); - void RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha = 1.0f); - void RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha = 1.0f); - void RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags); - void RenderSpeedupmap(CSpeedupTile *pSpeedup, int w, int h, float Scale, ColorRGBA Color, int RenderFlags); - void RenderSwitchmap(CSwitchTile *pSwitch, int w, int h, float Scale, ColorRGBA Color, int RenderFlags); - void RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags); + void RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha = 1.0f) const; + void RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, float Alpha = 1.0f) const; + void RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha = 1.0f) const; + void RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha = 1.0f) const; + void RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const; + void RenderSpeedupmap(CSpeedupTile *pSpeedup, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const; + void RenderSwitchmap(CSwitchTile *pSwitch, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const; + void RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const; }; #endif diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp index 5e858292d4c..8241a3d22e2 100644 --- a/src/game/client/render_map.cpp +++ b/src/game/client/render_map.cpp @@ -351,7 +351,7 @@ static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation) pPoint->y = (int)(x * std::sin(Rotation) + y * std::cos(Rotation) + pCenter->y); } -void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser) +void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser) const { if(!g_Config.m_ClShowQuads || g_Config.m_ClOverlayEntities == 100) return; @@ -359,7 +359,7 @@ void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENV ForceRenderQuads(pQuads, NumQuads, RenderFlags, pfnEval, pUser, (100 - g_Config.m_ClOverlayEntities) / 100.0f); } -void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha) +void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha) const { Graphics()->TrianglesBegin(); float Conv = 1 / 255.0f; @@ -443,7 +443,7 @@ void CRenderTools::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags void CRenderTools::RenderTileRectangle(int RectX, int RectY, int RectW, int RectH, unsigned char IndexIn, unsigned char IndexOut, float Scale, ColorRGBA Color, int RenderFlags, - ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) + ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const { float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); @@ -541,7 +541,7 @@ void CRenderTools::RenderTileRectangle(int RectX, int RectY, int RectW, int Rect } void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, - ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) + ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset) const { float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); @@ -707,7 +707,7 @@ void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, Color Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha) +void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, float Alpha) const { if(!g_Config.m_ClTextEntities) return; @@ -757,7 +757,7 @@ void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, float Alpha) +void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, float Alpha) const { float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); @@ -826,7 +826,7 @@ void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, fl Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha) +void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, float Alpha) const { if(!g_Config.m_ClTextEntities) return; @@ -886,7 +886,7 @@ void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha) +void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, float Alpha) const { if(!g_Config.m_ClTextEntities) return; @@ -935,7 +935,7 @@ void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -void CRenderTools::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) +void CRenderTools::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const { float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); @@ -1052,7 +1052,7 @@ void CRenderTools::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, Co Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -void CRenderTools::RenderSpeedupmap(CSpeedupTile *pSpeedupTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) +void CRenderTools::RenderSpeedupmap(CSpeedupTile *pSpeedupTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const { //Graphics()->TextureSet(img_get(tmap->image)); float ScreenX0, ScreenY0, ScreenX1, ScreenY1; @@ -1171,7 +1171,7 @@ void CRenderTools::RenderSpeedupmap(CSpeedupTile *pSpeedupTile, int w, int h, fl Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -void CRenderTools::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) +void CRenderTools::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const { //Graphics()->TextureSet(img_get(tmap->image)); float ScreenX0, ScreenY0, ScreenX1, ScreenY1; @@ -1333,7 +1333,7 @@ void CRenderTools::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -void CRenderTools::RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) +void CRenderTools::RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags) const { float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 9cae1a04bf1..3795e3401cd 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -152,7 +152,7 @@ void CUI::AddUIElement(CUIElement *pElement) m_vpUIElements.push_back(pElement); } -void CUI::ResetUIElement(CUIElement &UIElement) +void CUI::ResetUIElement(CUIElement &UIElement) const { for(CUIElement::SUIElementRect &Rect : UIElement.m_vUIRects) { @@ -533,7 +533,7 @@ EEditState CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, return Res; } -void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed) +void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed) const { // reset scrolling if it's not necessary anymore if(TotalSize < ViewPortSize) @@ -666,7 +666,7 @@ vec2 CUI::CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, c return Cursor; } -void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps) +void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps) const { const int Flags = GetFlagsForLabelProperties(LabelProps, nullptr); const SCursorAndBoundingBox TextBounds = CalcFontSizeCursorHeightAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps); @@ -678,7 +678,7 @@ void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align TextRender()->TextEx(&Cursor, pText, -1); } -void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) +void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) const { const int Flags = GetFlagsForLabelProperties(LabelProps, pReadCursor); const SCursorAndBoundingBox TextBounds = CalcFontSizeCursorHeightAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps); @@ -705,7 +705,7 @@ void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, cons RectEl.m_Cursor = Cursor; } -void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) +void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) const { const int ReadCursorGlyphCount = pReadCursor == nullptr ? -1 : pReadCursor->m_GlyphCount; bool NeedsRecreate = false; @@ -1335,7 +1335,7 @@ void CUI::DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect, *pOption = Value; } -void CUI::RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props) +void CUI::RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props) const { static float s_SpinnerOffset = 0.0f; static float s_LastRender = Client()->LocalTime(); diff --git a/src/game/client/ui.h b/src/game/client/ui.h index 324d4cff524..6c3c5d9596d 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -408,7 +408,7 @@ class CUI HOTKEY_END = 1 << 11, }; - void ResetUIElement(CUIElement &UIElement); + void ResetUIElement(CUIElement &UIElement) const; CUIElement *GetNewUIElement(int RequestedRectCount); @@ -505,13 +505,13 @@ class CUI int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect); int DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted); EEditState DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY); - void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp = false, float ScrollSpeed = 10.0f); + void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp = false, float ScrollSpeed = 10.0f) const; static vec2 CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, const float *pBiggestCharHeight = nullptr); - void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}); + void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}) const; - void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr); - void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr); + void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr) const; + void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr) const; bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL); bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL); @@ -538,7 +538,7 @@ class CUI void DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect, const char *pStr, int Min, int Max, const IScrollbarScale *pScale = &ms_LinearScrollbarScale, unsigned Flags = 0u, const char *pSuffix = ""); // progress spinner - void RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props = {}); + void RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props = {}) const; // popup menu void DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, const SPopupMenuProperties &Props = {}); diff --git a/src/game/collision.cpp b/src/game/collision.cpp index 5306d589b69..8612ad7dc26 100644 --- a/src/game/collision.cpp +++ b/src/game/collision.cpp @@ -225,7 +225,7 @@ static int GetMoveRestrictions(int Direction, int Tile, int Flags) return Result & GetMoveRestrictionsMask(Direction); } -int CCollision::GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance, int OverrideCenterTileIndex) +int CCollision::GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance, int OverrideCenterTileIndex) const { static const vec2 DIRECTIONS[NUM_MR_DIRS] = { diff --git a/src/game/collision.h b/src/game/collision.h index ceccc03d4d9..351ac05dd2c 100644 --- a/src/game/collision.h +++ b/src/game/collision.h @@ -62,7 +62,7 @@ class CCollision int GetIndex(vec2 PrevPos, vec2 Pos) const; int GetFIndex(int x, int y) const; - int GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance = 18.0f, int OverrideCenterTileIndex = -1); + int GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance = 18.0f, int OverrideCenterTileIndex = -1) const; int GetMoveRestrictions(vec2 Pos, float Distance = 18.0f) { return GetMoveRestrictions(nullptr, nullptr, Pos, Distance); diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index b63c3e676dd..4fc1cd80da8 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -508,7 +508,7 @@ std::vector CEditor::GetSelectedQuads() return vpQuads; } -CSoundSource *CEditor::GetSelectedSource() +CSoundSource *CEditor::GetSelectedSource() const { std::shared_ptr pSounds = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_SOUNDS)); if(!pSounds) diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 7eb0cfe2009..f9bbcc52ebf 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -467,7 +467,7 @@ class CEditor : public IEditor std::shared_ptr GetSelectedLayerType(int Index, int Type) const; std::shared_ptr GetSelectedLayer(int Index) const; std::shared_ptr GetSelectedGroup() const; - CSoundSource *GetSelectedSource(); + CSoundSource *GetSelectedSource() const; void SelectLayer(int LayerIndex, int GroupIndex = -1); void AddSelectedLayer(int LayerIndex); void SelectQuad(int Index); diff --git a/src/game/editor/map_grid.cpp b/src/game/editor/map_grid.cpp index 43e413bad93..e241642aa6d 100644 --- a/src/game/editor/map_grid.cpp +++ b/src/game/editor/map_grid.cpp @@ -74,7 +74,7 @@ int CMapGrid::GridLineDistance() const return 512; } -void CMapGrid::SnapToGrid(float &x, float &y) +void CMapGrid::SnapToGrid(float &x, float &y) const { const int GridDistance = GridLineDistance() * m_GridFactor; x = (int)((x + (x >= 0 ? 1.0f : -1.0f) * GridDistance / 2) / GridDistance) * GridDistance; diff --git a/src/game/editor/map_grid.h b/src/game/editor/map_grid.h index dc589018557..3a6e26a3be1 100644 --- a/src/game/editor/map_grid.h +++ b/src/game/editor/map_grid.h @@ -9,7 +9,7 @@ class CMapGrid : public CEditorComponent void OnReset() override; void OnRender(CUIRect View) override; - void SnapToGrid(float &x, float &y); + void SnapToGrid(float &x, float &y) const; int GridLineDistance() const; /** diff --git a/src/game/editor/map_view.cpp b/src/game/editor/map_view.cpp index 35bdd8c03c3..99337b8f76b 100644 --- a/src/game/editor/map_view.cpp +++ b/src/game/editor/map_view.cpp @@ -135,7 +135,7 @@ void CMapView::ResetZoom() m_Zoom.SetValue(100.0f); } -float CMapView::ScaleLength(float Value) +float CMapView::ScaleLength(float Value) const { return m_WorldZoom * Value; } diff --git a/src/game/editor/map_view.h b/src/game/editor/map_view.h index 5da4db23609..5aa86254d64 100644 --- a/src/game/editor/map_view.h +++ b/src/game/editor/map_view.h @@ -34,7 +34,7 @@ class CMapView : public CEditorComponent /** * Scale length according to zoom value. */ - float ScaleLength(float Value); + float ScaleLength(float Value) const; bool m_ShowPicker; // TODO: make private diff --git a/src/game/editor/mapitems/layer_group.cpp b/src/game/editor/mapitems/layer_group.cpp index 6529e0584c0..2607fc9a3fc 100644 --- a/src/game/editor/mapitems/layer_group.cpp +++ b/src/game/editor/mapitems/layer_group.cpp @@ -27,13 +27,13 @@ CLayerGroup::~CLayerGroup() m_vpLayers.clear(); } -void CLayerGroup::Convert(CUIRect *pRect) +void CLayerGroup::Convert(CUIRect *pRect) const { pRect->x += m_OffsetX; pRect->y += m_OffsetY; } -void CLayerGroup::Mapping(float *pPoints) +void CLayerGroup::Mapping(float *pPoints) const { float NormalParallaxZoom = clamp((double)(maximum(m_ParallaxX, m_ParallaxY)), 0., 100.); float ParallaxZoom = m_pMap->m_pEditor->m_PreviewZoom ? NormalParallaxZoom : 100.0f; @@ -49,7 +49,7 @@ void CLayerGroup::Mapping(float *pPoints) pPoints[3] += m_pMap->m_pEditor->MapView()->GetEditorOffset().y; } -void CLayerGroup::MapScreen() +void CLayerGroup::MapScreen() const { float aPoints[4]; Mapping(aPoints); diff --git a/src/game/editor/mapitems/layer_group.h b/src/game/editor/mapitems/layer_group.h index 8325069a41c..78e1488e0b3 100644 --- a/src/game/editor/mapitems/layer_group.h +++ b/src/game/editor/mapitems/layer_group.h @@ -33,10 +33,10 @@ class CLayerGroup CLayerGroup(); ~CLayerGroup(); - void Convert(CUIRect *pRect); + void Convert(CUIRect *pRect) const; void Render(); - void MapScreen(); - void Mapping(float *pPoints); + void MapScreen() const; + void Mapping(float *pPoints) const; void GetSize(float *pWidth, float *pHeight) const; diff --git a/src/game/editor/mapitems/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp index e65e65fa655..e93229066cb 100644 --- a/src/game/editor/mapitems/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -84,7 +84,7 @@ void CLayerTiles::SetTile(int x, int y, CTile Tile) RecordStateChange(x, y, CurrentTile, Tile); } -void CLayerTiles::SetTileIgnoreHistory(int x, int y, CTile Tile) +void CLayerTiles::SetTileIgnoreHistory(int x, int y, CTile Tile) const { m_pTiles[y * m_Width + x] = Tile; } @@ -111,7 +111,7 @@ void CLayerTiles::PrepareForSave() } } -void CLayerTiles::ExtractTiles(int TilemapItemVersion, const CTile *pSavedTiles, size_t SavedTilesSize) +void CLayerTiles::ExtractTiles(int TilemapItemVersion, const CTile *pSavedTiles, size_t SavedTilesSize) const { const size_t DestSize = (size_t)m_Width * m_Height; if(TilemapItemVersion >= CMapItemLayerTilemap::TILE_SKIP_MIN_VERSION) @@ -120,7 +120,7 @@ void CLayerTiles::ExtractTiles(int TilemapItemVersion, const CTile *pSavedTiles, mem_copy(m_pTiles, pSavedTiles, DestSize * sizeof(CTile)); } -void CLayerTiles::MakePalette() +void CLayerTiles::MakePalette() const { for(int y = 0; y < m_Height; y++) for(int x = 0; x < m_Width; x++) @@ -171,7 +171,7 @@ void CLayerTiles::Render(bool Tileset) int CLayerTiles::ConvertX(float x) const { return (int)(x / 32.0f); } int CLayerTiles::ConvertY(float y) const { return (int)(y / 32.0f); } -void CLayerTiles::Convert(CUIRect Rect, RECTi *pOut) +void CLayerTiles::Convert(CUIRect Rect, RECTi *pOut) const { pOut->x = ConvertX(Rect.x); pOut->y = ConvertY(Rect.y); @@ -179,7 +179,7 @@ void CLayerTiles::Convert(CUIRect Rect, RECTi *pOut) pOut->h = ConvertY(Rect.y + Rect.h + 31) - pOut->y; } -void CLayerTiles::Snap(CUIRect *pRect) +void CLayerTiles::Snap(CUIRect *pRect) const { RECTi Out; Convert(*pRect, &Out); @@ -189,7 +189,7 @@ void CLayerTiles::Snap(CUIRect *pRect) pRect->h = Out.h * 32.0f; } -void CLayerTiles::Clamp(RECTi *pRect) +void CLayerTiles::Clamp(RECTi *pRect) const { if(pRect->x < 0) { diff --git a/src/game/editor/mapitems/layer_tiles.h b/src/game/editor/mapitems/layer_tiles.h index 10e7fc25afb..ca8e84e8892 100644 --- a/src/game/editor/mapitems/layer_tiles.h +++ b/src/game/editor/mapitems/layer_tiles.h @@ -102,19 +102,19 @@ class CLayerTiles : public CLayer virtual CTile GetTile(int x, int y); virtual void SetTile(int x, int y, CTile Tile); - void SetTileIgnoreHistory(int x, int y, CTile Tile); + void SetTileIgnoreHistory(int x, int y, CTile Tile) const; virtual void Resize(int NewW, int NewH); virtual void Shift(int Direction); - void MakePalette(); + void MakePalette() const; void Render(bool Tileset = false) override; int ConvertX(float x) const; int ConvertY(float y) const; - void Convert(CUIRect Rect, RECTi *pOut); - void Snap(CUIRect *pRect); - void Clamp(RECTi *pRect); + void Convert(CUIRect Rect, RECTi *pOut) const; + void Snap(CUIRect *pRect) const; + void Clamp(RECTi *pRect) const; virtual bool IsEntitiesLayer() const override; @@ -151,7 +151,7 @@ class CLayerTiles : public CLayer void ModifyEnvelopeIndex(FIndexModifyFunction pfnFunc) override; void PrepareForSave(); - void ExtractTiles(int TilemapItemVersion, const CTile *pSavedTiles, size_t SavedTilesSize); + void ExtractTiles(int TilemapItemVersion, const CTile *pSavedTiles, size_t SavedTilesSize) const; void GetSize(float *pWidth, float *pHeight) override { diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index 3858aa6a5e0..3e0f921c665 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -547,7 +547,7 @@ void CCharacterCore::Move() m_Pos = NewPos; } -void CCharacterCore::Write(CNetObj_CharacterCore *pObjCore) +void CCharacterCore::Write(CNetObj_CharacterCore *pObjCore) const { pObjCore->m_X = round_to_int(m_Pos.x); pObjCore->m_Y = round_to_int(m_Pos.y); diff --git a/src/game/gamecore.h b/src/game/gamecore.h index 07c6d188fd0..8b16cfcaaec 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -277,7 +277,7 @@ class CCharacterCore void Move(); void Read(const CNetObj_CharacterCore *pObjCore); - void Write(CNetObj_CharacterCore *pObjCore); + void Write(CNetObj_CharacterCore *pObjCore) const; void Quantize(); // DDRace diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 8da773c9b4d..e63dcbe5810 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -941,7 +941,7 @@ void CGameContext::ConUnlockTeam(IConsole::IResult *pResult, void *pUserData) pSelf->UnlockTeam(pResult->m_ClientID, Team); } -void CGameContext::UnlockTeam(int ClientID, int Team) +void CGameContext::UnlockTeam(int ClientID, int Team) const { m_pController->Teams().SetTeamLock(Team, false); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 18eacd20543..f2da3cd6073 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -362,7 +362,7 @@ void CGameContext::CreateSound(vec2 Pos, int Sound, CClientMask Mask) } } -void CGameContext::CreateSoundGlobal(int Sound, int Target) +void CGameContext::CreateSoundGlobal(int Sound, int Target) const { if(Sound < 0) return; @@ -434,7 +434,7 @@ void CGameContext::SnapSwitchers(int SnappingClient) } } -bool CGameContext::SnapLaserObject(const CSnapContext &Context, int SnapID, const vec2 &To, const vec2 &From, int StartTick, int Owner, int LaserType, int Subtype, int SwitchNumber) +bool CGameContext::SnapLaserObject(const CSnapContext &Context, int SnapID, const vec2 &To, const vec2 &From, int StartTick, int Owner, int LaserType, int Subtype, int SwitchNumber) const { if(Context.GetClientVersion() >= VERSION_DDNET_MULTI_LASER) { @@ -469,7 +469,7 @@ bool CGameContext::SnapLaserObject(const CSnapContext &Context, int SnapID, cons return true; } -bool CGameContext::SnapPickup(const CSnapContext &Context, int SnapID, const vec2 &Pos, int Type, int SubType, int SwitchNumber) +bool CGameContext::SnapPickup(const CSnapContext &Context, int SnapID, const vec2 &Pos, int Type, int SubType, int SwitchNumber) const { if(Context.IsSixup()) { @@ -543,7 +543,7 @@ void CGameContext::CallVote(int ClientID, const char *pDesc, const char *pCmd, c pPlayer->m_LastVoteCall = Now; } -void CGameContext::SendChatTarget(int To, const char *pText, int Flags) +void CGameContext::SendChatTarget(int To, const char *pText, int Flags) const { CNetMsg_Sv_Chat Msg; Msg.m_Team = 0; @@ -574,7 +574,7 @@ void CGameContext::SendChatTarget(int To, const char *pText, int Flags) } } -void CGameContext::SendChatTeam(int Team, const char *pText) +void CGameContext::SendChatTeam(int Team, const char *pText) const { for(int i = 0; i < MAX_CLIENTS; i++) if(m_apPlayers[i] != nullptr && GetDDRaceTeam(i) == Team) @@ -673,7 +673,7 @@ void CGameContext::SendStartWarning(int ClientID, const char *pMessage) } } -void CGameContext::SendEmoticon(int ClientID, int Emoticon, int TargetClientID) +void CGameContext::SendEmoticon(int ClientID, int Emoticon, int TargetClientID) const { CNetMsg_Sv_Emoticon Msg; Msg.m_ClientID = ClientID; @@ -681,21 +681,21 @@ void CGameContext::SendEmoticon(int ClientID, int Emoticon, int TargetClientID) Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, TargetClientID); } -void CGameContext::SendWeaponPickup(int ClientID, int Weapon) +void CGameContext::SendWeaponPickup(int ClientID, int Weapon) const { CNetMsg_Sv_WeaponPickup Msg; Msg.m_Weapon = Weapon; Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID); } -void CGameContext::SendMotd(int ClientID) +void CGameContext::SendMotd(int ClientID) const { CNetMsg_Sv_Motd Msg; Msg.m_pMessage = g_Config.m_SvMotd; Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID); } -void CGameContext::SendSettings(int ClientID) +void CGameContext::SendSettings(int ClientID) const { protocol7::CNetMsg_Sv_ServerSettings Msg; Msg.m_KickVote = g_Config.m_SvVoteKick; @@ -1351,9 +1351,9 @@ void CGameContext::OnClientPredictedEarlyInput(int ClientID, void *pInput) } } -struct CVoteOptionServer *CGameContext::GetVoteOption(int Index) +const CVoteOptionServer *CGameContext::GetVoteOption(int Index) const { - CVoteOptionServer *pCurrent; + const CVoteOptionServer *pCurrent; for(pCurrent = m_pVoteOptionFirst; Index > 0 && pCurrent; Index--, pCurrent = pCurrent->m_pNext) @@ -1404,7 +1404,7 @@ void CGameContext::ProgressVoteOptions(int ClientID) OptionMsg.m_pDescription14 = ""; // get current vote option by index - CVoteOptionServer *pCurrent = GetVoteOption(pPl->m_SendVoteIndex); + const CVoteOptionServer *pCurrent = GetVoteOption(pPl->m_SendVoteIndex); while(CurIndex < NumVotesToSend && pCurrent != NULL) { @@ -4245,7 +4245,7 @@ bool CGameContext::ProcessSpamProtection(int ClientID, bool RespectChatInitialDe return false; } -int CGameContext::GetDDRaceTeam(int ClientID) +int CGameContext::GetDDRaceTeam(int ClientID) const { return m_pController->Teams().m_Core.Team(ClientID); } @@ -4514,7 +4514,7 @@ int CGameContext::GetClientVersion(int ClientID) const return Server()->GetClientVersion(ClientID); } -CClientMask CGameContext::ClientsMaskExcludeClientVersionAndHigher(int Version) +CClientMask CGameContext::ClientsMaskExcludeClientVersionAndHigher(int Version) const { CClientMask Mask; for(int i = 0; i < MAX_CLIENTS; ++i) @@ -4622,7 +4622,7 @@ bool CGameContext::RateLimitPlayerVote(int ClientID) return false; } -bool CGameContext::RateLimitPlayerMapVote(int ClientID) +bool CGameContext::RateLimitPlayerMapVote(int ClientID) const { if(!Server()->GetAuthedState(ClientID) && time_get() < m_LastMapVote + (time_freq() * g_Config.m_SvVoteMapTimeDelay)) { diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index caf2919b8b2..34e1cf76953 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -237,11 +237,11 @@ class CGameContext : public IGameServer void CreatePlayerSpawn(vec2 Pos, CClientMask Mask = CClientMask().set()); void CreateDeath(vec2 Pos, int ClientID, CClientMask Mask = CClientMask().set()); void CreateSound(vec2 Pos, int Sound, CClientMask Mask = CClientMask().set()); - void CreateSoundGlobal(int Sound, int Target = -1); + void CreateSoundGlobal(int Sound, int Target = -1) const; void SnapSwitchers(int SnappingClient); - bool SnapLaserObject(const CSnapContext &Context, int SnapID, const vec2 &To, const vec2 &From, int StartTick, int Owner = -1, int LaserType = -1, int Subtype = -1, int SwitchNumber = -1); - bool SnapPickup(const CSnapContext &Context, int SnapID, const vec2 &Pos, int Type, int SubType, int SwitchNumber); + bool SnapLaserObject(const CSnapContext &Context, int SnapID, const vec2 &To, const vec2 &From, int StartTick, int Owner = -1, int LaserType = -1, int Subtype = -1, int SwitchNumber = -1) const; + bool SnapPickup(const CSnapContext &Context, int SnapID, const vec2 &Pos, int Type, int SubType, int SwitchNumber) const; enum { @@ -258,14 +258,14 @@ class CGameContext : public IGameServer // network void CallVote(int ClientID, const char *pDesc, const char *pCmd, const char *pReason, const char *pChatmsg, const char *pSixupDesc = 0); - void SendChatTarget(int To, const char *pText, int Flags = CHAT_SIX | CHAT_SIXUP); - void SendChatTeam(int Team, const char *pText); + void SendChatTarget(int To, const char *pText, int Flags = CHAT_SIX | CHAT_SIXUP) const; + void SendChatTeam(int Team, const char *pText) const; void SendChat(int ClientID, int Team, const char *pText, int SpamProtectionClientID = -1, int Flags = CHAT_SIX | CHAT_SIXUP); void SendStartWarning(int ClientID, const char *pMessage); - void SendEmoticon(int ClientID, int Emoticon, int TargetClientID); - void SendWeaponPickup(int ClientID, int Weapon); - void SendMotd(int ClientID); - void SendSettings(int ClientID); + void SendEmoticon(int ClientID, int Emoticon, int TargetClientID) const; + void SendWeaponPickup(int ClientID, int Weapon) const; + void SendMotd(int ClientID) const; + void SendSettings(int ClientID) const; void SendBroadcast(const char *pText, int ClientID, bool IsImportant = true); void List(int ClientID, const char *pFilter); @@ -274,7 +274,7 @@ class CGameContext : public IGameServer void CheckPureTuning(); void SendTuningParams(int ClientID, int Zone = 0); - struct CVoteOptionServer *GetVoteOption(int Index); + const CVoteOptionServer *GetVoteOption(int Index) const; void ProgressVoteOptions(int ClientID); // @@ -339,12 +339,12 @@ class CGameContext : public IGameServer bool OnClientDDNetVersionKnown(int ClientID); void FillAntibot(CAntibotRoundData *pData) override; bool ProcessSpamProtection(int ClientID, bool RespectChatInitialDelay = true); - int GetDDRaceTeam(int ClientID); + int GetDDRaceTeam(int ClientID) const; // Describes the time when the first player joined the server. int64_t m_NonEmptySince; int64_t m_LastMapVote; int GetClientVersion(int ClientID) const; - CClientMask ClientsMaskExcludeClientVersionAndHigher(int Version); + CClientMask ClientsMaskExcludeClientVersionAndHigher(int Version) const; bool PlayerExists(int ClientID) const override { return m_apPlayers[ClientID]; } // Returns true if someone is actively moderating. bool PlayerModerating() const; @@ -352,7 +352,7 @@ class CGameContext : public IGameServer // Checks if player can vote and notify them about the reason bool RateLimitPlayerVote(int ClientID); - bool RateLimitPlayerMapVote(int ClientID); + bool RateLimitPlayerMapVote(int ClientID) const; void OnUpdatePlayerServerInfo(char *aBuf, int BufSize, int ID) override; @@ -505,7 +505,7 @@ class CGameContext : public IGameServer void WhisperID(int ClientID, int VictimID, const char *pMessage); void Converse(int ClientID, char *pStr); bool IsVersionBanned(int Version); - void UnlockTeam(int ClientID, int Team); + void UnlockTeam(int ClientID, int Team) const; enum { diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 11dab99af65..77630471f51 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -837,7 +837,7 @@ int CPlayer::IsPaused() const return m_ForcePauseTime ? m_ForcePauseTime : -1 * m_Paused; } -bool CPlayer::IsPlaying() +bool CPlayer::IsPlaying() const { return m_pCharacter && m_pCharacter->IsAlive(); } diff --git a/src/game/server/player.h b/src/game/server/player.h index 4c9224c215c..8afed78165e 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -178,7 +178,7 @@ class CPlayer int ForcePause(int Time); int IsPaused() const; - bool IsPlaying(); + bool IsPlaying() const; int64_t m_Last_KickVote; int64_t m_Last_Team; int m_ShowOthers; diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index af183a20a36..c5b1841d4d9 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -596,7 +596,7 @@ void CSaveTeam::Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakSt pGameServer->m_World.RemoveEntitiesFromPlayers(aPlayerCIDs, m_MembersCount); } -CCharacter *CSaveTeam::MatchCharacter(CGameContext *pGameServer, int ClientID, int SaveID, bool KeepCurrentCharacter) +CCharacter *CSaveTeam::MatchCharacter(CGameContext *pGameServer, int ClientID, int SaveID, bool KeepCurrentCharacter) const { if(KeepCurrentCharacter && pGameServer->m_apPlayers[ClientID]->GetCharacter()) { @@ -785,7 +785,7 @@ int CSaveTeam::FromString(const char *pString) return 0; } -bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen) +bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen) const { if(NumPlayer > m_MembersCount) { diff --git a/src/game/server/save.h b/src/game/server/save.h index 3e079363c93..14abd7f3732 100644 --- a/src/game/server/save.h +++ b/src/game/server/save.h @@ -140,7 +140,7 @@ class CSaveTeam // MatchPlayers has to be called afterwards int FromString(const char *pString); // returns true if a team can load, otherwise writes a nice error Message in pMessage - bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen); + bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen) const; int Save(CGameContext *pGameServer, int Team, bool Dry = false); void Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong); @@ -150,7 +150,7 @@ class CSaveTeam static bool HandleSaveError(int Result, int ClientID, CGameContext *pGameContext); private: - CCharacter *MatchCharacter(CGameContext *pGameServer, int ClientID, int SaveID, bool KeepCurrentCharacter); + CCharacter *MatchCharacter(CGameContext *pGameServer, int ClientID, int SaveID, bool KeepCurrentCharacter) const; char m_aString[65536]; diff --git a/src/test/score.cpp b/src/test/score.cpp index fddcefd8b3b..2a2d0ba2ea7 100644 --- a/src/test/score.cpp +++ b/src/test/score.cpp @@ -25,7 +25,7 @@ int CSaveTeam::FromString(const char *) return 1; } -bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen) +bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen) const { // Dummy implementation for testing return false; From aacd37b4122db72fba104d84757e673fddf62cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 14 Dec 2023 21:20:25 +0100 Subject: [PATCH 117/198] Fix `readability-static-accessed-through-instance` --- src/game/client/components/menus.cpp | 4 ++-- src/game/editor/mapitems/layer_tiles.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 9474f7cfc9f..3d0648d2687 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -1634,7 +1634,7 @@ int CMenus::Render() char aFilePath[IO_MAX_PATH_LENGTH]; char aSaveFolder[IO_MAX_PATH_LENGTH]; - Storage()->GetCompletePath(Storage()->TYPE_SAVE, "videos", aSaveFolder, sizeof(aSaveFolder)); + Storage()->GetCompletePath(IStorage::TYPE_SAVE, "videos", aSaveFolder, sizeof(aSaveFolder)); str_format(aFilePath, sizeof(aFilePath), "%s/%s.mp4", aSaveFolder, m_DemoRenderInput.GetString()); Box.HSplitBottom(20.f, &Box, &Part); @@ -2008,7 +2008,7 @@ void CMenus::OnRender() RenderDemoPlayer(*UI()->Screen()); } - if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_ServerMode == m_pClient->SERVERMODE_PUREMOD) + if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_ServerMode == CGameClient::SERVERMODE_PUREMOD) { Client()->Disconnect(); SetActive(true); diff --git a/src/game/editor/mapitems/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp index e93229066cb..a3ed05c33a1 100644 --- a/src/game/editor/mapitems/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -1197,7 +1197,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta { if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned) { - pEditor->m_PopupEventType = pEditor->POPEVENT_LARGELAYER; + pEditor->m_PopupEventType = CEditor::POPEVENT_LARGELAYER; pEditor->m_PopupEventActivated = true; pEditor->m_LargeLayerWasWarned = true; } @@ -1207,7 +1207,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta { if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned) { - pEditor->m_PopupEventType = pEditor->POPEVENT_LARGELAYER; + pEditor->m_PopupEventType = CEditor::POPEVENT_LARGELAYER; pEditor->m_PopupEventActivated = true; pEditor->m_LargeLayerWasWarned = true; } From bab382f4cc855599e07ba1e38393152cbf62dd1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 14 Dec 2023 21:22:45 +0100 Subject: [PATCH 118/198] Fix `modernize-use-emplace` --- src/game/editor/editor_actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/editor/editor_actions.cpp b/src/game/editor/editor_actions.cpp index 07da003668d..475dd99864c 100644 --- a/src/game/editor/editor_actions.cpp +++ b/src/game/editor/editor_actions.cpp @@ -48,7 +48,7 @@ CEditorBrushDrawAction::CEditorBrushDrawAction(CEditor *pEditor, int Group) : if(!pLayerTiles->m_TilesHistory.empty()) { - m_vTileChanges.emplace_back(std::make_pair(k, std::map(pLayerTiles->m_TilesHistory))); + m_vTileChanges.emplace_back(k, std::map(pLayerTiles->m_TilesHistory)); pLayerTiles->ClearHistory(); } } From f50eeab690bc332782b09ec4c2ec2f9a5199afb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 14 Dec 2023 21:24:21 +0100 Subject: [PATCH 119/198] Fix `performance-inefficient-vector-operation` --- src/game/editor/editor_actions.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game/editor/editor_actions.cpp b/src/game/editor/editor_actions.cpp index 475dd99864c..b2851b5d088 100644 --- a/src/game/editor/editor_actions.cpp +++ b/src/game/editor/editor_actions.cpp @@ -1078,6 +1078,7 @@ void CEditorActionEditLayersGroupAndOrder::Undo() auto &pCurrentGroup = Map.m_vpGroups[m_NewGroupIndex]; auto &pPreviousGroup = Map.m_vpGroups[m_GroupIndex]; std::vector> vpLayers; + vpLayers.reserve(m_NewLayerIndices.size()); for(auto &LayerIndex : m_NewLayerIndices) vpLayers.push_back(pCurrentGroup->m_vpLayers[LayerIndex]); @@ -1099,6 +1100,7 @@ void CEditorActionEditLayersGroupAndOrder::Redo() auto &pCurrentGroup = Map.m_vpGroups[m_GroupIndex]; auto &pPreviousGroup = Map.m_vpGroups[m_NewGroupIndex]; std::vector> vpLayers; + vpLayers.reserve(m_LayerIndices.size()); for(auto &LayerIndex : m_LayerIndices) vpLayers.push_back(pCurrentGroup->m_vpLayers[LayerIndex]); From 7d37db6d6e7c67d4954b84484dfecbbe38f03019 Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Fri, 15 Dec 2023 00:58:09 +0100 Subject: [PATCH 120/198] Version 17.4.1 --- src/game/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/version.h b/src/game/version.h index dba4a386ad3..ad48edffc3b 100644 --- a/src/game/version.h +++ b/src/game/version.h @@ -3,11 +3,11 @@ #ifndef GAME_VERSION_H #define GAME_VERSION_H #ifndef GAME_RELEASE_VERSION -#define GAME_RELEASE_VERSION "17.4" +#define GAME_RELEASE_VERSION "17.4.1" #endif #define GAME_VERSION "0.6.4, " GAME_RELEASE_VERSION #define GAME_NETVERSION "0.6 626fce9a778df4d4" -#define DDNET_VERSION_NUMBER 17040 +#define DDNET_VERSION_NUMBER 17041 extern const char *GIT_SHORTREV_HASH; #define GAME_NAME "DDNet" #endif From 5ff8697be1eeedda9524a1220a53fe5927970067 Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Fri, 15 Dec 2023 00:51:02 +0100 Subject: [PATCH 121/198] Disable CURL_LOCK_DATA_CONNECT (fixes #7636) --- src/engine/shared/http.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/shared/http.cpp b/src/engine/shared/http.cpp index 090e9aabd55..259ff8d0446 100644 --- a/src/engine/shared/http.cpp +++ b/src/engine/shared/http.cpp @@ -90,7 +90,6 @@ bool HttpInit(IStorage *pStorage) curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); - curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); curl_share_setopt(gs_pShare, CURLSHOPT_LOCKFUNC, CurlLock); curl_share_setopt(gs_pShare, CURLSHOPT_UNLOCKFUNC, CurlUnlock); From 255cf408cdb0c1e7caa62b934f94a71107523add Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Fri, 15 Dec 2023 00:55:24 +0100 Subject: [PATCH 122/198] Fix discord build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /home/deen/isos/ddnet/ddnet-source/src/engine/client/discord.cpp: In member function ‘bool CDiscord::Init(FDiscordCreate)’: /home/deen/isos/ddnet/ddnet-source/src/engine/client/discord.cpp:39:17: error: ‘mem_zero’ was not declared in this scope 39 | mem_zero(&m_ActivityEvents, sizeof(m_ActivityEvents)); | ^~~~~~~~ --- src/engine/client/discord.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/client/discord.cpp b/src/engine/client/discord.cpp index bd83eb41e3e..bb9dc7b4c15 100644 --- a/src/engine/client/discord.cpp +++ b/src/engine/client/discord.cpp @@ -1,3 +1,4 @@ +#include #include // Hack for universal binary builds on macOS: Ignore arm64 until Discord From 1feee077cac0581e8dc1473dfbdac57af66bfc3b Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Fri, 15 Dec 2023 13:00:43 +0100 Subject: [PATCH 123/198] Returning `false` commits you to setting a message --- src/game/server/gamecontext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index b51c9aaa520..e55cfd4db9d 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -2463,7 +2463,7 @@ void CGameContext::OnSetTeamNetMessage(const CNetMsg_Cl_SetTeam *pMsg, int Clien m_pController->DoTeamChange(pPlayer, pMsg->m_Team); pPlayer->m_TeamChangeTick = Server()->Tick(); } - else if(aTeamJoinError[0]) + else SendBroadcast(aTeamJoinError, ClientID); } From 184ada3c95873e97917bda7bbe00863273e80b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 15 Dec 2023 17:48:54 +0100 Subject: [PATCH 124/198] Fix various lineinput issues/regressions Fix Ctrl+C not working to copy text in console when the command input already contains text, as the changed flag was never reset properly. Fix scroll position of UI editboxes not being updated when moving cursor without changing text. Fix lineinput selection change being detected as content change, causing the editor modified state to be set incorrectly. Fix cursor blinking not being disabled correctly after changing text without changing cursor position. --- src/game/client/components/chat.cpp | 4 +++- src/game/client/components/console.cpp | 4 +++- src/game/client/lineinput.cpp | 2 +- src/game/client/ui.cpp | 5 +++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 22fabed7c52..b5ab779ff0b 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -1217,7 +1217,9 @@ void CChat::OnRender() m_Input.Activate(EInputPriority::CHAT); // Ensure that the input is active const CUIRect InputCursorRect = {Cursor.m_X, Cursor.m_Y - ScrollOffset, 0.0f, 0.0f}; - const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, m_Input.WasCursorChanged(), MessageMaxWidth, 0.0f); + // Arithmetic or to ensure that both functions are called so both flags are purged + const bool Changed = m_Input.WasChanged() | m_Input.WasCursorChanged(); + const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, Changed, MessageMaxWidth, 0.0f); Graphics()->ClipDisable(); diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 2855b26fa6b..f18336d7633 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -970,7 +970,9 @@ void CGameConsole::OnRender() pConsole->m_Input.SetHidden(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->State() == IClient::STATE_ONLINE && !Client()->RconAuthed() && (pConsole->m_UserGot || !pConsole->m_UsernameReq)); pConsole->m_Input.Activate(EInputPriority::CONSOLE); // Ensure that the input is active const CUIRect InputCursorRect = {x, y + FONT_SIZE, 0.0f, 0.0f}; - pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FONT_SIZE, TEXTALIGN_BL, pConsole->m_Input.WasCursorChanged(), Screen.w - 10.0f - x, LINE_SPACING); + // Arithmetic or to ensure that both functions are called so both flags are purged + const bool Changed = pConsole->m_Input.WasChanged() | pConsole->m_Input.WasCursorChanged(); + pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FONT_SIZE, TEXTALIGN_BL, Changed, Screen.w - 10.0f - x, LINE_SPACING); if(pConsole->m_LastInputHeight == 0.0f && pConsole->m_BoundingBox.m_H != 0.0f) pConsole->m_LastInputHeight = pConsole->m_BoundingBox.m_H; if(pConsole->m_Input.HasSelection()) diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index 212c353aff4..7af53941a0b 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -385,7 +385,7 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event) } m_WasCursorChanged |= OldCursorPos != m_CursorPos; - m_WasChanged |= SelectionLength != GetSelectionLength(); + m_WasCursorChanged |= SelectionLength != GetSelectionLength(); return m_WasChanged || m_WasCursorChanged || KeyHandled; } diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 3795e3401cd..65618adc512 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -766,6 +766,7 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize const bool Inside = MouseHovered(pRect); const bool Active = m_pLastActiveItem == pLineInput; const bool Changed = pLineInput->WasChanged(); + const bool CursorChanged = pLineInput->WasCursorChanged(); const float VSpacing = 2.0f; CUIRect Textbox; @@ -842,11 +843,11 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize pRect->Draw(ms_LightButtonColorFunction.GetColor(Active, HotItem() == pLineInput), Corners, 3.0f); ClipEnable(pRect); Textbox.x -= ScrollOffset; - const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, pLineInput->WasCursorChanged(), -1.0f, 0.0f); + const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed || CursorChanged, -1.0f, 0.0f); ClipDisable(); // Scroll left or right if necessary - if(Active && !JustGotActive && (Changed || Input()->HasComposition())) + if(Active && !JustGotActive && (Changed || CursorChanged || Input()->HasComposition())) { const float CaretPositionX = pLineInput->GetCaretPosition().x - Textbox.x - ScrollOffset - ScrollOffsetChange; if(CaretPositionX > Textbox.w) From 25ffc421cc185d724db6a877b8091c99fe6a0d86 Mon Sep 17 00:00:00 2001 From: furo Date: Fri, 15 Dec 2023 21:34:33 +0100 Subject: [PATCH 125/198] Render the tee being spectated after everyone else. --- src/game/client/components/players.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index e4ef6aec83d..cf69fc34d5b 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -836,10 +836,12 @@ void CPlayers::OnRender() RenderTools()->RenderTee(CAnimState::GetIdle(), &RenderInfoSpec, EMOTE_BLINK, vec2(1, 0), m_aClient.m_SpecChar); } - // render everyone else's tee, then our own + // render everyone else's tee, then either our own or the tee we are spectating. + const int RenderLastID = (m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW && m_pClient->m_Snap.m_SpecInfo.m_Active) ? m_pClient->m_Snap.m_SpecInfo.m_SpectatorID : LocalClientID; + for(int ClientID = 0; ClientID < MAX_CLIENTS; ClientID++) { - if(ClientID == LocalClientID || !m_pClient->m_Snap.m_aCharacters[ClientID].m_Active || !IsPlayerInfoAvailable(ClientID)) + if(ClientID == RenderLastID || !m_pClient->m_Snap.m_aCharacters[ClientID].m_Active || !IsPlayerInfoAvailable(ClientID)) { continue; } @@ -854,11 +856,11 @@ void CPlayers::OnRender() } RenderPlayer(&m_pClient->m_aClients[ClientID].m_RenderPrev, &m_pClient->m_aClients[ClientID].m_RenderCur, &aRenderInfo[ClientID], ClientID); } - if(LocalClientID != -1 && m_pClient->m_Snap.m_aCharacters[LocalClientID].m_Active && IsPlayerInfoAvailable(LocalClientID)) + if(RenderLastID != -1 && m_pClient->m_Snap.m_aCharacters[RenderLastID].m_Active && IsPlayerInfoAvailable(RenderLastID)) { - const CGameClient::CClientData *pLocalClientData = &m_pClient->m_aClients[LocalClientID]; - RenderHookCollLine(&pLocalClientData->m_RenderPrev, &pLocalClientData->m_RenderCur, LocalClientID); - RenderPlayer(&pLocalClientData->m_RenderPrev, &pLocalClientData->m_RenderCur, &aRenderInfo[LocalClientID], LocalClientID); + const CGameClient::CClientData *pClientData = &m_pClient->m_aClients[RenderLastID]; + RenderHookCollLine(&pClientData->m_RenderPrev, &pClientData->m_RenderCur, RenderLastID); + RenderPlayer(&pClientData->m_RenderPrev, &pClientData->m_RenderCur, &aRenderInfo[RenderLastID], RenderLastID); } } From a41da8182b7e3fe4624a9d169906537cca779c89 Mon Sep 17 00:00:00 2001 From: archimede67 Date: Tue, 12 Dec 2023 13:46:06 +0100 Subject: [PATCH 126/198] Preserve and show axis when dragging quad points while holding Shift --- src/game/editor/editor.cpp | 153 ++++++++++++++++++++++++++++--------- src/game/editor/editor.h | 14 ++++ 2 files changed, 133 insertions(+), 34 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 4fc1cd80da8..3cd320cda83 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1529,6 +1529,51 @@ void CEditor::DoSoundSource(CSoundSource *pSource, int Index) Graphics()->QuadsDraw(&QuadItem, 1); } +void CEditor::PreparePointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex) +{ + m_QuadDragOriginalPoints[QuadIndex][PointIndex] = pQuad->m_aPoints[PointIndex]; +} + +void CEditor::DoPointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY) +{ + EAxis Axis = GetDragAxis(OffsetX, OffsetY); + pQuad->m_aPoints[PointIndex].x = m_QuadDragOriginalPoints[QuadIndex][PointIndex].x + ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_X) ? OffsetX : 0); + pQuad->m_aPoints[PointIndex].y = m_QuadDragOriginalPoints[QuadIndex][PointIndex].y + ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_Y) ? OffsetY : 0); +} + +CEditor::EAxis CEditor::GetDragAxis(int OffsetX, int OffsetY) +{ + if(Input()->ShiftIsPressed()) + if(absolute(OffsetX) < absolute(OffsetY)) + return EAxis::AXIS_Y; + else + return EAxis::AXIS_X; + else + return EAxis::AXIS_NONE; +} + +void CEditor::DrawAxis(EAxis Axis, CPoint &Point) +{ + if(Axis == EAxis::AXIS_NONE) + return; + + Graphics()->SetColor(1, 0, 0.1f, 1); + if(Axis == EAxis::AXIS_X) + { + IGraphics::CQuadItem QuadItem(0, fx2f(Point.y), Graphics()->ScreenWidth() * m_MouseWScale, 1.0f * m_MouseWScale); + Graphics()->QuadsDraw(&QuadItem, 1); + } + else if(Axis == EAxis::AXIS_Y) + { + IGraphics::CQuadItem QuadItem(fx2f(Point.x), 0, 1.0f * m_MouseWScale, Graphics()->ScreenHeight() * m_MouseWScale); + Graphics()->QuadsDraw(&QuadItem, 1); + } + + // Draw ghost of original point + IGraphics::CQuadItem QuadItem(fx2f(Point.x), fx2f(Point.y), 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); + Graphics()->QuadsDraw(&QuadItem, 1); +} + void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, int Index) { enum @@ -1551,6 +1596,7 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i static float s_RotateAngle = 0; float wx = UI()->MouseWorldX(); float wy = UI()->MouseWorldY(); + static CPoint s_OriginalPosition; // get pivot float CenterX = fx2f(pQuad->m_aPoints[4].x); @@ -1558,6 +1604,18 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i const bool IgnoreGrid = Input()->AltIsPressed(); + auto &&GetDragOffset = [&]() -> ivec2 { + float x = wx; + float y = wy; + if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid) + MapView()->MapGrid()->SnapToGrid(x, y); + + int OffsetX = f2fx(x) - s_OriginalPosition.x; + int OffsetY = f2fx(y) - s_OriginalPosition.y; + + return {OffsetX, OffsetY}; + }; + // draw selection background if(IsQuadSelected(Index)) { @@ -1580,10 +1638,27 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i if(!IsQuadSelected(Index)) SelectQuad(Index); + s_OriginalPosition = pQuad->m_aPoints[4]; + if(Input()->ShiftIsPressed()) + { s_Operation = OP_MOVE_PIVOT; + for(size_t i = 0; i < m_vSelectedQuads.size(); ++i) + { + CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]]; + PreparePointDrag(pLayer, pCurrentQuad, m_vSelectedQuads[i], 4); + } + } else + { s_Operation = OP_MOVE_ALL; + for(size_t i = 0; i < m_vSelectedQuads.size(); ++i) + { + CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]]; + for(size_t v = 0; v < 5; v++) + PreparePointDrag(pLayer, pCurrentQuad, m_vSelectedQuads[i], v); + } + } } } @@ -1591,41 +1666,27 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i if(s_Operation == OP_MOVE_PIVOT) { m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); - float x = wx; - float y = wy; - if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid) - MapView()->MapGrid()->SnapToGrid(x, y); - int OffsetX = f2fx(x) - pQuad->m_aPoints[4].x; - int OffsetY = f2fx(y) - pQuad->m_aPoints[4].y; + ivec2 Offset = GetDragOffset(); for(auto &Selected : m_vSelectedQuads) { CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected]; - pCurrentQuad->m_aPoints[4].x += OffsetX; - pCurrentQuad->m_aPoints[4].y += OffsetY; + DoPointDrag(pLayer, pCurrentQuad, Selected, 4, Offset.x, Offset.y); } } else if(s_Operation == OP_MOVE_ALL) { m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); // move all points including pivot - float x = wx; - float y = wy; - if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid) - MapView()->MapGrid()->SnapToGrid(x, y); - - int OffsetX = f2fx(x) - pQuad->m_aPoints[4].x; - int OffsetY = f2fx(y) - pQuad->m_aPoints[4].y; + ivec2 Offset = GetDragOffset(); - for(auto &Selected : m_vSelectedQuads) + for(size_t i = 0; i < m_vSelectedQuads.size(); i++) { + int Selected = m_vSelectedQuads[i]; CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected]; - for(auto &Point : pCurrentQuad->m_aPoints) - { - Point.x += OffsetX; - Point.y += OffsetY; - } + for(int v = 0; v < 5; v++) + DoPointDrag(pLayer, pCurrentQuad, Selected, v, Offset.x, Offset.y); } } else if(s_Operation == OP_ROTATE) @@ -1649,6 +1710,14 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i } } + // Draw axis when moving + if(s_Operation == OP_MOVE_PIVOT || s_Operation == OP_MOVE_ALL) + { + ivec2 Offset = GetDragOffset(); + EAxis Axis = GetDragAxis(Offset.x, Offset.y); + DrawAxis(Axis, s_OriginalPosition); + } + if(s_Operation == OP_CONTEXT_MENU) { if(!UI()->MouseButton(1)) @@ -1822,9 +1891,22 @@ void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQu static int s_Operation = OP_NONE; static float s_MouseXStart = 0.0f; static float s_MouseYStart = 0.0f; + static CPoint s_OriginalPoint; const bool IgnoreGrid = Input()->AltIsPressed(); + auto &&GetDragOffset = [&]() -> ivec2 { + float x = wx; + float y = wy; + if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid) + MapView()->MapGrid()->SnapToGrid(x, y); + + int OffsetX = f2fx(x) - s_OriginalPoint.x; + int OffsetY = f2fx(y) - s_OriginalPoint.y; + + return {OffsetX, OffsetY}; + }; + if(UI()->CheckActiveItem(pID)) { if(m_MouseDeltaWx * m_MouseDeltaWx + m_MouseDeltaWy * m_MouseDeltaWy > 0.0f) @@ -1845,7 +1927,13 @@ void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQu UI()->EnableMouseLock(pID); } else + { s_Operation = OP_MOVEPOINT; + s_OriginalPoint = pQuad->m_aPoints[V]; + for(int m = 0; m < 4; m++) + if(IsQuadPointSelected(QuadIndex, m)) + PreparePointDrag(pLayer, pQuad, QuadIndex, m); + } } } @@ -1853,22 +1941,11 @@ void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQu { m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); - float x = wx; - float y = wy; - if(MapView()->MapGrid()->IsEnabled() && !IgnoreGrid) - MapView()->MapGrid()->SnapToGrid(x, y); - - int OffsetX = f2fx(x) - pQuad->m_aPoints[V].x; - int OffsetY = f2fx(y) - pQuad->m_aPoints[V].y; + ivec2 Offset = GetDragOffset(); for(int m = 0; m < 4; m++) - { if(IsQuadPointSelected(QuadIndex, m)) - { - pQuad->m_aPoints[m].x += OffsetX; - pQuad->m_aPoints[m].y += OffsetY; - } - } + DoPointDrag(pLayer, pQuad, QuadIndex, m, Offset.x, Offset.y); } else if(s_Operation == OP_MOVEUV) { @@ -1895,6 +1972,14 @@ void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQu } } + // Draw axis when dragging + if(s_Operation == OP_MOVEPOINT) + { + ivec2 Offset = GetDragOffset(); + EAxis Axis = GetDragAxis(Offset.x, Offset.y); + DrawAxis(Axis, s_OriginalPoint); + } + if(s_Operation == OP_CONTEXT_MENU) { if(!UI()->MouseButton(1)) diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index f9bbcc52ebf..643ed495cf0 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -853,10 +853,20 @@ class CEditor : public IEditor void DoSoundSource(CSoundSource *pSource, int Index); + enum class EAxis + { + AXIS_NONE = 0, + AXIS_X, + AXIS_Y + }; void DoMapEditor(CUIRect View); void DoToolbarLayers(CUIRect Toolbar); void DoToolbarSounds(CUIRect Toolbar); void DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, int Index); + void PreparePointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex); + void DoPointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY); + EAxis GetDragAxis(int OffsetX, int OffsetY); + void DrawAxis(EAxis Axis, CPoint &Point); ColorRGBA GetButtonColor(const void *pID, int Checked); bool ReplaceImage(const char *pFilename, int StorageType, bool CheckDuplicate); @@ -1011,6 +1021,7 @@ class CEditor : public IEditor unsigned char m_SwitchNum; unsigned char m_SwitchDelay; +public: // Undo/Redo CEditorHistory m_EditorHistory; CEditorHistory m_ServerSettingsHistory; @@ -1021,6 +1032,9 @@ class CEditor : public IEditor private: void UndoLastAction(); void RedoLastAction(); + +private: + std::map> m_QuadDragOriginalPoints; }; // make sure to inline this function From 5dbd5a1f4992071d8f87a3b9bc5d4b966dbc6ae5 Mon Sep 17 00:00:00 2001 From: archimede67 Date: Wed, 13 Dec 2023 20:54:59 +0100 Subject: [PATCH 127/198] Added quad alignment/snapping to other quads/points --- src/engine/shared/config_variables.h | 2 + src/game/editor/editor.cpp | 526 +++++++++++++++++++++++++-- src/game/editor/editor.h | 44 ++- src/game/editor/popups.cpp | 52 +++ 4 files changed, 589 insertions(+), 35 deletions(-) diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 436daae7333..790416ddea5 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -101,6 +101,8 @@ MACRO_CONFIG_INT(EdSmoothZoomTime, ed_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CL MACRO_CONFIG_INT(EdLimitMaxZoomLevel, ed_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming in the editor should be limited or not (0 = no limit)") MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target") MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show pressed keys") +MACRO_CONFIG_INT(EdAlignQuads, ed_align_quads, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable/disable quad alignment. When enabled, red lines appear to show how quad/points are aligned and snapped to other quads/points when moving them") +MACRO_CONFIG_INT(EdShowQuadsRect, ed_show_quads_rect, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the bounds of the selected quad. In case of multiple quads, it shows the bounds of the englobing rect. Can be helpful when aligning a group of quads") MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show welcome message indicating the first launch of the client") MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long to show the server message of the day") diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 3cd320cda83..7d826ea4d12 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1536,9 +1536,8 @@ void CEditor::PreparePointDrag(const std::shared_ptr &pLayer, CQuad void CEditor::DoPointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY) { - EAxis Axis = GetDragAxis(OffsetX, OffsetY); - pQuad->m_aPoints[PointIndex].x = m_QuadDragOriginalPoints[QuadIndex][PointIndex].x + ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_X) ? OffsetX : 0); - pQuad->m_aPoints[PointIndex].y = m_QuadDragOriginalPoints[QuadIndex][PointIndex].y + ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_Y) ? OffsetY : 0); + pQuad->m_aPoints[PointIndex].x = m_QuadDragOriginalPoints[QuadIndex][PointIndex].x + OffsetX; + pQuad->m_aPoints[PointIndex].y = m_QuadDragOriginalPoints[QuadIndex][PointIndex].y + OffsetY; } CEditor::EAxis CEditor::GetDragAxis(int OffsetX, int OffsetY) @@ -1552,7 +1551,7 @@ CEditor::EAxis CEditor::GetDragAxis(int OffsetX, int OffsetY) return EAxis::AXIS_NONE; } -void CEditor::DrawAxis(EAxis Axis, CPoint &Point) +void CEditor::DrawAxis(EAxis Axis, CPoint &OriginalPoint, CPoint &Point) { if(Axis == EAxis::AXIS_NONE) return; @@ -1560,20 +1559,431 @@ void CEditor::DrawAxis(EAxis Axis, CPoint &Point) Graphics()->SetColor(1, 0, 0.1f, 1); if(Axis == EAxis::AXIS_X) { - IGraphics::CQuadItem QuadItem(0, fx2f(Point.y), Graphics()->ScreenWidth() * m_MouseWScale, 1.0f * m_MouseWScale); - Graphics()->QuadsDraw(&QuadItem, 1); + IGraphics::CQuadItem Line(fx2f(OriginalPoint.x + Point.x) / 2.0f, fx2f(OriginalPoint.y), fx2f(Point.x - OriginalPoint.x), 1.0f * m_MouseWScale); + Graphics()->QuadsDraw(&Line, 1); } else if(Axis == EAxis::AXIS_Y) { - IGraphics::CQuadItem QuadItem(fx2f(Point.x), 0, 1.0f * m_MouseWScale, Graphics()->ScreenHeight() * m_MouseWScale); - Graphics()->QuadsDraw(&QuadItem, 1); + IGraphics::CQuadItem Line(fx2f(OriginalPoint.x), fx2f(OriginalPoint.y + Point.y) / 2.0f, 1.0f * m_MouseWScale, fx2f(Point.y - OriginalPoint.y)); + Graphics()->QuadsDraw(&Line, 1); } // Draw ghost of original point - IGraphics::CQuadItem QuadItem(fx2f(Point.x), fx2f(Point.y), 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); + IGraphics::CQuadItem QuadItem(fx2f(OriginalPoint.x), fx2f(OriginalPoint.y), 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); Graphics()->QuadsDraw(&QuadItem, 1); } +void CEditor::ComputePointAlignments(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY, std::vector &vAlignments, bool Append) const +{ + if(!Append) + vAlignments.clear(); + if(!g_Config.m_EdAlignQuads) + return; + + // Perform computation from the original position of this point + int Threshold = f2fx(maximum(10.0f, 10.0f * m_MouseWScale)); + CPoint OrigPoint = m_QuadDragOriginalPoints.at(QuadIndex)[PointIndex]; + // Get the "current" point by applying the offset + CPoint Point = OrigPoint + ivec2(OffsetX, OffsetY); + + // Save smallest diff on both axis to only keep closest alignments + int SmallestDiffX = Threshold + 1, SmallestDiffY = Threshold + 1; + // Store both axis alignments in separate vectors + std::vector vAlignmentsX, vAlignmentsY; + + // Check if we can align/snap to a specific point + auto &&CheckAlignment = [&](CPoint *pQuadPoint) { + int DX = pQuadPoint->x - Point.x; + int DY = pQuadPoint->y - Point.y; + int DiffX = absolute(DX); + int DiffY = absolute(DY); + + // Check the X axis + if(DiffX <= Threshold) + { + // Only store alignments that have the smallest difference + if(DiffX < SmallestDiffX) + { + vAlignmentsX.clear(); + SmallestDiffX = DiffX; + } + + // We can have multiple alignments having the same difference/distance + if(DiffX == SmallestDiffX) + { + vAlignmentsX.push_back(SAlignmentInfo{ + *pQuadPoint, // Aligned point + {OrigPoint.y}, // Value that can change (which is not snapped), original position + EAxis::AXIS_Y, // The alignment axis + PointIndex, // The index of the point + DX, + }); + } + } + if(DiffY <= Threshold) + { + // Only store alignments that have the smallest difference + if(DiffY < SmallestDiffY) + { + vAlignmentsY.clear(); + SmallestDiffY = DiffY; + } + + if(DiffY == SmallestDiffY) + { + vAlignmentsY.push_back(SAlignmentInfo{ + *pQuadPoint, + {OrigPoint.x}, + EAxis::AXIS_X, + PointIndex, + DY, + }); + } + } + }; + + // Iterate through all the quads of the current layer + // Check alignment with each point of the quad (corners & pivot) + // Compute an AABB (Axis Aligned Bounding Box) to get the center of the quad + // Check alignment with the center of the quad + for(size_t i = 0; i < pLayer->m_vQuads.size(); i++) + { + auto *pCurrentQuad = &pLayer->m_vQuads[i]; + CPoint Min = pCurrentQuad->m_aPoints[0]; + CPoint Max = pCurrentQuad->m_aPoints[0]; + + for(int v = 0; v < 5; v++) + { + CPoint *pQuadPoint = &pCurrentQuad->m_aPoints[v]; + + if(v != 4) + { // Don't use pivot to compute AABB + if(pQuadPoint->x < Min.x) + Min.x = pQuadPoint->x; + if(pQuadPoint->y < Min.y) + Min.y = pQuadPoint->y; + if(pQuadPoint->x > Max.x) + Max.x = pQuadPoint->x; + if(pQuadPoint->y > Max.y) + Max.y = pQuadPoint->y; + } + + // Don't check alignment with current point + if(pQuadPoint == &pQuad->m_aPoints[PointIndex]) + continue; + + // Don't check alignment with other selected points + bool IsCurrentPointSelected = IsQuadSelected(i) && (IsQuadCornerSelected(v) || (v == PointIndex && PointIndex == 4)); + if(IsCurrentPointSelected) + continue; + + CheckAlignment(pQuadPoint); + } + + CPoint Center = (Min + Max) / 2.0f; + CheckAlignment(&Center); + } + + // Finally concatenate both alignment vectors into the output + vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size()); + vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end()); + vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end()); +} + +void CEditor::ComputePointsAlignments(const std::shared_ptr &pLayer, bool Pivot, int OffsetX, int OffsetY, std::vector &vAlignments) const +{ + // This method is used to compute alignments from selected points + // and only apply the closest alignment on X and Y to the offset. + + vAlignments.clear(); + std::vector vAllAlignments; + + for(int Selected : m_vSelectedQuads) + { + CQuad *pQuad = &pLayer->m_vQuads[Selected]; + + if(!Pivot) + { + for(int m = 0; m < 4; m++) + { + if(IsQuadPointSelected(Selected, m)) + { + ComputePointAlignments(pLayer, pQuad, Selected, m, OffsetX, OffsetY, vAllAlignments, true); + } + } + } + else + { + ComputePointAlignments(pLayer, pQuad, Selected, 4, OffsetX, OffsetY, vAllAlignments, true); + } + } + + int SmallestDiffX, SmallestDiffY; + SmallestDiffX = SmallestDiffY = std::numeric_limits::max(); + + std::vector vAlignmentsX, vAlignmentsY; + + for(const auto &Alignment : vAllAlignments) + { + int AbsDiff = absolute(Alignment.m_Diff); + if(Alignment.m_Axis == EAxis::AXIS_X) + { + if(AbsDiff < SmallestDiffY) + { + SmallestDiffY = AbsDiff; + vAlignmentsY.clear(); + } + if(AbsDiff == SmallestDiffY) + vAlignmentsY.emplace_back(Alignment); + } + else if(Alignment.m_Axis == EAxis::AXIS_Y) + { + if(AbsDiff < SmallestDiffX) + { + SmallestDiffX = AbsDiff; + vAlignmentsX.clear(); + } + if(AbsDiff == SmallestDiffX) + vAlignmentsX.emplace_back(Alignment); + } + } + + vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size()); + vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end()); + vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end()); +} + +void CEditor::ComputeAABBAlignments(const std::shared_ptr &pLayer, const SAxisAlignedBoundingBox &AABB, int OffsetX, int OffsetY, std::vector &vAlignments) const +{ + vAlignments.clear(); + if(!g_Config.m_EdAlignQuads) + return; + + // This method is a bit different than the point alignment in the way where instead of trying to aling 1 point to all quads, + // we try to align 5 points to all quads, these 5 points being 5 points of an AABB. + // Otherwise, the concept is the same, we use the original position of the AABB to make the computations. + int Threshold = f2fx(maximum(10.0f, 10.0f * m_MouseWScale)); + int SmallestDiffX = Threshold + 1, SmallestDiffY = Threshold + 1; + std::vector vAlignmentsX, vAlignmentsY; + + auto &&CheckAlignment = [&](CPoint &Aligned, int Point) { + CPoint ToCheck = AABB.m_aPoints[Point] + ivec2(OffsetX, OffsetY); + int DX = Aligned.x - ToCheck.x; + int DY = Aligned.y - ToCheck.y; + int DiffX = absolute(DX); + int DiffY = absolute(DY); + + if(DiffX <= Threshold) + { + if(DiffX < SmallestDiffX) + { + SmallestDiffX = DiffX; + vAlignmentsX.clear(); + } + + if(DiffX == SmallestDiffX) + { + vAlignmentsX.push_back(SAlignmentInfo{ + Aligned, + {AABB.m_aPoints[Point].y}, + EAxis::AXIS_Y, + Point, + DX, + }); + } + } + if(DiffY <= Threshold) + { + if(DiffY < SmallestDiffY) + { + SmallestDiffY = DiffY; + vAlignmentsY.clear(); + } + + if(DiffY == SmallestDiffY) + { + vAlignmentsY.push_back(SAlignmentInfo{ + Aligned, + {AABB.m_aPoints[Point].x}, + EAxis::AXIS_X, + Point, + DY, + }); + } + } + }; + + auto &&CheckAABBAlignment = [&](CPoint &QuadMin, CPoint &QuadMax) { + CPoint QuadCenter = (QuadMin + QuadMax) / 2.0f; + CPoint aQuadPoints[5] = { + QuadMin, // Top left + {QuadMax.x, QuadMin.y}, // Top right + {QuadMin.x, QuadMax.y}, // Bottom left + QuadMax, // Bottom right + QuadCenter, + }; + + // Check all points with all the other points + for(auto &QuadPoint : aQuadPoints) + { + // i is the quad point which is "aligned" and that we want to compare with + for(int j = 0; j < 5; j++) + { + // j is the point we try to align + CheckAlignment(QuadPoint, j); + } + } + }; + + // Iterate through all quads of the current layer + // Compute AABB of all quads and check if the dragged AABB can be aligned to this AABB. + for(size_t i = 0; i < pLayer->m_vQuads.size(); i++) + { + auto *pCurrentQuad = &pLayer->m_vQuads[i]; + if(IsQuadSelected(i)) // Don't check with other selected quads + continue; + + // Get AABB of this quad + CPoint QuadMin = pCurrentQuad->m_aPoints[0], QuadMax = pCurrentQuad->m_aPoints[0]; + for(int v = 1; v < 4; v++) + { + QuadMin.x = minimum(QuadMin.x, pCurrentQuad->m_aPoints[v].x); + QuadMin.y = minimum(QuadMin.y, pCurrentQuad->m_aPoints[v].y); + QuadMax.x = maximum(QuadMax.x, pCurrentQuad->m_aPoints[v].x); + QuadMax.y = maximum(QuadMax.y, pCurrentQuad->m_aPoints[v].y); + } + + CheckAABBAlignment(QuadMin, QuadMax); + } + + // Finally, concatenate both alignment vectors into the output + vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size()); + vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end()); + vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end()); +} + +void CEditor::DrawPointAlignments(const std::vector &vAlignments, int OffsetX, int OffsetY) +{ + if(!g_Config.m_EdAlignQuads) + return; + + // Drawing an alignment is easy, we convert fixed to float for the aligned point coords + // and we also convert the "changing" value after applying the offset (which might be edited to actually align the value with the alignment). + Graphics()->SetColor(1, 0, 0.1f, 1); + for(const SAlignmentInfo &Alignment : vAlignments) + { + // We don't use IGraphics::CLineItem to draw because we don't want to stop QuadsBegin(), quads work just fine. + if(Alignment.m_Axis == EAxis::AXIS_X) + { // Alignment on X axis is same Y values but different X values + IGraphics::CQuadItem Line(fx2f(Alignment.m_AlignedPoint.x), fx2f(Alignment.m_AlignedPoint.y), fx2f(Alignment.m_X + OffsetX - Alignment.m_AlignedPoint.x), 1.0f * m_MouseWScale); + Graphics()->QuadsDrawTL(&Line, 1); + } + else if(Alignment.m_Axis == EAxis::AXIS_Y) + { // Alignment on Y axis is same X values but different Y values + IGraphics::CQuadItem Line(fx2f(Alignment.m_AlignedPoint.x), fx2f(Alignment.m_AlignedPoint.y), 1.0f * m_MouseWScale, fx2f(Alignment.m_Y + OffsetY - Alignment.m_AlignedPoint.y)); + Graphics()->QuadsDrawTL(&Line, 1); + } + } +} + +void CEditor::DrawAABB(const SAxisAlignedBoundingBox &AABB, int OffsetX, int OffsetY) +{ + // Drawing an AABB is simply converting the points from fixed to float + // Then making lines out of quads and drawing them + vec2 TL = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TL].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TL].y + OffsetY)}; + vec2 TR = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TR].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TR].y + OffsetY)}; + vec2 BL = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BL].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BL].y + OffsetY)}; + vec2 BR = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BR].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BR].y + OffsetY)}; + vec2 Center = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_CENTER].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_CENTER].y + OffsetY)}; + + // We don't use IGraphics::CLineItem to draw because we don't want to stop QuadsBegin(), quads work just fine. + IGraphics::CQuadItem Lines[4] = { + {TL.x, TL.y, TR.x - TL.x, 1.0f * m_MouseWScale}, + {TL.x, TL.y, 1.0f * m_MouseWScale, BL.y - TL.y}, + {TR.x, TR.y, 1.0f * m_MouseWScale, BR.y - TR.y}, + {BL.x, BL.y, BR.x - BL.x, 1.0f * m_MouseWScale}, + }; + Graphics()->SetColor(1, 0, 1, 1); + Graphics()->QuadsDrawTL(Lines, 4); + + IGraphics::CQuadItem CenterQuad(Center.x, Center.y, 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); + Graphics()->QuadsDraw(&CenterQuad, 1); +} + +void CEditor::QuadSelectionAABB(const std::shared_ptr &pLayer, SAxisAlignedBoundingBox &OutAABB) +{ + // Compute an englobing AABB of the current selection of quads + CPoint Min{ + std::numeric_limits::max(), + std::numeric_limits::max(), + }; + CPoint Max{ + std::numeric_limits::min(), + std::numeric_limits::min(), + }; + for(int Selected : m_vSelectedQuads) + { + CQuad *pQuad = &pLayer->m_vQuads[Selected]; + for(auto &Point : pQuad->m_aPoints) + { + Min.x = minimum(Min.x, Point.x); + Min.y = minimum(Min.y, Point.y); + Max.x = maximum(Max.x, Point.x); + Max.y = maximum(Max.y, Point.y); + } + } + CPoint Center = (Min + Max) / 2.0f; + CPoint aPoints[SAxisAlignedBoundingBox::NUM_POINTS] = { + Min, // Top left + {Max.x, Min.y}, // Top right + {Min.x, Max.y}, // Bottom left + Max, // Bottom right + Center, + }; + mem_copy(OutAABB.m_aPoints, aPoints, sizeof(CPoint) * SAxisAlignedBoundingBox::NUM_POINTS); +} + +void CEditor::ApplyAlignments(const std::vector &vAlignments, int &OffsetX, int &OffsetY) +{ + if(vAlignments.empty()) + return; + + // Find X and Y aligment + const int *pAlignedX = nullptr; + const int *pAlignedY = nullptr; + + // To Find the alignments we simply iterate through the vector of alignments and find the first + // X and Y alignments. + // Then, we use the saved m_Diff to adjust the offset + int AdjustX = 0, AdjustY = 0; + for(const SAlignmentInfo &Alignment : vAlignments) + { + if(Alignment.m_Axis == EAxis::AXIS_X && !pAlignedY) + { + pAlignedY = &Alignment.m_AlignedPoint.y; + AdjustY = Alignment.m_Diff; + } + else if(Alignment.m_Axis == EAxis::AXIS_Y && !pAlignedX) + { + pAlignedX = &Alignment.m_AlignedPoint.x; + AdjustX = Alignment.m_Diff; + } + } + + // Adjust offset + OffsetX += AdjustX; + OffsetY += AdjustY; +} + +void CEditor::ApplyAxisAlignment(int &OffsetX, int &OffsetY) +{ + // This is used to preserve axis alignment when pressing `Shift` + // Should be called before any other computation + EAxis Axis = GetDragAxis(OffsetX, OffsetY); + OffsetX = ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_X) ? OffsetX : 0); + OffsetY = ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_Y) ? OffsetY : 0); +} + void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, int Index) { enum @@ -1597,6 +2007,10 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i float wx = UI()->MouseWorldX(); float wy = UI()->MouseWorldY(); static CPoint s_OriginalPosition; + static std::vector s_PivotAlignments; // Alignments per pivot per quad + static std::vector s_vAABBAlignments; // Alignments for one AABB (single quad or selection of multiple quads) + static SAxisAlignedBoundingBox s_SelectionAABB; // Selection AABB + static ivec2 s_LastOffset; // Last offset, stored as static so we can use it to draw every frame // get pivot float CenterX = fx2f(pQuad->m_aPoints[4].x); @@ -1643,21 +2057,26 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i if(Input()->ShiftIsPressed()) { s_Operation = OP_MOVE_PIVOT; - for(size_t i = 0; i < m_vSelectedQuads.size(); ++i) + // When moving, we need to save the original position of all selected pivots + for(int Selected : m_vSelectedQuads) { - CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]]; - PreparePointDrag(pLayer, pCurrentQuad, m_vSelectedQuads[i], 4); + CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected]; + PreparePointDrag(pLayer, pCurrentQuad, Selected, 4); } } else { s_Operation = OP_MOVE_ALL; - for(size_t i = 0; i < m_vSelectedQuads.size(); ++i) + // When moving, we need to save the original position of all selected quads points + for(int Selected : m_vSelectedQuads) { - CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]]; + CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected]; for(size_t v = 0; v < 5; v++) - PreparePointDrag(pLayer, pCurrentQuad, m_vSelectedQuads[i], v); + PreparePointDrag(pLayer, pCurrentQuad, Selected, v); } + // And precompute AABB of selection since it will not change during drag + if(g_Config.m_EdAlignQuads) + QuadSelectionAABB(pLayer, s_SelectionAABB); } } } @@ -1667,26 +2086,36 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i { m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); - ivec2 Offset = GetDragOffset(); + s_LastOffset = GetDragOffset(); // Update offset + ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y); // Apply axis alignment to the offset + + ComputePointsAlignments(pLayer, true, s_LastOffset.x, s_LastOffset.y, s_PivotAlignments); + ApplyAlignments(s_PivotAlignments, s_LastOffset.x, s_LastOffset.y); for(auto &Selected : m_vSelectedQuads) { CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected]; - DoPointDrag(pLayer, pCurrentQuad, Selected, 4, Offset.x, Offset.y); + DoPointDrag(pLayer, pCurrentQuad, Selected, 4, s_LastOffset.x, s_LastOffset.y); } } else if(s_Operation == OP_MOVE_ALL) { m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); - // move all points including pivot - ivec2 Offset = GetDragOffset(); - for(size_t i = 0; i < m_vSelectedQuads.size(); i++) + // Compute drag offset + s_LastOffset = GetDragOffset(); + ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y); + + // Then compute possible alignments with the selection AABB + ComputeAABBAlignments(pLayer, s_SelectionAABB, s_LastOffset.x, s_LastOffset.y, s_vAABBAlignments); + // Apply alignments before drag + ApplyAlignments(s_vAABBAlignments, s_LastOffset.x, s_LastOffset.y); + // Then do the drag + for(int Selected : m_vSelectedQuads) { - int Selected = m_vSelectedQuads[i]; CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected]; for(int v = 0; v < 5; v++) - DoPointDrag(pLayer, pCurrentQuad, Selected, v, Offset.x, Offset.y); + DoPointDrag(pLayer, pCurrentQuad, Selected, v, s_LastOffset.x, s_LastOffset.y); } } else if(s_Operation == OP_ROTATE) @@ -1710,12 +2139,22 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i } } - // Draw axis when moving + // Draw axis and aligments when moving if(s_Operation == OP_MOVE_PIVOT || s_Operation == OP_MOVE_ALL) { - ivec2 Offset = GetDragOffset(); - EAxis Axis = GetDragAxis(Offset.x, Offset.y); - DrawAxis(Axis, s_OriginalPosition); + EAxis Axis = GetDragAxis(s_LastOffset.x, s_LastOffset.y); + DrawAxis(Axis, s_OriginalPosition, pQuad->m_aPoints[4]); + } + + if(s_Operation == OP_MOVE_PIVOT) + DrawPointAlignments(s_PivotAlignments, s_LastOffset.x, s_LastOffset.y); + + if(s_Operation == OP_MOVE_ALL) + { + DrawPointAlignments(s_vAABBAlignments, s_LastOffset.x, s_LastOffset.y); + + if(g_Config.m_EdShowQuadsRect) + DrawAABB(s_SelectionAABB, s_LastOffset.x, s_LastOffset.y); } if(s_Operation == OP_CONTEXT_MENU) @@ -1791,6 +2230,11 @@ void CEditor::DoQuad(const std::shared_ptr &pLayer, CQuad *pQuad, i UI()->DisableMouseLock(); s_Operation = OP_NONE; UI()->SetActiveItem(nullptr); + + s_LastOffset = ivec2(); + s_OriginalPosition = ivec2(); + s_vAABBAlignments.clear(); + s_PivotAlignments.clear(); } } @@ -1892,6 +2336,8 @@ void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQu static float s_MouseXStart = 0.0f; static float s_MouseYStart = 0.0f; static CPoint s_OriginalPoint; + static std::vector s_Alignments; // Alignments + static ivec2 s_LastOffset; const bool IgnoreGrid = Input()->AltIsPressed(); @@ -1929,6 +2375,7 @@ void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQu else { s_Operation = OP_MOVEPOINT; + // Save original positions before moving s_OriginalPoint = pQuad->m_aPoints[V]; for(int m = 0; m < 4; m++) if(IsQuadPointSelected(QuadIndex, m)) @@ -1941,11 +2388,19 @@ void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQu { m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads); - ivec2 Offset = GetDragOffset(); + s_LastOffset = GetDragOffset(); // Update offset + ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y); // Apply axis alignment to offset + + ComputePointsAlignments(pLayer, false, s_LastOffset.x, s_LastOffset.y, s_Alignments); + ApplyAlignments(s_Alignments, s_LastOffset.x, s_LastOffset.y); for(int m = 0; m < 4; m++) + { if(IsQuadPointSelected(QuadIndex, m)) - DoPointDrag(pLayer, pQuad, QuadIndex, m, Offset.x, Offset.y); + { + DoPointDrag(pLayer, pQuad, QuadIndex, m, s_LastOffset.x, s_LastOffset.y); + } + } } else if(s_Operation == OP_MOVEUV) { @@ -1972,12 +2427,17 @@ void CEditor::DoQuadPoint(const std::shared_ptr &pLayer, CQuad *pQu } } - // Draw axis when dragging + // Draw axis and alignments when dragging if(s_Operation == OP_MOVEPOINT) { - ivec2 Offset = GetDragOffset(); - EAxis Axis = GetDragAxis(Offset.x, Offset.y); - DrawAxis(Axis, s_OriginalPoint); + Graphics()->SetColor(1, 0, 0.1f, 1); + + // Axis + EAxis Axis = GetDragAxis(s_LastOffset.x, s_LastOffset.y); + DrawAxis(Axis, s_OriginalPoint, pQuad->m_aPoints[V]); + + // Alignments + DrawPointAlignments(s_Alignments, s_LastOffset.x, s_LastOffset.y); } if(s_Operation == OP_CONTEXT_MENU) @@ -7402,7 +7862,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar) if(DoButton_Menu(&s_SettingsButton, "Settings", 0, &SettingsButton, 0, nullptr)) { static SPopupMenuId s_PopupMenuEntitiesId; - UI()->DoPopupMenu(&s_PopupMenuEntitiesId, SettingsButton.x, SettingsButton.y + SettingsButton.h - 1.0f, 200.0f, 64.0f, this, PopupMenuSettings, PopupProperties); + UI()->DoPopupMenu(&s_PopupMenuEntitiesId, SettingsButton.x, SettingsButton.y + SettingsButton.h - 1.0f, 200.0f, 92.0f, this, PopupMenuSettings, PopupProperties); } CUIRect ChangedIndicator, Info, Close; diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 643ed495cf0..7977cb0904c 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -859,6 +859,19 @@ class CEditor : public IEditor AXIS_X, AXIS_Y }; + struct SAxisAlignedBoundingBox + { + enum + { + POINT_TL = 0, + POINT_TR, + POINT_BL, + POINT_BR, + POINT_CENTER, + NUM_POINTS + }; + CPoint m_aPoints[NUM_POINTS]; + }; void DoMapEditor(CUIRect View); void DoToolbarLayers(CUIRect Toolbar); void DoToolbarSounds(CUIRect Toolbar); @@ -866,9 +879,36 @@ class CEditor : public IEditor void PreparePointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex); void DoPointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY); EAxis GetDragAxis(int OffsetX, int OffsetY); - void DrawAxis(EAxis Axis, CPoint &Point); + void DrawAxis(EAxis Axis, CPoint &OriginalPoint, CPoint &Point); + void DrawAABB(const SAxisAlignedBoundingBox &AABB, int OffsetX = 0, int OffsetY = 0); ColorRGBA GetButtonColor(const void *pID, int Checked); + // Alignment methods + // These methods take `OffsetX` and `OffsetY` because the calculations are made with the original positions + // of the quad(s), before we started dragging. This allows us to edit `OffsetX` and `OffsetY` based on the previously + // calculated alignments. + struct SAlignmentInfo + { + CPoint m_AlignedPoint; // The "aligned" point, which we want to align/snap to + union + { + // The current changing value when aligned to this point. When aligning to a point on the X axis, then the X value is changing because + // we aligned the Y values (X axis aligned => Y values are the same, Y axis aligned => X values are the same). + int m_X; + int m_Y; + }; + EAxis m_Axis; // The axis we are aligning on + int m_PointIndex; // The point index we are aligning + int m_Diff; // Store the difference + }; + void ComputePointAlignments(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY, std::vector &vAlignments, bool Append = false) const; + void ComputePointsAlignments(const std::shared_ptr &pLayer, bool Pivot, int OffsetX, int OffsetY, std::vector &vAlignments) const; + void ComputeAABBAlignments(const std::shared_ptr &pLayer, const SAxisAlignedBoundingBox &AABB, int OffsetX, int OffsetY, std::vector &vAlignments) const; + void DrawPointAlignments(const std::vector &vAlignments, int OffsetX, int OffsetY); + void QuadSelectionAABB(const std::shared_ptr &pLayer, SAxisAlignedBoundingBox &OutAABB); + void ApplyAlignments(const std::vector &vAlignments, int &OffsetX, int &OffsetY); + void ApplyAxisAlignment(int &OffsetX, int &OffsetY); + bool ReplaceImage(const char *pFilename, int StorageType, bool CheckDuplicate); static bool ReplaceImageCallback(const char *pFilename, int StorageType, void *pUser); bool ReplaceSound(const char *pFileName, int StorageType, bool CheckDuplicate); @@ -1034,7 +1074,7 @@ class CEditor : public IEditor void RedoLastAction(); private: - std::map> m_QuadDragOriginalPoints; + std::map m_QuadDragOriginalPoints; }; // make sure to inline this function diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 2e5210db4ec..e9bcb721c47 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -328,6 +328,58 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect } } + View.HSplitTop(2.0f, nullptr, &View); + View.HSplitTop(12.0f, &Slot, &View); + { + Slot.VMargin(5.0f, &Slot); + + CUIRect Label, Selector; + Slot.VSplitMid(&Label, &Selector); + CUIRect No, Yes; + Selector.VSplitMid(&No, &Yes); + + pEditor->UI()->DoLabel(&Label, "Align quads", 10.0f, TEXTALIGN_ML); + if(pEditor->m_AllowPlaceUnusedTiles != -1) + { + static int s_ButtonNo = 0; + static int s_ButtonYes = 0; + if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !g_Config.m_EdAlignQuads, &No, 0, "Do not perform quad alignment to other quads/points when moving quads")) + { + g_Config.m_EdAlignQuads = false; + } + if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", g_Config.m_EdAlignQuads, &Yes, 0, "Allow quad alignment to other quads/points when moving quads")) + { + g_Config.m_EdAlignQuads = true; + } + } + } + + View.HSplitTop(2.0f, nullptr, &View); + View.HSplitTop(12.0f, &Slot, &View); + { + Slot.VMargin(5.0f, &Slot); + + CUIRect Label, Selector; + Slot.VSplitMid(&Label, &Selector); + CUIRect No, Yes; + Selector.VSplitMid(&No, &Yes); + + pEditor->UI()->DoLabel(&Label, "Show quads bounds", 10.0f, TEXTALIGN_ML); + if(pEditor->m_AllowPlaceUnusedTiles != -1) + { + static int s_ButtonNo = 0; + static int s_ButtonYes = 0; + if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !g_Config.m_EdShowQuadsRect, &No, 0, "Do not show quad bounds when moving quads")) + { + g_Config.m_EdShowQuadsRect = false; + } + if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", g_Config.m_EdShowQuadsRect, &Yes, 0, "Show quad bounds when moving quads")) + { + g_Config.m_EdShowQuadsRect = true; + } + } + } + return CUI::POPUP_KEEP_OPEN; } From 5da5eb79bbb84b6897fad11196030b4cc707467e Mon Sep 17 00:00:00 2001 From: Egehan <138960752+eghwand@users.noreply.github.com> Date: Sat, 16 Dec 2023 11:06:29 +0300 Subject: [PATCH 128/198] Add ui setting cl_same_clan_color --- src/game/client/components/menus_settings.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 7a54d9797ac..8687b02bbc6 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2980,7 +2980,9 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) Section.HSplitTop(LineSize, &Button, &Section); ColorRGBA GreenDefault(0.78f, 1.0f, 0.8f, 1.0f); static CButtonContainer s_AuthedColor; + static CButtonContainer s_SameClanColor; DoLine_ColorPicker(&s_AuthedColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Authed name color in scoreboard"), &g_Config.m_ClAuthedPlayerColor, GreenDefault, false); + DoLine_ColorPicker(&s_SameClanColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Same clan color in scoreboard"), &g_Config.m_ClSameClanColor, GreenDefault, false); } else if(s_CurTab == APPEARANCE_TAB_HOOK_COLLISION) { From 2993d3ad11504b36bfd6eb7a81e80fe442e8f25c Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Sat, 16 Dec 2023 11:10:02 +0100 Subject: [PATCH 129/198] Fix style --- src/game/client/components/menus_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 8687b02bbc6..21414e7ba3d 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2982,7 +2982,7 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) static CButtonContainer s_AuthedColor; static CButtonContainer s_SameClanColor; DoLine_ColorPicker(&s_AuthedColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Authed name color in scoreboard"), &g_Config.m_ClAuthedPlayerColor, GreenDefault, false); - DoLine_ColorPicker(&s_SameClanColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Same clan color in scoreboard"), &g_Config.m_ClSameClanColor, GreenDefault, false); + DoLine_ColorPicker(&s_SameClanColor, 25.0f, 13.0f, 5.0f, &Button, Localize("Same clan color in scoreboard"), &g_Config.m_ClSameClanColor, GreenDefault, false); } else if(s_CurTab == APPEARANCE_TAB_HOOK_COLLISION) { From 67c101b43d9cb38627feded29551161ee247efd4 Mon Sep 17 00:00:00 2001 From: furo Date: Sat, 16 Dec 2023 01:41:03 +0100 Subject: [PATCH 130/198] Add `/join [player]`. --- src/game/ddracechat.h | 9 +- src/game/server/ddracechat.cpp | 189 +++++++++++++++++++-------------- src/game/server/gamecontext.h | 11 +- 3 files changed, 123 insertions(+), 86 deletions(-) diff --git a/src/game/ddracechat.h b/src/game/ddracechat.h index 16f86fc106e..b26dceac216 100644 --- a/src/game/ddracechat.h +++ b/src/game/ddracechat.h @@ -45,10 +45,11 @@ CHAT_COMMAND("points", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConPoin CHAT_COMMAND("top5points", "?i[number]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTopPoints, this, "Shows five points of the global point ladder beginning with rank i (1 by default)") CHAT_COMMAND("timecp", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTimeCP, this, "Set your checkpoints based on another player") -CHAT_COMMAND("team", "?i[id]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConJoinTeam, this, "Lets you join team i (shows your team if left blank)") -CHAT_COMMAND("lock", "?i['0'|'1']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConLockTeam, this, "Toggle team lock so no one else can join and so the team restarts when a player dies. /lock 0 to unlock, /lock 1 to lock.") -CHAT_COMMAND("unlock", "", CFGFLAG_CHAT | CFGFLAG_SERVER, ConUnlockTeam, this, "Unlock a team") -CHAT_COMMAND("invite", "r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConInviteTeam, this, "Invite a person to a locked team") +CHAT_COMMAND("team", "?i[id]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeam, this, "Lets you join team i (shows your team if left blank)") +CHAT_COMMAND("lock", "?i['0'|'1']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConLock, this, "Toggle team lock so no one else can join and so the team restarts when a player dies. /lock 0 to unlock, /lock 1 to lock.") +CHAT_COMMAND("unlock", "", CFGFLAG_CHAT | CFGFLAG_SERVER, ConUnlock, this, "Unlock a team") +CHAT_COMMAND("invite", "r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConInvite, this, "Invite a person to a locked team") +CHAT_COMMAND("join", "r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConJoin, this, "Join the team of the specified player") CHAT_COMMAND("showothers", "?i['0'|'1'|'2']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConShowOthers, this, "Whether to show players from other teams or not (off by default), optional i = 0 for off, i = 1 for on, i = 2 for own team only") CHAT_COMMAND("showall", "?i['0'|'1']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConShowAll, this, "Whether to show players at any distance (off by default), optional i = 0 for off else for on") diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index e63dcbe5810..f422657ddba 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -871,7 +871,7 @@ void CGameContext::ConRank(IConsole::IResult *pResult, void *pUserData) pSelf->Server()->ClientName(pResult->m_ClientID)); } -void CGameContext::ConLockTeam(IConsole::IResult *pResult, void *pUserData) +void CGameContext::ConLock(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; if(!CheckClientID(pResult->m_ClientID)) @@ -917,7 +917,7 @@ void CGameContext::ConLockTeam(IConsole::IResult *pResult, void *pUserData) } } -void CGameContext::ConUnlockTeam(IConsole::IResult *pResult, void *pUserData) +void CGameContext::ConUnlock(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; if(!CheckClientID(pResult->m_ClientID)) @@ -950,7 +950,81 @@ void CGameContext::UnlockTeam(int ClientID, int Team) const SendChatTeam(Team, aBuf); } -void CGameContext::ConInviteTeam(IConsole::IResult *pResult, void *pUserData) +void CGameContext::AttemptJoinTeam(int ClientID, int Team) +{ + CPlayer *pPlayer = m_apPlayers[ClientID]; + if(!pPlayer) + return; + + if(m_VoteCloseTime && m_VoteCreator == ClientID && (IsKickVote() || IsSpecVote())) + { + Console()->Print( + IConsole::OUTPUT_LEVEL_STANDARD, + "chatresp", + "You are running a vote please try again after the vote is done!"); + } + else if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) + { + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", + "Teams are disabled"); + } + else if(g_Config.m_SvTeam == SV_TEAM_MANDATORY && Team == 0 && pPlayer->GetCharacter() && pPlayer->GetCharacter()->m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed()) + { + Console()->Print( + IConsole::OUTPUT_LEVEL_STANDARD, + "chatresp", + "You must join a team and play with somebody or else you can\'t play"); + pPlayer->GetCharacter()->m_LastStartWarning = Server()->Tick(); + } + + if(pPlayer->GetCharacter() == 0) + { + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", + "You can't change teams while you are dead/a spectator."); + } + else + { + if(Team < 0 || Team >= MAX_CLIENTS) + Team = m_pController->Teams().GetFirstEmptyTeam(); + + if(pPlayer->m_Last_Team + (int64_t)Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > Server()->Tick()) + { + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", + "You can\'t change teams that fast!"); + } + else if(Team > 0 && Team < MAX_CLIENTS && m_pController->Teams().TeamLocked(Team) && !m_pController->Teams().IsInvited(Team, ClientID)) + { + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", + g_Config.m_SvInvite ? + "This team is locked using /lock. Only members of the team can unlock it using /lock." : + "This team is locked using /lock. Only members of the team can invite you or unlock it using /lock."); + } + else if(Team > 0 && Team < MAX_CLIENTS && m_pController->Teams().Count(Team) >= g_Config.m_SvMaxTeamSize) + { + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "This team already has the maximum allowed size of %d players", g_Config.m_SvMaxTeamSize); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", aBuf); + } + else if(const char *pError = m_pController->Teams().SetCharacterTeam(pPlayer->GetCID(), Team)) + { + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", pError); + } + else + { + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "'%s' joined team %d", + Server()->ClientName(pPlayer->GetCID()), + Team); + SendChat(-1, CGameContext::CHAT_ALL, aBuf); + pPlayer->m_Last_Team = Server()->Tick(); + + if(m_pController->Teams().IsPractice(Team)) + SendChatTarget(pPlayer->GetCID(), "Practice mode enabled for your team, happy practicing!"); + } + } +} + +void CGameContext::ConInvite(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; auto *pController = pSelf->m_pController; @@ -1004,7 +1078,7 @@ void CGameContext::ConInviteTeam(IConsole::IResult *pResult, void *pUserData) pSelf->m_apPlayers[pResult->m_ClientID]->m_LastInvited = pSelf->Server()->Tick(); char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "'%s' invited you to team %d.", pSelf->Server()->ClientName(pResult->m_ClientID), Team); + str_format(aBuf, sizeof(aBuf), "'%s' invited you to team %d. Use /team %d to join.", pSelf->Server()->ClientName(pResult->m_ClientID), Team, Team); pSelf->SendChatTarget(Target, aBuf); str_format(aBuf, sizeof(aBuf), "'%s' invited '%s' to your team.", pSelf->Server()->ClientName(pResult->m_ClientID), pSelf->Server()->ClientName(Target)); @@ -1014,10 +1088,9 @@ void CGameContext::ConInviteTeam(IConsole::IResult *pResult, void *pUserData) pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "Can't invite players to this team"); } -void CGameContext::ConJoinTeam(IConsole::IResult *pResult, void *pUserData) +void CGameContext::ConTeam(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; - auto *pController = pSelf->m_pController; if(!CheckClientID(pResult->m_ClientID)) return; @@ -1025,78 +1098,9 @@ void CGameContext::ConJoinTeam(IConsole::IResult *pResult, void *pUserData) if(!pPlayer) return; - if(pSelf->m_VoteCloseTime && pSelf->m_VoteCreator == pResult->m_ClientID && (pSelf->IsKickVote() || pSelf->IsSpecVote())) - { - pSelf->Console()->Print( - IConsole::OUTPUT_LEVEL_STANDARD, - "chatresp", - "You are running a vote please try again after the vote is done!"); - return; - } - else if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) - { - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", - "Teams are disabled"); - return; - } - else if(g_Config.m_SvTeam == SV_TEAM_MANDATORY && pResult->GetInteger(0) == 0 && pPlayer->GetCharacter() && pPlayer->GetCharacter()->m_LastStartWarning < pSelf->Server()->Tick() - 3 * pSelf->Server()->TickSpeed()) - { - pSelf->Console()->Print( - IConsole::OUTPUT_LEVEL_STANDARD, - "chatresp", - "You must join a team and play with somebody or else you can\'t play"); - pPlayer->GetCharacter()->m_LastStartWarning = pSelf->Server()->Tick(); - } - if(pResult->NumArguments() > 0) { - if(pPlayer->GetCharacter() == 0) - { - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", - "You can't change teams while you are dead/a spectator."); - } - else - { - int Team = pResult->GetInteger(0); - - if(Team < 0 || Team >= MAX_CLIENTS) - Team = pController->Teams().GetFirstEmptyTeam(); - - if(pPlayer->m_Last_Team + (int64_t)pSelf->Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > pSelf->Server()->Tick()) - { - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", - "You can\'t change teams that fast!"); - } - else if(Team > 0 && Team < MAX_CLIENTS && pController->Teams().TeamLocked(Team) && !pController->Teams().IsInvited(Team, pResult->m_ClientID)) - { - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", - g_Config.m_SvInvite ? - "This team is locked using /lock. Only members of the team can unlock it using /lock." : - "This team is locked using /lock. Only members of the team can invite you or unlock it using /lock."); - } - else if(Team > 0 && Team < MAX_CLIENTS && pController->Teams().Count(Team) >= g_Config.m_SvMaxTeamSize) - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "This team already has the maximum allowed size of %d players", g_Config.m_SvMaxTeamSize); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", aBuf); - } - else if(const char *pError = pController->Teams().SetCharacterTeam(pPlayer->GetCID(), Team)) - { - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", pError); - } - else - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "'%s' joined team %d", - pSelf->Server()->ClientName(pPlayer->GetCID()), - Team); - pSelf->SendChat(-1, CGameContext::CHAT_ALL, aBuf); - pPlayer->m_Last_Team = pSelf->Server()->Tick(); - - if(pController->Teams().IsPractice(Team)) - pSelf->SendChatTarget(pPlayer->GetCID(), "Practice mode enabled for your team, happy practicing!"); - } - } + pSelf->AttemptJoinTeam(pResult->m_ClientID, pResult->GetInteger(0)); } else { @@ -1115,10 +1119,39 @@ void CGameContext::ConJoinTeam(IConsole::IResult *pResult, void *pUserData) sizeof(aBuf), "You are in team %d", pSelf->GetDDRaceTeam(pResult->m_ClientID)); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", - aBuf); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", aBuf); + } + } +} + +void CGameContext::ConJoin(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + if(!CheckClientID(pResult->m_ClientID)) + return; + + int Target = -1; + const char *pName = pResult->GetString(0); + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(!str_comp(pName, pSelf->Server()->ClientName(i))) + { + Target = i; + break; } } + + if(Target == -1) + { + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "Player not found"); + return; + } + + int Team = pSelf->GetDDRaceTeam(Target); + if(pSelf->ProcessSpamProtection(pResult->m_ClientID, false)) + return; + + pSelf->AttemptJoinTeam(pResult->m_ClientID, Team); } void CGameContext::ConMe(IConsole::IResult *pResult, void *pUserData) diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 34e1cf76953..67a2a375971 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -437,10 +437,12 @@ class CGameContext : public IGameServer static void ConMap(IConsole::IResult *pResult, void *pUserData); static void ConTeamRank(IConsole::IResult *pResult, void *pUserData); static void ConRank(IConsole::IResult *pResult, void *pUserData); - static void ConJoinTeam(IConsole::IResult *pResult, void *pUserData); - static void ConLockTeam(IConsole::IResult *pResult, void *pUserData); - static void ConUnlockTeam(IConsole::IResult *pResult, void *pUserData); - static void ConInviteTeam(IConsole::IResult *pResult, void *pUserData); + static void ConTeam(IConsole::IResult *pResult, void *pUserData); + static void ConLock(IConsole::IResult *pResult, void *pUserData); + static void ConUnlock(IConsole::IResult *pResult, void *pUserData); + static void ConInvite(IConsole::IResult *pResult, void *pUserData); + static void ConJoin(IConsole::IResult *pResult, void *pUserData); + static void ConAccept(IConsole::IResult *pResult, void *pUserData); static void ConMe(IConsole::IResult *pResult, void *pUserData); static void ConWhisper(IConsole::IResult *pResult, void *pUserData); static void ConConverse(IConsole::IResult *pResult, void *pUserData); @@ -506,6 +508,7 @@ class CGameContext : public IGameServer void Converse(int ClientID, char *pStr); bool IsVersionBanned(int Version); void UnlockTeam(int ClientID, int Team) const; + void AttemptJoinTeam(int ClientID, int Team); enum { From b067ccafc66bf45cda78f746435d3edd6d440169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 13 Dec 2023 21:41:39 +0100 Subject: [PATCH 131/198] Improve vote config variable descriptions and value range Closes #7584. --- src/engine/shared/config_variables.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 790416ddea5..4d85b46d431 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -456,9 +456,9 @@ MACRO_CONFIG_INT(SvVoteTime, sv_vote_time, 25, 1, 60, CFGFLAG_SERVER, "The time MACRO_CONFIG_INT(SvVoteMapTimeDelay, sv_vote_map_delay, 0, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between map votes") MACRO_CONFIG_INT(SvVoteDelay, sv_vote_delay, 3, 0, 9999, CFGFLAG_SERVER, "The time in seconds between any vote") MACRO_CONFIG_INT(SvVoteKickDelay, sv_vote_kick_delay, 0, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between kick votes") -MACRO_CONFIG_INT(SvVoteYesPercentage, sv_vote_yes_percentage, 50, 1, 100, CFGFLAG_SERVER, "The percent of people that need to agree or deny for the vote to succeed/fail") -MACRO_CONFIG_INT(SvVoteMajority, sv_vote_majority, 0, 0, 1, CFGFLAG_SERVER, "Whether No. of Yes is compared to No. of No votes or to number of total Players ( Default is 0 Y compare N)") -MACRO_CONFIG_INT(SvVoteMaxTotal, sv_vote_max_total, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "How many people can participate in a vote at max (0 = no limit by default)") +MACRO_CONFIG_INT(SvVoteYesPercentage, sv_vote_yes_percentage, 50, 1, 99, CFGFLAG_SERVER, "More than this percentage of players need to agree for a vote to succeed") +MACRO_CONFIG_INT(SvVoteMajority, sv_vote_majority, 0, 0, 1, CFGFLAG_SERVER, "Whether non-voting players are considered as votes for \"no\" (0) or are ignored (1)") +MACRO_CONFIG_INT(SvVoteMaxTotal, sv_vote_max_total, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "How many players can participate in a vote at max (0 = no limit)") MACRO_CONFIG_INT(SvVoteVetoTime, sv_vote_veto_time, 20, 0, 1000, CFGFLAG_SERVER, "Minutes of time on a server until a player can veto map change votes (0 = disabled)") MACRO_CONFIG_INT(SvKillDelay, sv_kill_delay, 1, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between kills") From 013b8dffa575165952921763a3627cee6d4c5194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 3 Dec 2023 12:53:20 +0100 Subject: [PATCH 132/198] Add `INotifications` kernel interface Add a proper kernel interface `INotifications` for the notifications component instead of using a C style interface. Add parameter for the application name when initializing notifications to avoid hardcoding the application name. The implementation for macOS is kept in Objective-C and a TODO is added, as the API we are currently using appears to be deprecated. --- CMakeLists.txt | 1 + src/engine/client/client.cpp | 10 ++++---- src/engine/client/client.h | 3 +++ src/engine/client/notifications.cpp | 36 +++++++++++++++++------------ src/engine/client/notifications.h | 14 ++++++++--- src/engine/notifications.h | 17 ++++++++++++++ src/macos/notifications.mm | 11 ++------- 7 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 src/engine/notifications.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e0b2860c66..239f3a185f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1888,6 +1888,7 @@ set_src(ENGINE_INTERFACE GLOB src/engine keys.h map.h message.h + notifications.h rust.h server.h serverbrowser.h diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index fdd46fee4eb..37ba2d8a900 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -2683,6 +2683,7 @@ void CClient::InitInterfaces() #endif m_pDiscord = Kernel()->RequestInterface(); m_pSteam = Kernel()->RequestInterface(); + m_pNotifications = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage); @@ -3959,7 +3960,7 @@ void CClient::Notify(const char *pTitle, const char *pMessage) if(m_pGraphics->WindowActive() || !g_Config.m_ClShowNotifications) return; - NotificationsNotify(pTitle, pMessage); + Notifications()->Notify(pTitle, pMessage); Graphics()->NotifyWindow(); } @@ -4290,9 +4291,6 @@ int main(int argc, const char **argv) if(!RandInitFailed) CleanerFunctions.emplace([]() { secure_random_uninit(); }); - NotificationsInit(); - CleanerFunctions.emplace([]() { NotificationsUninit(); }); - // Register SDL for cleanup before creating the kernel and client, // so SDL is shutdown after kernel and client. Otherwise the client // may crash when shutting down after SDL is already shutdown. @@ -4394,6 +4392,9 @@ int main(int argc, const char **argv) ISteam *pSteam = CreateSteam(); pKernel->RegisterInterface(pSteam); + INotifications *pNotifications = CreateNotifications(); + pKernel->RegisterInterface(pNotifications); + pKernel->RegisterInterface(CreateEditor(), false); pKernel->RegisterInterface(CreateFavorites().release()); pKernel->RegisterInterface(CreateGameClient()); @@ -4401,6 +4402,7 @@ int main(int argc, const char **argv) pEngine->Init(); pConsole->Init(); pConfigManager->Init(); + pNotifications->Init(GAME_NAME " Client"); // register all console commands pClient->RegisterCommands(); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 8b4601ac2d9..48cfca82328 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -39,6 +39,7 @@ class IEngineSound; class IFriends; class ILogger; class ISteam; +class INotifications; class IStorage; class IUpdater; @@ -71,6 +72,7 @@ class CClient : public IClient, public CDemoPlayer::IListener IEngineMap *m_pMap = nullptr; IEngineSound *m_pSound = nullptr; ISteam *m_pSteam = nullptr; + INotifications *m_pNotifications = nullptr; IStorage *m_pStorage = nullptr; IEngineTextRender *m_pTextRender = nullptr; IUpdater *m_pUpdater = nullptr; @@ -260,6 +262,7 @@ class CClient : public IClient, public CDemoPlayer::IListener IEngineInput *Input() { return m_pInput; } IEngineSound *Sound() { return m_pSound; } ISteam *Steam() { return m_pSteam; } + INotifications *Notifications() { return m_pNotifications; } IStorage *Storage() { return m_pStorage; } IEngineTextRender *TextRender() { return m_pTextRender; } IUpdater *Updater() { return m_pUpdater; } diff --git a/src/engine/client/notifications.cpp b/src/engine/client/notifications.cpp index 4cacd6519d7..577b4f4256b 100644 --- a/src/engine/client/notifications.cpp +++ b/src/engine/client/notifications.cpp @@ -4,35 +4,41 @@ #if defined(CONF_PLATFORM_MACOS) // Code is in src/macos/notification.mm. +void NotificationsNotifyMacOsInternal(const char *pTitle, const char *pMessage); #elif defined(CONF_FAMILY_UNIX) && !defined(CONF_PLATFORM_ANDROID) && !defined(CONF_PLATFORM_HAIKU) && !defined(CONF_WEBASM) #include -void NotificationsInit() +#define NOTIFICATIONS_USE_LIBNOTIFY +#endif + +void CNotifications::Init(const char *pAppname) { - notify_init("DDNet Client"); +#if defined(NOTIFICATIONS_USE_LIBNOTIFY) + notify_init(pAppname); +#endif } -void NotificationsUninit() + +void CNotifications::Shutdown() { +#if defined(NOTIFICATIONS_USE_LIBNOTIFY) notify_uninit(); +#endif } -void NotificationsNotify(const char *pTitle, const char *pMessage) + +void CNotifications::Notify(const char *pTitle, const char *pMessage) { +#if defined(CONF_PLATFORM_MACOS) + NotificationsNotifyMacOsInternal(pTitle, pMessage); +#elif defined(NOTIFICATIONS_USE_LIBNOTIFY) NotifyNotification *pNotif = notify_notification_new(pTitle, pMessage, "ddnet"); if(pNotif) { notify_notification_show(pNotif, NULL); g_object_unref(G_OBJECT(pNotif)); } +#endif } -#else -void NotificationsInit() -{ -} -void NotificationsUninit() -{ -} -void NotificationsNotify(const char *pTitle, const char *pMessage) + +INotifications *CreateNotifications() { - (void)pTitle; - (void)pMessage; + return new CNotifications(); } -#endif diff --git a/src/engine/client/notifications.h b/src/engine/client/notifications.h index 35d7b588a88..02463535e48 100644 --- a/src/engine/client/notifications.h +++ b/src/engine/client/notifications.h @@ -1,6 +1,14 @@ #ifndef ENGINE_CLIENT_NOTIFICATIONS_H #define ENGINE_CLIENT_NOTIFICATIONS_H -void NotificationsInit(); -void NotificationsUninit(); -void NotificationsNotify(const char *pTitle, const char *pMessage); + +#include + +class CNotifications : public INotifications +{ +public: + void Init(const char *pAppname) override; + void Shutdown() override; + void Notify(const char *pTitle, const char *pMessage) override; +}; + #endif // ENGINE_CLIENT_NOTIFICATIONS_H diff --git a/src/engine/notifications.h b/src/engine/notifications.h new file mode 100644 index 00000000000..38cfe88b0a6 --- /dev/null +++ b/src/engine/notifications.h @@ -0,0 +1,17 @@ +#ifndef ENGINE_NOTIFICATIONS_H +#define ENGINE_NOTIFICATIONS_H + +#include "kernel.h" + +class INotifications : public IInterface +{ + MACRO_INTERFACE("notifications") +public: + virtual void Init(const char *pAppname) = 0; + virtual void Shutdown() override = 0; + virtual void Notify(const char *pTitle, const char *pMessage) = 0; +}; + +INotifications *CreateNotifications(); + +#endif // ENGINE_NOTIFICATIONS_H diff --git a/src/macos/notifications.mm b/src/macos/notifications.mm index 39ed0aee106..2501fe0e6b1 100644 --- a/src/macos/notifications.mm +++ b/src/macos/notifications.mm @@ -1,16 +1,9 @@ -#import - #import #import #import -void NotificationsInit() -{ -} -void NotificationsUninit() -{ -} -void NotificationsNotify(const char *pTitle, const char *pMessage) +// TODO: NSUserNotification is deprecated. Use the User Notifications framework instead: https://developer.apple.com/documentation/usernotifications?language=objc +void NotificationsNotifyMacOsInternal(const char *pTitle, const char *pMessage) { NSString* pNsTitle = [NSString stringWithCString:pTitle encoding:NSUTF8StringEncoding]; NSString* pNsMsg = [NSString stringWithCString:pMessage encoding:NSUTF8StringEncoding]; From d2efc936cf816b190ca4990da93df610607732bf Mon Sep 17 00:00:00 2001 From: furo Date: Sat, 16 Dec 2023 19:02:56 +0100 Subject: [PATCH 133/198] Use rest of string parameter for `save_replay` --- src/engine/client/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index fdd46fee4eb..79fb87f3330 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -4097,7 +4097,7 @@ void CClient::RegisterCommands() m_pConsole->Register("demo_play", "", CFGFLAG_CLIENT, Con_DemoPlay, this, "Play demo"); m_pConsole->Register("demo_speed", "i[speed]", CFGFLAG_CLIENT, Con_DemoSpeed, this, "Set demo speed"); - m_pConsole->Register("save_replay", "?i[length] s[filename]", CFGFLAG_CLIENT, Con_SaveReplay, this, "Save a replay of the last defined amount of seconds"); + m_pConsole->Register("save_replay", "?i[length] ?r[filename]", CFGFLAG_CLIENT, Con_SaveReplay, this, "Save a replay of the last defined amount of seconds"); m_pConsole->Register("benchmark_quit", "i[seconds] r[file]", CFGFLAG_CLIENT | CFGFLAG_STORE, Con_BenchmarkQuit, this, "Benchmark frame times for number of seconds to file, then quit"); RustVersionRegister(*m_pConsole); From eb222ae02a5377f8bb8e40fe81ff65024186c2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 16 Dec 2023 13:42:56 +0100 Subject: [PATCH 134/198] Remove `CFGFLAG_SAVE` when `CFGFLAG_SERVER` used Server variables are currently not saved. --- src/engine/shared/config_variables.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 790416ddea5..6f835bf6a7f 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -180,7 +180,7 @@ MACRO_CONFIG_INT(ClSkipStartMenu, cl_skip_start_menu, 0, 0, 1, CFGFLAG_CLIENT | // server MACRO_CONFIG_INT(SvWarmup, sv_warmup, 0, 0, 0, CFGFLAG_SERVER, "Number of seconds to do warmup before round starts") MACRO_CONFIG_STR(SvMotd, sv_motd, 900, "", CFGFLAG_SERVER, "Message of the day to display for the clients") -MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "ddnet", CFGFLAG_SAVE | CFGFLAG_SERVER, "Game type (ddnet, mod)") +MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "ddnet", CFGFLAG_SERVER, "Game type (ddnet, mod)") MACRO_CONFIG_INT(SvTournamentMode, sv_tournament_mode, 0, 0, 1, CFGFLAG_SERVER, "Tournament mode. When enabled, players joins the server as spectator") MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection") @@ -373,7 +373,7 @@ MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server MACRO_CONFIG_STR(Bindaddr, bindaddr, 128, "", CFGFLAG_CLIENT | CFGFLAG_SERVER | CFGFLAG_MASTER, "Address to bind the client/server to") MACRO_CONFIG_INT(SvIpv4Only, sv_ipv4only, 0, 0, 1, CFGFLAG_SERVER, "Whether to bind only to ipv4, otherwise bind to all available interfaces") MACRO_CONFIG_INT(SvPort, sv_port, 0, 0, 0, CFGFLAG_SERVER, "Port to use for the server (Only ports 8303-8310 work in LAN server browser, 0 to automatically find a free port in 8303-8310)") -MACRO_CONFIG_STR(SvHostname, sv_hostname, 128, "", CFGFLAG_SAVE | CFGFLAG_SERVER, "Server hostname (0.7 only)") +MACRO_CONFIG_STR(SvHostname, sv_hostname, 128, "", CFGFLAG_SERVER, "Server hostname (0.7 only)") MACRO_CONFIG_STR(SvMap, sv_map, 128, "Sunny Side Up", CFGFLAG_SERVER, "Map to use on the server") MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, MAX_CLIENTS, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server") MACRO_CONFIG_INT(SvMaxClientsPerIP, sv_max_clients_per_ip, 4, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients with the same IP that can connect to the server") From c984c6895a696236f7d8b3e8a1919528361b9a6f Mon Sep 17 00:00:00 2001 From: furo Date: Sat, 16 Dec 2023 21:46:06 +0100 Subject: [PATCH 135/198] Fix clang warning `-Wbitwise-instead-of-logical` --- src/game/client/components/chat.cpp | 5 +++-- src/game/client/components/console.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index b5ab779ff0b..3d9ea8a1055 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -1217,8 +1217,9 @@ void CChat::OnRender() m_Input.Activate(EInputPriority::CHAT); // Ensure that the input is active const CUIRect InputCursorRect = {Cursor.m_X, Cursor.m_Y - ScrollOffset, 0.0f, 0.0f}; - // Arithmetic or to ensure that both functions are called so both flags are purged - const bool Changed = m_Input.WasChanged() | m_Input.WasCursorChanged(); + const bool WasChanged = m_Input.WasChanged(); + const bool WasCursorChanged = m_Input.WasCursorChanged(); + const bool Changed = WasChanged || WasCursorChanged; const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, Changed, MessageMaxWidth, 0.0f); Graphics()->ClipDisable(); diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index f18336d7633..3031b6120d4 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -970,8 +970,9 @@ void CGameConsole::OnRender() pConsole->m_Input.SetHidden(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->State() == IClient::STATE_ONLINE && !Client()->RconAuthed() && (pConsole->m_UserGot || !pConsole->m_UsernameReq)); pConsole->m_Input.Activate(EInputPriority::CONSOLE); // Ensure that the input is active const CUIRect InputCursorRect = {x, y + FONT_SIZE, 0.0f, 0.0f}; - // Arithmetic or to ensure that both functions are called so both flags are purged - const bool Changed = pConsole->m_Input.WasChanged() | pConsole->m_Input.WasCursorChanged(); + const bool WasChanged = pConsole->m_Input.WasChanged(); + const bool WasCursorChanged = pConsole->m_Input.WasCursorChanged(); + const bool Changed = WasChanged || WasCursorChanged; pConsole->m_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FONT_SIZE, TEXTALIGN_BL, Changed, Screen.w - 10.0f - x, LINE_SPACING); if(pConsole->m_LastInputHeight == 0.0f && pConsole->m_BoundingBox.m_H != 0.0f) pConsole->m_LastInputHeight = pConsole->m_BoundingBox.m_H; From caecb317510b69b7dca0b22a97340c7057fb8191 Mon Sep 17 00:00:00 2001 From: Jupeyy Date: Sat, 16 Dec 2023 23:43:22 +0100 Subject: [PATCH 136/198] Fix multi sampling accuracy --- data/shader/tile_border.frag | 2 +- data/shader/vulkan/tile_border.frag | 2 +- src/engine/client/backend/glsl_shader_compiler.cpp | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/shader/tile_border.frag b/data/shader/tile_border.frag index a186901c317..2182dd97967 100644 --- a/data/shader/tile_border.frag +++ b/data/shader/tile_border.frag @@ -9,7 +9,7 @@ uniform sampler2DArray gTextureSampler; uniform vec4 gVertColor; #ifdef TW_TILE_TEXTURED -noperspective in vec3 TexCoord; +noperspective centroid in vec3 TexCoord; #endif out vec4 FragClr; diff --git a/data/shader/vulkan/tile_border.frag b/data/shader/vulkan/tile_border.frag index 31166b66155..841a3f4d4d8 100644 --- a/data/shader/vulkan/tile_border.frag +++ b/data/shader/vulkan/tile_border.frag @@ -10,7 +10,7 @@ layout(push_constant) uniform SVertexColorBO { } gColorBO; #ifdef TW_TILE_TEXTURED -layout (location = 0) noperspective in vec3 TexCoord; +layout (location = 0) noperspective centroid in vec3 TexCoord; #endif layout (location = 0) out vec4 FragClr; diff --git a/src/engine/client/backend/glsl_shader_compiler.cpp b/src/engine/client/backend/glsl_shader_compiler.cpp index 1077221acd3..f4c6fa095d8 100644 --- a/src/engine/client/backend/glsl_shader_compiler.cpp +++ b/src/engine/client/backend/glsl_shader_compiler.cpp @@ -84,6 +84,8 @@ void CGLSLCompiler::ParseLine(std::string &Line, const char *pReadLine, EGLSLSha //search for 'in' or 'out' while(*pBuff && ((*pBuff != 'i' || *(pBuff + 1) != 'n') && (*pBuff != 'o' || (*(pBuff + 1) && *(pBuff + 1) != 'u') || *(pBuff + 2) != 't'))) { + // append anything that is inbetween noperspective & in/out vars + Line.push_back(*pBuff); ++pBuff; } From 1f5a6486006f30d30a31d52bc52b06e45fd8c538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 17 Dec 2023 12:53:08 +0100 Subject: [PATCH 137/198] Minor refactoring of `demo_extract_chat` tool Replace unnecessary `gameclient.h` include with more specific includes. Fix storage creation error message not being logged as the logger was initialized after checking for the failed storage creation. However, in this case we want to avoid non-error log messages so the tool's output is only the extracted demo chat, except in error cases. Rename `Process` function to `ExtractDemoChat` and make it `static` to avoid exporting it. Use `log_error` instead of `dbg_msg`. --- src/tools/demo_extract_chat.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/tools/demo_extract_chat.cpp b/src/tools/demo_extract_chat.cpp index 1f2d2b260ed..82f1879e3d4 100644 --- a/src/tools/demo_extract_chat.cpp +++ b/src/tools/demo_extract_chat.cpp @@ -1,7 +1,13 @@ #include #include + #include -#include +#include +#include +#include +#include + +#include static const char *TOOL_NAME = "demo_extract_chat"; @@ -190,14 +196,14 @@ class CDemoPlayerMessageListener : public CDemoPlayer::IListener } }; -int Process(const char *pDemoFilePath, IStorage *pStorage) +static int ExtractDemoChat(const char *pDemoFilePath, IStorage *pStorage) { CSnapshotDelta DemoSnapshotDelta; CDemoPlayer DemoPlayer(&DemoSnapshotDelta, false); if(DemoPlayer.Load(pStorage, nullptr, pDemoFilePath, IStorage::TYPE_ALL_OR_ABSOLUTE) == -1) { - dbg_msg(TOOL_NAME, "Demo file '%s' failed to load: %s", pDemoFilePath, DemoPlayer.ErrorMessage()); + log_error(TOOL_NAME, "Demo file '%s' failed to load: %s", pDemoFilePath, DemoPlayer.ErrorMessage()); return -1; } @@ -225,21 +231,23 @@ int Process(const char *pDemoFilePath, IStorage *pStorage) int main(int argc, const char *argv[]) { + // Create storage before setting logger to avoid log messages from storage creation IStorage *pStorage = CreateLocalStorage(); + + CCmdlineFix CmdlineFix(&argc, &argv); + log_set_global_logger_default(); + if(!pStorage) { - dbg_msg(TOOL_NAME, "Error loading storage"); + log_error(TOOL_NAME, "Error creating local storage"); return -1; } - CCmdlineFix CmdlineFix(&argc, &argv); - log_set_global_logger_default(); - if(argc != 2) { - dbg_msg(TOOL_NAME, "Usage: %s ", TOOL_NAME); + log_error(TOOL_NAME, "Usage: %s ", TOOL_NAME); return -1; } - return Process(argv[1], pStorage); + return ExtractDemoChat(argv[1], pStorage); } From a883018c88f8dd0bcaca3a86e422c30003a53b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 17 Dec 2023 12:39:35 +0100 Subject: [PATCH 138/198] Refactor `map_resave` tool Add log messages for errors and success. Extract function `ResaveMap`. Ensure reader is closed when writer could not be opened. --- src/tools/map_resave.cpp | 46 ++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/tools/map_resave.cpp b/src/tools/map_resave.cpp index 1f6698828f6..5ff2090504d 100644 --- a/src/tools/map_resave.cpp +++ b/src/tools/map_resave.cpp @@ -1,24 +1,30 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ + +#include #include + #include #include -int main(int argc, const char **argv) -{ - CCmdlineFix CmdlineFix(&argc, &argv); - - IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_BASIC, argc, argv); - if(!pStorage || argc != 3) - return -1; +static const char *TOOL_NAME = "map_resave"; +static int ResaveMap(const char *pSourceMap, const char *pDestinationMap, IStorage *pStorage) +{ CDataFileReader Reader; - if(!Reader.Open(pStorage, argv[1], IStorage::TYPE_ABSOLUTE)) + if(!Reader.Open(pStorage, pSourceMap, IStorage::TYPE_ABSOLUTE)) + { + log_error(TOOL_NAME, "Failed to open source map '%s' for reading", pSourceMap); return -1; + } CDataFileWriter Writer; - if(!Writer.Open(pStorage, argv[2])) + if(!Writer.Open(pStorage, pDestinationMap)) + { + log_error(TOOL_NAME, "Failed to open destination map '%s' for writing", pDestinationMap); + Reader.Close(); return -1; + } // add all items for(int Index = 0; Index < Reader.NumItems(); Index++) @@ -44,5 +50,27 @@ int main(int argc, const char **argv) Reader.Close(); Writer.Finish(); + log_info(TOOL_NAME, "Resaved '%s' to '%s'", pSourceMap, pDestinationMap); return 0; } + +int main(int argc, const char **argv) +{ + CCmdlineFix CmdlineFix(&argc, &argv); + log_set_global_logger_default(); + + if(argc != 3) + { + log_error(TOOL_NAME, "Usage: %s ", TOOL_NAME); + return -1; + } + + IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_BASIC, argc, argv); + if(!pStorage) + { + log_error(TOOL_NAME, "Error creating basic storage"); + return -1; + } + + return ResaveMap(argv[1], argv[2], pStorage); +} From 2f89297395ba30f4ddc87fde5b7e954b6521dae1 Mon Sep 17 00:00:00 2001 From: furo Date: Sun, 17 Dec 2023 04:12:48 +0100 Subject: [PATCH 139/198] Disable cycling through command history when logging in to rcon. --- src/game/client/components/console.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index f18336d7633..cf7497dbb7d 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -313,7 +313,11 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) } else if(Event.m_Key == KEY_UP) { - if(!m_Searching) + if(m_Searching) + { + SelectNextSearchMatch(-1); + } + else if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) { if(m_pHistoryEntry) { @@ -328,15 +332,15 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) if(m_pHistoryEntry) m_Input.Set(m_pHistoryEntry); } - else - { - SelectNextSearchMatch(-1); - } Handled = true; } else if(Event.m_Key == KEY_DOWN) { - if(!m_Searching) + if(m_Searching) + { + SelectNextSearchMatch(1); + } + else if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) { if(m_pHistoryEntry) m_pHistoryEntry = m_History.Next(m_pHistoryEntry); @@ -346,10 +350,6 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) else m_Input.Clear(); } - else - { - SelectNextSearchMatch(1); - } Handled = true; } else if(Event.m_Key == KEY_TAB) From 87063d048c915fa940237d20f7ebee3238874f21 Mon Sep 17 00:00:00 2001 From: Egehan <138960752+eghwand@users.noreply.github.com> Date: Sun, 17 Dec 2023 19:28:35 +0300 Subject: [PATCH 140/198] add ui setting for cl_show_local_time_always --- src/game/client/components/menus_settings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 21414e7ba3d..6327b64b580 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2492,7 +2492,7 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhud, Localize("Show ingame HUD"), &g_Config.m_ClShowhud, &Section, LineSize); // Switches of the various normal HUD elements - LeftView.HSplitTop(SectionTotalMargin + 5 * LineSize, &Section, &LeftView); + LeftView.HSplitTop(SectionTotalMargin + 6 * LineSize, &Section, &LeftView); Section.Margin(SectionMargin, &Section); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudHealthAmmo, Localize("Show health, shields and ammo"), &g_Config.m_ClShowhudHealthAmmo, &Section, LineSize); @@ -2500,6 +2500,7 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClNameplates, Localize("Show name plates"), &g_Config.m_ClNameplates, &Section, LineSize); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowKillMessages, Localize("Show kill messages"), &g_Config.m_ClShowKillMessages, &Section, LineSize); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowhudScore, Localize("Show score"), &g_Config.m_ClShowhudScore, &Section, LineSize); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClShowLocalTimeAlways, Localize("Show local time always"), &g_Config.m_ClShowLocalTimeAlways, &Section, LineSize); // Settings of the HUD element for votes LeftView.HSplitTop(SectionTotalMargin + LineSize, &Section, &LeftView); From c0ed140a314e6bcddfd49184132534d7923a5f4f Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Mon, 18 Dec 2023 08:57:14 +0100 Subject: [PATCH 141/198] Version 17.4.2 --- src/game/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/version.h b/src/game/version.h index ad48edffc3b..2ece03c5f8e 100644 --- a/src/game/version.h +++ b/src/game/version.h @@ -3,11 +3,11 @@ #ifndef GAME_VERSION_H #define GAME_VERSION_H #ifndef GAME_RELEASE_VERSION -#define GAME_RELEASE_VERSION "17.4.1" +#define GAME_RELEASE_VERSION "17.4.2" #endif #define GAME_VERSION "0.6.4, " GAME_RELEASE_VERSION #define GAME_NETVERSION "0.6 626fce9a778df4d4" -#define DDNET_VERSION_NUMBER 17041 +#define DDNET_VERSION_NUMBER 17042 extern const char *GIT_SHORTREV_HASH; #define GAME_NAME "DDNet" #endif From aa15d9b19b6c52c8803184d94661e4971ce656a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 18 Dec 2023 17:55:31 +0100 Subject: [PATCH 142/198] Use `GetDataSize` instead of `CMapItemSound::m_SoundDataSize` Do not use the `CMapItemSound::m_SoundDataSize` value as it is redundant. This value could also be incorrect because it can be freely set by the map creator (tool). Instead, use the map/datafile function `GetDataSize` to get the true size of the sound data in the file. The `m_SoundDataSize` value is still written to map files for compatibility with old versions. --- src/game/client/components/mapsounds.cpp | 5 +++-- src/game/editor/mapitems/map_io.cpp | 5 ++--- src/game/mapitems.h | 3 +++ src/tools/map_extract.cpp | 5 +++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/game/client/components/mapsounds.cpp b/src/game/client/components/mapsounds.cpp index 9f7c0b93e3b..3fbba3139b8 100644 --- a/src/game/client/components/mapsounds.cpp +++ b/src/game/client/components/mapsounds.cpp @@ -51,8 +51,9 @@ void CMapSounds::OnMapLoad() } else { - void *pData = pMap->GetData(pSound->m_SoundData); - m_aSounds[i] = Sound()->LoadOpusFromMem(pData, pSound->m_SoundDataSize); + const int SoundDataSize = pMap->GetDataSize(pSound->m_SoundData); + const void *pData = pMap->GetData(pSound->m_SoundData); + m_aSounds[i] = Sound()->LoadOpusFromMem(pData, SoundDataSize); pMap->UnloadData(pSound->m_SoundData); } ShowWarning = ShowWarning || m_aSounds[i] == -1; diff --git a/src/game/editor/mapitems/map_io.cpp b/src/game/editor/mapitems/map_io.cpp index 30a88ee40e8..208918d3154 100644 --- a/src/game/editor/mapitems/map_io.cpp +++ b/src/game/editor/mapitems/map_io.cpp @@ -146,6 +146,7 @@ bool CEditorMap::Save(const char *pFileName) Item.m_External = 0; Item.m_SoundName = Writer.AddDataString(pSound->m_aName); Item.m_SoundData = Writer.AddData(pSound->m_DataSize, pSound->m_pData); + // Value is not read in new versions, but we still need to write it for compatibility with old versions. Item.m_SoundDataSize = pSound->m_DataSize; Writer.AddItem(MAPITEMTYPE_SOUND, i, sizeof(Item), &Item); @@ -575,9 +576,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio } else { - pSound->m_DataSize = pItem->m_SoundDataSize; - - // copy sample data + pSound->m_DataSize = DataFile.GetDataSize(pItem->m_SoundData); void *pData = DataFile.GetData(pItem->m_SoundData); pSound->m_pData = malloc(pSound->m_DataSize); mem_copy(pSound->m_pData, pData, pSound->m_DataSize); diff --git a/src/game/mapitems.h b/src/game/mapitems.h index d89cde0cc29..54940501eb3 100644 --- a/src/game/mapitems.h +++ b/src/game/mapitems.h @@ -520,6 +520,9 @@ struct CMapItemSound int m_SoundName; int m_SoundData; + // Deprecated. Do not read this value, it could be wrong. + // Use GetDataSize instead, which returns the de facto size. + // Value must still be written for compatibility. int m_SoundDataSize; }; diff --git a/src/tools/map_extract.cpp b/src/tools/map_extract.cpp index d66bbf25eac..901e7463d12 100644 --- a/src/tools/map_extract.cpp +++ b/src/tools/map_extract.cpp @@ -102,12 +102,13 @@ bool Process(IStorage *pStorage, const char *pMapName, const char *pPathSave) continue; } + const int SoundDataSize = Reader.GetDataSize(pItem->m_SoundData); char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "%s/%s.opus", pPathSave, pName); - dbg_msg("map_extract", "writing sound: %s (%d B)", aBuf, pItem->m_SoundDataSize); + dbg_msg("map_extract", "writing sound: %s (%d B)", aBuf, SoundDataSize); IOHANDLE Opus = io_open(aBuf, IOFLAG_WRITE); - io_write(Opus, (unsigned char *)Reader.GetData(pItem->m_SoundData), pItem->m_SoundDataSize); + io_write(Opus, Reader.GetData(pItem->m_SoundData), SoundDataSize); io_close(Opus); } From fd2582c267509788ea8445ead674ae6e84d7b6aa Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 18 Dec 2023 18:35:13 +0100 Subject: [PATCH 143/198] Clamp volume envelopes between 0.0f and 1.0f --- src/game/editor/editor.cpp | 4 ++-- src/game/editor/editor_actions.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 7d826ea4d12..a14a10536df 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -6918,7 +6918,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) s_vAccurateDragValuesY[k] -= DeltaY; pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] = std::round(s_vAccurateDragValuesY[k]); - if(pEnvelope->GetChannels() == 4) + if(pEnvelope->GetChannels() == 1 || pEnvelope->GetChannels() == 4) { pEnvelope->m_vPoints[i].m_aValues[c] = clamp(pEnvelope->m_vPoints[i].m_aValues[c], 0, 1024); s_vAccurateDragValuesY[k] = clamp(s_vAccurateDragValuesY[k], 0, 1024); @@ -7383,7 +7383,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) else pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] = std::round(s_vInitialPositionsY[k] * s_ScaleFactorY); - if(pEnvelope->GetChannels() == 4) + if(pEnvelope->GetChannels() == 1 || pEnvelope->GetChannels() == 4) pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] = clamp(pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel], 0, 1024); } } diff --git a/src/game/editor/editor_actions.cpp b/src/game/editor/editor_actions.cpp index b2851b5d088..88ce5d55907 100644 --- a/src/game/editor/editor_actions.cpp +++ b/src/game/editor/editor_actions.cpp @@ -1534,7 +1534,7 @@ void CEditorActionEditEnvelopePointValue::Apply(bool Undo) } else { - if(pEnvelope->GetChannels() == 4) + if(pEnvelope->GetChannels() == 1 || pEnvelope->GetChannels() == 4) CurrentValue = clamp(CurrentValue, 0.0f, 1.0f); pEnvelope->m_vPoints[m_PtIndex].m_aValues[m_Channel] = f2fx(CurrentValue); From 3e89bbb15e08a045805dd39e250cbaf9e90d42b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 19 Dec 2023 20:31:18 +0100 Subject: [PATCH 144/198] Clear console selection only when scroll position is changed The selection is only cleared because it would be incorrect after scrolling, but it doesn't need to be cleared if the scroll position does not change, e.g. when pressing Home while already at the top of the backlog. --- src/game/client/components/console.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index c468481be91..62d5ed99811 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -278,6 +278,7 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) } }; + const int BacklogPrevLine = m_BacklogCurLine; if(Event.m_Flags & IInput::FLAG_PRESS) { if(Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER) @@ -410,24 +411,19 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) else if(Event.m_Key == KEY_PAGEUP) { m_BacklogCurLine += GetLinesToScroll(-1, m_LinesRendered); - m_HasSelection = false; } else if(Event.m_Key == KEY_PAGEDOWN) { - m_HasSelection = false; m_BacklogCurLine -= GetLinesToScroll(1, m_LinesRendered); - if(m_BacklogCurLine < 0) m_BacklogCurLine = 0; } else if(Event.m_Key == KEY_MOUSE_WHEEL_UP) { m_BacklogCurLine += GetLinesToScroll(-1, 1); - m_HasSelection = false; } else if(Event.m_Key == KEY_MOUSE_WHEEL_DOWN) { - m_HasSelection = false; --m_BacklogCurLine; if(m_BacklogCurLine < 0) m_BacklogCurLine = 0; @@ -436,15 +432,12 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) // react to it when the input is empty else if(Event.m_Key == KEY_HOME && m_Input.IsEmpty()) { - int Lines = GetLinesToScroll(-1, -1); - m_BacklogCurLine += Lines; + m_BacklogCurLine += GetLinesToScroll(-1, -1); m_BacklogLastActiveLine = m_BacklogCurLine; - m_HasSelection = false; } else if(Event.m_Key == KEY_END && m_Input.IsEmpty()) { m_BacklogCurLine = 0; - m_HasSelection = false; } else if(Event.m_Key == KEY_F && m_pGameConsole->Input()->ModifierIsPressed() && Event.m_Flags & IInput::FLAG_PRESS) { @@ -455,6 +448,11 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) } } + if(m_BacklogCurLine != BacklogPrevLine) + { + m_HasSelection = false; + } + if(!Handled) { Handled = m_Input.ProcessInput(Event); From 1c6e629a9b0308b09d02aa5ba516b371d17ae4d3 Mon Sep 17 00:00:00 2001 From: furo Date: Wed, 20 Dec 2023 04:10:51 +0100 Subject: [PATCH 145/198] Add missing return statements to `AttemptJoinTeam` --- src/game/server/ddracechat.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index f422657ddba..9443bdeaa41 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -962,11 +962,13 @@ void CGameContext::AttemptJoinTeam(int ClientID, int Team) IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "You are running a vote please try again after the vote is done!"); + return; } else if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "Teams are disabled"); + return; } else if(g_Config.m_SvTeam == SV_TEAM_MANDATORY && Team == 0 && pPlayer->GetCharacter() && pPlayer->GetCharacter()->m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed()) { From 40d56611ba9509e664ce4ea565e93efe38e16585 Mon Sep 17 00:00:00 2001 From: dobrykafe <121701317+dobrykafe@users.noreply.github.com> Date: Wed, 20 Dec 2023 16:09:10 +0100 Subject: [PATCH 146/198] report ddrace team to master --- src/game/server/gamecontext.cpp | 3 ++- src/game/server/gamecontroller.h | 1 + src/game/server/player.cpp | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index cf13e81add5..d2aef77d7e5 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -4645,6 +4645,7 @@ void CGameContext::OnUpdatePlayerServerInfo(char *aBuf, int BufSize, int ID) } } + const int Team = m_pController->IsTeamPlay() ? m_apPlayers[ID]->GetTeam() : m_apPlayers[ID]->GetTeam() == TEAM_SPECTATORS ? -1 : GetDDRaceTeam(ID); str_format(aBuf, BufSize, ",\"skin\":{" "%s" @@ -4653,5 +4654,5 @@ void CGameContext::OnUpdatePlayerServerInfo(char *aBuf, int BufSize, int ID) "\"team\":%d", aJsonSkin, JsonBool(m_apPlayers[ID]->IsAfk()), - m_apPlayers[ID]->GetTeam()); + Team); } diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h index a3044d151a2..c381c326572 100644 --- a/src/game/server/gamecontroller.h +++ b/src/game/server/gamecontroller.h @@ -151,6 +151,7 @@ class IGameController CClientMask GetMaskForPlayerWorldEvent(int Asker, int ExceptID = -1); virtual void InitTeleporter(); + bool IsTeamPlay() { return m_GameFlags & GAMEFLAG_TEAMS; } // DDRace float m_CurrentRecord; diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 11dab99af65..64a53722a04 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -626,6 +626,11 @@ void CPlayer::SetTeam(int Team, bool DoChatMsg) pPlayer->m_SpectatorID = SPEC_FREEVIEW; } } + + if(!GameServer()->m_pController->IsTeamPlay()) + { + Server()->ExpireServerInfo(); + } } bool CPlayer::SetTimerType(int TimerType) From d22806436175b9e6da144106f5ba9828fe1da4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 19 Dec 2023 22:10:44 +0100 Subject: [PATCH 147/198] Fix console selection not adjusted anymore when entries added The current mouse-based console selection was not being adjusted anymore when new lines are added to the console, as the `m_NewLineCounter` variable was decremented to `0` before the relevant check for `m_NewLineCounter > 0`. --- src/game/client/components/console.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 62d5ed99811..a0009c83507 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -1054,8 +1054,17 @@ void CGameConsole::OnRender() pConsole->PumpBacklogPending(); if(pConsole->m_NewLineCounter > 0) + { pConsole->UpdateSearch(); + // keep scroll position when new entries are printed. + if(pConsole->m_BacklogCurLine != 0) + { + pConsole->m_BacklogCurLine += pConsole->m_NewLineCounter; + pConsole->m_BacklogLastActiveLine += pConsole->m_NewLineCounter; + } + } + // render console log (current entry, status, wrap lines) CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.Last(); float OffsetY = 0.0f; @@ -1085,17 +1094,6 @@ void CGameConsole::OnRender() pConsole->UpdateEntryTextAttributes(pEntry); LineNum += pEntry->m_LineCount; - while(pConsole->m_NewLineCounter > 0) - { - --pConsole->m_NewLineCounter; - - // keep scroll position when new entries are printed. - if(pConsole->m_BacklogCurLine != 0) - { - pConsole->m_BacklogCurLine++; - pConsole->m_BacklogLastActiveLine++; - } - } if(LineNum < pConsole->m_BacklogLastActiveLine) { SkippedLines += pEntry->m_LineCount; @@ -1182,6 +1180,10 @@ void CGameConsole::OnRender() // reset color TextRender()->TextColor(TextRender()->DefaultTextColor()); + if(pConsole->m_NewLineCounter > 0) + { + --pConsole->m_NewLineCounter; + } First = false; if(!pEntry) From 949eea08df858f8579045e0220b23c3a85f24eb7 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Thu, 21 Dec 2023 00:22:12 +0100 Subject: [PATCH 148/198] Do not adjust console selection when keeping scroll offset Also decrease `m_NewLineCounter` by the entry line count. --- src/game/client/components/console.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index a0009c83507..0721ebe501c 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -1111,7 +1111,8 @@ void CGameConsole::OnRender() float LocalOffsetY = OffsetY + pEntry->m_YOffset / (float)pEntry->m_LineCount; OffsetY += pEntry->m_YOffset; - if((pConsole->m_HasSelection || pConsole->m_MouseIsPress) && pConsole->m_NewLineCounter > 0) + // Only apply offset if we do not keep scroll position (m_BacklogCurLine == 0) + if((pConsole->m_HasSelection || pConsole->m_MouseIsPress) && pConsole->m_NewLineCounter > 0 && pConsole->m_BacklogCurLine == 0) { float MouseExtraOff = pEntry->m_YOffset; pConsole->m_MousePress.y -= MouseExtraOff; @@ -1176,14 +1177,13 @@ void CGameConsole::OnRender() pConsole->m_HasSelection = true; } + if(pConsole->m_NewLineCounter > 0) // Decrease by the entry line count since we can have multiline entries + pConsole->m_NewLineCounter -= pEntry->m_LineCount; + pEntry = pConsole->m_Backlog.Prev(pEntry); // reset color TextRender()->TextColor(TextRender()->DefaultTextColor()); - if(pConsole->m_NewLineCounter > 0) - { - --pConsole->m_NewLineCounter; - } First = false; if(!pEntry) From 45ced1f49994d531109cbfda897096c6a87de9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 19 Dec 2023 21:39:34 +0100 Subject: [PATCH 149/198] Avoid duplicate calculation in `QuadsTex3DDrawTL` Only calculate `CurIndex` value once outside of loop instead of calculating it every iteration. --- src/engine/client/graphics_threaded.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index a293e4a0fc6..48384a9641e 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -1074,26 +1074,14 @@ void CGraphics_Threaded::QuadsDrawTL(const CQuadItem *pArray, int Num) void CGraphics_Threaded::QuadsTex3DDrawTL(const CQuadItem *pArray, int Num) { - int CurNumVert = m_NumVertices; - - int VertNum = 0; - if(g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad) - { - VertNum = 6; - } - else - { - VertNum = 4; - } + const int VertNum = g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad ? 6 : 4; + const float CurIndex = Uses2DTextureArrays() ? m_CurIndex : (m_CurIndex + 0.5f) / 256.0f; for(int i = 0; i < Num; ++i) { for(int n = 0; n < VertNum; ++n) { - if(Uses2DTextureArrays()) - m_aVerticesTex3D[CurNumVert + VertNum * i + n].m_Tex.w = (float)m_CurIndex; - else - m_aVerticesTex3D[CurNumVert + VertNum * i + n].m_Tex.w = ((float)m_CurIndex + 0.5f) / 256.f; + m_aVerticesTex3D[m_NumVertices + VertNum * i + n].m_Tex.w = CurIndex; } } From a9e7926c29a37cc82a53744a6980e4d3d4bb5ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 22 Dec 2023 00:10:34 +0100 Subject: [PATCH 150/198] Fix map tools crashing on maps with unknown UUID-based map items All `map_*` tools were crashing with the assertion `Invalid type` when used on maps that contain unknown UUID-based map items. When the UUID cannot be resolved, the type `-1` is returned by the `GetItem` function. The assertion predates UUID map items, so this crash has likely existed since the introduction of UUID map items. The editor was not affected and has instead always discarded map items that it does not support. The tools will now also discard map items with unknown UUIDs. See #7669. --- src/tools/map_convert_07.cpp | 7 ++++--- src/tools/map_create_pixelart.cpp | 7 ++++++- src/tools/map_optimize.cpp | 8 +++++--- src/tools/map_replace_area.cpp | 7 ++++++- src/tools/map_replace_image.cpp | 7 +++++-- src/tools/map_resave.cpp | 7 +++++-- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/tools/map_convert_07.cpp b/src/tools/map_convert_07.cpp index ebd6f567871..2794ed0f827 100644 --- a/src/tools/map_convert_07.cpp +++ b/src/tools/map_convert_07.cpp @@ -219,14 +219,15 @@ int main(int argc, const char **argv) { int Type, ID; void *pItem = g_DataReader.GetItem(Index, &Type, &ID); - int Size = g_DataReader.GetItemSize(Index); - // filter ITEMTYPE_EX items, they will be automatically added again - if(Type == ITEMTYPE_EX) + // Filter items with unknown type, as we can't write them back. + // Filter ITEMTYPE_EX items, they will be automatically added again. + if(Type < 0 || Type == ITEMTYPE_EX) { continue; } + int Size = g_DataReader.GetItemSize(Index); Success &= CheckImageDimensions(pItem, Type, pSourceFileName); CMapItemImage NewImageItem; diff --git a/src/tools/map_create_pixelart.cpp b/src/tools/map_create_pixelart.cpp index 52d54d471e1..18d51b5248c 100644 --- a/src/tools/map_create_pixelart.cpp +++ b/src/tools/map_create_pixelart.cpp @@ -377,8 +377,13 @@ void SaveOutputMap(CDataFileReader &InputMap, CDataFileWriter &OutputMap, CMapIt int ID, Type; void *pItem = InputMap.GetItem(i, &Type, &ID); - if(Type == ITEMTYPE_EX) + // Filter items with unknown type, as we can't write them back. + // Filter ITEMTYPE_EX items, they will be automatically added again. + if(Type < 0 || Type == ITEMTYPE_EX) + { continue; + } + if(i == NewItemNumber) pItem = pNewItem; diff --git a/src/tools/map_optimize.cpp b/src/tools/map_optimize.cpp index 2da28b1c092..f08334f57d5 100644 --- a/src/tools/map_optimize.cpp +++ b/src/tools/map_optimize.cpp @@ -140,13 +140,14 @@ int main(int argc, const char **argv) { int Type, ID; void *pPtr = Reader.GetItem(Index, &Type, &ID); - int Size = Reader.GetItemSize(Index); - // filter ITEMTYPE_EX items, they will be automatically added again - if(Type == ITEMTYPE_EX) + // Filter items with unknown type, as we can't write them back. + // Filter ITEMTYPE_EX items, they will be automatically added again. + if(Type < 0 || Type == ITEMTYPE_EX) { continue; } + // for all layers, check if it uses a image and set the corresponding flag if(Type == MAPITEMTYPE_LAYER) { @@ -203,6 +204,7 @@ int main(int argc, const char **argv) ++i; } + int Size = Reader.GetItemSize(Index); Writer.AddItem(Type, ID, Size, pPtr); } diff --git a/src/tools/map_replace_area.cpp b/src/tools/map_replace_area.cpp index f0d4aed3aeb..89187922c37 100644 --- a/src/tools/map_replace_area.cpp +++ b/src/tools/map_replace_area.cpp @@ -168,8 +168,13 @@ void SaveOutputMap(CDataFileReader &InputMap, CDataFileWriter &OutputMap) int ID, Type; void *pItem = InputMap.GetItem(i, &Type, &ID); - if(Type == ITEMTYPE_EX) + // Filter items with unknown type, as we can't write them back. + // Filter ITEMTYPE_EX items, they will be automatically added again. + if(Type < 0 || Type == ITEMTYPE_EX) + { continue; + } + if(g_apNewItem[i]) pItem = g_apNewItem[i]; diff --git a/src/tools/map_replace_image.cpp b/src/tools/map_replace_image.cpp index b67ce18aede..01aef691a49 100644 --- a/src/tools/map_replace_image.cpp +++ b/src/tools/map_replace_image.cpp @@ -150,9 +150,12 @@ int main(int argc, const char **argv) int Type, ID; void *pItem = g_DataReader.GetItem(Index, &Type, &ID); - // filter ITEMTYPE_EX items, they will be automatically added again - if(Type == ITEMTYPE_EX) + // Filter items with unknown type, as we can't write them back. + // Filter ITEMTYPE_EX items, they will be automatically added again. + if(Type < 0 || Type == ITEMTYPE_EX) + { continue; + } int Size = g_DataReader.GetItemSize(Index); diff --git a/src/tools/map_resave.cpp b/src/tools/map_resave.cpp index 5ff2090504d..2060851984c 100644 --- a/src/tools/map_resave.cpp +++ b/src/tools/map_resave.cpp @@ -32,9 +32,12 @@ static int ResaveMap(const char *pSourceMap, const char *pDestinationMap, IStora int Type, ID; const void *pPtr = Reader.GetItem(Index, &Type, &ID); - // filter ITEMTYPE_EX items, they will be automatically added again - if(Type == ITEMTYPE_EX) + // Filter items with unknown type, as we can't write them back. + // Filter ITEMTYPE_EX items, they will be automatically added again. + if(Type < 0 || Type == ITEMTYPE_EX) + { continue; + } int Size = Reader.GetItemSize(Index); Writer.AddItem(Type, ID, Size, pPtr); From 34a26daeba7df0a02f3f4799ec58df2cd175abd3 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Fri, 22 Dec 2023 18:15:37 +0100 Subject: [PATCH 151/198] Fix wrong value type for some map settings in `gamecontext.cpp` Fix some help texts starting with a lowercase character --- src/game/server/gamecontext.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index c8356790886..68075880d89 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -3486,15 +3486,15 @@ void CGameContext::OnConsoleInit() m_pEngine = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); - Console()->Register("tune", "s[tuning] ?i[value]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneParam, this, "Tune variable to value or show current value"); - Console()->Register("toggle_tune", "s[tuning] i[value 1] i[value 2]", CFGFLAG_SERVER | CFGFLAG_GAME, ConToggleTuneParam, this, "Toggle tune variable"); + Console()->Register("tune", "s[tuning] ?f[value]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneParam, this, "Tune variable to value or show current value"); + Console()->Register("toggle_tune", "s[tuning] f[value 1] f[value 2]", CFGFLAG_SERVER, ConToggleTuneParam, this, "Toggle tune variable"); Console()->Register("tune_reset", "?s[tuning]", CFGFLAG_SERVER, ConTuneReset, this, "Reset all or one tuning variable to default"); Console()->Register("tunes", "", CFGFLAG_SERVER, ConTunes, this, "List all tuning variables and their values"); - Console()->Register("tune_zone", "i[zone] s[tuning] i[value]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneZone, this, "Tune in zone a variable to value"); + Console()->Register("tune_zone", "i[zone] s[tuning] f[value]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneZone, this, "Tune in zone a variable to value"); Console()->Register("tune_zone_dump", "i[zone]", CFGFLAG_SERVER, ConTuneDumpZone, this, "Dump zone tuning in zone x"); - Console()->Register("tune_zone_reset", "?i[zone]", CFGFLAG_SERVER, ConTuneResetZone, this, "reset zone tuning in zone x or in all zones"); - Console()->Register("tune_zone_enter", "i[zone] r[message]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneSetZoneMsgEnter, this, "which message to display on zone enter; use 0 for normal area"); - Console()->Register("tune_zone_leave", "i[zone] r[message]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneSetZoneMsgLeave, this, "which message to display on zone leave; use 0 for normal area"); + Console()->Register("tune_zone_reset", "?i[zone]", CFGFLAG_SERVER, ConTuneResetZone, this, "Reset zone tuning in zone x or in all zones"); + Console()->Register("tune_zone_enter", "i[zone] r[message]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneSetZoneMsgEnter, this, "Which message to display on zone enter; use 0 for normal area"); + Console()->Register("tune_zone_leave", "i[zone] r[message]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneSetZoneMsgLeave, this, "Which message to display on zone leave; use 0 for normal area"); Console()->Register("mapbug", "s[mapbug]", CFGFLAG_SERVER | CFGFLAG_GAME, ConMapbug, this, "Enable map compatibility mode using the specified bug (example: grenade-doubleexplosion@ddnet.tw)"); Console()->Register("switch_open", "i[switch]", CFGFLAG_SERVER | CFGFLAG_GAME, ConSwitchOpen, this, "Whether a switch is deactivated by default (otherwise activated)"); Console()->Register("pause_game", "", CFGFLAG_SERVER, ConPause, this, "Pause/unpause game"); From 6fc3470a8d3b008daac662c9df2f73284501ff30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 22 Dec 2023 21:53:31 +0100 Subject: [PATCH 152/198] Check for valid favorite skin name, add `CSkin::IsValidName` Favorite skin names were previously not escaped as intended when saving, as the variable `aNameEscaped` was unused so the original skin name was saved instead of the escaped one. Escaping is not really necessary, as skins should not contain `\` and `"` anyway and it was only possible to add such favorites through the console or config files. Instead of escaping the favorite skin names when saving, now favorite skin names are validated when they are added so no escaping is necessary. Skins names are considered valid when they have a length of 1-23 bytes and don't contain the characters `/`, `\` and `"`. --- src/game/client/components/menus_settings.cpp | 13 +++++++++---- src/game/client/skin.h | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 6327b64b580..a737b0d400d 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -430,7 +430,15 @@ void CMenus::Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData) auto *pSelf = (CMenus *)pUserData; if(pResult->NumArguments() >= 1) { - pSelf->m_SkinFavorites.emplace(pResult->GetString(0)); + const char *pStr = pResult->GetString(0); + if(!CSkin::IsValidName(pStr)) + { + char aError[IConsole::CMDLINE_LENGTH + 64]; + str_format(aError, sizeof(aError), "Favorite skin name '%s' is not valid", pStr); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "menus/settings", aError); + return; + } + pSelf->m_SkinFavorites.emplace(pStr); pSelf->m_SkinFavoritesChanged = true; } } @@ -460,9 +468,6 @@ void CMenus::OnConfigSave(IConfigManager *pConfigManager) for(const auto &Entry : m_SkinFavorites) { char aBuffer[256]; - char aNameEscaped[256]; - char *pDst = aNameEscaped; - str_escape(&pDst, Entry.c_str(), aNameEscaped + std::size(aNameEscaped)); str_format(aBuffer, std::size(aBuffer), "add_favorite_skin \"%s\"", Entry.c_str()); pConfigManager->WriteLine(aBuffer); } diff --git a/src/game/client/skin.h b/src/game/client/skin.h index aed057e3c11..3e197c23bba 100644 --- a/src/game/client/skin.h +++ b/src/game/client/skin.h @@ -142,6 +142,23 @@ struct CSkin CSkin &operator=(CSkin &&) = default; const char *GetName() const { return m_aName; } + + static bool IsValidName(const char *pName) + { + if(pName[0] == '\0' || str_length(pName) >= (int)sizeof(CSkin("").m_aName)) + { + return false; + } + + for(int i = 0; pName[i] != '\0'; ++i) + { + if(pName[i] == '"' || pName[i] == '/' || pName[i] == '\\') + { + return false; + } + } + return true; + } }; #endif From 32b32d70521ae330c9bf8b807928f476e0e3ae40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 22 Dec 2023 22:07:27 +0100 Subject: [PATCH 153/198] Remove unnecessary checks for number of command arguments The argument of `add_favorite_skin` and `remove_favorite_skin` is mandatory, so the callback will never be called without it. --- src/game/client/components/menus_settings.cpp | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index a737b0d400d..0bb0f4778ee 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -428,32 +428,26 @@ void CMenus::RandomSkin() void CMenus::Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData) { auto *pSelf = (CMenus *)pUserData; - if(pResult->NumArguments() >= 1) + const char *pStr = pResult->GetString(0); + if(!CSkin::IsValidName(pStr)) { - const char *pStr = pResult->GetString(0); - if(!CSkin::IsValidName(pStr)) - { - char aError[IConsole::CMDLINE_LENGTH + 64]; - str_format(aError, sizeof(aError), "Favorite skin name '%s' is not valid", pStr); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "menus/settings", aError); - return; - } - pSelf->m_SkinFavorites.emplace(pStr); - pSelf->m_SkinFavoritesChanged = true; + char aError[IConsole::CMDLINE_LENGTH + 64]; + str_format(aError, sizeof(aError), "Favorite skin name '%s' is not valid", pStr); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "menus/settings", aError); + return; } + pSelf->m_SkinFavorites.emplace(pStr); + pSelf->m_SkinFavoritesChanged = true; } void CMenus::Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData) { auto *pSelf = (CMenus *)pUserData; - if(pResult->NumArguments() >= 1) + const auto it = pSelf->m_SkinFavorites.find(pResult->GetString(0)); + if(it != pSelf->m_SkinFavorites.end()) { - const auto it = pSelf->m_SkinFavorites.find(pResult->GetString(0)); - if(it != pSelf->m_SkinFavorites.end()) - { - pSelf->m_SkinFavorites.erase(it); - pSelf->m_SkinFavoritesChanged = true; - } + pSelf->m_SkinFavorites.erase(it); + pSelf->m_SkinFavoritesChanged = true; } } From fa0b218eedf4f36dde4dd412ca3141796a5589b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 22 Dec 2023 22:13:17 +0100 Subject: [PATCH 154/198] Use `str_copy` instead of `mem_copy` for skin names The `mem_copy` function does not respect zero termination so it reads beyond the size of the source buffer, if it's smaller than the destination buffer. --- src/game/client/components/menus_settings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 0bb0f4778ee..6767157d972 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -418,7 +418,7 @@ void CMenus::RandomSkin() unsigned *pColorBody = !m_Dummy ? &g_Config.m_ClPlayerColorBody : &g_Config.m_ClDummyColorBody; unsigned *pColorFeet = !m_Dummy ? &g_Config.m_ClPlayerColorFeet : &g_Config.m_ClDummyColorFeet; - mem_copy(pSkinName, pRandomSkinName, sizeof(g_Config.m_ClPlayerSkin)); + str_copy(pSkinName, pRandomSkinName, sizeof(g_Config.m_ClPlayerSkin)); *pColorBody = Body.Pack(false); *pColorFeet = Feet.Pack(false); @@ -862,7 +862,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) const int NewSelected = s_ListBox.DoEnd(); if(OldSelected != NewSelected) { - mem_copy(pSkinName, s_vSkinList[NewSelected].m_pSkin->GetName(), sizeof(g_Config.m_ClPlayerSkin)); + str_copy(pSkinName, s_vSkinList[NewSelected].m_pSkin->GetName(), sizeof(g_Config.m_ClPlayerSkin)); SetNeedSendInfo(); } From 5599de315383df712151d1d3b1a1f072b51f910a Mon Sep 17 00:00:00 2001 From: Corantin H Date: Sat, 23 Dec 2023 14:45:36 +0100 Subject: [PATCH 155/198] Use `CEditorComponent` methods instead of only `CMapView` Small changes to `CEditorObject` --- src/game/editor/editor.cpp | 14 ++++++++++++-- src/game/editor/editor_object.cpp | 3 +-- src/game/editor/editor_object.h | 4 ++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index a14a10536df..28624935740 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -8164,6 +8164,9 @@ void CEditor::Render() MapView()->Zoom()->ChangeValue(-20.0f); } + for(CEditorComponent &Component : m_vComponents) + Component.OnRender(View); + MapView()->UpdateZoom(); UI()->RenderPopupMenus(); @@ -8259,7 +8262,9 @@ void CEditor::Reset(bool CreateDefault) UI()->ClosePopupMenus(); m_Map.Clean(); - m_MapView.OnReset(); + for(CEditorComponent &Component : m_vComponents) + Component.OnReset(); + // create default layers if(CreateDefault) { @@ -8633,6 +8638,9 @@ void CEditor::OnUpdate() DispatchInputEvents(); HandleAutosave(); HandleWriterFinishJobs(); + + for(CEditorComponent &Component : m_vComponents) + Component.OnUpdate(); } void CEditor::OnRender() @@ -8760,7 +8768,9 @@ bool CEditor::Load(const char *pFileName, int StorageType) str_copy(m_aFileName, pFileName); SortImages(); SelectGameLayer(); - MapView()->OnMapLoad(); + + for(CEditorComponent &Component : m_vComponents) + Component.OnMapLoad(); } else { diff --git a/src/game/editor/editor_object.cpp b/src/game/editor/editor_object.cpp index 2154c3d10ac..0e182fa80f8 100644 --- a/src/game/editor/editor_object.cpp +++ b/src/game/editor/editor_object.cpp @@ -8,9 +8,8 @@ void CEditorObject::Init(CEditor *pEditor) OnReset(); } -void CEditorObject::OnUpdate(CUIRect View) +void CEditorObject::OnUpdate() { - OnRender(View); if(IsActive()) OnActive(); else if(IsHot()) diff --git a/src/game/editor/editor_object.h b/src/game/editor/editor_object.h index 87ec1df4e71..d0715091f86 100644 --- a/src/game/editor/editor_object.h +++ b/src/game/editor/editor_object.h @@ -31,9 +31,9 @@ class CEditorObject virtual void Init(CEditor *pEditor); /** - * Calls `OnRender` and then maybe `OnHot` or `OnActive`. + * Maybe calls `OnHot` or `OnActive`. */ - void OnUpdate(CUIRect View); + virtual void OnUpdate(); /** * Gets called before `OnRender`. Should return true From c31f82a9e946f7bef29ed807063bee0f9110df2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 31 Mar 2023 22:31:54 +0200 Subject: [PATCH 156/198] Refactor graphics `SetColor` functions Reduce duplicate code. Replace `clampf` function with `NormalizeColorComponent` function that convert color component from `float` to `unsigned char`. Use `size_t` instead of `int`. --- src/engine/client/graphics_threaded.cpp | 130 ++++++------------------ src/engine/client/graphics_threaded.h | 4 +- src/engine/graphics.h | 6 +- 3 files changed, 38 insertions(+), 102 deletions(-) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index 48384a9641e..dacd8803246 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -891,49 +891,36 @@ void CGraphics_Threaded::QuadsSetRotation(float Angle) m_Rotation = Angle; } -inline void clampf(float &Value, float Min, float Max) +static unsigned char NormalizeColorComponent(float ColorComponent) { - if(Value > Max) - Value = Max; - else if(Value < Min) - Value = Min; + return (unsigned char)(clamp(ColorComponent, 0.0f, 1.0f) * 255.0f); } -void CGraphics_Threaded::SetColorVertex(const CColorVertex *pArray, int Num) +void CGraphics_Threaded::SetColorVertex(const CColorVertex *pArray, size_t Num) { dbg_assert(m_Drawing != 0, "called Graphics()->SetColorVertex without begin"); - for(int i = 0; i < Num; ++i) + for(size_t i = 0; i < Num; ++i) { - float r = pArray[i].m_R, g = pArray[i].m_G, b = pArray[i].m_B, a = pArray[i].m_A; - clampf(r, 0.f, 1.f); - clampf(g, 0.f, 1.f); - clampf(b, 0.f, 1.f); - clampf(a, 0.f, 1.f); - m_aColor[pArray[i].m_Index].r = (unsigned char)(r * 255.f); - m_aColor[pArray[i].m_Index].g = (unsigned char)(g * 255.f); - m_aColor[pArray[i].m_Index].b = (unsigned char)(b * 255.f); - m_aColor[pArray[i].m_Index].a = (unsigned char)(a * 255.f); + const CColorVertex &Vertex = pArray[i]; + CCommandBuffer::SColor &Color = m_aColor[Vertex.m_Index]; + Color.r = NormalizeColorComponent(Vertex.m_R); + Color.g = NormalizeColorComponent(Vertex.m_G); + Color.b = NormalizeColorComponent(Vertex.m_B); + Color.a = NormalizeColorComponent(Vertex.m_A); } } void CGraphics_Threaded::SetColor(float r, float g, float b, float a) { - clampf(r, 0.f, 1.f); - clampf(g, 0.f, 1.f); - clampf(b, 0.f, 1.f); - clampf(a, 0.f, 1.f); - r *= 255.f; - g *= 255.f; - b *= 255.f; - a *= 255.f; - - for(auto &Color : m_aColor) + CCommandBuffer::SColor NewColor; + NewColor.r = NormalizeColorComponent(r); + NewColor.g = NormalizeColorComponent(g); + NewColor.b = NormalizeColorComponent(b); + NewColor.a = NormalizeColorComponent(a); + for(CCommandBuffer::SColor &Color : m_aColor) { - Color.r = (unsigned char)(r); - Color.g = (unsigned char)(g); - Color.b = (unsigned char)(b); - Color.a = (unsigned char)(a); + Color = NewColor; } } @@ -944,25 +931,20 @@ void CGraphics_Threaded::SetColor(ColorRGBA Color) void CGraphics_Threaded::SetColor4(ColorRGBA TopLeft, ColorRGBA TopRight, ColorRGBA BottomLeft, ColorRGBA BottomRight) { - dbg_assert(m_Drawing != 0, "called Graphics()->SetColor without begin"); - CColorVertex Array[4] = { - CColorVertex(0, TopLeft.r, TopLeft.g, TopLeft.b, TopLeft.a), - CColorVertex(1, TopRight.r, TopRight.g, TopRight.b, TopRight.a), - CColorVertex(2, BottomRight.r, BottomRight.g, BottomRight.b, BottomRight.a), - CColorVertex(3, BottomLeft.r, BottomLeft.g, BottomLeft.b, BottomLeft.a)}; - SetColorVertex(Array, 4); + CColorVertex aArray[] = { + CColorVertex(0, TopLeft), + CColorVertex(1, TopRight), + CColorVertex(2, BottomRight), + CColorVertex(3, BottomLeft)}; + SetColorVertex(aArray, std::size(aArray)); } void CGraphics_Threaded::ChangeColorOfCurrentQuadVertices(float r, float g, float b, float a) { - clampf(r, 0.f, 1.f); - clampf(g, 0.f, 1.f); - clampf(b, 0.f, 1.f); - clampf(a, 0.f, 1.f); - m_aColor[0].r = (unsigned char)(r * 255.f); - m_aColor[0].g = (unsigned char)(g * 255.f); - m_aColor[0].b = (unsigned char)(b * 255.f); - m_aColor[0].a = (unsigned char)(a * 255.f); + m_aColor[0].r = NormalizeColorComponent(r); + m_aColor[0].g = NormalizeColorComponent(g); + m_aColor[0].b = NormalizeColorComponent(b); + m_aColor[0].a = NormalizeColorComponent(a); for(int i = 0; i < m_NumVertices; ++i) { @@ -970,61 +952,13 @@ void CGraphics_Threaded::ChangeColorOfCurrentQuadVertices(float r, float g, floa } } -void CGraphics_Threaded::ChangeColorOfQuadVertices(int QuadOffset, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +void CGraphics_Threaded::ChangeColorOfQuadVertices(size_t QuadOffset, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { - if(g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad) - { - m_aVertices[QuadOffset * 6].m_Color.r = r; - m_aVertices[QuadOffset * 6].m_Color.g = g; - m_aVertices[QuadOffset * 6].m_Color.b = b; - m_aVertices[QuadOffset * 6].m_Color.a = a; - - m_aVertices[QuadOffset * 6 + 1].m_Color.r = r; - m_aVertices[QuadOffset * 6 + 1].m_Color.g = g; - m_aVertices[QuadOffset * 6 + 1].m_Color.b = b; - m_aVertices[QuadOffset * 6 + 1].m_Color.a = a; - - m_aVertices[QuadOffset * 6 + 2].m_Color.r = r; - m_aVertices[QuadOffset * 6 + 2].m_Color.g = g; - m_aVertices[QuadOffset * 6 + 2].m_Color.b = b; - m_aVertices[QuadOffset * 6 + 2].m_Color.a = a; - - m_aVertices[QuadOffset * 6 + 3].m_Color.r = r; - m_aVertices[QuadOffset * 6 + 3].m_Color.g = g; - m_aVertices[QuadOffset * 6 + 3].m_Color.b = b; - m_aVertices[QuadOffset * 6 + 3].m_Color.a = a; - - m_aVertices[QuadOffset * 6 + 4].m_Color.r = r; - m_aVertices[QuadOffset * 6 + 4].m_Color.g = g; - m_aVertices[QuadOffset * 6 + 4].m_Color.b = b; - m_aVertices[QuadOffset * 6 + 4].m_Color.a = a; - - m_aVertices[QuadOffset * 6 + 5].m_Color.r = r; - m_aVertices[QuadOffset * 6 + 5].m_Color.g = g; - m_aVertices[QuadOffset * 6 + 5].m_Color.b = b; - m_aVertices[QuadOffset * 6 + 5].m_Color.a = a; - } - else + const CCommandBuffer::SColor Color(r, g, b, a); + const size_t VertNum = g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad ? 6 : 4; + for(size_t i = 0; i < VertNum; ++i) { - m_aVertices[QuadOffset * 4].m_Color.r = r; - m_aVertices[QuadOffset * 4].m_Color.g = g; - m_aVertices[QuadOffset * 4].m_Color.b = b; - m_aVertices[QuadOffset * 4].m_Color.a = a; - - m_aVertices[QuadOffset * 4 + 1].m_Color.r = r; - m_aVertices[QuadOffset * 4 + 1].m_Color.g = g; - m_aVertices[QuadOffset * 4 + 1].m_Color.b = b; - m_aVertices[QuadOffset * 4 + 1].m_Color.a = a; - - m_aVertices[QuadOffset * 4 + 2].m_Color.r = r; - m_aVertices[QuadOffset * 4 + 2].m_Color.g = g; - m_aVertices[QuadOffset * 4 + 2].m_Color.b = b; - m_aVertices[QuadOffset * 4 + 2].m_Color.a = a; - - m_aVertices[QuadOffset * 4 + 3].m_Color.r = r; - m_aVertices[QuadOffset * 4 + 3].m_Color.g = g; - m_aVertices[QuadOffset * 4 + 3].m_Color.b = b; - m_aVertices[QuadOffset * 4 + 3].m_Color.a = a; + m_aVertices[QuadOffset * VertNum + i].m_Color = Color; } } diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h index eaf0570d66e..99892a4952a 100644 --- a/src/engine/client/graphics_threaded.h +++ b/src/engine/client/graphics_threaded.h @@ -998,14 +998,14 @@ class CGraphics_Threaded : public IEngineGraphics pVert->m_Color = m_aColor[ColorIndex]; } - void SetColorVertex(const CColorVertex *pArray, int Num) override; + void SetColorVertex(const CColorVertex *pArray, size_t Num) override; void SetColor(float r, float g, float b, float a) override; void SetColor(ColorRGBA Color) override; void SetColor4(ColorRGBA TopLeft, ColorRGBA TopRight, ColorRGBA BottomLeft, ColorRGBA BottomRight) override; // go through all vertices and change their color (only works for quads) void ChangeColorOfCurrentQuadVertices(float r, float g, float b, float a) override; - void ChangeColorOfQuadVertices(int QuadOffset, unsigned char r, unsigned char g, unsigned char b, unsigned char a) override; + void ChangeColorOfQuadVertices(size_t QuadOffset, unsigned char r, unsigned char g, unsigned char b, unsigned char a) override; void QuadsSetSubset(float TlU, float TlV, float BrU, float BrV) override; void QuadsSetSubsetFree( diff --git a/src/engine/graphics.h b/src/engine/graphics.h index 951748b1170..86d5fc9a21f 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -503,13 +503,15 @@ class IGraphics : public IInterface CColorVertex() {} CColorVertex(int i, float r, float g, float b, float a) : m_Index(i), m_R(r), m_G(g), m_B(b), m_A(a) {} + CColorVertex(int i, ColorRGBA Color) : + m_Index(i), m_R(Color.r), m_G(Color.g), m_B(Color.b), m_A(Color.a) {} }; - virtual void SetColorVertex(const CColorVertex *pArray, int Num) = 0; + virtual void SetColorVertex(const CColorVertex *pArray, size_t Num) = 0; virtual void SetColor(float r, float g, float b, float a) = 0; virtual void SetColor(ColorRGBA Color) = 0; virtual void SetColor4(ColorRGBA TopLeft, ColorRGBA TopRight, ColorRGBA BottomLeft, ColorRGBA BottomRight) = 0; virtual void ChangeColorOfCurrentQuadVertices(float r, float g, float b, float a) = 0; - virtual void ChangeColorOfQuadVertices(int QuadOffset, unsigned char r, unsigned char g, unsigned char b, unsigned char a) = 0; + virtual void ChangeColorOfQuadVertices(size_t QuadOffset, unsigned char r, unsigned char g, unsigned char b, unsigned char a) = 0; virtual void TakeScreenshot(const char *pFilename) = 0; virtual void TakeCustomScreenshot(const char *pFilename) = 0; From 76fa62d8fb3bfee20926e87680641b59b5bdd0fc Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 25 Dec 2023 01:17:36 +0100 Subject: [PATCH 157/198] Revert removal of "Detail" from sound layers. --- src/game/client/components/mapsounds.cpp | 3 ++- src/game/client/components/mapsounds.h | 1 + src/game/editor/popups.cpp | 6 ------ 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/game/client/components/mapsounds.cpp b/src/game/client/components/mapsounds.cpp index 9f7c0b93e3b..df47cb4f0ce 100644 --- a/src/game/client/components/mapsounds.cpp +++ b/src/game/client/components/mapsounds.cpp @@ -98,6 +98,7 @@ void CMapSounds::OnMapLoad() CSourceQueueEntry Source; Source.m_Sound = pSoundLayer->m_Sound; Source.m_pSource = &pSources[i]; + Source.m_HighDetail = pLayer->m_Flags & LAYERFLAG_DETAIL; if(!Source.m_pSource || Source.m_Sound < 0 || Source.m_Sound >= m_Count) continue; @@ -127,7 +128,7 @@ void CMapSounds::OnRender() Client()->IntraGameTick(g_Config.m_ClDummy)); } float Offset = s_Time - Source.m_pSource->m_TimeDelay; - if(!DemoPlayerPaused && Offset >= 0.0f && g_Config.m_SndEnable) + if(!DemoPlayerPaused && Offset >= 0.0f && g_Config.m_SndEnable && (g_Config.m_GfxHighDetail || !Source.m_HighDetail)) { if(Source.m_Voice.IsValid()) { diff --git a/src/game/client/components/mapsounds.h b/src/game/client/components/mapsounds.h index afdd7353fb6..455326434b3 100644 --- a/src/game/client/components/mapsounds.h +++ b/src/game/client/components/mapsounds.h @@ -18,6 +18,7 @@ class CMapSounds : public CComponent struct CSourceQueueEntry { int m_Sound; + bool m_HighDetail; ISound::CVoiceHandle m_Voice; CSoundSource *m_pSource; diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index e9bcb721c47..2fdd634cbe1 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -768,12 +768,6 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, aProps[2].m_Type = PROPTYPE_NULL; } - // don't use Detail from the selection if this is a sound layer - if(pCurrentLayer->m_Type == LAYERTYPE_SOUNDS) - { - aProps[2].m_Type = PROPTYPE_NULL; - } - static int s_aIds[(int)ELayerProp::NUM_PROPS] = {0}; int NewVal = 0; auto [State, Prop] = pEditor->DoPropertiesWithState(&View, aProps, s_aIds, &NewVal); From 1eada0a8baef7934268dec0621aeda25a863dd2a Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 25 Dec 2023 12:02:18 +0100 Subject: [PATCH 158/198] Support `Ctrl + Shift + Z` as editor redo hotkey. --- src/game/editor/editor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index a14a10536df..6e59d9bfd35 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -7949,9 +7949,9 @@ void CEditor::Render() if(m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr) { // handle undo/redo hotkeys - if(Input()->KeyPress(KEY_Z) && Input()->ModifierIsPressed()) + if(Input()->KeyPress(KEY_Z) && Input()->ModifierIsPressed() && !Input()->ShiftIsPressed()) UndoLastAction(); - if(Input()->KeyPress(KEY_Y) && Input()->ModifierIsPressed()) + if((Input()->KeyPress(KEY_Y) && Input()->ModifierIsPressed()) || (Input()->KeyPress(KEY_Z) && Input()->ModifierIsPressed() && Input()->ShiftIsPressed())) RedoLastAction(); // handle brush save/load hotkeys From f7c618d8b1ef7a0cc2ac3c2f4f3c9fc0060f390a Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 25 Dec 2023 14:58:18 +0100 Subject: [PATCH 159/198] Start recording of server auto demo on init. --- src/game/server/gamecontext.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 68075880d89..8bb560595b3 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -3734,6 +3734,8 @@ void CGameContext::OnInit(const void *pPersistentData) } } + Server()->DemoRecorder_HandleAutoStart(); + if(!m_pScore) { m_pScore = new CScore(this, ((CServer *)Server())->DbPool()); From 1c098c294124bdfdc982c8b3978f2bfc7664b4f3 Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 25 Dec 2023 15:02:10 +0100 Subject: [PATCH 160/198] Don't use the same demo recorder for auto and manual demos. --- src/engine/server/server.cpp | 30 ++++++++++++++++++------------ src/engine/server/server.h | 9 ++++++++- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 753faf03b7a..475456c416c 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -315,7 +315,8 @@ CServer::CServer() m_pConfig = &g_Config; for(int i = 0; i < MAX_CLIENTS; i++) m_aDemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta, true); - m_aDemoRecorder[MAX_CLIENTS] = CDemoRecorder(&m_SnapshotDelta, false); + m_aDemoRecorder[RECORDER_MANUAL] = CDemoRecorder(&m_SnapshotDelta, false); + m_aDemoRecorder[RECORDER_AUTO] = CDemoRecorder(&m_SnapshotDelta, false); m_pGameServer = 0; @@ -928,8 +929,10 @@ int CServer::SendMsg(CMsgPacker *pMsg, int Flags, int ClientID) { if(m_aDemoRecorder[ClientID].IsRecording()) m_aDemoRecorder[ClientID].RecordMessage(Pack.Data(), Pack.Size()); - if(m_aDemoRecorder[MAX_CLIENTS].IsRecording()) - m_aDemoRecorder[MAX_CLIENTS].RecordMessage(Pack.Data(), Pack.Size()); + if(m_aDemoRecorder[RECORDER_MANUAL].IsRecording()) + m_aDemoRecorder[RECORDER_MANUAL].RecordMessage(Pack.Data(), Pack.Size()); + if(m_aDemoRecorder[RECORDER_AUTO].IsRecording()) + m_aDemoRecorder[RECORDER_AUTO].RecordMessage(Pack.Data(), Pack.Size()); } if(!(Flags & MSGFLAG_NOSEND)) @@ -962,9 +965,9 @@ void CServer::DoSnapshot() { GameServer()->OnPreSnap(); - // create snapshot for demo recording - if(m_aDemoRecorder[MAX_CLIENTS].IsRecording()) + if(m_aDemoRecorder[RECORDER_MANUAL].IsRecording() || m_aDemoRecorder[RECORDER_AUTO].IsRecording()) { + // create snapshot for demo recording char aData[CSnapshot::MAX_SIZE]; // build snap and possibly add some messages @@ -973,7 +976,10 @@ void CServer::DoSnapshot() int SnapshotSize = m_SnapshotBuilder.Finish(aData); // write snapshot - m_aDemoRecorder[MAX_CLIENTS].RecordSnapshot(Tick(), aData, SnapshotSize); + if(m_aDemoRecorder[RECORDER_MANUAL].IsRecording()) + m_aDemoRecorder[RECORDER_MANUAL].RecordSnapshot(Tick(), aData, SnapshotSize); + if(m_aDemoRecorder[RECORDER_AUTO].IsRecording()) + m_aDemoRecorder[RECORDER_AUTO].RecordSnapshot(Tick(), aData, SnapshotSize); } // create snapshots for all clients @@ -3393,12 +3399,12 @@ void CServer::DemoRecorder_HandleAutoStart() { if(Config()->m_SvAutoDemoRecord) { - m_aDemoRecorder[MAX_CLIENTS].Stop(); + m_aDemoRecorder[RECORDER_AUTO].Stop(); char aFilename[IO_MAX_PATH_LENGTH]; char aDate[20]; str_timestamp(aDate, sizeof(aDate)); str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", "auto/autorecord", aDate); - m_aDemoRecorder[MAX_CLIENTS].Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_aCurrentMapSha256[MAP_TYPE_SIX], m_aCurrentMapCrc[MAP_TYPE_SIX], "server", m_aCurrentMapSize[MAP_TYPE_SIX], m_apCurrentMapData[MAP_TYPE_SIX]); + m_aDemoRecorder[RECORDER_AUTO].Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_aCurrentMapSha256[MAP_TYPE_SIX], m_aCurrentMapCrc[MAP_TYPE_SIX], "server", m_aCurrentMapSize[MAP_TYPE_SIX], m_apCurrentMapData[MAP_TYPE_SIX]); if(Config()->m_SvAutoDemoMax) { // clean up auto recorded demos @@ -3452,7 +3458,7 @@ bool CServer::IsRecording(int ClientID) void CServer::StopDemos() { - for(int i = 0; i < MAX_CLIENTS + 1; i++) + for(int i = 0; i < NUM_RECORDERS; i++) { if(!m_aDemoRecorder[i].IsRecording()) continue; @@ -3474,7 +3480,7 @@ void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) CServer *pServer = (CServer *)pUser; char aFilename[IO_MAX_PATH_LENGTH]; - if(pServer->IsRecording(MAX_CLIENTS)) + if(pServer->IsRecording(RECORDER_MANUAL)) { pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Demo recorder already recording"); return; @@ -3488,12 +3494,12 @@ void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) str_timestamp(aDate, sizeof(aDate)); str_format(aFilename, sizeof(aFilename), "demos/demo_%s.demo", aDate); } - pServer->m_aDemoRecorder[MAX_CLIENTS].Start(pServer->Storage(), pServer->Console(), aFilename, pServer->GameServer()->NetVersion(), pServer->m_aCurrentMap, pServer->m_aCurrentMapSha256[MAP_TYPE_SIX], pServer->m_aCurrentMapCrc[MAP_TYPE_SIX], "server", pServer->m_aCurrentMapSize[MAP_TYPE_SIX], pServer->m_apCurrentMapData[MAP_TYPE_SIX]); + pServer->m_aDemoRecorder[RECORDER_MANUAL].Start(pServer->Storage(), pServer->Console(), aFilename, pServer->GameServer()->NetVersion(), pServer->m_aCurrentMap, pServer->m_aCurrentMapSha256[MAP_TYPE_SIX], pServer->m_aCurrentMapCrc[MAP_TYPE_SIX], "server", pServer->m_aCurrentMapSize[MAP_TYPE_SIX], pServer->m_apCurrentMapData[MAP_TYPE_SIX]); } void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser) { - ((CServer *)pUser)->m_aDemoRecorder[MAX_CLIENTS].Stop(); + ((CServer *)pUser)->m_aDemoRecorder[RECORDER_MANUAL].Stop(); } void CServer::ConMapReload(IConsole::IResult *pResult, void *pUser) diff --git a/src/engine/server/server.h b/src/engine/server/server.h index a5dcb34ae10..0819551d7df 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -268,13 +268,20 @@ class CServer : public IServer NUM_MAP_TYPES }; + enum + { + RECORDER_MANUAL = MAX_CLIENTS, + RECORDER_AUTO = MAX_CLIENTS + 1, + NUM_RECORDERS = MAX_CLIENTS + 2, + }; + char m_aCurrentMap[IO_MAX_PATH_LENGTH]; SHA256_DIGEST m_aCurrentMapSha256[NUM_MAP_TYPES]; unsigned m_aCurrentMapCrc[NUM_MAP_TYPES]; unsigned char *m_apCurrentMapData[NUM_MAP_TYPES]; unsigned int m_aCurrentMapSize[NUM_MAP_TYPES]; - CDemoRecorder m_aDemoRecorder[MAX_CLIENTS + 1]; + CDemoRecorder m_aDemoRecorder[NUM_RECORDERS]; CAuthManager m_AuthManager; int64_t m_ServerInfoFirstRequest; From b1f9137e34142e1c7f72e984e232f6aba1b0a709 Mon Sep 17 00:00:00 2001 From: furo Date: Mon, 25 Dec 2023 15:19:12 +0100 Subject: [PATCH 161/198] Change path to `demos/auto/server`. Use same filename format as client. --- src/engine/server/server.cpp | 4 ++-- src/engine/shared/storage.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 475456c416c..113fbe34627 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -3403,13 +3403,13 @@ void CServer::DemoRecorder_HandleAutoStart() char aFilename[IO_MAX_PATH_LENGTH]; char aDate[20]; str_timestamp(aDate, sizeof(aDate)); - str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", "auto/autorecord", aDate); + str_format(aFilename, sizeof(aFilename), "demos/auto/server/%s_%s.demo", m_aCurrentMap, aDate); m_aDemoRecorder[RECORDER_AUTO].Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_aCurrentMapSha256[MAP_TYPE_SIX], m_aCurrentMapCrc[MAP_TYPE_SIX], "server", m_aCurrentMapSize[MAP_TYPE_SIX], m_apCurrentMapData[MAP_TYPE_SIX]); if(Config()->m_SvAutoDemoMax) { // clean up auto recorded demos CFileCollection AutoDemos; - AutoDemos.Init(Storage(), "demos/server", "autorecord", ".demo", Config()->m_SvAutoDemoMax); + AutoDemos.Init(Storage(), "demos/auto/server", "", ".demo", Config()->m_SvAutoDemoMax); } } } diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index 3782830a663..052a43f0c4e 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -109,6 +109,7 @@ class CStorage : public IStorage CreateFolder("demos", TYPE_SAVE); CreateFolder("demos/auto", TYPE_SAVE); CreateFolder("demos/auto/race", TYPE_SAVE); + CreateFolder("demos/auto/server", TYPE_SAVE); CreateFolder("demos/replays", TYPE_SAVE); CreateFolder("editor", TYPE_SAVE); CreateFolder("ghosts", TYPE_SAVE); From 5f6cec20c6849e7dfb04af0f7e8fd31633e2029c Mon Sep 17 00:00:00 2001 From: Corantin H Date: Mon, 25 Dec 2023 18:52:14 +0100 Subject: [PATCH 162/198] Fix infinite console scroll --- src/game/client/components/console.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 0721ebe501c..dce793c414e 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -1190,6 +1190,14 @@ void CGameConsole::OnRender() break; } + // Make sure to reset m_NewLineCounter when we are done drawing + // This is because otherwise, if many entries are printed at once while console is + // hidden, m_NewLineCounter will always be > 0 since the console won't be able to render + // them all, thus wont be able to decrease m_NewLineCounter to 0. + // This leads to an infinite increase of m_BacklogCurLine and m_BacklogLastActiveLine + // when we want to keep scroll position. + pConsole->m_NewLineCounter = 0; + Graphics()->ClipDisable(); pConsole->m_BacklogLastActiveLine = pConsole->m_BacklogCurLine; From 4c223a0002f3cbc4d3c7bf5c170e7bf0621c1a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 11 Dec 2023 22:14:45 +0100 Subject: [PATCH 163/198] Improve text line spacing and console text selection Consider line spacing to belong to the previous line when calculating and rendering text selection. Instead of handling spacing between entries separately in the console, also include line spacing for the last line in the height calculation. Pixel align the line spacing in addition to the font size, as previously some gaps between the entries were larger than others due to missing pixel alignment. This allows rendering the text selection in the console smoothly without any gaps between the console entries/lines. Closes #7617. --- src/engine/client/text.cpp | 49 +++++++++++++------------- src/engine/textrender.h | 3 +- src/game/client/components/console.cpp | 9 +++-- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index 7aa3b666ad1..bf30eac2259 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -1268,7 +1268,9 @@ class CTextRender : public IEngineTextRender pCursor->m_GlyphCount = 0; pCursor->m_CharCount = 0; pCursor->m_MaxLines = 0; + pCursor->m_LineSpacing = 0; + pCursor->m_AlignedLineSpacing = 0; pCursor->m_StartX = x; pCursor->m_StartY = y; @@ -1481,7 +1483,7 @@ class CTextRender : public IEngineTextRender const float CursorY = round_to_int(pCursor->m_Y * FakeToScreen.y) / FakeToScreen.y; const int ActualSize = round_truncate(pCursor->m_FontSize * FakeToScreen.y); pCursor->m_AlignedFontSize = ActualSize / FakeToScreen.y; - const float LineSpacing = pCursor->m_LineSpacing; + pCursor->m_AlignedLineSpacing = round_truncate(pCursor->m_LineSpacing * FakeToScreen.y) / FakeToScreen.y; // string length if(Length < 0) @@ -1539,38 +1541,34 @@ class CTextRender : public IEngineTextRender const auto &&CheckInsideChar = [&](bool CheckOuter, vec2 CursorPos, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool { return (LastCharX - LastCharWidth / 2 <= CursorPos.x && CharX + CharWidth / 2 > CursorPos.x && - CharY - pCursor->m_AlignedFontSize - LineSpacing <= CursorPos.y && - CharY + LineSpacing > CursorPos.y) || + CursorPos.y >= CharY - pCursor->m_AlignedFontSize && + CursorPos.y < CharY + pCursor->m_AlignedLineSpacing) || (CheckOuter && - CharY - pCursor->m_AlignedFontSize + LineSpacing > CursorPos.y); + CursorPos.y <= CharY - pCursor->m_AlignedFontSize); }; const auto &&CheckSelectionStart = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) { - if(!SelectionStarted && !SelectionUsedCase) + if(!SelectionStarted && !SelectionUsedCase && + CheckInsideChar(CheckOuter, CursorPos, LastCharX, LastCharWidth, CharX, CharWidth, CharY)) { - if(CheckInsideChar(CheckOuter, CursorPos, LastCharX, LastCharWidth, CharX, CharWidth, CharY)) - { - SelectionChar = pCursor->m_GlyphCount; - SelectionStarted = !SelectionStarted; - SelectionUsedCase = true; - } + SelectionChar = pCursor->m_GlyphCount; + SelectionStarted = !SelectionStarted; + SelectionUsedCase = true; } }; const auto &&CheckOutsideChar = [&](bool CheckOuter, vec2 CursorPos, float CharX, float CharWidth, float CharY) -> bool { return (CharX + CharWidth / 2 > CursorPos.x && - CharY - pCursor->m_AlignedFontSize - LineSpacing <= CursorPos.y && - CharY + LineSpacing > CursorPos.y) || + CursorPos.y >= CharY - pCursor->m_AlignedFontSize && + CursorPos.y < CharY + pCursor->m_AlignedLineSpacing) || (CheckOuter && - CharY - LineSpacing <= CursorPos.y); + CursorPos.y >= CharY + pCursor->m_AlignedLineSpacing); }; const auto &&CheckSelectionEnd = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) { - if(SelectionStarted && !SelectionUsedCase) + if(SelectionStarted && !SelectionUsedCase && + CheckOutsideChar(CheckOuter, CursorPos, CharX, CharWidth, CharY)) { - if(CheckOutsideChar(CheckOuter, CursorPos, CharX, CharWidth, CharY)) - { - SelectionChar = pCursor->m_GlyphCount; - SelectionStarted = !SelectionStarted; - SelectionUsedCase = true; - } + SelectionChar = pCursor->m_GlyphCount; + SelectionStarted = !SelectionStarted; + SelectionUsedCase = true; } }; @@ -1585,7 +1583,7 @@ class CTextRender : public IEngineTextRender return false; DrawX = pCursor->m_StartX; - DrawY += pCursor->m_AlignedFontSize + pCursor->m_LineSpacing; + DrawY += pCursor->m_AlignedFontSize + pCursor->m_AlignedLineSpacing; if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { DrawX = round_to_int(DrawX * FakeToScreen.x) / FakeToScreen.x; // realign @@ -1868,7 +1866,8 @@ class CTextRender : public IEngineTextRender if(SelectionStarted && IsRendered) { - vSelectionQuads.emplace_back(SelX, DrawY + (1.0f - pCursor->m_SelectionHeightFactor) * pCursor->m_AlignedFontSize, SelWidth, pCursor->m_SelectionHeightFactor * pCursor->m_AlignedFontSize); + const float SelectionHeight = pCursor->m_AlignedFontSize + pCursor->m_AlignedLineSpacing; + vSelectionQuads.emplace_back(SelX, DrawY + (1.0f - pCursor->m_SelectionHeightFactor) * SelectionHeight, SelWidth, pCursor->m_SelectionHeightFactor * SelectionHeight); } LastSelX = SelX; @@ -1957,9 +1956,9 @@ class CTextRender : public IEngineTextRender if(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex == -1) TextContainer.m_StringInfo.m_SelectionQuadContainerIndex = Graphics()->CreateQuadContainer(false); if(HasCursor) - Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, aCursorQuads, 2); + Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, aCursorQuads, std::size(aCursorQuads)); if(HasSelection) - Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, vSelectionQuads.data(), (int)vSelectionQuads.size()); + Graphics()->QuadContainerAddQuads(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex, vSelectionQuads.data(), vSelectionQuads.size()); Graphics()->QuadContainerUpload(TextContainer.m_StringInfo.m_SelectionQuadContainerIndex); TextContainer.m_HasCursor = HasCursor; diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 0d1287c3d4e..41ddc7a8f61 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -212,6 +212,7 @@ class CTextCursor float m_FontSize; float m_AlignedFontSize; float m_LineSpacing; + float m_AlignedLineSpacing; ETextCursorSelectionMode m_CalculateSelectionMode; float m_SelectionHeightFactor; @@ -237,7 +238,7 @@ class CTextCursor float Height() const { - return m_LineCount * m_AlignedFontSize + std::max(0, m_LineCount - 1) * m_LineSpacing; + return m_LineCount * (m_AlignedFontSize + m_AlignedLineSpacing); } STextBoundingBox BoundingBox() const diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 0721ebe501c..6845fe6e43e 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -575,7 +575,7 @@ void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry) c Cursor.m_MaxLines = 10; Cursor.m_LineSpacing = LINE_SPACING; m_pGameConsole->TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); - pEntry->m_YOffset = Cursor.Height() + LINE_SPACING; + pEntry->m_YOffset = Cursor.Height(); pEntry->m_LineCount = Cursor.m_LineCount; } @@ -909,9 +909,9 @@ void CGameConsole::OnRender() { // Get height of 1 line - float LineHeight = TextRender()->TextBoundingBox(FONT_SIZE, " ", -1, -1, LINE_SPACING).m_H + LINE_SPACING; + float LineHeight = TextRender()->TextBoundingBox(FONT_SIZE, " ", -1, -1, LINE_SPACING).m_H; - float RowHeight = FONT_SIZE * 1.25f; + float RowHeight = FONT_SIZE * 1.5f; float x = 3; float y = ConsoleHeight - RowHeight - 5.0f; @@ -1104,8 +1104,7 @@ void CGameConsole::OnRender() if(First) { - int Diff = pConsole->m_BacklogLastActiveLine - SkippedLines; - OffsetY -= Diff * LineHeight - LINE_SPACING; + OffsetY -= (pConsole->m_BacklogLastActiveLine - SkippedLines) * LineHeight; } float LocalOffsetY = OffsetY + pEntry->m_YOffset / (float)pEntry->m_LineCount; From 5b5ead8356f6fb6d6e9e09814be31afabf9e5374 Mon Sep 17 00:00:00 2001 From: furo Date: Tue, 26 Dec 2023 03:28:56 +0100 Subject: [PATCH 164/198] Rework `CFileCollection`. - Use existing functions from `system.h` - Use sorting from `` - Don't recall `ListDirectory` for every removal, which caused the client to hang previously with a lot of files. --- src/engine/shared/filecollection.cpp | 161 ++++----------------------- src/engine/shared/filecollection.h | 22 ++-- 2 files changed, 35 insertions(+), 148 deletions(-) diff --git a/src/engine/shared/filecollection.cpp b/src/engine/shared/filecollection.cpp index 0dab1c258f4..6a31c41a50f 100644 --- a/src/engine/shared/filecollection.cpp +++ b/src/engine/shared/filecollection.cpp @@ -1,8 +1,8 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include -#include #include @@ -10,6 +10,9 @@ bool CFileCollection::IsFilenameValid(const char *pFilename) { + if(!str_endswith(pFilename, m_aFileExt)) + return false; + if(m_aFileDesc[0] == '\0') { int FilenameLength = str_length(pFilename); @@ -23,8 +26,7 @@ bool CFileCollection::IsFilenameValid(const char *pFilename) else { if(str_length(pFilename) != m_FileDescLength + TIMESTAMP_LENGTH + m_FileExtLength || - str_comp_num(pFilename, m_aFileDesc, m_FileDescLength) || - str_comp(pFilename + m_FileDescLength + TIMESTAMP_LENGTH, m_aFileExt)) + !str_startswith(pFilename, m_aFileDesc)) return false; pFilename += m_FileDescLength; @@ -85,51 +87,9 @@ int64_t CFileCollection::ExtractTimestamp(const char *pTimestring) return Timestamp; } -void CFileCollection::BuildTimestring(int64_t Timestamp, char *pTimestring) -{ - pTimestring[19] = 0; - pTimestring[18] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[17] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[16] = '-'; - pTimestring[15] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[14] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[13] = '-'; - pTimestring[12] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[11] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[10] = '_'; - pTimestring[9] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[8] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[7] = '-'; - pTimestring[6] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[5] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[4] = '-'; - pTimestring[3] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[2] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[1] = (Timestamp & 0xF) + '0'; - Timestamp >>= 4; - pTimestring[0] = (Timestamp & 0xF) + '0'; -} - void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries) { - mem_zero(m_aTimestamps, sizeof(m_aTimestamps)); - m_NumTimestamps = 0; - m_Remove = -1; - // MAX_ENTRIES - 1 to make sure that we can insert one entry into the sorted - // list and then remove the oldest one - m_MaxEntries = clamp(MaxEntries, 1, static_cast(MAX_ENTRIES) - 1); + m_vTimestamps.clear(); str_copy(m_aFileDesc, pFileDesc); m_FileDescLength = str_length(m_aFileDesc); str_copy(m_aFileExt, pFileExt); @@ -138,84 +98,28 @@ void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pF m_pStorage = pStorage; m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this); -} + std::sort(m_vTimestamps.begin(), m_vTimestamps.end(), [](const CFileEntry &lhs, const CFileEntry &rhs) { return lhs.m_Timestamp < rhs.m_Timestamp; }); -void CFileCollection::AddEntry(int64_t Timestamp) -{ - if(m_NumTimestamps == 0) - { - // empty list - m_aTimestamps[m_NumTimestamps++] = Timestamp; - } - else + int FilesDeleted = 0; + for(auto FileEntry : m_vTimestamps) { - // add entry to the sorted list - if(Timestamp < m_aTimestamps[0]) - { - // first entry - if(m_NumTimestamps <= m_MaxEntries) - { - mem_move(m_aTimestamps + 1, m_aTimestamps, m_NumTimestamps * sizeof(int64_t)); - m_aTimestamps[0] = Timestamp; - ++m_NumTimestamps; - } - } - else if(Timestamp >= m_aTimestamps[m_NumTimestamps - 1]) + if((int)m_vTimestamps.size() - FilesDeleted <= MaxEntries) + break; + + char aBuf[IO_MAX_PATH_LENGTH]; + if(m_aFileDesc[0] == '\0') { - // last entry - if(m_NumTimestamps > m_MaxEntries) - { - mem_move(m_aTimestamps, m_aTimestamps + 1, (m_NumTimestamps - 1) * sizeof(int64_t)); - m_aTimestamps[m_NumTimestamps - 1] = Timestamp; - } - else - m_aTimestamps[m_NumTimestamps++] = Timestamp; + str_format(aBuf, sizeof(aBuf), "%s/%s", m_aPath, FileEntry.m_aFilename); } else { - // middle entry - int Left = 0, Right = m_NumTimestamps - 1; - while(Right - Left > 1) - { - int Mid = (Left + Right) / 2; - if(m_aTimestamps[Mid] > Timestamp) - Right = Mid; - else - Left = Mid; - } - - if(m_NumTimestamps > m_MaxEntries) - { - mem_move(m_aTimestamps, m_aTimestamps + 1, (Right - 1) * sizeof(int64_t)); - m_aTimestamps[Right - 1] = Timestamp; - } - else - { - mem_move(m_aTimestamps + Right + 1, m_aTimestamps + Right, (m_NumTimestamps - Right) * sizeof(int64_t)); - m_aTimestamps[Right] = Timestamp; - ++m_NumTimestamps; - } + char aTimestring[TIMESTAMP_LENGTH]; + str_timestamp_ex(FileEntry.m_Timestamp, aTimestring, sizeof(aBuf), FORMAT_NOSPACE); + str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt); } - // remove old file only after we inserted the new entry, otherwise we can't - // know which one is the oldest - if(m_NumTimestamps > m_MaxEntries) - { - if(m_aFileDesc[0] == '\0') // consider an empty file desc as a wild card - { - m_Remove = m_aTimestamps[0]; - m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, RemoveCallback, this); - } - else - { - char aBuf[IO_MAX_PATH_LENGTH]; - char aTimestring[TIMESTAMP_LENGTH]; - BuildTimestring(m_aTimestamps[0], aTimestring); - - str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt); - m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE); - } - } + m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE); + FilesDeleted++; } } @@ -244,30 +148,7 @@ int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int Stor int64_t Timestamp = pThis->GetTimestamp(pFilename); // add the entry - pThis->AddEntry(Timestamp); - - return 0; -} - -int CFileCollection::RemoveCallback(const char *pFilename, int IsDir, int StorageType, void *pUser) -{ - CFileCollection *pThis = static_cast(pUser); - - // check for valid file name format - if(IsDir || !pThis->IsFilenameValid(pFilename)) - return 0; - - // extract the timestamp - int64_t Timestamp = pThis->GetTimestamp(pFilename); - - if(Timestamp == pThis->m_Remove) - { - char aBuf[IO_MAX_PATH_LENGTH]; - str_format(aBuf, sizeof(aBuf), "%s/%s", pThis->m_aPath, pFilename); - pThis->m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE); - pThis->m_Remove = -1; - return 1; - } + pThis->m_vTimestamps.emplace_back(Timestamp, pFilename); return 0; } diff --git a/src/engine/shared/filecollection.h b/src/engine/shared/filecollection.h index 9a4b561c65a..cbc54cd2fe2 100644 --- a/src/engine/shared/filecollection.h +++ b/src/engine/shared/filecollection.h @@ -3,9 +3,11 @@ #ifndef ENGINE_SHARED_FILECOLLECTION_H #define ENGINE_SHARED_FILECOLLECTION_H +#include #include #include +#include class IStorage; @@ -13,32 +15,36 @@ class CFileCollection { enum { - MAX_ENTRIES = 1001, TIMESTAMP_LENGTH = 20, // _YYYY-MM-DD_HH-MM-SS }; - int64_t m_aTimestamps[MAX_ENTRIES]; - int m_NumTimestamps; - int m_MaxEntries; + struct CFileEntry + { + int64_t m_Timestamp; + char m_aFilename[IO_MAX_PATH_LENGTH]; + CFileEntry(int64_t Timestamp, const char *pFilename) + { + m_Timestamp = Timestamp; + str_copy(m_aFilename, pFilename); + } + }; + + std::vector m_vTimestamps; char m_aFileDesc[128]; int m_FileDescLength; char m_aFileExt[32]; int m_FileExtLength; char m_aPath[IO_MAX_PATH_LENGTH]; IStorage *m_pStorage; - int64_t m_Remove; // Timestamp we want to remove bool IsFilenameValid(const char *pFilename); int64_t ExtractTimestamp(const char *pTimestring); - void BuildTimestring(int64_t Timestamp, char *pTimestring); int64_t GetTimestamp(const char *pFilename); public: void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries); - void AddEntry(int64_t Timestamp); static int FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser); - static int RemoveCallback(const char *pFilename, int IsDir, int StorageType, void *pUser); }; #endif From c3689cd9b405507920ac9efae28dfd3f7519de2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 26 Dec 2023 14:06:24 +0100 Subject: [PATCH 165/198] Remove unused `VkBufferImageCopy Region` variable According to the Vulkan specification, the struct `VkBufferImageCopy` is used only for `vkCmdCopyBufferToImage` and `vkCmdCopyImageToBuffer`. The variable `Region` is only initialized but not passed to either of those functions. --- src/engine/client/backend/vulkan/backend_vulkan.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/engine/client/backend/vulkan/backend_vulkan.cpp b/src/engine/client/backend/vulkan/backend_vulkan.cpp index 84a8b48d869..ae346cd1686 100644 --- a/src/engine/client/backend/vulkan/backend_vulkan.cpp +++ b/src/engine/client/backend/vulkan/backend_vulkan.cpp @@ -1400,17 +1400,6 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase return false; VkCommandBuffer &CommandBuffer = *pCommandBuffer; - VkBufferImageCopy Region{}; - Region.bufferOffset = 0; - Region.bufferRowLength = 0; - Region.bufferImageHeight = 0; - Region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - Region.imageSubresource.mipLevel = 0; - Region.imageSubresource.baseArrayLayer = 0; - Region.imageSubresource.layerCount = 1; - Region.imageOffset = {0, 0, 0}; - Region.imageExtent = {Viewport.width, Viewport.height, 1}; - auto &SwapImg = m_vSwapChainImages[m_LastPresentedSwapChainImageIndex]; if(!ImageBarrier(m_GetPresentedImgDataHelperImage, 0, 1, 0, 1, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)) From 0a0e066535ccbc9bb8910cd628bf25b84eb4cc18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 26 Dec 2023 17:29:01 +0100 Subject: [PATCH 166/198] Remove unused `FlipImgData` parameter from Vulkan backend The parameter is always set to `false` so the code is unused. --- .../client/backend/vulkan/backend_vulkan.cpp | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/engine/client/backend/vulkan/backend_vulkan.cpp b/src/engine/client/backend/vulkan/backend_vulkan.cpp index ae346cd1686..a7ebc65377b 100644 --- a/src/engine/client/backend/vulkan/backend_vulkan.cpp +++ b/src/engine/client/backend/vulkan/backend_vulkan.cpp @@ -1378,7 +1378,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase } } - [[nodiscard]] bool GetPresentedImageDataImpl(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData, bool FlipImgData, bool ResetAlpha) + [[nodiscard]] bool GetPresentedImageDataImpl(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData, bool ResetAlpha) { bool IsB8G8R8A8 = m_VKSurfFormat.format == VK_FORMAT_B8G8R8A8_UNORM; bool UsesRGBALikeFormat = m_VKSurfFormat.format == VK_FORMAT_R8G8B8A8_UNORM || IsB8G8R8A8; @@ -1474,8 +1474,8 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase vkInvalidateMappedMemoryRanges(m_VKDevice, 1, &MemRange); size_t RealFullImageSize = maximum(ImageTotalSize, (size_t)(Height * m_GetPresentedImgDataHelperMappedLayoutPitch)); - if(vDstData.size() < RealFullImageSize + (Width * 4)) - vDstData.resize(RealFullImageSize + (Width * 4)); // extra space for flipping + if(vDstData.size() < RealFullImageSize) + vDstData.resize(RealFullImageSize); mem_copy(vDstData.data(), pResImageData, RealFullImageSize); @@ -1507,17 +1507,6 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase } } - if(FlipImgData) - { - uint8_t *pTempRow = vDstData.data() + Width * Height * 4; - for(uint32_t Y = 0; Y < Height / 2; ++Y) - { - mem_copy(pTempRow, vDstData.data() + Y * Width * 4, Width * 4); - mem_copy(vDstData.data() + Y * Width * 4, vDstData.data() + ((Height - Y) - 1) * Width * 4, Width * 4); - mem_copy(vDstData.data() + ((Height - Y) - 1) * Width * 4, pTempRow, Width * 4); - } - } - return true; } else @@ -1536,7 +1525,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase [[nodiscard]] bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData) override { - return GetPresentedImageDataImpl(Width, Height, Format, vDstData, false, false); + return GetPresentedImageDataImpl(Width, Height, Format, vDstData, false); } /************************ @@ -6768,7 +6757,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase uint32_t Width; uint32_t Height; CImageInfo::EImageFormat Format; - if(GetPresentedImageDataImpl(Width, Height, Format, m_vScreenshotHelper, false, true)) + if(GetPresentedImageDataImpl(Width, Height, Format, m_vScreenshotHelper, true)) { size_t ImgSize = (size_t)Width * (size_t)Height * (size_t)4; pCommand->m_pImage->m_pData = malloc(ImgSize); From f3960564256148ad42d673bf08a6f3344c635ecb Mon Sep 17 00:00:00 2001 From: Alexander Akulich Date: Tue, 26 Dec 2023 19:37:10 +0300 Subject: [PATCH 167/198] CRenderTools: Mark some methods static and (some) const --- src/game/client/render.cpp | 2 +- src/game/client/render.h | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp index 0e081c27812..998cfec4e5c 100644 --- a/src/game/client/render.cpp +++ b/src/game/client/render.cpp @@ -262,7 +262,7 @@ void CRenderTools::GetRenderTeeOffsetToRenderedTee(const CAnimState *pAnim, cons TeeOffsetToMid.y = -MidOfRendered; } -void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) +void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) const { vec2 Direction = Dir; vec2 Position = Pos; diff --git a/src/game/client/render.h b/src/game/client/render.h index 65028b7dbb3..692985ea24f 100644 --- a/src/game/client/render.h +++ b/src/game/client/render.h @@ -121,8 +121,8 @@ class CRenderTools int m_TeeQuadContainerIndex; - void GetRenderTeeBodyScale(float BaseSize, float &BodyScale); - void GetRenderTeeFeetScale(float BaseSize, float &FeetScaleWidth, float &FeetScaleHeight); + static void GetRenderTeeBodyScale(float BaseSize, float &BodyScale); + static void GetRenderTeeFeetScale(float BaseSize, float &FeetScaleWidth, float &FeetScaleHeight); public: class IGraphics *Graphics() const { return m_pGraphics; } @@ -147,14 +147,14 @@ class CRenderTools int QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height) const; // larger rendering methods - void GetRenderTeeBodySize(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &BodyOffset, float &Width, float &Height); - void GetRenderTeeFeetSize(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &FeetOffset, float &Width, float &Height); - void GetRenderTeeAnimScaleAndBaseSize(const CTeeRenderInfo *pInfo, float &AnimScale, float &BaseSize); + static void GetRenderTeeBodySize(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &BodyOffset, float &Width, float &Height); + static void GetRenderTeeFeetSize(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &FeetOffset, float &Width, float &Height); + static void GetRenderTeeAnimScaleAndBaseSize(const CTeeRenderInfo *pInfo, float &AnimScale, float &BaseSize); // returns the offset to use, to render the tee with @see RenderTee exactly in the mid - void GetRenderTeeOffsetToRenderedTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &TeeOffsetToMid); + static void GetRenderTeeOffsetToRenderedTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &TeeOffsetToMid); // object render methods - void RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha = 1.0f); + void RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha = 1.0f) const; // map render methods (render_map.cpp) static void RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result); From 7e95d4be227ef051d2271d06989969f8dd20f07a Mon Sep 17 00:00:00 2001 From: Alexander Akulich Date: Sun, 24 Dec 2023 17:06:26 +0300 Subject: [PATCH 168/198] CGameClient::CClientData::Reset: Reset skin info --- src/game/client/gameclient.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 29063df98ec..2d400d0b834 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -2211,6 +2211,8 @@ void CGameClient::CClientData::Reset() m_aName[0] = 0; m_aClan[0] = 0; m_Country = -1; + m_aSkinName[0] = '\0'; + m_SkinColor = 0; m_Team = 0; m_Angle = 0; m_Emoticon = 0; From 70d263a7d5cfb102921d51d8c7a042fe37e63bfd Mon Sep 17 00:00:00 2001 From: Corantin H Date: Tue, 26 Dec 2023 18:35:36 +0100 Subject: [PATCH 169/198] Remove `if`s when drawing quads QoL buttons --- src/game/editor/popups.cpp | 40 +++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 2fdd634cbe1..ce5f294110a 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -339,18 +339,16 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect Selector.VSplitMid(&No, &Yes); pEditor->UI()->DoLabel(&Label, "Align quads", 10.0f, TEXTALIGN_ML); - if(pEditor->m_AllowPlaceUnusedTiles != -1) + + static int s_ButtonNo = 0; + static int s_ButtonYes = 0; + if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !g_Config.m_EdAlignQuads, &No, 0, "Do not perform quad alignment to other quads/points when moving quads")) { - static int s_ButtonNo = 0; - static int s_ButtonYes = 0; - if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !g_Config.m_EdAlignQuads, &No, 0, "Do not perform quad alignment to other quads/points when moving quads")) - { - g_Config.m_EdAlignQuads = false; - } - if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", g_Config.m_EdAlignQuads, &Yes, 0, "Allow quad alignment to other quads/points when moving quads")) - { - g_Config.m_EdAlignQuads = true; - } + g_Config.m_EdAlignQuads = false; + } + if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", g_Config.m_EdAlignQuads, &Yes, 0, "Allow quad alignment to other quads/points when moving quads")) + { + g_Config.m_EdAlignQuads = true; } } @@ -365,18 +363,16 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect Selector.VSplitMid(&No, &Yes); pEditor->UI()->DoLabel(&Label, "Show quads bounds", 10.0f, TEXTALIGN_ML); - if(pEditor->m_AllowPlaceUnusedTiles != -1) + + static int s_ButtonNo = 0; + static int s_ButtonYes = 0; + if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !g_Config.m_EdShowQuadsRect, &No, 0, "Do not show quad bounds when moving quads")) { - static int s_ButtonNo = 0; - static int s_ButtonYes = 0; - if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !g_Config.m_EdShowQuadsRect, &No, 0, "Do not show quad bounds when moving quads")) - { - g_Config.m_EdShowQuadsRect = false; - } - if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", g_Config.m_EdShowQuadsRect, &Yes, 0, "Show quad bounds when moving quads")) - { - g_Config.m_EdShowQuadsRect = true; - } + g_Config.m_EdShowQuadsRect = false; + } + if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", g_Config.m_EdShowQuadsRect, &Yes, 0, "Show quad bounds when moving quads")) + { + g_Config.m_EdShowQuadsRect = true; } } From 43710fe5411cb77b579f725e5200358c90a7542e Mon Sep 17 00:00:00 2001 From: Alexander Akulich Date: Tue, 26 Dec 2023 20:16:06 +0300 Subject: [PATCH 170/198] Use CRenderTools::GetRenderTeeOffsetToRenderedTee via class name instead of instance Fixes clang-tidy warning. --- src/game/client/components/chat.cpp | 2 +- src/game/client/components/hud.cpp | 4 ++-- src/game/client/components/infomessages.cpp | 6 +++--- src/game/client/components/menus_browser.cpp | 4 ++-- src/game/client/components/menus_ingame.cpp | 4 ++-- src/game/client/components/menus_settings.cpp | 4 ++-- src/game/client/components/players.cpp | 2 +- src/game/client/components/scoreboard.cpp | 2 +- src/game/client/components/spectator.cpp | 2 +- src/game/client/components/statboard.cpp | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 3d9ea8a1055..afac56aaa98 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -1312,7 +1312,7 @@ void CChat::OnRender() const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &RenderInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &RenderInfo, OffsetToMid); vec2 TeeRenderPos(x + (RealMsgPaddingX + TeeSize) / 2.0f, y + OffsetTeeY + FullHeightMinusTee / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &RenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), TeeRenderPos, Blend); } diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index e4e34a5ca84..bb7ac5948f8 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -271,7 +271,7 @@ void CHud::RenderScoreHud() const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); 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); @@ -434,7 +434,7 @@ void CHud::RenderScoreHud() const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); 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); diff --git a/src/game/client/components/infomessages.cpp b/src/game/client/components/infomessages.cpp index d461d670513..6893edde8ce 100644 --- a/src/game/client/components/infomessages.cpp +++ b/src/game/client/components/infomessages.cpp @@ -397,7 +397,7 @@ void CInfoMessages::RenderKillMsg(CInfoMsg *pInfoMsg, float x, float y) const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[j], OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[j], OffsetToMid); const vec2 TeeRenderPos = vec2(x, y + 46.0f / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[j], EMOTE_PAIN, vec2(-1, 0), TeeRenderPos); @@ -439,7 +439,7 @@ void CInfoMessages::RenderKillMsg(CInfoMsg *pInfoMsg, float x, float y) { const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_KillerRenderInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_KillerRenderInfo, OffsetToMid); const vec2 TeeRenderPos = vec2(x, y + 46.0f / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &pInfoMsg->m_KillerRenderInfo, EMOTE_ANGRY, vec2(1, 0), TeeRenderPos); } @@ -500,7 +500,7 @@ void CInfoMessages::RenderFinishMsg(CInfoMsg *pInfoMsg, float x, float y) const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[0], OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[0], OffsetToMid); const vec2 TeeRenderPos = vec2(x, y + 46.0f / 2.0f + OffsetToMid.y); const int Emote = pInfoMsg->m_RecordPersonal ? EMOTE_HAPPY : EMOTE_NORMAL; RenderTools()->RenderTee(pIdleState, &pInfoMsg->m_aVictimRenderInfo[0], Emote, vec2(-1, 0), TeeRenderPos); diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 34d0250a11d..6bebd8b8b96 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -1222,7 +1222,7 @@ void CMenus::RenderServerbrowserInfoScoreboard(CUIRect View, const CServerInfo * const CTeeRenderInfo TeeInfo = GetTeeRenderInfo(vec2(Skin.w, Skin.h), CurrentClient.m_aSkin, CurrentClient.m_CustomSkinColors, CurrentClient.m_CustomSkinColorBody, CurrentClient.m_CustomSkinColorFeet); const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); const vec2 TeeRenderPos = vec2(Skin.x + TeeInfo.m_Size / 2.0f, Skin.y + Skin.h / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, CurrentClient.m_Afk ? EMOTE_BLINK : EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); } @@ -1423,7 +1423,7 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) const CTeeRenderInfo TeeInfo = GetTeeRenderInfo(vec2(Skin.w, Skin.h), Friend.Skin(), Friend.CustomSkinColors(), Friend.CustomSkinColorBody(), Friend.CustomSkinColorFeet()); const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); const vec2 TeeRenderPos = vec2(Skin.x + Skin.w / 2.0f, Skin.y + Skin.h * 0.55f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, Friend.IsAfk() ? EMOTE_BLINK : EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); } diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 1834c3d5aa3..ab7ff9c4781 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -303,7 +303,7 @@ void CMenus::RenderPlayers(CUIRect MainView) const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); vec2 TeeRenderPos(Button.x + Button.h / 2, Button.y + Button.h / 2 + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); @@ -604,7 +604,7 @@ bool CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators) const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); vec2 TeeRenderPos(TeeRect.x + TeeInfo.m_Size / 2, TeeRect.y + TeeInfo.m_Size / 2 + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 6767157d972..7549a5b03cf 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -587,7 +587,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) Label.VSplitLeft(260.0f, &Label, 0); const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &OwnSkinInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &OwnSkinInfo, OffsetToMid); vec2 TeeRenderPos(Label.x + 30.0f, Label.y + Label.h / 2.0f + OffsetToMid.y); int Emote = m_Dummy ? g_Config.m_ClDummyDefaultEyes : g_Config.m_ClPlayerDefaultEyes; RenderTools()->RenderTee(pIdleState, &OwnSkinInfo, Emote, vec2(1, 0), TeeRenderPos); @@ -805,7 +805,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) Info.m_ColorableRenderSkin = pSkinToBeDraw->m_ColorableSkin; Info.m_SkinMetrics = pSkinToBeDraw->m_Metrics; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &Info, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &Info, OffsetToMid); TeeRenderPos = vec2(OriginalRect.x + 30, OriginalRect.y + OriginalRect.h / 2 + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &Info, Emote, vec2(1.0f, 0.0f), TeeRenderPos); diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index cf69fc34d5b..0cdf8779db0 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -672,7 +672,7 @@ void CPlayers::RenderPlayer( RenderTools()->RenderTee(&State, &RenderInfo, Player.m_Emote, Direction, Position, Alpha); float TeeAnimScale, TeeBaseSize; - RenderTools()->GetRenderTeeAnimScaleAndBaseSize(&RenderInfo, TeeAnimScale, TeeBaseSize); + CRenderTools::GetRenderTeeAnimScaleAndBaseSize(&RenderInfo, TeeAnimScale, TeeBaseSize); vec2 BodyPos = Position + vec2(State.GetBody()->m_X, State.GetBody()->m_Y) * TeeAnimScale; if(RenderInfo.m_TeeRenderFlags & TEE_EFFECT_FROZEN) { diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp index 5643f57a4d1..65d7d4f0244 100644 --- a/src/game/client/components/scoreboard.cpp +++ b/src/game/client/components/scoreboard.cpp @@ -434,7 +434,7 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch TeeInfo.m_Size *= TeeSizeMod; const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); vec2 TeeRenderPos(TeeOffset + TeeLength / 2, y + LineHeight / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index f01f2543338..458f4446b04 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -501,7 +501,7 @@ void CSpectator::OnRender() const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); vec2 TeeRenderPos(Width / 2.0f + x + 20.0f, Height / 2.0f + y + BoxMove + LineHeight / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos, TeeAlpha); diff --git a/src/game/client/components/statboard.cpp b/src/game/client/components/statboard.cpp index be9bed21950..4af3a2f5d18 100644 --- a/src/game/client/components/statboard.cpp +++ b/src/game/client/components/statboard.cpp @@ -292,7 +292,7 @@ void CStatboard::RenderGlobalStats() const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &Teeinfo, OffsetToMid); + CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &Teeinfo, OffsetToMid); vec2 TeeRenderPos(x + Teeinfo.m_Size / 2, y + LineHeight / 2.0f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &Teeinfo, EMOTE_NORMAL, vec2(1, 0), TeeRenderPos); From e9958aca195c8128b4e434ebdeec66ad02507d29 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Tue, 26 Dec 2023 19:11:36 +0100 Subject: [PATCH 171/198] Show unused tiles warning popup for all entities layer --- src/game/editor/mapitems/layer_front.cpp | 7 +------ src/game/editor/mapitems/layer_game.cpp | 7 +------ src/game/editor/mapitems/layer_speedup.cpp | 6 ++++++ src/game/editor/mapitems/layer_switch.cpp | 6 ++++++ src/game/editor/mapitems/layer_tele.cpp | 6 ++++++ src/game/editor/mapitems/layer_tiles.cpp | 10 ++++++++++ src/game/editor/mapitems/layer_tiles.h | 2 ++ src/game/editor/mapitems/layer_tune.cpp | 6 ++++++ 8 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/game/editor/mapitems/layer_front.cpp b/src/game/editor/mapitems/layer_front.cpp index f046299c4b8..b553ead212e 100644 --- a/src/game/editor/mapitems/layer_front.cpp +++ b/src/game/editor/mapitems/layer_front.cpp @@ -27,12 +27,7 @@ void CLayerFront::SetTile(int x, int y, CTile Tile) { CTile air = {TILE_AIR}; CLayerTiles::SetTile(x, y, air); - if(!m_pEditor->m_PreventUnusedTilesWasWarned) - { - m_pEditor->m_PopupEventType = CEditor::POPEVENT_PREVENTUNUSEDTILES; - m_pEditor->m_PopupEventActivated = true; - m_pEditor->m_PreventUnusedTilesWasWarned = true; - } + ShowPreventUnusedTilesWarning(); } } diff --git a/src/game/editor/mapitems/layer_game.cpp b/src/game/editor/mapitems/layer_game.cpp index eb0ee541fcd..168595d9747 100644 --- a/src/game/editor/mapitems/layer_game.cpp +++ b/src/game/editor/mapitems/layer_game.cpp @@ -56,12 +56,7 @@ void CLayerGame::SetTile(int x, int y, CTile Tile) { CTile air = {TILE_AIR}; CLayerTiles::SetTile(x, y, air); - if(!m_pEditor->m_PreventUnusedTilesWasWarned) - { - m_pEditor->m_PopupEventType = CEditor::POPEVENT_PREVENTUNUSEDTILES; - m_pEditor->m_PopupEventActivated = true; - m_pEditor->m_PreventUnusedTilesWasWarned = true; - } + ShowPreventUnusedTilesWarning(); } } } diff --git a/src/game/editor/mapitems/layer_speedup.cpp b/src/game/editor/mapitems/layer_speedup.cpp index 02795a43169..9222bf551d2 100644 --- a/src/game/editor/mapitems/layer_speedup.cpp +++ b/src/game/editor/mapitems/layer_speedup.cpp @@ -144,6 +144,9 @@ void CLayerSpeedup::BrushDraw(std::shared_ptr pBrush, float wx, float wy m_pSpeedupTile[Index].m_Angle = 0; m_pSpeedupTile[Index].m_Type = 0; m_pTiles[Index].m_Index = 0; + + if(pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index != TILE_AIR) + ShowPreventUnusedTilesWarning(); } SSpeedupTileStateChange::SData Current{ @@ -258,6 +261,9 @@ void CLayerSpeedup::FillSelection(bool Empty, std::shared_ptr pBrush, CU m_pTiles[TgtIndex].m_Index = 0; m_pSpeedupTile[TgtIndex].m_Force = 0; m_pSpeedupTile[TgtIndex].m_Angle = 0; + + if(!Empty) + ShowPreventUnusedTilesWarning(); } else { diff --git a/src/game/editor/mapitems/layer_switch.cpp b/src/game/editor/mapitems/layer_switch.cpp index 6d59903b6a4..35d9a104532 100644 --- a/src/game/editor/mapitems/layer_switch.cpp +++ b/src/game/editor/mapitems/layer_switch.cpp @@ -144,6 +144,9 @@ void CLayerSwitch::BrushDraw(std::shared_ptr pBrush, float wx, float wy) m_pSwitchTile[Index].m_Flags = 0; m_pSwitchTile[Index].m_Delay = 0; m_pTiles[Index].m_Index = 0; + + if(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index != TILE_AIR) + ShowPreventUnusedTilesWarning(); } SSwitchTileStateChange::SData Current{ @@ -265,6 +268,9 @@ void CLayerSwitch::FillSelection(bool Empty, std::shared_ptr pBrush, CUI m_pSwitchTile[TgtIndex].m_Type = 0; m_pSwitchTile[TgtIndex].m_Number = 0; m_pSwitchTile[TgtIndex].m_Delay = 0; + + if(!Empty) + ShowPreventUnusedTilesWarning(); } else { diff --git a/src/game/editor/mapitems/layer_tele.cpp b/src/game/editor/mapitems/layer_tele.cpp index 0006a74c908..a4faf327746 100644 --- a/src/game/editor/mapitems/layer_tele.cpp +++ b/src/game/editor/mapitems/layer_tele.cpp @@ -142,6 +142,9 @@ void CLayerTele::BrushDraw(std::shared_ptr pBrush, float wx, float wy) m_pTeleTile[Index].m_Number = 0; m_pTeleTile[Index].m_Type = 0; m_pTiles[Index].m_Index = 0; + + if(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index != TILE_AIR) + ShowPreventUnusedTilesWarning(); } STeleTileStateChange::SData Current{ @@ -254,6 +257,9 @@ void CLayerTele::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRe m_pTiles[TgtIndex].m_Index = 0; m_pTeleTile[TgtIndex].m_Type = 0; m_pTeleTile[TgtIndex].m_Number = 0; + + if(!Empty) + ShowPreventUnusedTilesWarning(); } else { diff --git a/src/game/editor/mapitems/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp index a3ed05c33a1..c134a78e7bb 100644 --- a/src/game/editor/mapitems/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -1259,3 +1259,13 @@ void CLayerTiles::ModifyEnvelopeIndex(FIndexModifyFunction Func) { Func(&m_ColorEnv); } + +void CLayerTiles::ShowPreventUnusedTilesWarning() +{ + if(!m_pEditor->m_PreventUnusedTilesWasWarned) + { + m_pEditor->m_PopupEventType = CEditor::POPEVENT_PREVENTUNUSEDTILES; + m_pEditor->m_PopupEventActivated = true; + m_pEditor->m_PreventUnusedTilesWasWarned = true; + } +} diff --git a/src/game/editor/mapitems/layer_tiles.h b/src/game/editor/mapitems/layer_tiles.h index ca8e84e8892..c778c8d453b 100644 --- a/src/game/editor/mapitems/layer_tiles.h +++ b/src/game/editor/mapitems/layer_tiles.h @@ -188,6 +188,8 @@ class CLayerTiles : public CLayer protected: void RecordStateChange(int x, int y, CTile Previous, CTile Tile); + void ShowPreventUnusedTilesWarning(); + friend class CAutoMapper; }; diff --git a/src/game/editor/mapitems/layer_tune.cpp b/src/game/editor/mapitems/layer_tune.cpp index 0a41fcc7b20..c14a67767e0 100644 --- a/src/game/editor/mapitems/layer_tune.cpp +++ b/src/game/editor/mapitems/layer_tune.cpp @@ -127,6 +127,9 @@ void CLayerTune::BrushDraw(std::shared_ptr pBrush, float wx, float wy) m_pTuneTile[Index].m_Number = 0; m_pTuneTile[Index].m_Type = 0; m_pTiles[Index].m_Index = 0; + + if(pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index != TILE_AIR) + ShowPreventUnusedTilesWarning(); } STuneTileStateChange::SData Current{ @@ -235,6 +238,9 @@ void CLayerTune::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRe m_pTiles[TgtIndex].m_Index = 0; m_pTuneTile[TgtIndex].m_Type = 0; m_pTuneTile[TgtIndex].m_Number = 0; + + if(!Empty) + ShowPreventUnusedTilesWarning(); } else { From e7f28f78810778e94c59481254c0f0ce0f02b22f Mon Sep 17 00:00:00 2001 From: MrBlubberBut <54787701+MrBlubberBut@users.noreply.github.com> Date: Tue, 26 Dec 2023 13:36:40 -0500 Subject: [PATCH 172/198] Update README.md Clarify the build process :) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01e9cbfe400..ac3e0ca964c 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ Building on Windows with Visual Studio Download and install some version of [Microsoft Visual Studio](https://www.visualstudio.com/) (as of writing, MSVS Community 2017) with **C++ support**, install [Python 3](https://www.python.org/downloads/) **for all users** and install [CMake](https://cmake.org/download/#latest). You also need to install [Rust](https://rustup.rs/). -Start CMake and select the source code folder (where DDNet resides, the directory with `CMakeLists.txt`). Additionally select a build folder, e.g. create a build subdirectory in the source code directory. Click "Configure" and select the Visual Studio generator (it should be pre-selected, so pressing "Finish" will suffice). After configuration finishes and the "Generate" reactivates, click it. When that finishes, click "Open Project". Visual Studio should open. You can compile the DDNet client by right-clicking the DDNet project (not the solution) and select "Select as StartUp project". Now you should be able to compile DDNet by clicking the green, triangular "Run" button. +Start CMake and select the source code folder (where DDNet resides, the directory with `CMakeLists.txt`). Additionally select a build folder, e.g. create a build subdirectory in the source code directory. Click "Configure" and select the Visual Studio generator (it should be pre-selected, so pressing "Finish" will suffice). After configuration finishes and the "Generate" reactivates, click it. When that finishes, click "Open Project". Visual Studio should open. You can compile the DDNet client by right-clicking the "game-client" project and select "Set as Startup project". Now you should be able to compile DDNet by clicking the green, triangular "Run" button. Cross-compiling on Linux to Windows x86/x86\_64 ----------------------------------------------- From 32e187f18e90fb7712deb1b5e840e77a1bb2a080 Mon Sep 17 00:00:00 2001 From: MrBlubberBut <54787701+MrBlubberBut@users.noreply.github.com> Date: Tue, 26 Dec 2023 13:59:06 -0500 Subject: [PATCH 173/198] Fix automatic hammer on release when cl_dummy_control is set to 1 Let's say you have this bind: ```bind x +toggle cl_dummy_hammer 1 0``` and you set cl_dummy_control to 1. When you press the bind i mentioned above, and then release, the dummy will hammer where he is looking (not at you). So, in total, there will be two hammers. One hammer when you press down the button and the dummy hammers towards you, and then another hammer when you release the button and the dummy hammers where he is looking. This fixes it, and also makes sure it does not conflict with cl_dummy_copy_moves (as if it is enabled and cl_dummy_control is enabled, the dummy will not copy fire, hook, or jump) so I made sure it keeps this functionality as it's pretty cool. This does not fix any other bugs yet, maybe I will fix those in the future but we'll see. Any bug you may encounter with this change is also probably present in the main branch, such as resetonswitch not working perfectly with dummy_control, but if you do find something different then let me know. --- src/game/client/components/controls.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index f72cce98f70..ca18e092495 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -284,8 +284,10 @@ int CControls::SnapInput(int *pData) pDummyInput->m_TargetX = m_aInputData[g_Config.m_ClDummy].m_TargetX; pDummyInput->m_TargetY = m_aInputData[g_Config.m_ClDummy].m_TargetY; pDummyInput->m_WantedWeapon = m_aInputData[g_Config.m_ClDummy].m_WantedWeapon; - - pDummyInput->m_Fire += m_aInputData[g_Config.m_ClDummy].m_Fire - m_aLastData[g_Config.m_ClDummy].m_Fire; + + if(!g_Config.m_ClDummyControl) + pDummyInput->m_Fire += m_aInputData[g_Config.m_ClDummy].m_Fire - m_aLastData[g_Config.m_ClDummy].m_Fire; + pDummyInput->m_NextWeapon += m_aInputData[g_Config.m_ClDummy].m_NextWeapon - m_aLastData[g_Config.m_ClDummy].m_NextWeapon; pDummyInput->m_PrevWeapon += m_aInputData[g_Config.m_ClDummy].m_PrevWeapon - m_aLastData[g_Config.m_ClDummy].m_PrevWeapon; @@ -296,7 +298,12 @@ int CControls::SnapInput(int *pData) { CNetObj_PlayerInput *pDummyInput = &m_pClient->m_DummyInput; pDummyInput->m_Jump = g_Config.m_ClDummyJump; - pDummyInput->m_Fire = g_Config.m_ClDummyFire; + + if(g_Config.m_ClDummyFire) + pDummyInput->m_Fire = g_Config.m_ClDummyFire; + else if((pDummyInput->m_Fire & 1) != 0) + pDummyInput->m_Fire++; + pDummyInput->m_Hook = g_Config.m_ClDummyHook; } From 643cdf8717f601d6427128b17818664577cfb940 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Tue, 26 Dec 2023 20:17:36 +0100 Subject: [PATCH 174/198] Add shortcut to change tele, switch and tune number of current brush (#5400) --- src/game/editor/editor.cpp | 87 +++++++++++++++++++++++++++++++++++--- src/game/editor/editor.h | 2 + 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 38026fa5bb3..96db5fa2de3 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -8158,10 +8158,21 @@ void CEditor::Render() MapView()->Zoom()->ChangeValue(-50.0f); if(Input()->KeyPress(KEY_KP_MULTIPLY)) MapView()->ResetZoom(); - if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) - MapView()->Zoom()->ChangeValue(20.0f); - if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) - MapView()->Zoom()->ChangeValue(-20.0f); + + if(m_pBrush->IsEmpty() || !Input()->ShiftIsPressed()) + { + if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) + MapView()->Zoom()->ChangeValue(20.0f); + if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) + MapView()->Zoom()->ChangeValue(-20.0f); + } + else + { + if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) + AdjustBrushSpecialTiles(-1); + if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) + AdjustBrushSpecialTiles(1); + } } for(CEditorComponent &Component : m_vComponents) @@ -8872,8 +8883,6 @@ bool CEditor::Append(const char *pFileName, int StorageType, bool IgnoreHistory) return true; } -IEditor *CreateEditor() { return new CEditor; } - void CEditor::UndoLastAction() { if(m_ActiveExtraEditor == EXTRAEDITOR_SERVER_SETTINGS) @@ -8893,3 +8902,69 @@ void CEditor::RedoLastAction() else m_EditorHistory.Redo(); } + +void CEditor::AdjustBrushSpecialTiles(int Adjust) +{ + // Adjust m_Number field of tune, switch and tele tiles by `Adjust` + + auto &&AdjustNumber = [Adjust](unsigned char &Number) { + Number = ((Number + Adjust) - 1 + 255) % 255 + 1; + }; + + for(auto &pLayer : m_pBrush->m_vpLayers) + { + if(pLayer->m_Type != LAYERTYPE_TILES) + continue; + + std::shared_ptr pLayerTiles = std::static_pointer_cast(pLayer); + + // Only handle tele, switch and tune layers + if(pLayerTiles->m_Tele) + { + std::shared_ptr pTeleLayer = std::static_pointer_cast(pLayer); + for(int y = 0; y < pTeleLayer->m_Height; y++) + { + for(int x = 0; x < pTeleLayer->m_Width; x++) + { + int i = y * pTeleLayer->m_Width + x; + if(!IsValidTeleTile(pTeleLayer->m_pTiles[i].m_Index) || !pTeleLayer->m_pTeleTile[i].m_Number) + continue; + + AdjustNumber(pTeleLayer->m_pTeleTile[i].m_Number); + } + } + } + else if(pLayerTiles->m_Tune) + { + std::shared_ptr pTuneLayer = std::static_pointer_cast(pLayer); + for(int y = 0; y < pTuneLayer->m_Height; y++) + { + for(int x = 0; x < pTuneLayer->m_Width; x++) + { + int i = y * pTuneLayer->m_Width + x; + if(!IsValidTuneTile(pTuneLayer->m_pTiles[i].m_Index) || !pTuneLayer->m_pTuneTile[i].m_Number) + continue; + + AdjustNumber(pTuneLayer->m_pTuneTile[i].m_Number); + } + } + } + else if(pLayerTiles->m_Switch) + { + std::shared_ptr pSwitchLayer = std::static_pointer_cast(pLayer); + for(int y = 0; y < pSwitchLayer->m_Height; y++) + { + for(int x = 0; x < pSwitchLayer->m_Width; x++) + { + int i = y * pSwitchLayer->m_Width + x; + if(!IsValidSwitchTile(pSwitchLayer->m_pTiles[i].m_Index) || !pSwitchLayer->m_pSwitchTile[i].m_Number) + continue; + + AdjustNumber(pSwitchLayer->m_pSwitchTile[i].m_Number); + } + } + } + } +} + +IEditor *CreateEditor() { return new CEditor; } diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 7977cb0904c..88f15855ab6 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -1061,6 +1061,8 @@ class CEditor : public IEditor unsigned char m_SwitchNum; unsigned char m_SwitchDelay; + void AdjustBrushSpecialTiles(int Adjust); + public: // Undo/Redo CEditorHistory m_EditorHistory; From 166b27fc27c52daadfb7accd344a1eec9284962f Mon Sep 17 00:00:00 2001 From: Corantin H Date: Tue, 26 Dec 2023 20:42:42 +0100 Subject: [PATCH 175/198] Use `ctrl+f` when brush is not empty to replace tile numbers with next free --- src/game/editor/editor.cpp | 85 ++++++++++++++++++++++++++++++-------- src/game/editor/editor.h | 3 +- src/game/editor/popups.cpp | 28 ++----------- 3 files changed, 73 insertions(+), 43 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 96db5fa2de3..d51e09c81d4 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -8166,12 +8166,19 @@ void CEditor::Render() if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) MapView()->Zoom()->ChangeValue(-20.0f); } - else + if(!m_pBrush->IsEmpty()) { - if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) - AdjustBrushSpecialTiles(-1); - if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) - AdjustBrushSpecialTiles(1); + if(Input()->ShiftIsPressed()) + { + if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN)) + AdjustBrushSpecialTiles(false, -1); + if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP)) + AdjustBrushSpecialTiles(false, 1); + } + + // Use ctrl+f to replace number in brush with next free + if(Input()->ModifierIsPressed() && Input()->KeyPress(KEY_F)) + AdjustBrushSpecialTiles(true); } } @@ -8903,9 +8910,10 @@ void CEditor::RedoLastAction() m_EditorHistory.Redo(); } -void CEditor::AdjustBrushSpecialTiles(int Adjust) +void CEditor::AdjustBrushSpecialTiles(bool UseNextFree, int Adjust) { - // Adjust m_Number field of tune, switch and tele tiles by `Adjust` + // Adjust m_Number field of tune, switch and tele tiles by `Adjust` if `UseNextFree` is false + // If true, then use the next free number instead auto &&AdjustNumber = [Adjust](unsigned char &Number) { Number = ((Number + Adjust) - 1 + 255) % 255 + 1; @@ -8921,50 +8929,91 @@ void CEditor::AdjustBrushSpecialTiles(int Adjust) // Only handle tele, switch and tune layers if(pLayerTiles->m_Tele) { + int NextFreeNumber = FindNextFreeTileNumber(LAYERTYPE_TELE); + std::shared_ptr pTeleLayer = std::static_pointer_cast(pLayer); for(int y = 0; y < pTeleLayer->m_Height; y++) { for(int x = 0; x < pTeleLayer->m_Width; x++) { int i = y * pTeleLayer->m_Width + x; - if(!IsValidTeleTile(pTeleLayer->m_pTiles[i].m_Index) || !pTeleLayer->m_pTeleTile[i].m_Number) + if(!IsValidTeleTile(pTeleLayer->m_pTiles[i].m_Index) || (!UseNextFree && !pTeleLayer->m_pTeleTile[i].m_Number)) continue; - AdjustNumber(pTeleLayer->m_pTeleTile[i].m_Number); + if(UseNextFree) + pTeleLayer->m_pTeleTile[i].m_Number = NextFreeNumber; + else + AdjustNumber(pTeleLayer->m_pTeleTile[i].m_Number); } } } else if(pLayerTiles->m_Tune) { - std::shared_ptr pTuneLayer = std::static_pointer_cast(pLayer); - for(int y = 0; y < pTuneLayer->m_Height; y++) + if(!UseNextFree) { - for(int x = 0; x < pTuneLayer->m_Width; x++) + std::shared_ptr pTuneLayer = std::static_pointer_cast(pLayer); + for(int y = 0; y < pTuneLayer->m_Height; y++) { - int i = y * pTuneLayer->m_Width + x; - if(!IsValidTuneTile(pTuneLayer->m_pTiles[i].m_Index) || !pTuneLayer->m_pTuneTile[i].m_Number) - continue; + for(int x = 0; x < pTuneLayer->m_Width; x++) + { + int i = y * pTuneLayer->m_Width + x; + if(!IsValidTuneTile(pTuneLayer->m_pTiles[i].m_Index) || !pTuneLayer->m_pTuneTile[i].m_Number) + continue; - AdjustNumber(pTuneLayer->m_pTuneTile[i].m_Number); + AdjustNumber(pTuneLayer->m_pTuneTile[i].m_Number); + } } } } else if(pLayerTiles->m_Switch) { + int NextFreeNumber = FindNextFreeTileNumber(LAYERTYPE_SWITCH); + std::shared_ptr pSwitchLayer = std::static_pointer_cast(pLayer); for(int y = 0; y < pSwitchLayer->m_Height; y++) { for(int x = 0; x < pSwitchLayer->m_Width; x++) { int i = y * pSwitchLayer->m_Width + x; - if(!IsValidSwitchTile(pSwitchLayer->m_pTiles[i].m_Index) || !pSwitchLayer->m_pSwitchTile[i].m_Number) + if(!IsValidSwitchTile(pSwitchLayer->m_pTiles[i].m_Index) || (!UseNextFree && !pSwitchLayer->m_pSwitchTile[i].m_Number)) continue; - AdjustNumber(pSwitchLayer->m_pSwitchTile[i].m_Number); + if(UseNextFree) + pSwitchLayer->m_pSwitchTile[i].m_Number = NextFreeNumber; + else + AdjustNumber(pSwitchLayer->m_pSwitchTile[i].m_Number); } } } } } +int CEditor::FindNextFreeTileNumber(int Type) +{ + int Number = -1; + if(Type == LAYERTYPE_TELE) + { + for(int i = 1; i <= 255; i++) + { + if(!m_Map.m_pTeleLayer->ContainsElementWithId(i)) + { + Number = i; + break; + } + } + } + else if(Type == LAYERTYPE_SWITCH) + { + for(int i = 1; i <= 255; i++) + { + if(!m_Map.m_pSwitchLayer->ContainsElementWithId(i)) + { + Number = i; + break; + } + } + } + return Number; +} + IEditor *CreateEditor() { return new CEditor; } diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 88f15855ab6..1f077de91b3 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -1061,7 +1061,8 @@ class CEditor : public IEditor unsigned char m_SwitchNum; unsigned char m_SwitchDelay; - void AdjustBrushSpecialTiles(int Adjust); + void AdjustBrushSpecialTiles(bool UseNextFree, int Adjust = 0); + int FindNextFreeTileNumber(int Type); public: // Undo/Redo diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 2fdd634cbe1..3ad06ed9e42 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -2434,20 +2434,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupTele(void *pContext, CUIRect View, b static int s_EmptySlotPid = 0; if(pEditor->DoButton_Editor(&s_EmptySlotPid, "F", 0, &FindEmptySlot, 0, "[ctrl+f] Find empty slot") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) { - int number = -1; - for(int i = 1; i <= 255; i++) - { - if(!pEditor->m_Map.m_pTeleLayer->ContainsElementWithId(i)) - { - number = i; - break; - } - } + int Number = pEditor->FindNextFreeTileNumber(LAYERTYPE_TELE); - if(number != -1) - { - pEditor->m_TeleNumber = number; - } + if(Number != -1) + pEditor->m_TeleNumber = Number; } } @@ -2540,20 +2530,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, static int s_EmptySlotPid = 0; if(pEditor->DoButton_Editor(&s_EmptySlotPid, "F", 0, &FindEmptySlot, 0, "[ctrl+f] Find empty slot") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) { - int Number = -1; - for(int i = 1; i <= 255; i++) - { - if(!pEditor->m_Map.m_pSwitchLayer->ContainsElementWithId(i)) - { - Number = i; - break; - } - } + int Number = pEditor->FindNextFreeTileNumber(LAYERTYPE_SWITCH); if(Number != -1) - { pEditor->m_SwitchNum = Number; - } } } From fcc095b6756f05d692095fd3c848364b06d7b717 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 00:16:44 +0100 Subject: [PATCH 176/198] Improve text rendering inside tele tiles in editor --- src/game/client/render_map.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp index 8241a3d22e2..eaf3c05ebff 100644 --- a/src/game/client/render_map.cpp +++ b/src/game/client/render_map.cpp @@ -724,7 +724,6 @@ void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale return; // its useless to render text at this distance float Size = g_Config.m_ClTextEntitiesSize / 100.f; - float ToCenterOffset = (1 - Size) / 2.f; for(int y = StartY; y < EndY; y++) for(int x = StartX; x < EndX; x++) @@ -749,7 +748,14 @@ void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale char aBuf[16]; str_from_int(Index, aBuf); TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - TextRender()->Text(mx * Scale - 3.f, (my + ToCenterOffset) * Scale, Size * Scale, aBuf, -1.0f); + + // Auto-resize text to fit inside the tile + float ScaledWidth = TextRender()->TextWidth(Size * Scale, aBuf, -1); + float Factor = clamp(Scale / ScaledWidth, 0.0f, 1.0f); + float LocalSize = Size * Factor; + float ToCenterOffset = (1 - LocalSize) / 2.f; + TextRender()->Text((mx + 0.5f) * Scale - (ScaledWidth * Factor) / 2.0f, (my + ToCenterOffset) * Scale, LocalSize * Scale, aBuf, -1.0f); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); } } From 9828da6f3eb6b067515b3d7513a40039c57afd98 Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Wed, 27 Dec 2023 09:45:10 +0100 Subject: [PATCH 177/198] Fix formatting --- src/game/client/components/controls.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index ca18e092495..c9c852b179d 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -284,10 +284,10 @@ int CControls::SnapInput(int *pData) pDummyInput->m_TargetX = m_aInputData[g_Config.m_ClDummy].m_TargetX; pDummyInput->m_TargetY = m_aInputData[g_Config.m_ClDummy].m_TargetY; pDummyInput->m_WantedWeapon = m_aInputData[g_Config.m_ClDummy].m_WantedWeapon; - + if(!g_Config.m_ClDummyControl) pDummyInput->m_Fire += m_aInputData[g_Config.m_ClDummy].m_Fire - m_aLastData[g_Config.m_ClDummy].m_Fire; - + pDummyInput->m_NextWeapon += m_aInputData[g_Config.m_ClDummy].m_NextWeapon - m_aLastData[g_Config.m_ClDummy].m_NextWeapon; pDummyInput->m_PrevWeapon += m_aInputData[g_Config.m_ClDummy].m_PrevWeapon - m_aLastData[g_Config.m_ClDummy].m_PrevWeapon; @@ -298,12 +298,12 @@ int CControls::SnapInput(int *pData) { CNetObj_PlayerInput *pDummyInput = &m_pClient->m_DummyInput; pDummyInput->m_Jump = g_Config.m_ClDummyJump; - + if(g_Config.m_ClDummyFire) pDummyInput->m_Fire = g_Config.m_ClDummyFire; else if((pDummyInput->m_Fire & 1) != 0) pDummyInput->m_Fire++; - + pDummyInput->m_Hook = g_Config.m_ClDummyHook; } From 4c2dfa24db9a8b6bc898b5dce92e61c337c903fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 27 Dec 2023 11:06:34 +0100 Subject: [PATCH 178/198] Improve performance when rendering map overlay texts The text color was previously set and reset for every individual number being rendered for non-empty tele, speedup, switch and tune tiles. The color is the same for all tiles from each entities layer, so most of these `TextColor` calls are unnecessary. Now the text color is only set and reset once when rendering each entities layer. --- src/game/client/render_map.cpp | 60 ++++++++++++++++------------------ 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp index eaf3c05ebff..48a511965df 100644 --- a/src/game/client/render_map.cpp +++ b/src/game/client/render_map.cpp @@ -724,8 +724,11 @@ void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale return; // its useless to render text at this distance float Size = g_Config.m_ClTextEntitiesSize / 100.f; + char aBuf[16]; + TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); for(int y = StartY; y < EndY; y++) + { for(int x = StartX; x < EndX; x++) { int mx = x; @@ -745,21 +748,17 @@ void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale unsigned char Index = pTele[c].m_Number; if(Index && IsTeleTileNumberUsed(pTele[c].m_Type)) { - char aBuf[16]; str_from_int(Index, aBuf); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - // Auto-resize text to fit inside the tile float ScaledWidth = TextRender()->TextWidth(Size * Scale, aBuf, -1); float Factor = clamp(Scale / ScaledWidth, 0.0f, 1.0f); float LocalSize = Size * Factor; float ToCenterOffset = (1 - LocalSize) / 2.f; - TextRender()->Text((mx + 0.5f) * Scale - (ScaledWidth * Factor) / 2.0f, (my + ToCenterOffset) * Scale, LocalSize * Scale, aBuf, -1.0f); - - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->Text((mx + 0.5f) * Scale - (ScaledWidth * Factor) / 2.0f, (my + ToCenterOffset) * Scale, LocalSize * Scale, aBuf); } } - + } + TextRender()->TextColor(TextRender()->DefaultTextColor()); Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } @@ -778,8 +777,11 @@ void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, fl float Size = g_Config.m_ClTextEntitiesSize / 100.f; float ToCenterOffset = (1 - Size) / 2.f; + char aBuf[16]; + TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); for(int y = StartY; y < EndY; y++) + { for(int x = StartX; x < EndX; x++) { int mx = x; @@ -803,32 +805,27 @@ void CRenderTools::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, fl // draw arrow Graphics()->TextureSet(g_pData->m_aImages[IMAGE_SPEEDUP_ARROW].m_Id); Graphics()->QuadsBegin(); - Graphics()->SetColor(255.0f, 255.0f, 255.0f, Alpha); - + Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha); SelectSprite(SPRITE_SPEEDUP_ARROW); Graphics()->QuadsSetRotation(pSpeedup[c].m_Angle * (pi / 180.0f)); DrawSprite(mx * Scale + 16, my * Scale + 16, 35.0f); - Graphics()->QuadsEnd(); + // draw force and max speed if(g_Config.m_ClTextEntities) { - // draw force - char aBuf[16]; str_from_int(Force, aBuf); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - TextRender()->Text(mx * Scale, (my + 0.5f + ToCenterOffset / 2) * Scale, Size * Scale / 2.f, aBuf, -1.0f); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->Text(mx * Scale, (my + 0.5f + ToCenterOffset / 2) * Scale, Size * Scale / 2.f, aBuf); if(MaxSpeed) { str_from_int(MaxSpeed, aBuf); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - TextRender()->Text(mx * Scale, (my + ToCenterOffset / 2) * Scale, Size * Scale / 2.f, aBuf, -1.0f); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->Text(mx * Scale, (my + ToCenterOffset / 2) * Scale, Size * Scale / 2.f, aBuf); } } } } + } + TextRender()->TextColor(TextRender()->DefaultTextColor()); Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } @@ -850,8 +847,11 @@ void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float float Size = g_Config.m_ClTextEntitiesSize / 100.f; float ToCenterOffset = (1 - Size) / 2.f; + char aBuf[16]; + TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); for(int y = StartY; y < EndY; y++) + { for(int x = StartX; x < EndX; x++) { int mx = x; @@ -871,24 +871,19 @@ void CRenderTools::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float unsigned char Index = pSwitch[c].m_Number; if(Index && IsSwitchTileNumberUsed(pSwitch[c].m_Type)) { - char aBuf[16]; str_from_int(Index, aBuf); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - TextRender()->Text(mx * Scale, (my + ToCenterOffset / 2) * Scale, Size * Scale / 2.f, aBuf, -1.0f); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->Text(mx * Scale, (my + ToCenterOffset / 2) * Scale, Size * Scale / 2.f, aBuf); } unsigned char Delay = pSwitch[c].m_Delay; if(Delay && IsSwitchTileDelayUsed(pSwitch[c].m_Type)) { - char aBuf[16]; str_from_int(Delay, aBuf); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - TextRender()->Text(mx * Scale, (my + 0.5f + ToCenterOffset / 2) * Scale, Size * Scale / 2.f, aBuf, -1.0f); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->Text(mx * Scale, (my + 0.5f + ToCenterOffset / 2) * Scale, Size * Scale / 2.f, aBuf); } } - + } + TextRender()->TextColor(TextRender()->DefaultTextColor()); Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } @@ -909,8 +904,11 @@ void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale return; // its useless to render text at this distance float Size = g_Config.m_ClTextEntitiesSize / 100.f; + char aBuf[16]; + TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); for(int y = StartY; y < EndY; y++) + { for(int x = StartX; x < EndX; x++) { int mx = x; @@ -930,14 +928,12 @@ void CRenderTools::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale unsigned char Index = pTune[c].m_Number; if(Index) { - char aBuf[16]; str_from_int(Index, aBuf); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); - TextRender()->Text(mx * Scale + 11.f, my * Scale + 6.f, Size * Scale / 1.5f - 5.f, aBuf, -1.0f); // numbers shouldn't be too big and in the center of the tile - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->Text(mx * Scale + 11.f, my * Scale + 6.f, Size * Scale / 1.5f - 5.f, aBuf); // numbers shouldn't be too big and in the center of the tile } } - + } + TextRender()->TextColor(TextRender()->DefaultTextColor()); Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } From d3ff602f62eaf1d7130ffbaebf9b38eba6925ad3 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 12:29:25 +0100 Subject: [PATCH 179/198] Only include `layer_tiles.h` in `auto_map.cpp` --- src/game/editor/auto_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp index 4f58f8e0fda..09cddebf571 100644 --- a/src/game/editor/auto_map.cpp +++ b/src/game/editor/auto_map.cpp @@ -5,10 +5,10 @@ #include #include +#include #include #include "auto_map.h" -#include "editor.h" // TODO: only needs CLayerTiles #include "editor_actions.h" // Based on triple32inc from https://github.com/skeeto/hash-prospector/tree/79a6074062a84907df6e45b756134b74e2956760 From 4e10d55b36be6fbc5b3859431e1ccbe2074963b7 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 12:57:36 +0100 Subject: [PATCH 180/198] Allow panning outside initial container --- src/game/editor/editor.cpp | 61 ++++++++++++++++++++++++-------------- src/game/editor/editor.h | 1 + 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index d51e09c81d4..09e6b341ece 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -3133,26 +3133,38 @@ void CEditor::DoMapEditor(CUIRect View) MapView()->MapGrid()->OnRender(View); } + if(m_pContainerPanned == &s_pEditorID) + { + // do panning + if((Input()->ModifierIsPressed() && UI()->MouseButton(0)) || UI()->MouseButton(2)) + { + if(Input()->ShiftIsPressed()) + s_Operation = OP_PAN_EDITOR; + else + s_Operation = OP_PAN_WORLD; + } + else + s_Operation = OP_NONE; + + if(s_Operation == OP_PAN_WORLD) + MapView()->OffsetWorld(-vec2(m_MouseDeltaX, m_MouseDeltaY) * m_MouseWScale); + else if(s_Operation == OP_PAN_EDITOR) + MapView()->OffsetEditor(-vec2(m_MouseDeltaX, m_MouseDeltaY) * m_MouseWScale); + + if(s_Operation == OP_NONE) + m_pContainerPanned = nullptr; + } + if(Inside) { UI()->SetHotItem(s_pEditorID); // do global operations like pan and zoom - if(UI()->CheckActiveItem(nullptr) && (UI()->MouseButton(0) || UI()->MouseButton(2))) + if(UI()->CheckActiveItem(nullptr) && m_pContainerPanned == nullptr && ((Input()->ModifierIsPressed() && UI()->MouseButton(0)) || UI()->MouseButton(2))) { s_StartWx = wx; s_StartWy = wy; - - if(Input()->ModifierIsPressed() || UI()->MouseButton(2)) - { - if(Input()->ShiftIsPressed()) - s_Operation = OP_PAN_EDITOR; - else - s_Operation = OP_PAN_WORLD; - UI()->SetActiveItem(s_pEditorID); - } - else - s_Operation = OP_NONE; + m_pContainerPanned = &s_pEditorID; } // brush editing @@ -3516,12 +3528,6 @@ void CEditor::DoMapEditor(CUIRect View) if(UI()->CheckActiveItem(s_pEditorID)) { - // do panning - if(s_Operation == OP_PAN_WORLD) - MapView()->OffsetWorld(-vec2(m_MouseDeltaX, m_MouseDeltaY) * m_MouseWScale); - else if(s_Operation == OP_PAN_EDITOR) - MapView()->OffsetEditor(-vec2(m_MouseDeltaX, m_MouseDeltaY) * m_MouseWScale); - // release mouse if(!UI()->MouseButton(0)) { @@ -6431,15 +6437,25 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ToolBar.VSplitLeft(40.0f, &Button, &ToolBar); UI()->DoLabel(&Button, "Sync.", 10.0f, TEXTALIGN_ML); - if(UI()->MouseInside(&View) && m_Dialog == DIALOG_NONE) + const bool ShouldPan = s_Operation == EEnvelopeEditorOp::OP_NONE && (UI()->MouseButton(2) || (UI()->MouseButton(0) && Input()->ModifierIsPressed())); + if(m_pContainerPanned == &s_EnvelopeEditorID) { - UI()->SetHotItem(&s_EnvelopeEditorID); - - if(s_Operation == EEnvelopeEditorOp::OP_NONE && (UI()->MouseButton(2) || (UI()->MouseButton(0) && Input()->ModifierIsPressed()))) + if(!ShouldPan) + m_pContainerPanned = nullptr; + else { m_OffsetEnvelopeX += UI()->MouseDeltaX() / Graphics()->ScreenWidth() * UI()->Screen()->w / View.w; m_OffsetEnvelopeY -= UI()->MouseDeltaY() / Graphics()->ScreenHeight() * UI()->Screen()->h / View.h; } + } + + if(UI()->MouseInside(&View) && m_Dialog == DIALOG_NONE) + { + UI()->SetHotItem(&s_EnvelopeEditorID); + + if(ShouldPan && m_pContainerPanned == nullptr) + m_pContainerPanned = &s_EnvelopeEditorID; + if(Input()->KeyPress(KEY_KP_MULTIPLY)) ResetZoomEnvelope(pEnvelope, s_ActiveChannels); if(Input()->ShiftIsPressed()) @@ -8302,6 +8318,7 @@ void CEditor::Reset(bool CreateDefault) m_MouseDeltaY = 0; m_MouseDeltaWx = 0; m_MouseDeltaWy = 0; + m_pContainerPanned = nullptr; m_Map.m_Modified = false; m_Map.m_ModifiedAuto = false; diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 1f077de91b3..3a3cc6f5211 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -680,6 +680,7 @@ class CEditor : public IEditor float m_MouseDeltaY; float m_MouseDeltaWx; float m_MouseDeltaWy; + void *m_pContainerPanned; enum EShowTile { From 6cc42aa58cb8aa16be85010cf210e9116e4f93d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 26 Dec 2023 21:52:27 +0100 Subject: [PATCH 181/198] Fix slightly incorrect color components shown in color pickers The individual color components were rounded down when being displayed on their own in color pickers, whereas the components are rounded to the nearest integer when packing the colors into the hex string. This was causing minor discrepancies between the color being displayed/saved as hex and the individual components. Rounding the components when packing is necessary to reduce the error when converting between color spaces. --- src/game/client/ui.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 65618adc512..587a1542f7a 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -1825,10 +1825,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View // Editboxes Area if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_HSVA) { - const unsigned OldH = (unsigned)(PickerColorHSV.h * 255.0f); - const unsigned OldS = (unsigned)(PickerColorHSV.s * 255.0f); - const unsigned OldV = (unsigned)(PickerColorHSV.v * 255.0f); - const unsigned OldA = (unsigned)(PickerColorHSV.a * 255.0f); + const unsigned OldH = round_to_int(PickerColorHSV.h * 255.0f); + const unsigned OldS = round_to_int(PickerColorHSV.s * 255.0f); + const unsigned OldV = round_to_int(PickerColorHSV.v * 255.0f); + const unsigned OldA = round_to_int(PickerColorHSV.a * 255.0f); const auto [StateH, H] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); const auto [StateS, S] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); @@ -1853,10 +1853,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View } else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_RGBA) { - const unsigned OldR = (unsigned)(PickerColorRGB.r * 255.0f); - const unsigned OldG = (unsigned)(PickerColorRGB.g * 255.0f); - const unsigned OldB = (unsigned)(PickerColorRGB.b * 255.0f); - const unsigned OldA = (unsigned)(PickerColorRGB.a * 255.0f); + const unsigned OldR = round_to_int(PickerColorRGB.r * 255.0f); + const unsigned OldG = round_to_int(PickerColorRGB.g * 255.0f); + const unsigned OldB = round_to_int(PickerColorRGB.b * 255.0f); + const unsigned OldA = round_to_int(PickerColorRGB.a * 255.0f); const auto [StateR, R] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "R:", OldR, 0, 255); const auto [StateG, G] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "G:", OldG, 0, 255); @@ -1881,10 +1881,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View } else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_HSLA) { - const unsigned OldH = (unsigned)(PickerColorHSL.h * 255.0f); - const unsigned OldS = (unsigned)(PickerColorHSL.s * 255.0f); - const unsigned OldL = (unsigned)(PickerColorHSL.l * 255.0f); - const unsigned OldA = (unsigned)(PickerColorHSL.a * 255.0f); + const unsigned OldH = round_to_int(PickerColorHSL.h * 255.0f); + const unsigned OldS = round_to_int(PickerColorHSL.s * 255.0f); + const unsigned OldL = round_to_int(PickerColorHSL.l * 255.0f); + const unsigned OldA = round_to_int(PickerColorHSL.a * 255.0f); const auto [StateH, H] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); const auto [StateS, S] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); From 32373c05592118bc2aeae597eb535e8af9032cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 26 Dec 2023 22:03:37 +0100 Subject: [PATCH 182/198] Fix color rendering being inconsistent with displayed color values When normalizing color components in the engine graphics, round the components to the nearest integer instead of rounding down. Otherwise the color that is rendered in color pickers may be off by 1 in any of its RGB components from the color that the color picker displays as text (hex string and individual components). The slightly incorrect color can be confirmed by creating a screenshot or otherwise reading the backbuffer (planned editor pipette feature). This should not change map rendering, since maps already store quantized RGBA values on which the rounding mode should have no effect. It may however slightly change appearance of colors in all other places (at most +1 in every RGB component). --- src/engine/client/graphics_threaded.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index dacd8803246..64d2885ee45 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -893,7 +893,7 @@ void CGraphics_Threaded::QuadsSetRotation(float Angle) static unsigned char NormalizeColorComponent(float ColorComponent) { - return (unsigned char)(clamp(ColorComponent, 0.0f, 1.0f) * 255.0f); + return (unsigned char)(clamp(ColorComponent, 0.0f, 1.0f) * 255.0f + 0.5f); // +0.5f to round to nearest } void CGraphics_Threaded::SetColorVertex(const CColorVertex *pArray, size_t Num) From 84c216865069d16ea2f343295f78008063a0f160 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 15:23:57 +0100 Subject: [PATCH 183/198] Differentiate between normal and checkpoint teles --- src/game/client/components/maplayers.cpp | 2 +- src/game/client/render_map.cpp | 2 +- src/game/editor/editor.cpp | 47 ++++++++------- src/game/editor/editor.h | 12 ++-- src/game/editor/editor_props.cpp | 34 ++++++----- src/game/editor/mapitems/layer_tele.cpp | 24 +++++--- src/game/editor/mapitems/layer_tele.h | 3 +- src/game/editor/mapitems/layer_tiles.cpp | 10 +++- src/game/editor/popups.cpp | 74 ++++++++++++++++-------- src/game/mapitems.cpp | 15 ++++- src/game/mapitems.h | 4 +- 11 files changed, 148 insertions(+), 79 deletions(-) diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp index 191e613a9f8..ab47be47299 100644 --- a/src/game/client/components/maplayers.cpp +++ b/src/game/client/components/maplayers.cpp @@ -581,7 +581,7 @@ void CMapLayers::OnMapLoad() Flags = 0; if(CurOverlay == 1) { - if(IsTeleTileNumberUsed(Index)) + if(IsTeleTileNumberUsedAny(Index)) Index = ((CTeleTile *)pTiles)[y * pTMap->m_Width + x].m_Number; else Index = 0; diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp index 48a511965df..3f171cae3f5 100644 --- a/src/game/client/render_map.cpp +++ b/src/game/client/render_map.cpp @@ -746,7 +746,7 @@ void CRenderTools::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale int c = mx + my * w; unsigned char Index = pTele[c].m_Number; - if(Index && IsTeleTileNumberUsed(pTele[c].m_Type)) + if(Index && IsTeleTileNumberUsedAny(pTele[c].m_Type)) { str_from_int(Index, aBuf); // Auto-resize text to fit inside the tile diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index d51e09c81d4..a285ae7a4a9 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -330,7 +330,7 @@ void CEditor::RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture, Graphics()->QuadsEnd(); } -SEditResult CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree, bool IsHex, int Corners, ColorRGBA *pColor, bool ShowValue) +SEditResult CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree, bool IsHex, int Corners, const ColorRGBA *pColor, bool ShowValue) { // logic static float s_Value; @@ -1326,7 +1326,7 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) { pButtonName = "Tele"; pfnPopupFunc = PopupTele; - Rows = 1; + Rows = 2; } if(pButtonName != nullptr) @@ -8929,7 +8929,8 @@ void CEditor::AdjustBrushSpecialTiles(bool UseNextFree, int Adjust) // Only handle tele, switch and tune layers if(pLayerTiles->m_Tele) { - int NextFreeNumber = FindNextFreeTileNumber(LAYERTYPE_TELE); + int NextFreeTeleNumber = FindNextFreeTeleNumber(); + int NextFreeCPNumber = FindNextFreeTeleNumber(true); std::shared_ptr pTeleLayer = std::static_pointer_cast(pLayer); for(int y = 0; y < pTeleLayer->m_Height; y++) @@ -8941,7 +8942,12 @@ void CEditor::AdjustBrushSpecialTiles(bool UseNextFree, int Adjust) continue; if(UseNextFree) - pTeleLayer->m_pTeleTile[i].m_Number = NextFreeNumber; + { + if(IsTeleTileCheckpoint(pTeleLayer->m_pTiles[i].m_Index)) + pTeleLayer->m_pTeleTile[i].m_Number = NextFreeCPNumber; + else + pTeleLayer->m_pTeleTile[i].m_Number = NextFreeTeleNumber; + } else AdjustNumber(pTeleLayer->m_pTeleTile[i].m_Number); } @@ -8967,7 +8973,7 @@ void CEditor::AdjustBrushSpecialTiles(bool UseNextFree, int Adjust) } else if(pLayerTiles->m_Switch) { - int NextFreeNumber = FindNextFreeTileNumber(LAYERTYPE_SWITCH); + int NextFreeNumber = FindNextFreeSwitchNumber(); std::shared_ptr pSwitchLayer = std::static_pointer_cast(pLayer); for(int y = 0; y < pSwitchLayer->m_Height; y++) @@ -8988,29 +8994,30 @@ void CEditor::AdjustBrushSpecialTiles(bool UseNextFree, int Adjust) } } -int CEditor::FindNextFreeTileNumber(int Type) +int CEditor::FindNextFreeSwitchNumber() { int Number = -1; - if(Type == LAYERTYPE_TELE) + + for(int i = 1; i <= 255; i++) { - for(int i = 1; i <= 255; i++) + if(!m_Map.m_pSwitchLayer->ContainsElementWithId(i)) { - if(!m_Map.m_pTeleLayer->ContainsElementWithId(i)) - { - Number = i; - break; - } + Number = i; + break; } } - else if(Type == LAYERTYPE_SWITCH) + return Number; +} + +int CEditor::FindNextFreeTeleNumber(bool IsCheckpoint) +{ + int Number = -1; + for(int i = 1; i <= 255; i++) { - for(int i = 1; i <= 255; i++) + if(!m_Map.m_pTeleLayer->ContainsElementWithId(i, IsCheckpoint)) { - if(!m_Map.m_pSwitchLayer->ContainsElementWithId(i)) - { - Number = i; - break; - } + Number = i; + break; } } return Number; diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 1f077de91b3..983f86c04dc 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -300,6 +300,7 @@ class CEditor : public IEditor }; std::shared_ptr m_apSavedBrushes[10]; + static const ColorRGBA ms_DefaultPropColor; public: class IInput *Input() { return m_pInput; } @@ -403,6 +404,7 @@ class CEditor : public IEditor // DDRace m_TeleNumber = 1; + m_TeleCheckpointNumber = 1; m_SwitchNum = 1; m_TuningNum = 1; m_SwitchDelay = 0; @@ -499,8 +501,8 @@ class CEditor : public IEditor std::pair EnvGetSelectedTimeAndValue() const; template - SEditResult DoPropertiesWithState(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f)); - int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f)); + SEditResult DoPropertiesWithState(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, const std::vector &vColors = {}); + int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, const std::vector &vColors = {}); CUI::SColorPickerPopupContext m_ColorPickerPopupContext; const void *m_pColorPickerPopupActiveID = nullptr; @@ -785,7 +787,7 @@ class CEditor : public IEditor void RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture, float Size, float Brightness); - SEditResult UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree = false, bool IsHex = false, int corners = IGraphics::CORNER_ALL, ColorRGBA *pColor = nullptr, bool ShowValue = true); + SEditResult UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree = false, bool IsHex = false, int corners = IGraphics::CORNER_ALL, const ColorRGBA *pColor = nullptr, bool ShowValue = true); static CUI::EPopupMenuFunctionResult PopupMenuFile(void *pContext, CUIRect View, bool Active); static CUI::EPopupMenuFunctionResult PopupMenuTools(void *pContext, CUIRect View, bool Active); @@ -1051,6 +1053,7 @@ class CEditor : public IEditor IGraphics::CTextureHandle GetTuneTexture(); unsigned char m_TeleNumber; + unsigned char m_TeleCheckpointNumber; unsigned char m_TuningNum; @@ -1062,7 +1065,8 @@ class CEditor : public IEditor unsigned char m_SwitchDelay; void AdjustBrushSpecialTiles(bool UseNextFree, int Adjust = 0); - int FindNextFreeTileNumber(int Type); + int FindNextFreeSwitchNumber(); + int FindNextFreeTeleNumber(bool IsCheckpoint = false); public: // Undo/Redo diff --git a/src/game/editor/editor_props.cpp b/src/game/editor/editor_props.cpp index 7d57b9c8f2f..9f42409c3cb 100644 --- a/src/game/editor/editor_props.cpp +++ b/src/game/editor/editor_props.cpp @@ -3,20 +3,24 @@ #include #include -int CEditor::DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color) +const ColorRGBA CEditor::ms_DefaultPropColor = ColorRGBA(1, 1, 1, 0.5f); + +int CEditor::DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, const std::vector &vColors) { - auto Res = DoPropertiesWithState(pToolbox, pProps, pIDs, pNewVal, Color); + auto Res = DoPropertiesWithState(pToolbox, pProps, pIDs, pNewVal, vColors); return Res.m_Value; } template -SEditResult CEditor::DoPropertiesWithState(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color) +SEditResult CEditor::DoPropertiesWithState(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *pNewVal, const std::vector &vColors) { int Change = -1; EEditState State = EEditState::EDITING; for(int i = 0; pProps[i].m_pName; i++) { + const ColorRGBA *pColor = i >= (int)vColors.size() ? &ms_DefaultPropColor : &vColors[i]; + CUIRect Slot; pToolBox->HSplitTop(13.0f, &Slot, pToolBox); CUIRect Label, Shifter; @@ -32,7 +36,7 @@ SEditResult CEditor::DoPropertiesWithState(CUIRect *pToolBox, CProperty *pPro Shifter.VSplitRight(10.0f, &Shifter, &Inc); Shifter.VSplitLeft(10.0f, &Dec, &Shifter); str_from_int(pProps[i].m_Value, aBuf); - auto NewValueRes = UiDoValueSelector((char *)&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0, &Color); + auto NewValueRes = UiDoValueSelector((char *)&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0, pColor); int NewValue = NewValueRes.m_Value; if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING) { @@ -269,14 +273,14 @@ SEditResult CEditor::DoPropertiesWithState(CUIRect *pToolBox, CProperty *pPro return SEditResult{State, static_cast(Change)}; } -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); -template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); +template SEditResult CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector &); diff --git a/src/game/editor/mapitems/layer_tele.cpp b/src/game/editor/mapitems/layer_tele.cpp index a4faf327746..fbc621dbc16 100644 --- a/src/game/editor/mapitems/layer_tele.cpp +++ b/src/game/editor/mapitems/layer_tele.cpp @@ -98,23 +98,28 @@ void CLayerTele::BrushDraw(std::shared_ptr pBrush, float wx, float wy) if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTeleTile(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) && pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index != TILE_AIR) { - if(!IsTeleTileNumberUsed(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) + bool IsCheckpoint = IsTeleTileCheckpoint(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index); + if(!IsCheckpoint && !IsTeleTileNumberUsed(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index, false)) { // Tele tile number is unused. Set a known value which is not 0, // as tiles with number 0 would be ignored by previous versions. m_pTeleTile[Index].m_Number = 255; } - else if(m_pEditor->m_TeleNumber != pTeleLayer->m_TeleNum) + else if(!IsCheckpoint && m_pEditor->m_TeleNumber != pTeleLayer->m_TeleNum) { m_pTeleTile[Index].m_Number = m_pEditor->m_TeleNumber; } + else if(IsCheckpoint && m_pEditor->m_TeleCheckpointNumber != pTeleLayer->m_TeleCheckpointNum) + { + m_pTeleTile[Index].m_Number = m_pEditor->m_TeleCheckpointNumber; + } else if(pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number) { m_pTeleTile[Index].m_Number = pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number; } else { - if(!m_pEditor->m_TeleNumber) + if((!IsCheckpoint && !m_pEditor->m_TeleNumber) || (IsCheckpoint && !m_pEditor->m_TeleCheckpointNumber)) { m_pTeleTile[Index].m_Number = 0; m_pTeleTile[Index].m_Type = 0; @@ -130,7 +135,7 @@ void CLayerTele::BrushDraw(std::shared_ptr pBrush, float wx, float wy) } else { - m_pTeleTile[Index].m_Number = m_pEditor->m_TeleNumber; + m_pTeleTile[Index].m_Number = IsCheckpoint ? m_pEditor->m_TeleCheckpointNumber : m_pEditor->m_TeleNumber; } } @@ -267,15 +272,18 @@ void CLayerTele::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRe if(pLt->m_Tele && m_pTiles[TgtIndex].m_Index > 0) { m_pTeleTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index; + bool IsCheckpoint = IsTeleTileCheckpoint(m_pTiles[TgtIndex].m_Index); - if(!IsTeleTileNumberUsed(m_pTeleTile[TgtIndex].m_Type)) + if(!IsCheckpoint && !IsTeleTileNumberUsed(m_pTeleTile[TgtIndex].m_Type, false)) { // Tele tile number is unused. Set a known value which is not 0, // as tiles with number 0 would be ignored by previous versions. m_pTeleTile[TgtIndex].m_Number = 255; } - else if((pLt->m_pTeleTile[SrcIndex].m_Number == 0 && m_pEditor->m_TeleNumber) || m_pEditor->m_TeleNumber != pLt->m_TeleNum) + else if(!IsCheckpoint && ((pLt->m_pTeleTile[SrcIndex].m_Number == 0 && m_pEditor->m_TeleNumber) || m_pEditor->m_TeleNumber != pLt->m_TeleNum)) m_pTeleTile[TgtIndex].m_Number = m_pEditor->m_TeleNumber; + else if(IsCheckpoint && ((pLt->m_pTeleTile[SrcIndex].m_Number == 0 && m_pEditor->m_TeleCheckpointNumber) || m_pEditor->m_TeleCheckpointNumber != pLt->m_TeleCheckpointNum)) + m_pTeleTile[TgtIndex].m_Number = m_pEditor->m_TeleCheckpointNumber; else m_pTeleTile[TgtIndex].m_Number = pLt->m_pTeleTile[SrcIndex].m_Number; } @@ -292,13 +300,13 @@ void CLayerTele::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRe FlagModified(sx, sy, w, h); } -bool CLayerTele::ContainsElementWithId(int Id) +bool CLayerTele::ContainsElementWithId(int Id, bool Checkpoint) { for(int y = 0; y < m_Height; ++y) { for(int x = 0; x < m_Width; ++x) { - if(IsTeleTileNumberUsed(m_pTeleTile[y * m_Width + x].m_Type) && m_pTeleTile[y * m_Width + x].m_Number == Id) + if(IsTeleTileNumberUsed(m_pTeleTile[y * m_Width + x].m_Type, Checkpoint) && m_pTeleTile[y * m_Width + x].m_Number == Id) { return true; } diff --git a/src/game/editor/mapitems/layer_tele.h b/src/game/editor/mapitems/layer_tele.h index 569dd305e47..9c652ff21f1 100644 --- a/src/game/editor/mapitems/layer_tele.h +++ b/src/game/editor/mapitems/layer_tele.h @@ -23,6 +23,7 @@ class CLayerTele : public CLayerTiles CTeleTile *m_pTeleTile; unsigned char m_TeleNum; + unsigned char m_TeleCheckpointNum; void Resize(int NewW, int NewH) override; void Shift(int Direction) override; @@ -32,7 +33,7 @@ class CLayerTele : public CLayerTiles void BrushFlipY() override; void BrushRotate(float Amount) override; void FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) override; - virtual bool ContainsElementWithId(int Id); + virtual bool ContainsElementWithId(int Id, bool Checkpoint); EditorTileStateChangeHistory m_History; inline void ClearHistory() override diff --git a/src/game/editor/mapitems/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp index c134a78e7bb..188cf82fb89 100644 --- a/src/game/editor/mapitems/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -308,12 +308,18 @@ int CLayerTiles::BrushGrab(std::shared_ptr pBrush, CUIRect Rect) for(int x = 0; x < r.w; x++) { pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x] = static_cast(this)->m_pTeleTile[(r.y + y) * m_Width + (r.x + x)]; - if(IsValidTeleTile(pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Type) && IsTeleTileNumberUsed(pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Type)) + if(IsValidTeleTile(pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Type)) { - m_pEditor->m_TeleNumber = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number; + if(IsTeleTileNumberUsed(pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Type, false)) + m_pEditor->m_TeleNumber = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number; + else if(IsTeleTileNumberUsed(pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Type, true)) + m_pEditor->m_TeleCheckpointNumber = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number; } } + pGrabbed->m_TeleNum = m_pEditor->m_TeleNumber; + pGrabbed->m_TeleCheckpointNum = m_pEditor->m_TeleCheckpointNumber; + str_copy(pGrabbed->m_aFileName, m_pEditor->m_aFileName); } else if(this->m_Speedup) diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 879760ccefd..53d53b4fdc6 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -2415,58 +2415,82 @@ CUI::EPopupMenuFunctionResult CEditor::PopupTele(void *pContext, CUIRect View, b { CEditor *pEditor = static_cast(pContext); - static int s_PreviousNumber = -1; + static int s_PreviousTeleNumber; + static int s_PreviousCheckpointNumber; CUIRect NumberPicker; CUIRect FindEmptySlot; + CUIRect FindFreeTeleSlot, FindFreeCheckpointSlot; View.VSplitRight(15.f, &NumberPicker, &FindEmptySlot); NumberPicker.VSplitRight(2.f, &NumberPicker, nullptr); - FindEmptySlot.HSplitTop(13.0f, &FindEmptySlot, nullptr); - FindEmptySlot.HMargin(1.0f, &FindEmptySlot); - // find empty number button + FindEmptySlot.HSplitTop(13.0f, &FindFreeTeleSlot, &FindEmptySlot); + FindEmptySlot.HSplitTop(13.0f, &FindFreeCheckpointSlot, &FindEmptySlot); + + FindFreeTeleSlot.HMargin(1.0f, &FindFreeTeleSlot); + FindFreeCheckpointSlot.HMargin(1.0f, &FindFreeCheckpointSlot); + + // find next free numbers buttons { - static int s_EmptySlotPid = 0; - if(pEditor->DoButton_Editor(&s_EmptySlotPid, "F", 0, &FindEmptySlot, 0, "[ctrl+f] Find empty slot") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) + // Pressing ctrl+f will find next free numbers for both tele and checkpoints + + static int s_NextFreeTelePid = 0; + if(pEditor->DoButton_Editor(&s_NextFreeTelePid, "F", 0, &FindFreeTeleSlot, 0, "[ctrl+f] Find next free tele number") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) { - int Number = pEditor->FindNextFreeTileNumber(LAYERTYPE_TELE); + int TeleNumber = pEditor->FindNextFreeTeleNumber(); - if(Number != -1) - pEditor->m_TeleNumber = Number; + if(TeleNumber != -1) + pEditor->m_TeleNumber = TeleNumber; + } + + static int s_NextFreeCheckpointPid = 0; + if(pEditor->DoButton_Editor(&s_NextFreeCheckpointPid, "F", 0, &FindFreeCheckpointSlot, 0, "[ctrl+f] Find next free checkpoint number") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) + { + int CPNumber = pEditor->FindNextFreeTeleNumber(true); + + if(CPNumber != -1) + pEditor->m_TeleCheckpointNumber = CPNumber; } } // number picker { - static ColorRGBA s_Color = ColorRGBA(0.5f, 1, 0.5f, 0.5f); + static std::vector s_vColors = { + ColorRGBA(0.5f, 1, 0.5f, 0.5f), + ColorRGBA(0.5f, 1, 0.5f, 0.5f), + }; enum { PROP_TELE = 0, + PROP_TELE_CP = 1, NUM_PROPS, }; CProperty aProps[] = { {"Number", pEditor->m_TeleNumber, PROPTYPE_INT_STEP, 1, 255}, + {"Checkpoint", pEditor->m_TeleCheckpointNumber, PROPTYPE_INT_STEP, 1, 255}, {nullptr}, }; static int s_aIds[NUM_PROPS] = {0}; static int NewVal = 0; - int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_Color); + int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_vColors); if(Prop == PROP_TELE) - { pEditor->m_TeleNumber = (NewVal - 1 + 255) % 255 + 1; - } + else if(Prop == PROP_TELE_CP) + pEditor->m_TeleCheckpointNumber = (NewVal - 1 + 255) % 255 + 1; - if(s_PreviousNumber == 1 || s_PreviousNumber != pEditor->m_TeleNumber) - { - s_Color = pEditor->m_Map.m_pTeleLayer->ContainsElementWithId(pEditor->m_TeleNumber) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); - } + if(s_PreviousTeleNumber == 1 || s_PreviousTeleNumber != pEditor->m_TeleNumber) + s_vColors[PROP_TELE] = pEditor->m_Map.m_pTeleLayer->ContainsElementWithId(pEditor->m_TeleNumber, false) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); + + if(s_PreviousCheckpointNumber == 1 || s_PreviousCheckpointNumber != pEditor->m_TeleCheckpointNumber) + s_vColors[PROP_TELE_CP] = pEditor->m_Map.m_pTeleLayer->ContainsElementWithId(pEditor->m_TeleCheckpointNumber, true) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); } - s_PreviousNumber = pEditor->m_TeleNumber; + s_PreviousTeleNumber = pEditor->m_TeleNumber; + s_PreviousCheckpointNumber = pEditor->m_TeleCheckpointNumber; return CUI::POPUP_KEEP_OPEN; } @@ -2526,7 +2550,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, static int s_EmptySlotPid = 0; if(pEditor->DoButton_Editor(&s_EmptySlotPid, "F", 0, &FindEmptySlot, 0, "[ctrl+f] Find empty slot") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) { - int Number = pEditor->FindNextFreeTileNumber(LAYERTYPE_SWITCH); + int Number = pEditor->FindNextFreeSwitchNumber(); if(Number != -1) pEditor->m_SwitchNum = Number; @@ -2536,7 +2560,9 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, // number picker static int s_PreviousNumber = -1; { - static ColorRGBA s_Color = ColorRGBA(1, 1, 1, 0.5f); + static std::vector s_vColors = { + ColorRGBA(1, 1, 1, 0.5f), + }; enum { @@ -2553,7 +2579,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, static int s_aIds[NUM_PROPS] = {0}; int NewVal = 0; - int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_Color); + int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_vColors); if(Prop == PROP_SWITCH_NUMBER) { @@ -2565,9 +2591,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, } if(s_PreviousNumber == 1 || s_PreviousNumber != pEditor->m_SwitchNum) - { - s_Color = pEditor->m_Map.m_pSwitchLayer->ContainsElementWithId(pEditor->m_SwitchNum) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); - } + s_vColors[PROP_SWITCH_NUMBER] = pEditor->m_Map.m_pSwitchLayer->ContainsElementWithId(pEditor->m_SwitchNum) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); } s_PreviousNumber = pEditor->m_SwitchNum; @@ -2622,7 +2646,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGoto(void *pContext, CUIRect View, b static int s_aIds[NUM_PROPS] = {0}; int NewVal = 0; - int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal, ColorRGBA(1, 1, 1, 0.5f)); + int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop == PROP_COORD_X) { diff --git a/src/game/mapitems.cpp b/src/game/mapitems.cpp index 2ccab306703..2abd7468e3f 100644 --- a/src/game/mapitems.cpp +++ b/src/game/mapitems.cpp @@ -57,7 +57,20 @@ bool IsValidTeleTile(int Index) Index == TILE_TELECHECKINEVIL); } -bool IsTeleTileNumberUsed(int Index) +bool IsTeleTileCheckpoint(int Index) +{ + return Index == TILE_TELECHECK || Index == TILE_TELECHECKOUT; +} + +bool IsTeleTileNumberUsed(int Index, bool Checkpoint) +{ + if(Checkpoint) + return IsTeleTileCheckpoint(Index); + return !IsTeleTileCheckpoint(Index) && Index != TILE_TELECHECKIN && + Index != TILE_TELECHECKINEVIL; +} + +bool IsTeleTileNumberUsedAny(int Index) { return Index != TILE_TELECHECKIN && Index != TILE_TELECHECKINEVIL; diff --git a/src/game/mapitems.h b/src/game/mapitems.h index 54940501eb3..9c8d464ff58 100644 --- a/src/game/mapitems.h +++ b/src/game/mapitems.h @@ -571,7 +571,9 @@ class CTuneTile bool IsValidGameTile(int Index); bool IsValidFrontTile(int Index); bool IsValidTeleTile(int Index); -bool IsTeleTileNumberUsed(int Index); // Assumes that Index is a valid tele tile index +bool IsTeleTileCheckpoint(int Index); // Assumes that Index is a valid tele tile index +bool IsTeleTileNumberUsed(int Index, bool Checkpoint); // Assumes that Index is a valid tele tile index +bool IsTeleTileNumberUsedAny(int Index); // Does not check for checkpoint only bool IsValidSpeedupTile(int Index); bool IsValidSwitchTile(int Index); bool IsSwitchTileFlagsUsed(int Index); // Assumes that Index is a valid switch tile index From 42771ac42fcc059759156b7bfbabfc0cf04fef51 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 02:23:26 +0100 Subject: [PATCH 184/198] Refactor argument autocompletion in console Allows autocompletion of specific argument instead of the first argument only --- src/game/client/components/console.cpp | 118 +++++++++++++++++++------ src/game/client/components/console.h | 1 + 2 files changed, 94 insertions(+), 25 deletions(-) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index f5e2065eecc..9131c8f07b4 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -73,11 +73,59 @@ void CConsoleLogger::OnConsoleDeletion() m_pConsole = nullptr; } -// TODO: support "tune_zone", which has tuning as second argument -static const char *gs_apTuningCommands[] = {"tune ", "tune_reset ", "toggle_tune "}; -static bool IsTuningCommandPrefix(const char *pStr) +static int ArgumentPosition(const char *pStr, const std::vector> &vCommands) { - return std::any_of(std::begin(gs_apTuningCommands), std::end(gs_apTuningCommands), [pStr](auto *pCmd) { return str_startswith_nocase(pStr, pCmd); }); + const char *pCommandStart = pStr; + const char *pIt = pStr; + pIt = str_skip_to_whitespace_const(pIt); + int CommandLength = pIt - pCommandStart; + const char *pCommandEnd = pIt; + + if(!CommandLength) + return -1; + + pIt = str_skip_whitespaces_const(pIt); + if(pIt == pCommandEnd) + return -1; + + for(const auto &[pCommand, ArgIndex] : vCommands) + { + int Length = maximum(str_length(pCommand), CommandLength); + if(str_comp_nocase_num(pCommand, pCommandStart, Length) == 0) + { + int CurrentArg = 0; + const char *pArgStart = nullptr, *pArgEnd = nullptr; + while(CurrentArg < ArgIndex) + { + pArgStart = pIt; + pIt = str_skip_to_whitespace_const(pIt); // Skip argument value + pArgEnd = pIt; + + if(!pIt[0] || pArgStart == pIt) // Check that argument is not empty + return -1; + + pIt = str_skip_whitespaces_const(pIt); // Go to next argument position + CurrentArg++; + } + if(pIt == pArgEnd) + return -1; // Check that there is at least one space after + return pIt - pStr; + } + } + return -1; +} + +// Vector of pair, where each pair is +static const std::vector> gs_vTuningCommands{ + {"tune", 0}, + {"tune_reset", 0}, + {"toggle_tune", 0}, + {"tune_zone", 1}, +}; +// Returns the position of the start of the autocompletion, or -1 +static int TuningCommandArgumentPos(const char *pStr) +{ + return ArgumentPosition(pStr, gs_vTuningCommands); } static int PossibleTunings(const char *pStr, IConsole::FPossibleCallback pfnCallback = IConsole::EmptyPossibleCommandCallback, void *pUser = nullptr) @@ -94,10 +142,15 @@ static int PossibleTunings(const char *pStr, IConsole::FPossibleCallback pfnCall return Index; } -static const char *gs_apSettingCommands[] = {"reset ", "toggle ", "access_level ", "+toggle "}; -static bool IsSettingCommandPrefix(const char *pStr) +static const std::vector> gs_vSettingCommands{ + {"reset", 0}, + {"toggle", 0}, + {"access_level", 0}, + {"+toggle", 0}, +}; +static int SettingCommandArgumentPos(const char *pStr) { - return std::any_of(std::begin(gs_apSettingCommands), std::end(gs_apSettingCommands), [pStr](auto *pCmd) { return str_startswith_nocase(pStr, pCmd); }); + return ArgumentPosition(pStr, gs_vSettingCommands); } const ColorRGBA CGameConsole::ms_SearchHighlightColor = ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f); @@ -124,6 +177,7 @@ CGameConsole::CInstance::CInstance(int Type) m_CompletionChosen = -1; m_aCompletionBufferArgument[0] = 0; m_CompletionChosenArgument = -1; + m_CompletionArgumentPosition = 0; Reset(); m_aUser[0] = '\0'; @@ -205,6 +259,7 @@ void CGameConsole::CInstance::Reset() m_pCommandName = ""; m_pCommandHelp = ""; m_pCommandParams = ""; + m_CompletionArgumentPosition = 0; } void CGameConsole::CInstance::ExecuteLine(const char *pLine) @@ -251,7 +306,7 @@ void CGameConsole::CInstance::PossibleArgumentsCompleteCallback(int Index, const { // get command char aBuf[IConsole::CMDLINE_LENGTH]; - StrCopyUntilSpace(aBuf, sizeof(aBuf), pInstance->GetString()); + str_copy(aBuf, pInstance->GetString(), pInstance->m_CompletionArgumentPosition); str_append(aBuf, " "); // append argument @@ -369,6 +424,7 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) if(m_CompletionChosen == -1 && Direction < 0) m_CompletionChosen = 0; m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; + m_CompletionArgumentPosition = 0; m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this); } else if(m_CompletionChosen != -1) @@ -379,11 +435,11 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) } // Argument completion - const bool TuningCompletion = IsTuningCommandPrefix(GetString()); - const bool SettingCompletion = IsSettingCommandPrefix(GetString()); - if(TuningCompletion) + const int SettingCompletionPos = SettingCommandArgumentPos(GetString()); + const int TuningCompletionPos = TuningCommandArgumentPos(GetString()); + if(TuningCompletionPos != -1) CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument); - else if(SettingCompletion) + else if(SettingCompletionPos != -1) CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands); if(CompletionEnumerationCount) @@ -391,10 +447,16 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) if(m_CompletionChosenArgument == -1 && Direction < 0) m_CompletionChosenArgument = 0; m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; - if(TuningCompletion && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE) + if(TuningCompletionPos != -1 && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE) + { + m_CompletionArgumentPosition = TuningCompletionPos; PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this); - else if(SettingCompletion) + } + else if(SettingCompletionPos != -1) + { + m_CompletionArgumentPosition = SettingCompletionPos; m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this); + } } else if(m_CompletionChosenArgument != -1) { @@ -467,21 +529,27 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) m_CompletionChosen = -1; str_copy(m_aCompletionBuffer, m_Input.GetString()); - for(const auto *pCmd : gs_apTuningCommands) + const char *pInputStr = m_Input.GetString(); + + const int TuningCompletionPos = TuningCommandArgumentPos(GetString()); + for(const auto &[pCmd, _] : gs_vTuningCommands) { - if(str_startswith_nocase(m_Input.GetString(), pCmd)) + const int Len = str_length(pCmd); + if(str_comp_nocase_num(pInputStr, pCmd, Len) == 0 && str_isspace(pInputStr[Len])) { m_CompletionChosenArgument = -1; - str_copy(m_aCompletionBufferArgument, &m_Input.GetString()[str_length(pCmd)]); + str_copy(m_aCompletionBufferArgument, &pInputStr[TuningCompletionPos]); } } - for(const auto *pCmd : gs_apSettingCommands) + const int SettingCompletionPos = SettingCommandArgumentPos(GetString()); + for(const auto &[pCmd, _] : gs_vSettingCommands) { - if(str_startswith_nocase(m_Input.GetString(), pCmd)) + const int Len = str_length(pCmd); + if(str_comp_nocase_num(pInputStr, pCmd, Len) == 0 && str_isspace(pInputStr[Len])) { m_CompletionChosenArgument = -1; - str_copy(m_aCompletionBufferArgument, &m_Input.GetString()[str_length(pCmd)]); + str_copy(m_aCompletionBufferArgument, &pInputStr[SettingCompletionPos]); } } @@ -1007,17 +1075,17 @@ void CGameConsole::OnRender() if(NumCommands <= 0 && pConsole->m_IsCommand) { - const bool TuningCompletion = IsTuningCommandPrefix(Info.m_pCurrentCmd); - const bool SettingCompletion = IsSettingCommandPrefix(Info.m_pCurrentCmd); + const int TuningCompletionPos = TuningCommandArgumentPos(Info.m_pCurrentCmd); + const int SettingCompletionPos = SettingCommandArgumentPos(Info.m_pCurrentCmd); int NumArguments = 0; - if(TuningCompletion || SettingCompletion) + if(TuningCompletionPos != -1 || SettingCompletionPos != -1) { Info.m_WantedCompletion = pConsole->m_CompletionChosenArgument; Info.m_TotalWidth = 0.0f; Info.m_pCurrentCmd = pConsole->m_aCompletionBufferArgument; - if(TuningCompletion) + if(TuningCompletionPos != -1) NumArguments = PossibleTunings(Info.m_pCurrentCmd, PossibleCommandsRenderCallback, &Info); - else if(SettingCompletion) + else if(SettingCompletionPos != -1) NumArguments = m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info); pConsole->m_CompletionRenderOffset = Info.m_Offset; } diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 66e3c90e0bf..101cc3e16c8 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -68,6 +68,7 @@ class CGameConsole : public CComponent int m_CompletionFlagmask; float m_CompletionRenderOffset; float m_CompletionRenderOffsetChange; + int m_CompletionArgumentPosition; char m_aUser[32]; bool m_UserGot; From b949a7de214bed048e21b42c48f17372aab3b1ab Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 17:22:16 +0100 Subject: [PATCH 185/198] Fix broken selection in editor --- src/game/editor/editor.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index b3d62b87c07..a06d4a0043d 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -3133,10 +3133,11 @@ void CEditor::DoMapEditor(CUIRect View) MapView()->MapGrid()->OnRender(View); } + const bool ShouldPan = (Input()->ModifierIsPressed() && UI()->MouseButton(0)) || UI()->MouseButton(2); if(m_pContainerPanned == &s_pEditorID) { // do panning - if((Input()->ModifierIsPressed() && UI()->MouseButton(0)) || UI()->MouseButton(2)) + if(ShouldPan) { if(Input()->ShiftIsPressed()) s_Operation = OP_PAN_EDITOR; @@ -3160,11 +3161,13 @@ void CEditor::DoMapEditor(CUIRect View) UI()->SetHotItem(s_pEditorID); // do global operations like pan and zoom - if(UI()->CheckActiveItem(nullptr) && m_pContainerPanned == nullptr && ((Input()->ModifierIsPressed() && UI()->MouseButton(0)) || UI()->MouseButton(2))) + if(UI()->CheckActiveItem(nullptr) && (UI()->MouseButton(0) || UI()->MouseButton(2))) { s_StartWx = wx; s_StartWy = wy; - m_pContainerPanned = &s_pEditorID; + + if(ShouldPan && m_pContainerPanned == nullptr) + m_pContainerPanned = &s_pEditorID; } // brush editing From f3fc85cfbba72ed66e1ef1e99d128c48075e68b7 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 17:44:52 +0100 Subject: [PATCH 186/198] Improve editor append (fixes #7625) Fix broken editor append. Check for image data when image names are identical. In such case, appended image is renamed until its name is unique. --- src/game/editor/editor.cpp | 48 +++++++++++++++++++++++++----- src/game/editor/mapitems/image.cpp | 26 ++++++++++++++++ src/game/editor/mapitems/image.h | 1 + 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 09e6b341ece..4f99f9f6b93 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -8832,31 +8832,63 @@ bool CEditor::Append(const char *pFileName, int StorageType, bool IgnoreHistory) (int)m_Map.m_vpSounds.size(), (int)m_Map.m_vpEnvelopes.size()}; + // Keep a map to check if specific indices have already been replaced to prevent + // replacing those indices again when transfering images + static std::map s_ReplacedMap; static const auto &&s_ReplaceIndex = [](int ToReplace, int ReplaceWith) { return [ToReplace, ReplaceWith](int *pIndex) { - if(*pIndex == ToReplace) + if(*pIndex == ToReplace && !s_ReplacedMap[pIndex]) + { *pIndex = ReplaceWith; + s_ReplacedMap[pIndex] = true; + } }; }; - //Transfer non-duplicate images + const auto &&Rename = [&](const std::shared_ptr &pImage) { + char aRenamed[IO_MAX_PATH_LENGTH]; + int DuplicateCount = 1; + str_copy(aRenamed, pImage->m_aName); + while(std::find_if(m_Map.m_vpImages.begin(), m_Map.m_vpImages.end(), [aRenamed](const std::shared_ptr &OtherImage) { return str_comp(OtherImage->m_aName, aRenamed) == 0; }) != m_Map.m_vpImages.end()) + str_format(aRenamed, sizeof(aRenamed), "%s (%d)", pImage->m_aName, DuplicateCount++); // Rename to "image_name (%d)" + str_copy(pImage->m_aName, aRenamed); + }; + + // Transfer non-duplicate images + s_ReplacedMap.clear(); for(auto NewMapIt = NewMap.m_vpImages.begin(); NewMapIt != NewMap.m_vpImages.end(); ++NewMapIt) { auto pNewImage = *NewMapIt; auto NameIsTaken = [pNewImage](const std::shared_ptr &OtherImage) { return str_comp(pNewImage->m_aName, OtherImage->m_aName) == 0; }; - auto MatchInCurrentMap = std::find_if(begin(m_Map.m_vpImages), end(m_Map.m_vpImages), NameIsTaken); + auto MatchInCurrentMap = std::find_if(m_Map.m_vpImages.begin(), m_Map.m_vpImages.end(), NameIsTaken); - const bool IsDuplicate = MatchInCurrentMap != std::end(m_Map.m_vpImages); + const bool IsDuplicate = MatchInCurrentMap != m_Map.m_vpImages.end(); const int IndexToReplace = NewMapIt - NewMap.m_vpImages.begin(); if(IsDuplicate) { - const int IndexToReplaceWith = MatchInCurrentMap - m_Map.m_vpImages.begin(); + // Check for image data + const bool ImageDataEquals = (*MatchInCurrentMap)->DataEquals(*pNewImage); + + if(ImageDataEquals) + { + const int IndexToReplaceWith = MatchInCurrentMap - m_Map.m_vpImages.begin(); - dbg_msg("editor", "map contains image %s already, removing duplicate", pNewImage->m_aName); + dbg_msg("editor", "map already contains image %s with the same data, removing duplicate", pNewImage->m_aName); - //In the new map, replace the index of the duplicate image to the index of the same in the current map. - NewMap.ModifyImageIndex(s_ReplaceIndex(IndexToReplace, IndexToReplaceWith)); + // In the new map, replace the index of the duplicate image to the index of the same in the current map. + NewMap.ModifyImageIndex(s_ReplaceIndex(IndexToReplace, IndexToReplaceWith)); + } + else + { + // Rename image and add it + Rename(pNewImage); + + dbg_msg("editor", "map already contains image %s but contents of appended image is different. Renaming to %s", (*MatchInCurrentMap)->m_aName, pNewImage->m_aName); + + NewMap.ModifyImageIndex(s_ReplaceIndex(IndexToReplace, m_Map.m_vpImages.size())); + m_Map.m_vpImages.push_back(pNewImage); + } } else { diff --git a/src/game/editor/mapitems/image.cpp b/src/game/editor/mapitems/image.cpp index 514a1cadf79..8da88a57c0d 100644 --- a/src/game/editor/mapitems/image.cpp +++ b/src/game/editor/mapitems/image.cpp @@ -54,3 +54,29 @@ void CEditorImage::AnalyseTileFlags() } } } + +bool CEditorImage::DataEquals(const CEditorImage &Other) const +{ + // If height, width or pixel size don't match, then data cannot be equal + const size_t ImgPixelSize = PixelSize(); + + if(Other.m_Height != m_Height || Other.m_Width != m_Width || Other.PixelSize() != ImgPixelSize) + return false; + + const auto &&GetPixel = [&](void *pData, int x, int y, size_t p) -> uint8_t { + return ((uint8_t *)pData)[x * ImgPixelSize + (m_Width * ImgPixelSize * y) + p]; + }; + + // Look through every pixel and check if there are any difference + for(int y = 0; y < m_Height; y += ImgPixelSize) + { + for(int x = 0; x < m_Width; x += ImgPixelSize) + { + for(size_t p = 0; p < ImgPixelSize; p++) + if(GetPixel(m_pData, x, y, p) != GetPixel(Other.m_pData, x, y, p)) + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/src/game/editor/mapitems/image.h b/src/game/editor/mapitems/image.h index cffbd6c7e71..1ba25e951f0 100644 --- a/src/game/editor/mapitems/image.h +++ b/src/game/editor/mapitems/image.h @@ -14,6 +14,7 @@ class CEditorImage : public CImageInfo, public CEditorComponent void Init(CEditor *pEditor) override; void AnalyseTileFlags(); + bool DataEquals(const CEditorImage &Other) const; IGraphics::CTextureHandle m_Texture; int m_External = 0; From 72019df288788b62483a1e319b28d19bf1616a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 27 Dec 2023 18:13:10 +0100 Subject: [PATCH 187/198] More efficient text selection rendering Render one quad for each line of the text selection instead of rendering one quad per selected character. This increases the average FPS when the console is open and all text is selected by around 10% (from around 849 to around 943 FPS) (on my machine, in release mode). --- src/engine/client/text.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index bf30eac2259..4e7f52aaf7f 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -1532,6 +1532,7 @@ class CTextRender : public IEngineTextRender const float CursorOuterInnerDiff = (CursorOuterWidth - CursorInnerWidth) / 2; std::vector vSelectionQuads; + int SelectionQuadLine = -1; bool SelectionStarted = false; bool SelectionUsedPress = false; bool SelectionUsedRelease = false; @@ -1866,8 +1867,18 @@ class CTextRender : public IEngineTextRender if(SelectionStarted && IsRendered) { - const float SelectionHeight = pCursor->m_AlignedFontSize + pCursor->m_AlignedLineSpacing; - vSelectionQuads.emplace_back(SelX, DrawY + (1.0f - pCursor->m_SelectionHeightFactor) * SelectionHeight, SelWidth, pCursor->m_SelectionHeightFactor * SelectionHeight); + if(!vSelectionQuads.empty() && SelectionQuadLine == pCursor->m_LineCount) + { + vSelectionQuads.back().m_Width += SelWidth; + } + else + { + const float SelectionHeight = pCursor->m_AlignedFontSize + pCursor->m_AlignedLineSpacing; + const float SelectionY = DrawY + (1.0f - pCursor->m_SelectionHeightFactor) * SelectionHeight; + const float ScaledSelectionHeight = pCursor->m_SelectionHeightFactor * SelectionHeight; + vSelectionQuads.emplace_back(SelX, SelectionY, SelWidth, ScaledSelectionHeight); + SelectionQuadLine = pCursor->m_LineCount; + } } LastSelX = SelX; From 83540ade5c8ab90f1f0317a1cee007fe27206d5b Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 23:11:52 +0100 Subject: [PATCH 188/198] Fix duplicate layers in popup layer context Clear layers from context before adding any layer, preventing layers from being kept in the vector until only a single layer is selected. Fixes #4978. --- src/game/editor/editor.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index ff1a1618100..66118afb37e 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -4072,6 +4072,9 @@ void CEditor::RenderLayers(CUIRect LayersBox) } else if(Result == 2) { + s_LayerPopupContext.m_vpLayers.clear(); + s_LayerPopupContext.m_vLayerIndices.clear(); + if(!IsLayerSelected) { SelectLayer(i, g); @@ -4099,11 +4102,6 @@ void CEditor::RenderLayers(CUIRect LayersBox) s_LayerPopupContext.m_vLayerIndices.clear(); } } - else - { - s_LayerPopupContext.m_vpLayers.clear(); - s_LayerPopupContext.m_vLayerIndices.clear(); - } UI()->DoPopupMenu(&s_LayerPopupContext, UI()->MouseX(), UI()->MouseY(), 120, 270, &s_LayerPopupContext, PopupLayer); } From 3d606847fd978448fb83d70951b1bf54dff0268e Mon Sep 17 00:00:00 2001 From: Corantin H Date: Thu, 28 Dec 2023 00:03:28 +0100 Subject: [PATCH 189/198] Fix group position properties not working correctly --- src/game/editor/popups.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 53d53b4fdc6..76bd8dc5f1e 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -643,11 +643,11 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, } else if(Prop == EGroupProp::PROP_POS_X) { - pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = NewVal; + pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal; } else if(Prop == EGroupProp::PROP_POS_Y) { - pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = NewVal; + pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = -NewVal; } else if(Prop == EGroupProp::PROP_USE_CLIPPING) { From c9640459e455cc097f1678d98bcb2456edb59136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 11 Nov 2023 18:12:10 +0100 Subject: [PATCH 190/198] Improve editor toolbar anim and grid buttons, add dropdowns Add dropdown menus for changing animation and grid settings (i.e. animation speed and grid size) instead of conditionally showing more buttons for this directly in the menu bar. This frees up space in the menu bar, which is currently full on 5:4 resolutions. The icons previously used for the default animation/grid buttons are now used for the main buttons that toggle animation/grid instead of using text. Support setting lower animation speeds with the plus and minus buttons by adjusting the step size when the animation speed is low. Support setting arbitrary animation speed by text input. --- src/engine/textrender.h | 1 + src/game/editor/editor.cpp | 82 +++++++++++------------------------- src/game/editor/editor.h | 4 ++ src/game/editor/map_grid.cpp | 47 +++++++++++++++++---- src/game/editor/map_grid.h | 11 +++-- src/game/editor/popups.cpp | 57 +++++++++++++++++++++++++ 6 files changed, 134 insertions(+), 68 deletions(-) diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 41ddc7a8f61..af920ea5f15 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -66,6 +66,7 @@ enum class EFontPreset namespace FontIcons { // Each font icon is named according to its official name in Font Awesome MAYBE_UNUSED static const char *FONT_ICON_PLUS = "+"; +MAYBE_UNUSED static const char *FONT_ICON_MINUS = "-"; MAYBE_UNUSED static const char *FONT_ICON_LOCK = "\xEF\x80\xA3"; MAYBE_UNUSED static const char *FONT_ICON_MAGNIFYING_GLASS = "\xEF\x80\x82"; MAYBE_UNUSED static const char *FONT_ICON_HEART = "\xEF\x80\x84"; diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 66118afb37e..ce58fa171a6 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1084,15 +1084,25 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); // animation button - TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); - static int s_AnimateButton = 0; - if(DoButton_Editor(&s_AnimateButton, "Anim", m_Animate, &Button, 0, "[ctrl+m] Toggle animation") || + TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); + static char s_AnimateButton; + if(DoButton_FontIcon(&s_AnimateButton, FONT_ICON_CIRCLE_PLAY, m_Animate, &Button, 0, "[ctrl+m] Toggle animation", IGraphics::CORNER_L) || (m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_M) && ModPressed)) { m_AnimateStart = time_get(); m_Animate = !m_Animate; } + // animation settings button + TB_Top.VSplitLeft(14.0f, &Button, &TB_Top); + static char s_AnimateSettingsButton; + if(DoButton_FontIcon(&s_AnimateSettingsButton, FONT_ICON_CIRCLE_CHEVRON_DOWN, 0, &Button, 0, "Change animation settings.", IGraphics::CORNER_R, 8.0f)) + { + m_AnimateUpdatePopup = true; + static SPopupMenuId s_PopupAnimateSettingsId; + UI()->DoPopupMenu(&s_PopupAnimateSettingsId, Button.x, Button.y + Button.h, 150.0f, 37.0f, this, PopupAnimateSettings); + } + TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); // proof button @@ -1125,20 +1135,28 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); // grid button - TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); + TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); static int s_GridButton = 0; - if(DoButton_Editor(&s_GridButton, "Grid", MapView()->MapGrid()->IsEnabled(), &Button, 0, "[ctrl+g] Toggle Grid") || + if(DoButton_FontIcon(&s_GridButton, FONT_ICON_BORDER_ALL, MapView()->MapGrid()->IsEnabled(), &Button, 0, "[ctrl+g] Toggle Grid", IGraphics::CORNER_L) || (m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_G) && ModPressed && !ShiftPressed)) { MapView()->MapGrid()->Toggle(); } + // grid settings button + TB_Top.VSplitLeft(14.0f, &Button, &TB_Top); + static char s_GridSettingsButton; + if(DoButton_FontIcon(&s_GridSettingsButton, FONT_ICON_CIRCLE_CHEVRON_DOWN, 0, &Button, 0, "Change grid settings.", IGraphics::CORNER_R, 8.0f)) + { + MapView()->MapGrid()->DoSettingsPopup(vec2(Button.x, Button.y + Button.h)); + } + TB_Top.VSplitLeft(5.0f, nullptr, &TB_Top); // zoom group TB_Top.VSplitLeft(20.0f, &Button, &TB_Top); static int s_ZoomOutButton = 0; - if(DoButton_FontIcon(&s_ZoomOutButton, "-", 0, &Button, 0, "[NumPad-] Zoom out", IGraphics::CORNER_L)) + if(DoButton_FontIcon(&s_ZoomOutButton, FONT_ICON_MINUS, 0, &Button, 0, "[NumPad-] Zoom out", IGraphics::CORNER_L)) { MapView()->Zoom()->ChangeValue(50.0f); } @@ -1152,7 +1170,7 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) TB_Top.VSplitLeft(20.0f, &Button, &TB_Top); static int s_ZoomInButton = 0; - if(DoButton_FontIcon(&s_ZoomInButton, "+", 0, &Button, 0, "[NumPad+] Zoom in", IGraphics::CORNER_R)) + if(DoButton_FontIcon(&s_ZoomInButton, FONT_ICON_PLUS, 0, &Button, 0, "[NumPad+] Zoom in", IGraphics::CORNER_R)) { MapView()->Zoom()->ChangeValue(-50.0f); } @@ -1229,56 +1247,6 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) for(auto &pLayer : m_pBrush->m_vpLayers) pLayer->BrushRotate(s_RotationAmount / 360.0f * pi * 2); } - - TB_Top.VSplitLeft(5.0f, &Button, &TB_Top); - } - - // animation speed - if(m_Animate) - { - TB_Top.VSplitLeft(20.0f, &Button, &TB_Top); - static int s_AnimSlowerButton = 0; - if(DoButton_FontIcon(&s_AnimSlowerButton, "-", 0, &Button, 0, "Decrease animation speed", IGraphics::CORNER_L)) - { - if(m_AnimateSpeed > 0.5f) - m_AnimateSpeed -= 0.5f; - } - - TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); - static int s_AnimNormalButton = 0; - if(DoButton_FontIcon(&s_AnimNormalButton, FONT_ICON_CIRCLE_PLAY, 0, &Button, 0, "Normal animation speed", 0)) - m_AnimateSpeed = 1.0f; - - TB_Top.VSplitLeft(20.0f, &Button, &TB_Top); - static int s_AnimFasterButton = 0; - if(DoButton_FontIcon(&s_AnimFasterButton, "+", 0, &Button, 0, "Increase animation speed", IGraphics::CORNER_R)) - m_AnimateSpeed += 0.5f; - - TB_Top.VSplitLeft(5.0f, &Button, &TB_Top); - } - - // grid zoom - if(MapView()->MapGrid()->IsEnabled()) - { - TB_Top.VSplitLeft(20.0f, &Button, &TB_Top); - static int s_GridIncreaseButton = 0; - if(DoButton_FontIcon(&s_GridIncreaseButton, "-", 0, &Button, 0, "Decrease grid", IGraphics::CORNER_L)) - { - MapView()->MapGrid()->DecreaseFactor(); - } - - TB_Top.VSplitLeft(25.0f, &Button, &TB_Top); - static int s_GridNormalButton = 0; - if(DoButton_FontIcon(&s_GridNormalButton, FONT_ICON_BORDER_ALL, 0, &Button, 0, "Normal grid", IGraphics::CORNER_NONE)) - MapView()->MapGrid()->ResetFactor(); - - TB_Top.VSplitLeft(20.0f, &Button, &TB_Top); - static int s_GridDecreaseButton = 0; - if(DoButton_FontIcon(&s_GridDecreaseButton, "+", 0, &Button, 0, "Increase grid", IGraphics::CORNER_R)) - { - MapView()->MapGrid()->IncreaseFactor(); - } - TB_Top.VSplitLeft(5.0f, &Button, &TB_Top); } } diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index aeac9594011..2a02c3de368 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -381,6 +381,7 @@ class CEditor : public IEditor m_AnimateStart = 0; m_AnimateTime = 0; m_AnimateSpeed = 1; + m_AnimateUpdatePopup = false; m_ShowEnvelopePreview = SHOWENV_NONE; m_SelectedQuadEnvelope = -1; @@ -692,10 +693,12 @@ class CEditor : public IEditor }; EShowTile m_ShowTileInfo; bool m_ShowDetail; + bool m_Animate; int64_t m_AnimateStart; float m_AnimateTime; float m_AnimateSpeed; + bool m_AnimateUpdatePopup; enum EExtraEditor { @@ -824,6 +827,7 @@ class CEditor : public IEditor static CUI::EPopupMenuFunctionResult PopupGoto(void *pContext, CUIRect View, bool Active); static CUI::EPopupMenuFunctionResult PopupEntities(void *pContext, CUIRect View, bool Active); static CUI::EPopupMenuFunctionResult PopupProofMode(void *pContext, CUIRect View, bool Active); + static CUI::EPopupMenuFunctionResult PopupAnimateSettings(void *pContext, CUIRect View, bool Active); static bool CallbackOpenMap(const char *pFileName, int StorageType, void *pUser); static bool CallbackAppendMap(const char *pFileName, int StorageType, void *pUser); diff --git a/src/game/editor/map_grid.cpp b/src/game/editor/map_grid.cpp index e241642aa6d..b02701c6853 100644 --- a/src/game/editor/map_grid.cpp +++ b/src/game/editor/map_grid.cpp @@ -4,6 +4,9 @@ #include "editor.h" +static constexpr int MIN_GRID_FACTOR = 1; +static constexpr int MAX_GRID_FACTOR = 15; + void CMapGrid::OnReset() { m_GridActive = false; @@ -96,19 +99,47 @@ int CMapGrid::Factor() const return m_GridFactor; } -void CMapGrid::ResetFactor() +void CMapGrid::SetFactor(int Factor) { - m_GridFactor = 1; + m_GridFactor = clamp(Factor, MIN_GRID_FACTOR, MAX_GRID_FACTOR); } -void CMapGrid::IncreaseFactor() +void CMapGrid::DoSettingsPopup(vec2 Position) { - if(m_GridFactor < 15) - m_GridFactor++; + UI()->DoPopupMenu(&m_PopupGridSettingsId, Position.x, Position.y, 120.0f, 37.0f, this, PopupGridSettings); } -void CMapGrid::DecreaseFactor() +CUI::EPopupMenuFunctionResult CMapGrid::PopupGridSettings(void *pContext, CUIRect View, bool Active) { - if(m_GridFactor > 1) - m_GridFactor--; + CMapGrid *pMapGrid = static_cast(pContext); + + enum + { + PROP_SIZE = 0, + NUM_PROPS, + }; + CProperty aProps[] = { + {"Size", pMapGrid->Factor(), PROPTYPE_INT_STEP, MIN_GRID_FACTOR, MAX_GRID_FACTOR}, + {nullptr}, + }; + + static int s_aIds[NUM_PROPS]; + int NewVal; + int Prop = pMapGrid->Editor()->DoProperties(&View, aProps, s_aIds, &NewVal); + + if(Prop == PROP_SIZE) + { + pMapGrid->SetFactor(NewVal); + } + + CUIRect Button; + View.HSplitBottom(12.0f, &View, &Button); + + static char s_DefaultButton; + if(pMapGrid->Editor()->DoButton_Ex(&s_DefaultButton, "Default", 0, &Button, 0, "Normal grid size", IGraphics::CORNER_ALL)) + { + pMapGrid->SetFactor(1); + } + + return CUI::POPUP_KEEP_OPEN; } diff --git a/src/game/editor/map_grid.h b/src/game/editor/map_grid.h index 3a6e26a3be1..69d79baf82b 100644 --- a/src/game/editor/map_grid.h +++ b/src/game/editor/map_grid.h @@ -3,6 +3,8 @@ #include "component.h" +#include + class CMapGrid : public CEditorComponent { public: @@ -20,13 +22,16 @@ class CMapGrid : public CEditorComponent void Toggle(); int Factor() const; - void ResetFactor(); - void IncreaseFactor(); - void DecreaseFactor(); + void SetFactor(int Factor); + + void DoSettingsPopup(vec2 Position); private: bool m_GridActive; int m_GridFactor; + + SPopupMenuId m_PopupGridSettingsId; + static CUI::EPopupMenuFunctionResult PopupGridSettings(void *pContext, CUIRect View, bool Active); }; #endif diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 76bd8dc5f1e..a9c8e68f61e 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -2725,3 +2725,60 @@ CUI::EPopupMenuFunctionResult CEditor::PopupProofMode(void *pContext, CUIRect Vi return CUI::POPUP_KEEP_OPEN; } + +CUI::EPopupMenuFunctionResult CEditor::PopupAnimateSettings(void *pContext, CUIRect View, bool Active) +{ + CEditor *pEditor = static_cast(pContext); + + constexpr float MIN_ANIM_SPEED = 0.001f; + constexpr float MAX_ANIM_SPEED = 1000000.0f; + + CUIRect Row, Label, ButtonDecrease, EditBox, ButtonIncrease, ButtonReset; + View.HSplitTop(13.0f, &Row, &View); + Row.VSplitMid(&Label, &Row); + Row.HMargin(1.0f, &Row); + Row.VSplitLeft(10.0f, &ButtonDecrease, &Row); + Row.VSplitRight(10.0f, &EditBox, &ButtonIncrease); + View.HSplitBottom(12.0f, &View, &ButtonReset); + pEditor->UI()->DoLabel(&Label, "Speed", 10.0f, TEXTALIGN_ML); + + static char s_DecreaseButton; + if(pEditor->DoButton_Ex(&s_DecreaseButton, "-", 0, &ButtonDecrease, 0, "Decrease animation speed", IGraphics::CORNER_L)) + { + pEditor->m_AnimateSpeed -= pEditor->m_AnimateSpeed <= 1.0f ? 0.1f : 0.5f; + pEditor->m_AnimateSpeed = maximum(pEditor->m_AnimateSpeed, MIN_ANIM_SPEED); + pEditor->m_AnimateUpdatePopup = true; + } + + static char s_IncreaseButton; + if(pEditor->DoButton_Ex(&s_IncreaseButton, "+", 0, &ButtonIncrease, 0, "Increase animation speed", IGraphics::CORNER_R)) + { + if(pEditor->m_AnimateSpeed < 0.1f) + pEditor->m_AnimateSpeed = 0.1f; + else + pEditor->m_AnimateSpeed += pEditor->m_AnimateSpeed < 1.0f ? 0.1f : 0.5f; + pEditor->m_AnimateSpeed = minimum(pEditor->m_AnimateSpeed, MAX_ANIM_SPEED); + pEditor->m_AnimateUpdatePopup = true; + } + + static char s_DefaultButton; + if(pEditor->DoButton_Ex(&s_DefaultButton, "Default", 0, &ButtonReset, 0, "Normal animation speed", IGraphics::CORNER_ALL)) + { + pEditor->m_AnimateSpeed = 1.0f; + pEditor->m_AnimateUpdatePopup = true; + } + + static CLineInputNumber s_SpeedInput; + if(pEditor->m_AnimateUpdatePopup) + { + s_SpeedInput.SetFloat(pEditor->m_AnimateSpeed); + pEditor->m_AnimateUpdatePopup = false; + } + + if(pEditor->DoEditBox(&s_SpeedInput, &EditBox, 10.0f, IGraphics::CORNER_NONE, "The animation speed")) + { + pEditor->m_AnimateSpeed = clamp(s_SpeedInput.GetFloat(), MIN_ANIM_SPEED, MAX_ANIM_SPEED); + } + + return CUI::POPUP_KEEP_OPEN; +} From c2ce2c9d1a9b7731935184d1a7ce45fd424809a9 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Wed, 27 Dec 2023 22:39:44 +0100 Subject: [PATCH 191/198] Refactor editor dragbar, allow resize of layers panel --- src/game/editor/editor.cpp | 52 ++++++++++++++++++++++++++++---------- src/game/editor/editor.h | 10 +++++++- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 66118afb37e..8c2d410418d 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -5705,7 +5705,9 @@ void CEditor::RenderModebar(CUIRect View) View.HSplitTop(12.0f, &Mentions, &View); View.HSplitTop(12.0f, &IngameMoved, &View); View.HSplitTop(8.0f, nullptr, &ModeButtons); - ModeButtons.VSplitLeft(96.0f, &ModeButtons, nullptr); + const float Width = m_ToolBoxWidth - 5.0f; + ModeButtons.VSplitLeft(Width, &ModeButtons, nullptr); + const float ButtonWidth = Width / 3; // mentions if(m_Mentions) @@ -5733,21 +5735,21 @@ void CEditor::RenderModebar(CUIRect View) // mode buttons { - ModeButtons.VSplitLeft(32.0f, &ModeButton, &ModeButtons); + ModeButtons.VSplitLeft(ButtonWidth, &ModeButton, &ModeButtons); static int s_LayersButton = 0; if(DoButton_FontIcon(&s_LayersButton, FONT_ICON_LAYER_GROUP, m_Mode == MODE_LAYERS, &ModeButton, 0, "Go to layers management.", IGraphics::CORNER_L)) { m_Mode = MODE_LAYERS; } - ModeButtons.VSplitLeft(32.0f, &ModeButton, &ModeButtons); + ModeButtons.VSplitLeft(ButtonWidth, &ModeButton, &ModeButtons); static int s_ImagesButton = 0; if(DoButton_FontIcon(&s_ImagesButton, FONT_ICON_IMAGE, m_Mode == MODE_IMAGES, &ModeButton, 0, "Go to images management.", IGraphics::CORNER_NONE)) { m_Mode = MODE_IMAGES; } - ModeButtons.VSplitLeft(32.0f, &ModeButton, &ModeButtons); + ModeButtons.VSplitLeft(ButtonWidth, &ModeButton, &ModeButtons); static int s_SoundsButton = 0; if(DoButton_FontIcon(&s_SoundsButton, FONT_ICON_MUSIC, m_Mode == MODE_SOUNDS, &ModeButton, 0, "Go to sounds management.", IGraphics::CORNER_R)) { @@ -6158,7 +6160,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) DragBar.y -= 2.0f; DragBar.w += 2.0f; DragBar.h += 4.0f; - RenderExtraEditorDragBar(View, DragBar); + DoEditorDragBar(View, &DragBar, EDragSide::SIDE_TOP, &m_aExtraEditorSplits[EXTRAEDITOR_ENVELOPES]); View.HSplitTop(15.0f, &ToolBar, &View); View.HSplitTop(15.0f, &CurveBar, &View); ToolBar.Margin(2.0f, &ToolBar); @@ -7498,7 +7500,7 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd DragBar.y -= 2.0f; DragBar.w += 2.0f; DragBar.h += 4.0f; - RenderExtraEditorDragBar(View, DragBar); + DoEditorDragBar(View, &DragBar, EDragSide::SIDE_TOP, &m_aExtraEditorSplits[EXTRAEDITOR_SERVER_SETTINGS]); View.HSplitTop(20.0f, &ToolBar, &View); View.HSplitTop(2.0f, nullptr, &List); ToolBar.HMargin(2.0f, &ToolBar); @@ -7682,7 +7684,7 @@ void CEditor::RenderEditorHistory(CUIRect View) DragBar.y -= 2.0f; DragBar.w += 2.0f; DragBar.h += 4.0f; - RenderExtraEditorDragBar(View, DragBar); + DoEditorDragBar(View, &DragBar, EDragSide::SIDE_TOP, &m_aExtraEditorSplits[EXTRAEDITOR_HISTORY]); View.HSplitTop(20.0f, &ToolBar, &View); View.HSplitTop(2.0f, nullptr, &List); ToolBar.HMargin(2.0f, &ToolBar); @@ -7812,7 +7814,7 @@ void CEditor::RenderEditorHistory(CUIRect View) } } -void CEditor::RenderExtraEditorDragBar(CUIRect View, CUIRect DragBar) +void CEditor::DoEditorDragBar(CUIRect View, CUIRect *pDragBar, EDragSide Side, float *pValue, float MinValue, float MaxValue) { enum EDragOperation { @@ -7823,26 +7825,41 @@ void CEditor::RenderExtraEditorDragBar(CUIRect View, CUIRect DragBar) static EDragOperation s_Operation = OP_NONE; static float s_InitialMouseY = 0.0f; static float s_InitialMouseOffsetY = 0.0f; + static float s_InitialMouseX = 0.0f; + static float s_InitialMouseOffsetX = 0.0f; bool Clicked; bool Abrupted; - if(int Result = DoButton_DraggableEx(&s_Operation, "", 8, &DragBar, &Clicked, &Abrupted, 0, "Change the size of the editor by dragging.")) + if(int Result = DoButton_DraggableEx(pDragBar, "", 8, pDragBar, &Clicked, &Abrupted, 0, "Change the size of the editor by dragging.")) { if(s_Operation == OP_NONE && Result == 1) { s_InitialMouseY = UI()->MouseY(); - s_InitialMouseOffsetY = UI()->MouseY() - DragBar.y; + s_InitialMouseOffsetY = UI()->MouseY() - pDragBar->y; + s_InitialMouseX = UI()->MouseX(); + s_InitialMouseOffsetX = UI()->MouseX() - pDragBar->x; s_Operation = OP_CLICKED; } if(Clicked || Abrupted) s_Operation = OP_NONE; - if(s_Operation == OP_CLICKED && absolute(UI()->MouseY() - s_InitialMouseY) > 5.0f) + bool IsVertical = Side == EDragSide::SIDE_TOP || Side == EDragSide::SIDE_BOTTOM; + + if(s_Operation == OP_CLICKED && absolute(IsVertical ? UI()->MouseY() - s_InitialMouseY : UI()->MouseX() - s_InitialMouseX) > 5.0f) s_Operation = OP_DRAGGING; if(s_Operation == OP_DRAGGING) - m_aExtraEditorSplits[(int)m_ActiveExtraEditor] = clamp(s_InitialMouseOffsetY + View.y + View.h - UI()->MouseY(), 100.0f, 400.0f); + { + if(Side == EDragSide::SIDE_TOP) + *pValue = clamp(s_InitialMouseOffsetY + View.y + View.h - UI()->MouseY(), MinValue, MaxValue); + else if(Side == EDragSide::SIDE_RIGHT) + *pValue = clamp(UI()->MouseX() - s_InitialMouseOffsetX - View.x + pDragBar->w, MinValue, MaxValue); + else if(Side == EDragSide::SIDE_BOTTOM) + *pValue = clamp(UI()->MouseY() - s_InitialMouseOffsetY - View.y + pDragBar->h, MinValue, MaxValue); + else if(Side == EDragSide::SIDE_LEFT) + *pValue = clamp(s_InitialMouseOffsetX + View.x + View.w - UI()->MouseX(), MinValue, MaxValue); + } } } @@ -7945,7 +7962,8 @@ void CEditor::Render() { View.HSplitTop(16.0f, &MenuBar, &View); View.HSplitTop(53.0f, &ToolBar, &View); - View.VSplitLeft(100.0f, &ToolBox, &View); + View.VSplitLeft(m_ToolBoxWidth, &ToolBox, &View); + View.HSplitBottom(16.0f, &View, &StatusBar); if(!m_ShowPicker && m_ActiveExtraEditor != EXTRAEDITOR_NONE) View.HSplitBottom(m_aExtraEditorSplits[(int)m_ActiveExtraEditor], &View, &ExtraEditor); @@ -8003,7 +8021,7 @@ void CEditor::Render() RenderBackground(ToolBar, m_BackgroundTexture, 128.0f, Brightness); ToolBar.Margin(2.0f, &ToolBar); - ToolBar.VSplitLeft(100.0f, &ModeBar, &ToolBar); + ToolBar.VSplitLeft(m_ToolBoxWidth, &ModeBar, &ToolBar); RenderBackground(StatusBar, m_BackgroundTexture, 128.0f, Brightness); StatusBar.Margin(2.0f, &StatusBar); @@ -8101,6 +8119,12 @@ void CEditor::Render() if(m_GuiActive) { + CUIRect DragBar; + ToolBox.VSplitRight(1.0f, &ToolBox, &DragBar); + DragBar.x -= 2.0f; + DragBar.w += 4.0f; + DoEditorDragBar(ToolBox, &DragBar, EDragSide::SIDE_RIGHT, &m_ToolBoxWidth); + if(m_Mode == MODE_LAYERS) RenderLayers(ToolBox); else if(m_Mode == MODE_IMAGES) diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index aeac9594011..4446611e39a 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -707,6 +707,7 @@ class CEditor : public IEditor }; EExtraEditor m_ActiveExtraEditor = EXTRAEDITOR_NONE; float m_aExtraEditorSplits[NUM_EXTRAEDITORS] = {250.0f, 250.0f, 250.0f}; + float m_ToolBoxWidth = 100.0f; enum EShowEnvelope { @@ -936,7 +937,14 @@ class CEditor : public IEditor void RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEditorLast); void RenderEditorHistory(CUIRect View); - void RenderExtraEditorDragBar(CUIRect View, CUIRect DragBar); + enum class EDragSide // Which side is the drag bar on + { + SIDE_BOTTOM, + SIDE_LEFT, + SIDE_TOP, + SIDE_RIGHT + }; + void DoEditorDragBar(CUIRect View, CUIRect *pDragBar, EDragSide Side, float *pValue, float MinValue = 100.0f, float MaxValue = 400.0f); void SetHotEnvelopePoint(const CUIRect &View, const std::shared_ptr &pEnvelope, int ActiveChannels); From ffd7776147f5ea76d57b0470aa8db6486d216cea Mon Sep 17 00:00:00 2001 From: Corantin H Date: Thu, 28 Dec 2023 01:29:17 +0100 Subject: [PATCH 192/198] Add cursor types and use resize cursor for dragbars --- CMakeLists.txt | 1 + data/editor/cursor_resize.png | Bin 0 -> 5481 bytes src/game/editor/editor.cpp | 18 ++++++++++++++---- src/game/editor/editor.h | 16 ++++++++++++++-- 4 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 data/editor/cursor_resize.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 239f3a185f3..ed53fa7715c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1391,6 +1391,7 @@ set(EXPECTED_DATA editor/background.png editor/checker.png editor/cursor.png + editor/cursor_resize.png editor/entities/DDNet.png editor/entities/FNG.png editor/entities/Race.png diff --git a/data/editor/cursor_resize.png b/data/editor/cursor_resize.png new file mode 100644 index 0000000000000000000000000000000000000000..ca696d06e98df0ea23ed5457e424a39de75a292f GIT binary patch literal 5481 zcmV-v6_)CWP)(#DuELHW_uvEDX1>O~EP*MPy{S%}ujR zL7;KIjztHSMj>4g(3YrdREExS>40Q?0YuhJc@wL&xA&gk{y68{K6aGeHw44J$&>bZ z?m55nd;gttp7SUX;e3X7wi!6vIPVn@I@1Fvr9O2(P{^g)K2xYOIY3`w9QqrxzxJo= zlPS)K0!}B0L*H@J>5~m)T>;4m;^4+krBAS5Hjp(1q$)(F$QIvOsREz1@Yx(QV-GkR z!uLfcGNi45b3yPR(v>5v5IEO_hxB<$$pg+s!B2%$WJ=ltJ}<&UYP=4p0Hsu>!hac9 z2OI)!O``o*fZad?aBaGSCe5>m^m}Cp54acT2E<;+lIXWN&|ZMmz_3ghIjxLmNcc;D zt$hR$;Q1u_{`M68jsTUJFqE_cvVfleotAM?N{Q!r0&D~FlIWigd;mycVPRiItObhF zWo$qNWQdD^x*)>=0X_msQ=s*rqcMN}eDQrhsDyWcN$E0qN(E#A-vl}wlO-i3vU>IE zAk*_xq&E!s1nWU7R;-YMf`TBuIPegVlP<&A^7wq98qkipdGqE;dwaXgm@&gKyO;Df zURFAqW5w=8U%y7I*fF3~d^70a6w6?a&mMvRk%$PBK6Xct~#ie#z%L2Z@y(kzQ(j&UD8w3|zP_H9Uw+vom*+5LF;naVZU<;=Y^1WXl29mw z7C|Y+lqpjfdeKlKkq9SGo&>l6V=MvwX8-n!fjnR$5Fs!N8!=)8&CUO$bo6LMYeWlL zYkc45*=L_cYwen5YJG<$j3692a)idlM&de-XpPpIXf(>SifOE0zn-$PGS|lkehq8@ zhMbOge+mczzXIL>M!Sbor%t85zMhcp5w{E&W(404ap=$?*R&He^UD_g1g2~~_Sj?S zxKq4Qemt%z9#YJUFTTWm_uWT9L4oUc6Yvi3wf+d7sy=@S_%rZz_aGb&vtq>xCQO)s z)|!|$E-wg5K%>w)Mt66&>rx2(3-~E!9>3o{7kaCLOI(f*A7hY@fnxiA2`~m&gsD2Y zU0q#>2pYjtpp=J>YfvT-XZ&CWgDyV*Wk%d}`XLwYFw6 ze?>GRLOiayp?nJQc$}`TE(!|^$;-TU&%^UPa&mGAg+lngkLUG9qWjJF zeUws!LLo+u97$&!Z6xi{_cf&L{OejPAG)udnivK zS`&+FJo7Usg|g!~I&SYm3&dkFE0|HR+1M~Gjj-mKMo^BXrQ;OP`&^+Ew6wNx$L)7; z{P=OF=M%v9flW@1c1-N!c^-=wFJ@ZBG{aO}n;1)s;xvH`gJ3iW3MZK0?nDeDh*+U) z@Qbn$WJ1a)Mcek0NDwMfYK>%`=b=z&YkidIFUlFo^b|`Upn2CWX3d&K0Ik5KE(F@F zYvhUuJ9g|qp%_1YJUu;8Jl`j#WB8s2n9M6$(9Y$wAZU{sto58p9g`yFf3Zq1&9!k~ z8^>C-O^v!&xZv{D3}?m{jkd{HYYBuS2~QCbeBYy^ql2ods=m4DLtuF@30?}kgvq*i zp2rneTtRhpHA9Mv>HN48(Z+m&Hg>VOP)WdA%DRfRgd~!tHHK2XNz1yZb6rP3x!Z)N zU_8QlocZ5+k~O;$+tv|O;39AFdI10w+a*hPXaUCgWTL)mMvSx zHzrQxMCV6|7(^Ls8y_~=*Tt-IIZeRxe0}!r?IG z<>gdXPUoVbUrZ>7H z!-tu0^?0<6HN$Lg7Ju~7rPS80<>0}C#N+X#H(dMs-Q9P0ck}MM`)O-yqqwMu;Uy(D z@0dJd)d?aTJop}W&%K*%+Y%GeIPe?bN7&LA#Oc-oZ(%k)HUmVXQR?dIm^JIi96Nf< zF_%{o4DHu{FTAftJ$=n9OomFpHw?n8igwEEePKx8J6C z$Ph-48V$(;H4{QP{k(1~H( zI1|hSvpam{SFe<9+qOwVLxXJEv`H!}rw4a~1hZR}(k5v#9uUNbm=Y2OCPbrAwSWJ9 z8X6iXyX-QC4jWE>ULIO&nwy&u5f2m7e@Ss12L-IctO|Mh>1T+=df2ybA9Lo+VaJXg zu3Z#ZjM+Fjeo9Jb08=sBUINIVL4#z$f(5d1<3`!AVS^ME6*;SP6WCKo5#M$6hYuev z8#iv0g$oyoGRsnT?ZG7L^pN4+_-Fzlp_GzaZuy>U*swufSo1=V$?7y{`w_uK8jZ;}5(l1 zi7Y5#IA%5$RKTVr`UMr&4g8!8Pqn9YLL`fdxE6EXXAkE5&p@~pbDn5D=6q2`7)Z9H zkI8+WPKiPC?H~}JZm5BU7L{5pwln%5XDI!Ni zq*O$1?rD6eYL7fvavukp0S%v;4)iODv{zs=a7j<$>=6$Tkt=}RzzU#G(0-yl6D5(U5?z$c@4&40t^R=p~N~ces?!X=cVx7>v&gmRIdbA!sd|31H z^4xNA&%3~oq!0jH3mkP|eBal?!q>E_s!G?bTNed+XD9U!A(BDfe*0~$s;bhJD_3ep zMn+Ws7eMZRjR0(VTNDL1X3Q8pR9>!?mB;l^d3kir9s#5aB>&cpk(ZaJ$19I(!@SVYfDJm)=FE0<@_g$B8oEPI!De@S%u22vh_A24+3it&! z>!dU_HKD2)6i-0J!vqFIQDFMdrgOs$*Ry2FQjQ%vhN=z$-Vu?4pf8{u6N-}hOvLHhhHu5$OlKK)BXLgnF328IjzDMf?5vZuFV9rBxsI9GaB%B8BRn;AifGXfJ z0MGMSym&E_^CnwRfw5~WF^XfNjdqRHxA{gm3n;1e z!s9&8LlBJhe~U-Os*B*$P(Q%oLx-3-b7sV6s8rSAj)0$H^L#&m)YMdF&6>qSa~`6p zu@TSnX^poM^L-nDjIm)LWLP7{+j{HDiT8xJIEy629Ijmwj949Q<|ag={Svb*Us;yX z*yJW67W4V!lTUcyfd{Cru8tD$k*Z#v2!bLq9C!m8b@4optFOMA_3PKuKRcVc`a0BD z5Q?$$5{@StnJo!jB_zd}h=vA*>plT9;jWrcAg6^U;{AS)up7QuxiyRemHI%r|Zrn)*u3ETo$m=*R9#a z{!790e0eOir82If3QKD^QyTv8~ ztt~BVcPKI(^_vI?jlLdu9eV<#PoF+anly=d^X7)Zr_Fsm&q7$U zW-Z&c{n2KQZkF`)G_JYkd)#yP-Hf^JIy5B7MGFa+*~ycqcxv%eoH%j9bq#=D0?(@I z*MOb%ETF3IVJ|&O09smF*tTsOGiT1C=HE3oE{8ip6Rzo#CqIrFe*k+cJR18Kmw zfD9lDyX(sg%jw~7X;|Wi1J?t8sjF+?z<~pl?%EZe1MK)}6bRZ5d zSl6beriOLv)={v$Adz1in;Mxtdp3Rh_I2Iv3Dav=R9^xqGm)zNN$3J8?~w0<0!-6P%fuslQ&aLf?GrEiElA)fqEpM6aqgcHv4) zfDha_#l?Tpl9CeLuwjE{XJ<#R!kV%7l->o8(FJh1*lRBZv|qn|x^Uq_-M)Rh78e(5 zR#sLN+|W*rafj1CXwV?tzJ0qcTC_;T9!uSG6u7q2c67F52yiI&B2vMA#JKa$yR^8t zSYO@pY82%9PU!Zrx5_Fax_QeM&B@7$>NMDI5`DX5LsvS7KraG*h0V7rB06Tw7%eO; z)bZoTJFpGFpbOMH&lx*x*s%5=L_P&3b`^WK5D)?UBkXrO1=RCAee%gCqwf0b1$x$( zfW~4mjm2V7PP&%RA8bci%wXyTI@s!P*l_Lqi5(cVkfkc66X$l(=T#DLV0SHR%LI47wR84b#=g z4z!;FoC3B2SM@Z$vz`Y3U!?bMa>)jeZ0RK+*#MF)y#yp1K(eKmfMf$mw)7H^YyioY fUILO0AldRCO+8sSQJvcX00000NkvXXu0mjfk4j|~ literal 0 HcmV?d00001 diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 8c2d410418d..82e6da5c00c 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -7828,6 +7828,11 @@ void CEditor::DoEditorDragBar(CUIRect View, CUIRect *pDragBar, EDragSide Side, f static float s_InitialMouseX = 0.0f; static float s_InitialMouseOffsetX = 0.0f; + bool IsVertical = Side == EDragSide::SIDE_TOP || Side == EDragSide::SIDE_BOTTOM; + + if(UI()->MouseInside(pDragBar) && UI()->HotItem() == pDragBar) + m_CursorType = IsVertical ? CURSOR_RESIZE_V : CURSOR_RESIZE_H; + bool Clicked; bool Abrupted; if(int Result = DoButton_DraggableEx(pDragBar, "", 8, pDragBar, &Clicked, &Abrupted, 0, "Change the size of the editor by dragging.")) @@ -7844,8 +7849,6 @@ void CEditor::DoEditorDragBar(CUIRect View, CUIRect *pDragBar, EDragSide Side, f if(Clicked || Abrupted) s_Operation = OP_NONE; - bool IsVertical = Side == EDragSide::SIDE_TOP || Side == EDragSide::SIDE_BOTTOM; - if(s_Operation == OP_CLICKED && absolute(IsVertical ? UI()->MouseY() - s_InitialMouseY : UI()->MouseX() - s_InitialMouseX) > 5.0f) s_Operation = OP_DRAGGING; @@ -7859,6 +7862,8 @@ void CEditor::DoEditorDragBar(CUIRect View, CUIRect *pDragBar, EDragSide Side, f *pValue = clamp(UI()->MouseY() - s_InitialMouseOffsetY - View.y + pDragBar->h, MinValue, MaxValue); else if(Side == EDragSide::SIDE_LEFT) *pValue = clamp(s_InitialMouseOffsetX + View.x + View.w - UI()->MouseX(), MinValue, MaxValue); + + m_CursorType = IsVertical ? CURSOR_RESIZE_V : CURSOR_RESIZE_H; } } } @@ -7945,6 +7950,7 @@ void CEditor::Render() Graphics()->Clear(0.0f, 0.0f, 0.0f); CUIRect View = *UI()->Screen(); UI()->MapScreen(); + m_CursorType = CURSOR_NORMAL; float Width = View.w; float Height = View.h; @@ -8306,8 +8312,10 @@ void CEditor::RenderMousePointer() return; Graphics()->WrapClamp(); - Graphics()->TextureSet(m_CursorTexture); + Graphics()->TextureSet(m_aCursorTextures[m_CursorType]); Graphics()->QuadsBegin(); + if(m_CursorType == CURSOR_RESIZE_V) + Graphics()->QuadsSetRotation(pi / 2); if(ms_pUiGotContext == UI()->HotItem()) Graphics()->SetColor(1, 0, 0, 1); IGraphics::CQuadItem QuadItem(UI()->MouseX(), UI()->MouseY(), 16.0f, 16.0f); @@ -8440,7 +8448,9 @@ void CEditor::Init() m_CheckerTexture = Graphics()->LoadTexture("editor/checker.png", IStorage::TYPE_ALL); m_BackgroundTexture = Graphics()->LoadTexture("editor/background.png", IStorage::TYPE_ALL); - m_CursorTexture = Graphics()->LoadTexture("editor/cursor.png", IStorage::TYPE_ALL); + m_aCursorTextures[CURSOR_NORMAL] = Graphics()->LoadTexture("editor/cursor.png", IStorage::TYPE_ALL); + m_aCursorTextures[CURSOR_RESIZE_H] = Graphics()->LoadTexture("editor/cursor_resize.png", IStorage::TYPE_ALL); + m_aCursorTextures[CURSOR_RESIZE_V] = m_aCursorTextures[CURSOR_RESIZE_H]; m_pTilesetPicker = std::make_shared(this, 16, 16); m_pTilesetPicker->MakePalette(); diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 4446611e39a..7f368e2e13e 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -397,7 +397,10 @@ class CEditor : public IEditor m_CheckerTexture.Invalidate(); m_BackgroundTexture.Invalidate(); - m_CursorTexture.Invalidate(); + for(int i = 0; i < NUM_CURSORS; i++) + m_aCursorTextures[i].Invalidate(); + + m_CursorType = CURSOR_NORMAL; ms_pUiGotContext = nullptr; @@ -741,7 +744,16 @@ class CEditor : public IEditor IGraphics::CTextureHandle m_CheckerTexture; IGraphics::CTextureHandle m_BackgroundTexture; - IGraphics::CTextureHandle m_CursorTexture; + + enum ECursorType + { + CURSOR_NORMAL, + CURSOR_RESIZE_V, + CURSOR_RESIZE_H, + NUM_CURSORS + }; + IGraphics::CTextureHandle m_aCursorTextures[ECursorType::NUM_CURSORS]; + ECursorType m_CursorType; IGraphics::CTextureHandle GetEntitiesTexture(); From 018529c68367e2a84fb8864e98970bd752b0a035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 28 Dec 2023 21:11:22 +0100 Subject: [PATCH 193/198] Fix mouse being outside of UI screen on right and bottom edges Ensure that the UI mouse position stays inside the UI screen rect. Previously, the mouse was not considered to be inside the UI screen when all the way at the right or bottom edge. This caused the map editor tooltip to not be shown when the mouse is all the way at the right side of the map editor view. This is a cleaner fix for #4553 which reverts the previous workaround from #6423. --- src/game/client/ui.cpp | 4 ++-- src/game/editor/editor.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 587a1542f7a..fdcdd185174 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -179,8 +179,8 @@ void CUI::OnCursorMove(float X, float Y) { if(!CheckMouseLock()) { - m_UpdatedMousePos.x = clamp(m_UpdatedMousePos.x + X, 0.0f, (float)Graphics()->WindowWidth()); - m_UpdatedMousePos.y = clamp(m_UpdatedMousePos.y + Y, 0.0f, (float)Graphics()->WindowHeight()); + m_UpdatedMousePos.x = clamp(m_UpdatedMousePos.x + X, 0.0f, Graphics()->WindowWidth() - 1.0f); + m_UpdatedMousePos.y = clamp(m_UpdatedMousePos.y + Y, 0.0f, Graphics()->WindowHeight() - 1.0f); } m_UpdatedMouseDelta += vec2(X, Y); diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index a1b243a6897..435e22deef9 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -2978,7 +2978,7 @@ void CEditor::DoMapEditor(CUIRect View) } static void *s_pEditorID = (void *)&s_pEditorID; - const bool Inside = !m_GuiActive || UI()->MouseInside(&View); + const bool Inside = UI()->MouseInside(&View); // fetch mouse position float wx = UI()->MouseWorldX(); @@ -8164,7 +8164,7 @@ void CEditor::Render() m_PopupEventWasActivated = true; } - if(m_Dialog == DIALOG_NONE && !UI()->IsPopupHovered() && (!m_GuiActive || UI()->MouseInside(&View))) + if(m_Dialog == DIALOG_NONE && !UI()->IsPopupHovered() && UI()->MouseInside(&View)) { // handle zoom hotkeys if(Input()->KeyPress(KEY_KP_MINUS)) @@ -8472,8 +8472,8 @@ void CEditor::HandleCursorMovement() if(!UI()->CheckMouseLock()) { - s_MouseX = clamp(s_MouseX + MouseRelX, 0.0f, Graphics()->WindowWidth()); - s_MouseY = clamp(s_MouseY + MouseRelY, 0.0f, Graphics()->WindowHeight()); + s_MouseX = clamp(s_MouseX + MouseRelX, 0.0f, Graphics()->WindowWidth() - 1.0f); + s_MouseY = clamp(s_MouseY + MouseRelY, 0.0f, Graphics()->WindowHeight() - 1.0f); } // update positions for ui, but only update ui when rendering From 429b0c6fdb5a047956c1720dd25e2aa09a16afd9 Mon Sep 17 00:00:00 2001 From: dobrykafe <121701317+dobrykafe@users.noreply.github.com> Date: Thu, 28 Dec 2023 22:22:46 +0100 Subject: [PATCH 194/198] add restart button for restart warning label --- src/game/client/components/menus.cpp | 21 +++++++++-- src/game/client/components/menus.h | 4 +- src/game/client/components/menus_settings.cpp | 37 ++++++++++++++----- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 3d0648d2687..003d4c7a2f1 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -1196,6 +1196,11 @@ int CMenus::Render() pTitle = Localize("Password incorrect"); pButtonText = Localize("Try again"); } + else if(m_Popup == POPUP_RESTART) + { + pTitle = Localize("Restart"); + pExtraText = Localize("Are you sure that you want to restart?"); + } else if(m_Popup == POPUP_QUIT) { pTitle = Localize("Quit"); @@ -1319,7 +1324,7 @@ int CMenus::Render() } } } - else if(m_Popup == POPUP_QUIT) + else if(m_Popup == POPUP_QUIT || m_Popup == POPUP_RESTART) { CUIRect Yes, No; Box.HSplitBottom(20.f, &Box, &Part); @@ -1329,7 +1334,7 @@ int CMenus::Render() Box.VMargin(20.f, &Box); if(m_pClient->Editor()->HasUnsavedData()) { - str_format(aBuf, sizeof(aBuf), "%s\n%s", Localize("There's an unsaved map in the editor, you might want to save it before you quit the game."), Localize("Quit anyway?")); + str_format(aBuf, sizeof(aBuf), "%s\n\n%s", Localize("There's an unsaved map in the editor, you might want to save it."), Localize("Continue anyway?")); Props.m_MaxWidth = Part.w - 20.0f; UI()->DoLabel(&Box, aBuf, 20.f, TEXTALIGN_ML, Props); } @@ -1347,8 +1352,16 @@ int CMenus::Render() static CButtonContainer s_ButtonTryAgain; if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)) { - m_Popup = POPUP_NONE; - Client()->Quit(); + if(m_Popup == POPUP_RESTART) + { + m_Popup = POPUP_NONE; + Client()->Restart(); + } + else + { + m_Popup = POPUP_NONE; + Client()->Quit(); + } } } else if(m_Popup == POPUP_PASSWORD) diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index b9dd4999f72..b6ccecd9f85 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -219,12 +219,9 @@ class CMenus : public CComponent static float ms_ListitemAdditionalHeight; // for settings - bool m_NeedRestartGeneral; - bool m_NeedRestartSkins; bool m_NeedRestartGraphics; bool m_NeedRestartSound; bool m_NeedRestartUpdate; - bool m_NeedRestartDDNet; bool m_NeedSendinfo; bool m_NeedSendDummyinfo; int m_SettingPlayerPage; @@ -745,6 +742,7 @@ class CMenus : public CComponent POPUP_RENDER_DONE, POPUP_PASSWORD, POPUP_QUIT, + POPUP_RESTART, POPUP_WARNING, // demo player states diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 7549a5b03cf..b0d91e5b449 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -1988,11 +1988,12 @@ bool CMenus::RenderLanguageSelection(CUIRect MainView) void CMenus::RenderSettings(CUIRect MainView) { // render background - CUIRect Button, TabBar, RestartWarning; + CUIRect Button, TabBar, RestartBar, RestartWarning, RestartButton; MainView.VSplitRight(120.0f, &MainView, &TabBar); MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f); MainView.Margin(10.0f, &MainView); - MainView.HSplitBottom(15.0f, &MainView, &RestartWarning); + MainView.HSplitBottom(15.0f, &MainView, &RestartBar); + RestartBar.VSplitRight(125.0f, &RestartWarning, &RestartButton); TabBar.HSplitTop(50.0f, &Button, &TabBar); Button.Draw(ms_ColorTabbarActive, IGraphics::CORNER_BR, 10.0f); @@ -2020,7 +2021,7 @@ void CMenus::RenderSettings(CUIRect MainView) } MainView.Margin(10.0f, &MainView); - RestartWarning.VMargin(10.0f, &RestartWarning); + RestartBar.VMargin(10.0f, &RestartBar); if(g_Config.m_UiSettingsPage == SETTINGS_LANGUAGE) { @@ -2073,14 +2074,32 @@ void CMenus::RenderSettings(CUIRect MainView) RenderSettingsCustom(MainView); } - if(m_NeedRestartUpdate) + if(m_NeedRestartGraphics || m_NeedRestartSound || m_NeedRestartUpdate) { - TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); - UI()->DoLabel(&RestartWarning, Localize("DDNet Client needs to be restarted to complete update!"), 14.0f, TEXTALIGN_ML); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + if(m_NeedRestartUpdate) + { + TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); + UI()->DoLabel(&RestartWarning, Localize("DDNet Client needs to be restarted to complete update!"), 14.0f, TEXTALIGN_ML); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + } + else + { + UI()->DoLabel(&RestartWarning, Localize("You must restart the game for all settings to take effect."), 14.0f, TEXTALIGN_ML); + } + + static CButtonContainer s_RestartButton; + if(DoButton_Menu(&s_RestartButton, Localize("Restart"), 0, &RestartButton)) + { + if(Client()->State() == IClient::STATE_ONLINE || m_pClient->Editor()->HasUnsavedData()) + { + m_Popup = POPUP_RESTART; + } + else + { + Client()->Restart(); + } + } } - else if(m_NeedRestartGeneral || m_NeedRestartSkins || m_NeedRestartGraphics || m_NeedRestartSound || m_NeedRestartDDNet) - UI()->DoLabel(&RestartWarning, Localize("You must restart the game for all settings to take effect."), 14.0f, TEXTALIGN_ML); } ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha, bool ClampedLight) From 1a94aa6d8a025ffb6e0244cff1b18ef89a49ce6b Mon Sep 17 00:00:00 2001 From: furo Date: Fri, 29 Dec 2023 08:33:36 +0100 Subject: [PATCH 195/198] Use 0.7 translation layer for `Sv_RaceFinish` netmessage. --- src/engine/server.h | 15 +++++++++++++++ src/game/server/teams.cpp | 34 ++++++++++------------------------ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/engine/server.h b/src/engine/server.h index 1e81f77397b..ac290f41780 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -157,6 +157,21 @@ class IServer : public IInterface return SendPackMsgOne(&MsgCopy, Flags, ClientID); } + int SendPackMsgTranslate(const CNetMsg_Sv_RaceFinish *pMsg, int Flags, int ClientID) + { + if(IsSixup(ClientID)) + { + protocol7::CNetMsg_Sv_RaceFinish Msg7; + Msg7.m_ClientID = pMsg->m_ClientID; + Msg7.m_Diff = pMsg->m_Diff; + Msg7.m_Time = pMsg->m_Time; + Msg7.m_RecordPersonal = pMsg->m_RecordPersonal; + Msg7.m_RecordServer = pMsg->m_RecordServer; + return SendPackMsgOne(&Msg7, Flags, ClientID); + } + return SendPackMsgOne(pMsg, Flags, ClientID); + } + template int SendPackMsgOne(const T *pMsg, int Flags, int ClientID) { diff --git a/src/game/server/teams.cpp b/src/game/server/teams.cpp index aa0cd418acb..2335eed755f 100644 --- a/src/game/server/teams.cpp +++ b/src/game/server/teams.cpp @@ -770,33 +770,19 @@ void CGameTeams::OnFinish(CPlayer *Player, float Time, const char *pTimestamp) Server()->SendPackMsg(&MsgLegacy, MSGFLAG_VITAL, ClientID); } } - - CNetMsg_Sv_RaceFinish RaceFinishMsg; - RaceFinishMsg.m_ClientID = ClientID; - RaceFinishMsg.m_Time = Time * 1000; - RaceFinishMsg.m_Diff = 0; - if(pData->m_BestTime) - { - RaceFinishMsg.m_Diff = Diff * 1000 * (Time < pData->m_BestTime ? -1 : 1); - } - RaceFinishMsg.m_RecordPersonal = (Time < pData->m_BestTime || !pData->m_BestTime); - RaceFinishMsg.m_RecordServer = Time < GameServer()->m_pController->m_CurrentRecord; - Server()->SendPackMsg(&RaceFinishMsg, MSGFLAG_VITAL | MSGFLAG_NORECORD, -1); } - else + + CNetMsg_Sv_RaceFinish RaceFinishMsg; + RaceFinishMsg.m_ClientID = ClientID; + RaceFinishMsg.m_Time = Time * 1000; + RaceFinishMsg.m_Diff = 0; + if(pData->m_BestTime) { - protocol7::CNetMsg_Sv_RaceFinish Msg; - Msg.m_ClientID = ClientID; - Msg.m_Time = Time * 1000; - Msg.m_Diff = 0; - if(pData->m_BestTime) - { - Msg.m_Diff = Diff * 1000 * (Time < pData->m_BestTime ? -1 : 1); - } - Msg.m_RecordPersonal = (Time < pData->m_BestTime || !pData->m_BestTime); - Msg.m_RecordServer = Time < GameServer()->m_pController->m_CurrentRecord; - Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, -1); + RaceFinishMsg.m_Diff = Diff * 1000 * (Time < pData->m_BestTime ? -1 : 1); } + RaceFinishMsg.m_RecordPersonal = (Time < pData->m_BestTime || !pData->m_BestTime); + RaceFinishMsg.m_RecordServer = Time < GameServer()->m_pController->m_CurrentRecord; + Server()->SendPackMsg(&RaceFinishMsg, MSGFLAG_VITAL | MSGFLAG_NORECORD, -1); bool CallSaveScore = g_Config.m_SvSaveWorseScores; bool NeedToSendNewPersonalRecord = false; From 7a84a746e0bb6f02b43cc65252cee1c2066de63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 29 Dec 2023 00:00:33 +0100 Subject: [PATCH 196/198] Add `CSample::TotalTime` function --- src/engine/client/sound.cpp | 2 +- src/engine/client/sound.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 86540773051..aae7e589752 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -623,7 +623,7 @@ float CSound::GetSampleTotalTime(int SampleID) if(SampleID == -1 || SampleID >= NUM_SAMPLES) return 0.0f; - return (m_aSamples[SampleID].m_NumFrames / (float)m_aSamples[SampleID].m_Rate); + return m_aSamples[SampleID].TotalTime(); } float CSound::GetSampleCurrentTime(int SampleID) diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index ea5eb5766f3..d033130c7e6 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -20,6 +20,11 @@ struct CSample int m_LoopStart; int m_LoopEnd; int m_PausedAt; + + float TotalTime() const + { + return m_NumFrames / (float)m_Rate; + } }; struct CChannel From 87191a380e6ef86a715666d783756533c2138060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 29 Dec 2023 12:35:45 +0100 Subject: [PATCH 197/198] Use sound lock when setting/getting current time of sample The lock needs to be owned when accessing the sound voices. Calling `IsPlaying` is redundant, as the loops effectively check whether the sample is playing. --- src/engine/client/sound.cpp | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index aae7e589752..df37d8ea0df 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -631,19 +631,17 @@ float CSound::GetSampleCurrentTime(int SampleID) if(SampleID == -1 || SampleID >= NUM_SAMPLES) return 0.0f; + const CLockScope LockScope(m_SoundLock); CSample *pSample = &m_aSamples[SampleID]; - if(IsPlaying(SampleID)) + for(auto &Voice : m_aVoices) { - for(auto &Voice : m_aVoices) + if(Voice.m_pSample == pSample) { - if(Voice.m_pSample == pSample) - { - return (Voice.m_Tick / (float)pSample->m_Rate); - } + return Voice.m_Tick / (float)pSample->m_Rate; } } - return (pSample->m_PausedAt / (float)pSample->m_Rate); + return pSample->m_PausedAt / (float)pSample->m_Rate; } void CSound::SetSampleCurrentTime(int SampleID, float Time) @@ -651,21 +649,18 @@ void CSound::SetSampleCurrentTime(int SampleID, float Time) if(SampleID == -1 || SampleID >= NUM_SAMPLES) return; + const CLockScope LockScope(m_SoundLock); CSample *pSample = &m_aSamples[SampleID]; - if(IsPlaying(SampleID)) + for(auto &Voice : m_aVoices) { - for(auto &Voice : m_aVoices) + if(Voice.m_pSample == pSample) { - if(Voice.m_pSample == pSample) - { - Voice.m_Tick = pSample->m_NumFrames * Time; - } + Voice.m_Tick = pSample->m_NumFrames * Time; + return; } } - else - { - pSample->m_PausedAt = pSample->m_NumFrames * Time; - } + + pSample->m_PausedAt = pSample->m_NumFrames * Time; } void CSound::SetChannel(int ChannelID, float Vol, float Pan) From 661339cc64dbae2780249e9c5dfb3a32d6cd9733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 29 Dec 2023 12:45:27 +0100 Subject: [PATCH 198/198] Optimize allocation of sound sample indices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace linear search for free sample index with free list. This brings sound sample index allocation down to constant complexity independent of the number of allocated sounds. On average the time to allocate sound samples is reduced by around 75% (843µs down to 223µs). For perspective, the time to load all default sounds on client launch is reduced by around 15ms (although this does not significantly affect launch time due to threaded loading). --- src/engine/client/sound.cpp | 102 +++++++++++++++++++++++++----------- src/engine/client/sound.h | 15 ++++-- 2 files changed, 82 insertions(+), 35 deletions(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index df37d8ea0df..d7a23544ded 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -21,6 +21,9 @@ extern "C" { #include +static constexpr int SAMPLE_INDEX_USED = -2; +static constexpr int SAMPLE_INDEX_FULL = -1; + void CSound::Mix(short *pFinalOut, unsigned Frames) { Frames = minimum(Frames, m_MaxFrames); @@ -240,6 +243,15 @@ int CSound::Init() #endif m_pMixBuffer = (int *)calloc(m_MaxFrames * 2, sizeof(int)); + m_FirstFreeSampleIndex = 0; + for(size_t i = 0; i < std::size(m_aSamples) - 1; ++i) + { + m_aSamples[i].m_Index = i; + m_aSamples[i].m_NextFreeSampleIndex = i + 1; + } + m_aSamples[std::size(m_aSamples) - 1].m_Index = std::size(m_aSamples) - 1; + m_aSamples[std::size(m_aSamples) - 1].m_NextFreeSampleIndex = SAMPLE_INDEX_FULL; + SDL_PauseAudioDevice(m_Device, 0); m_SoundEnabled = true; @@ -274,22 +286,28 @@ void CSound::Shutdown() m_pMixBuffer = nullptr; } -int CSound::AllocID() +CSample *CSound::AllocSample() { - // TODO: linear search, get rid of it - for(unsigned SampleID = 0; SampleID < NUM_SAMPLES; SampleID++) + if(m_FirstFreeSampleIndex == SAMPLE_INDEX_FULL) + return nullptr; + + CSample *pSample = &m_aSamples[m_FirstFreeSampleIndex]; + m_FirstFreeSampleIndex = pSample->m_NextFreeSampleIndex; + pSample->m_NextFreeSampleIndex = SAMPLE_INDEX_USED; + if(pSample->m_pData != nullptr) { - if(m_aSamples[SampleID].m_pData == nullptr) - return SampleID; + char aError[64]; + str_format(aError, sizeof(aError), "Sample was not unloaded (index=%d, duration=%f)", pSample->m_Index, pSample->TotalTime()); + dbg_assert(false, aError); } - - return -1; + return pSample; } void CSound::RateConvert(CSample &Sample) const { + dbg_assert(Sample.m_pData != nullptr, "Sample is not loaded"); // make sure that we need to convert this sound - if(!Sample.m_pData || Sample.m_Rate == m_MixingRate) + if(Sample.m_Rate == m_MixingRate) return; // allocate new data @@ -505,8 +523,8 @@ int CSound::LoadOpus(const char *pFilename, int StorageType) if(!m_pStorage) return -1; - const int SampleID = AllocID(); - if(SampleID < 0) + CSample *pSample = AllocSample(); + if(!pSample) { dbg_msg("sound/opus", "failed to allocate sample ID. filename='%s'", pFilename); return -1; @@ -516,20 +534,24 @@ int CSound::LoadOpus(const char *pFilename, int StorageType) unsigned DataSize; if(!m_pStorage->ReadFile(pFilename, StorageType, &pData, &DataSize)) { + UnloadSample(pSample->m_Index); dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename); return -1; } - const bool DecodeSuccess = DecodeOpus(m_aSamples[SampleID], pData, DataSize); + const bool DecodeSuccess = DecodeOpus(*pSample, pData, DataSize); free(pData); if(!DecodeSuccess) + { + UnloadSample(pSample->m_Index); return -1; + } if(g_Config.m_Debug) dbg_msg("sound/opus", "loaded %s", pFilename); - RateConvert(m_aSamples[SampleID]); - return SampleID; + RateConvert(*pSample); + return pSample->m_Index; } int CSound::LoadWV(const char *pFilename, int StorageType) @@ -541,8 +563,8 @@ int CSound::LoadWV(const char *pFilename, int StorageType) if(!m_pStorage) return -1; - const int SampleID = AllocID(); - if(SampleID < 0) + CSample *pSample = AllocSample(); + if(!pSample) { dbg_msg("sound/wv", "failed to allocate sample ID. filename='%s'", pFilename); return -1; @@ -552,20 +574,24 @@ int CSound::LoadWV(const char *pFilename, int StorageType) unsigned DataSize; if(!m_pStorage->ReadFile(pFilename, StorageType, &pData, &DataSize)) { + UnloadSample(pSample->m_Index); dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename); return -1; } - const bool DecodeSuccess = DecodeWV(m_aSamples[SampleID], pData, DataSize); + const bool DecodeSuccess = DecodeWV(*pSample, pData, DataSize); free(pData); if(!DecodeSuccess) + { + UnloadSample(pSample->m_Index); return -1; + } if(g_Config.m_Debug) dbg_msg("sound/wv", "loaded %s", pFilename); - RateConvert(m_aSamples[SampleID]); - return SampleID; + RateConvert(*pSample); + return pSample->m_Index; } int CSound::LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) @@ -577,15 +603,18 @@ int CSound::LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEdito if(!pData) return -1; - const int SampleID = AllocID(); - if(SampleID < 0) + CSample *pSample = AllocSample(); + if(!pSample) return -1; - if(!DecodeOpus(m_aSamples[SampleID], pData, DataSize)) + if(!DecodeOpus(*pSample, pData, DataSize)) + { + UnloadSample(pSample->m_Index); return -1; + } - RateConvert(m_aSamples[SampleID]); - return SampleID; + RateConvert(*pSample); + return pSample->m_Index; } int CSound::LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) @@ -597,15 +626,18 @@ int CSound::LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor if(!pData) return -1; - const int SampleID = AllocID(); - if(SampleID < 0) + CSample *pSample = AllocSample(); + if(!pSample) return -1; - if(!DecodeWV(m_aSamples[SampleID], pData, DataSize)) + if(!DecodeWV(*pSample, pData, DataSize)) + { + UnloadSample(pSample->m_Index); return -1; + } - RateConvert(m_aSamples[SampleID]); - return SampleID; + RateConvert(*pSample); + return pSample->m_Index; } void CSound::UnloadSample(int SampleID) @@ -614,8 +646,18 @@ void CSound::UnloadSample(int SampleID) return; Stop(SampleID); - free(m_aSamples[SampleID].m_pData); - m_aSamples[SampleID].m_pData = nullptr; + + // Free data + CSample &Sample = m_aSamples[SampleID]; + free(Sample.m_pData); + Sample.m_pData = nullptr; + + // Free slot + if(Sample.m_NextFreeSampleIndex == SAMPLE_INDEX_USED) + { + Sample.m_NextFreeSampleIndex = m_FirstFreeSampleIndex; + m_FirstFreeSampleIndex = Sample.m_Index; + } } float CSound::GetSampleTotalTime(int SampleID) diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index d033130c7e6..0363198232a 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -13,6 +13,9 @@ struct CSample { + int m_Index; + int m_NextFreeSampleIndex; + short *m_pData; int m_NumFrames; int m_Rate; @@ -66,6 +69,8 @@ class CSound : public IEngineSound CLock m_SoundLock; CSample m_aSamples[NUM_SAMPLES] = {{0}}; + int m_FirstFreeSampleIndex = 0; + CVoice m_aVoices[NUM_VOICES] = {{0}}; CChannel m_aChannels[NUM_CHANNELS] = {{255, 0}}; int m_NextVoice = 0; @@ -81,7 +86,7 @@ class CSound : public IEngineSound int *m_pMixBuffer = nullptr; - int AllocID(); + CSample *AllocSample(); void RateConvert(CSample &Sample) const; bool DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const; @@ -96,10 +101,10 @@ class CSound : public IEngineSound bool IsSoundEnabled() override { return m_SoundEnabled; } - int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override; - int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override; - int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; - int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; + int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override REQUIRES(!m_SoundLock); + int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override REQUIRES(!m_SoundLock); + int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor) override REQUIRES(!m_SoundLock); + int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override REQUIRES(!m_SoundLock); void UnloadSample(int SampleID) override REQUIRES(!m_SoundLock); float GetSampleTotalTime(int SampleID) override; // in s