From a3e19fc39a981ae8c81c37b73d7fc7bd251df751 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Thu, 26 Sep 2024 18:57:23 +0200 Subject: [PATCH 1/5] Make CCharacter::SetSuper() idempotent Specifically, SetSuper(false) followed by SetSuper(false) should not change teams. The following commit relies on this, but it is generally a good idea to remove this footgun. --- src/game/server/entities/character.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index ac98c8daa..d785e5179 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -174,14 +174,15 @@ void CCharacter::SetSolo(bool Solo) void CCharacter::SetSuper(bool Super) { + bool WasSuper = m_Core.m_Super; m_Core.m_Super = Super; - if(Super) + if(Super && !WasSuper) { m_TeamBeforeSuper = Team(); Teams()->SetCharacterTeam(GetPlayer()->GetCid(), TEAM_SUPER); m_DDRaceState = DDRACE_CHEAT; } - else + else if(!Super && WasSuper) { Teams()->SetForceCharacterTeam(GetPlayer()->GetCid(), m_TeamBeforeSuper); } From f51664e5ce8c8c89cead653f005d9ce4458e2280 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Tue, 24 Sep 2024 00:54:38 +0200 Subject: [PATCH 2/5] Add rcon command "invincible" which does nothing yet This commit adds the rcon command "invincible" which toggles the new m_Invincible variable on CCharacterCore. The feature is supposed to be an alterantive to super that is safe for /practice mode. The state of m_Invincible is synchronized to the client via the DDNetCharacter net object. Future commits will change checks for m_Super to check for m_Invincible as well in places where this is safe such as e.g. preventing freezing of the player. The ability to interact with players that are in other teams will remain exclusive to super. --- datasrc/network.py | 2 +- src/game/gamecore.cpp | 2 ++ src/game/gamecore.h | 1 + src/game/server/ddracecommands.cpp | 8 ++++++++ src/game/server/entities/character.cpp | 15 +++++++++++++++ src/game/server/entities/character.h | 1 + src/game/server/gamecontext.cpp | 1 + src/game/server/gamecontext.h | 1 + 8 files changed, 30 insertions(+), 1 deletion(-) diff --git a/datasrc/network.py b/datasrc/network.py index 04c02748b..01c6661e2 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -11,7 +11,7 @@ CharacterFlags = ["SOLO", "JETPACK", "COLLISION_DISABLED", "ENDLESS_HOOK", "ENDL "HAMMER_HIT_DISABLED", "SHOTGUN_HIT_DISABLED", "GRENADE_HIT_DISABLED", "LASER_HIT_DISABLED", "HOOK_HIT_DISABLED", "TELEGUN_GUN", "TELEGUN_GRENADE", "TELEGUN_LASER", "WEAPON_HAMMER", "WEAPON_GUN", "WEAPON_SHOTGUN", "WEAPON_GRENADE", "WEAPON_LASER", "WEAPON_NINJA", - "MOVEMENTS_DISABLED", "IN_FREEZE", "PRACTICE_MODE", "LOCK_MODE", "TEAM0_MODE"] + "MOVEMENTS_DISABLED", "IN_FREEZE", "PRACTICE_MODE", "LOCK_MODE", "TEAM0_MODE", "INVINCIBLE"] GameInfoFlags = [ "TIMESCORE", "GAMETYPE_RACE", "GAMETYPE_FASTCAP", "GAMETYPE_FNG", "GAMETYPE_DDRACE", "GAMETYPE_DDNET", "GAMETYPE_BLOCK_WORLDS", diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index 042e64579..45081cad3 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -173,6 +173,7 @@ void CCharacterCore::Reset() m_ShotgunHitDisabled = false; m_HookHitDisabled = false; m_Super = false; + m_Invincible = false; m_HasTelegunGun = false; m_HasTelegunGrenade = false; m_HasTelegunLaser = false; @@ -647,6 +648,7 @@ void CCharacterCore::ReadDDNet(const CNetObj_DDNetCharacter *pObjDDNet) m_LaserHitDisabled = pObjDDNet->m_Flags & CHARACTERFLAG_LASER_HIT_DISABLED; m_HookHitDisabled = pObjDDNet->m_Flags & CHARACTERFLAG_HOOK_HIT_DISABLED; m_Super = pObjDDNet->m_Flags & CHARACTERFLAG_SUPER; + m_Invincible = pObjDDNet->m_Flags & CHARACTERFLAG_INVINCIBLE; // Endless m_EndlessHook = pObjDDNet->m_Flags & CHARACTERFLAG_ENDLESS_HOOK; diff --git a/src/game/gamecore.h b/src/game/gamecore.h index c33b14e17..0cce06c2c 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -256,6 +256,7 @@ public: bool m_ShotgunHitDisabled; bool m_HookHitDisabled; bool m_Super; + bool m_Invincible; bool m_HasTelegunGun; bool m_HasTelegunGrenade; bool m_HasTelegunLaser; diff --git a/src/game/server/ddracecommands.cpp b/src/game/server/ddracecommands.cpp index e65cad482..e7e9b8251 100644 --- a/src/game/server/ddracecommands.cpp +++ b/src/game/server/ddracecommands.cpp @@ -160,6 +160,14 @@ void CGameContext::ConUnSuper(IConsole::IResult *pResult, void *pUserData) } } +void CGameContext::ConToggleInvincible(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + CCharacter *pChr = pSelf->GetPlayerChar(pResult->m_ClientId); + if(pChr) + pChr->SetInvincible(pResult->NumArguments() == 0 ? !pChr->Core()->m_Invincible : pResult->GetInteger(0)); +} + void CGameContext::ConSolo(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index d785e5179..214228809 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -174,6 +174,10 @@ void CCharacter::SetSolo(bool Solo) void CCharacter::SetSuper(bool Super) { + // Disable invincible mode before activating super mode. Both modes active at the same time wouldn't necessarily break anything but it's not useful. + if(Super) + SetInvincible(false); + bool WasSuper = m_Core.m_Super; m_Core.m_Super = Super; if(Super && !WasSuper) @@ -188,6 +192,15 @@ void CCharacter::SetSuper(bool Super) } } +void CCharacter::SetInvincible(bool Invincible) +{ + // Disable super mode before activating invincible mode. Both modes active at the same time wouldn't necessarily break anything but it's not useful. + if(Invincible) + SetSuper(false); + + m_Core.m_Invincible = Invincible; +} + void CCharacter::SetLiveFrozen(bool Active) { m_Core.m_LiveFrozen = Active; @@ -1220,6 +1233,8 @@ void CCharacter::Snap(int SnappingClient) pDDNetCharacter->m_Flags |= CHARACTERFLAG_SOLO; if(m_Core.m_Super) pDDNetCharacter->m_Flags |= CHARACTERFLAG_SUPER; + if(m_Core.m_Invincible) + pDDNetCharacter->m_Flags |= CHARACTERFLAG_INVINCIBLE; if(m_Core.m_EndlessHook) pDDNetCharacter->m_Flags |= CHARACTERFLAG_ENDLESS_HOOK; if(m_Core.m_CollisionDisabled || !Tuning()->m_PlayerCollision) diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h index df569dc2c..59793707c 100644 --- a/src/game/server/entities/character.h +++ b/src/game/server/entities/character.h @@ -51,6 +51,7 @@ public: void SetJumps(int Jumps); void SetSolo(bool Solo); void SetSuper(bool Super); + void SetInvincible(bool Invincible); void SetLiveFrozen(bool Active); void SetDeepFrozen(bool Active); void HandleWeaponSwitch(); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 3b0356a8e..73606fcac 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -3691,6 +3691,7 @@ void CGameContext::RegisterDDRaceCommands() Console()->Register("unninja", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnNinja, this, "Removes ninja from you"); Console()->Register("super", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConSuper, this, "Makes you super"); Console()->Register("unsuper", "", CFGFLAG_SERVER, ConUnSuper, this, "Removes super from you"); + Console()->Register("invincible", "?i['0'|'1']", CFGFLAG_SERVER | CMDFLAG_TEST, ConToggleInvincible, this, "Toggles invincible mode"); Console()->Register("endless_hook", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConEndlessHook, this, "Gives you endless hook"); Console()->Register("unendless_hook", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnEndlessHook, this, "Removes endless hook from you"); Console()->Register("solo", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConSolo, this, "Puts you into solo part"); diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index d25758c53..dce2499dc 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -391,6 +391,7 @@ private: static void ConUnLiveFreeze(IConsole::IResult *pResult, void *pUserData); static void ConUnSuper(IConsole::IResult *pResult, void *pUserData); static void ConSuper(IConsole::IResult *pResult, void *pUserData); + static void ConToggleInvincible(IConsole::IResult *pResult, void *pUserData); static void ConShotgun(IConsole::IResult *pResult, void *pUserData); static void ConGrenade(IConsole::IResult *pResult, void *pUserData); static void ConLaser(IConsole::IResult *pResult, void *pUserData); From b564110675d8c8be1433dab3cdea91f354d4a33b Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Tue, 24 Sep 2024 01:16:40 +0200 Subject: [PATCH 3/5] Make invincible players immune to freeze, death and teleport tiles and give them unlimited jumps Specifically: * Ignore freeze, deep freeze, deep unfreeze, live freeze, and live unfreeze tiles * Ignore also the switched variants of those tiles * Allow movement when deep and live frozen * Ignore death tiles * Ignore red tele, blue tele, red checkpoint tele, and blue checkpoint tele tiles * Unlimited jumps * Disable /rescue Switches, doors, draggers, etc. are not disabled for invincible players in this patch --- .../client/prediction/entities/character.cpp | 30 ++++++------- src/game/server/entities/character.cpp | 44 ++++++++++--------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/game/client/prediction/entities/character.cpp b/src/game/client/prediction/entities/character.cpp index 8ce77b67e..a3369651b 100644 --- a/src/game/client/prediction/entities/character.cpp +++ b/src/game/client/prediction/entities/character.cpp @@ -757,19 +757,19 @@ void CCharacter::HandleTiles(int Index) Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aType[Team()] = TILE_SWITCHCLOSE; Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aLastUpdateTick[Team()] = GameWorld()->GameTick(); } - else if(Collision()->GetSwitchType(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) { Freeze(Collision()->GetSwitchDelay(MapIndex)); } } - else if(Collision()->GetSwitchType(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) m_Core.m_DeepFrozen = true; } - else if(Collision()->GetSwitchType(MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) m_Core.m_DeepFrozen = false; @@ -817,14 +817,14 @@ void CCharacter::HandleTiles(int Index) if(NewJumps != m_Core.m_Jumps) m_Core.m_Jumps = NewJumps; } - else if(Collision()->GetSwitchType(MapIndex) == TILE_LFREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_LFREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) { m_Core.m_LiveFrozen = true; } } - else if(Collision()->GetSwitchType(MapIndex) == TILE_LUNFREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_LUNFREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) { @@ -833,7 +833,7 @@ void CCharacter::HandleTiles(int Index) } // freeze - if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Core.m_Super && !m_Core.m_DeepFrozen) + if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible && !m_Core.m_DeepFrozen) { Freeze(); } @@ -843,21 +843,21 @@ void CCharacter::HandleTiles(int Index) } // deep freeze - if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Core.m_Super && !m_Core.m_DeepFrozen) + if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible && !m_Core.m_DeepFrozen) { m_Core.m_DeepFrozen = true; } - else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Core.m_Super && m_Core.m_DeepFrozen) + else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible && m_Core.m_DeepFrozen) { m_Core.m_DeepFrozen = false; } // live freeze - if(((m_TileIndex == TILE_LFREEZE) || (m_TileFIndex == TILE_LFREEZE)) && !m_Core.m_Super) + if(((m_TileIndex == TILE_LFREEZE) || (m_TileFIndex == TILE_LFREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible) { m_Core.m_LiveFrozen = true; } - else if(((m_TileIndex == TILE_LUNFREEZE) || (m_TileFIndex == TILE_LUNFREEZE)) && !m_Core.m_Super) + else if(((m_TileIndex == TILE_LUNFREEZE) || (m_TileFIndex == TILE_LUNFREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible) { m_Core.m_LiveFrozen = false; } @@ -959,7 +959,7 @@ void CCharacter::HandleTuneLayer() void CCharacter::DDRaceTick() { mem_copy(&m_Input, &m_SavedInput, sizeof(m_Input)); - if(m_Core.m_LiveFrozen && !m_CanMoveInFreeze && !m_Core.m_Super) + if(m_Core.m_LiveFrozen && !m_CanMoveInFreeze && !m_Core.m_Super && !m_Core.m_Invincible) { m_Input.m_Direction = 0; m_Input.m_Jump = 0; @@ -1007,7 +1007,7 @@ void CCharacter::DDRacePostCoreTick() m_FrozenLastTick = false; - if(m_Core.m_DeepFrozen && !m_Core.m_Super) + if(m_Core.m_DeepFrozen && !m_Core.m_Super && !m_Core.m_Invincible) Freeze(); // following jump rules can be overridden by tiles, like Refill Jumps, Stopper and Wall Jump @@ -1032,9 +1032,9 @@ void CCharacter::DDRacePostCoreTick() m_Core.m_Jumped = 1; } - if((m_Core.m_Super || m_Core.m_EndlessJump) && m_Core.m_Jumped > 1) + if((m_Core.m_Super || m_Core.m_Invincible || m_Core.m_EndlessJump) && m_Core.m_Jumped > 1) { - // Super players and players with infinite jumps always have light feet + // Super players, invincible players and players with infinite jumps always have light feet m_Core.m_Jumped = 1; } @@ -1056,7 +1056,7 @@ bool CCharacter::Freeze(int Seconds) { if(!GameWorld()->m_WorldConfig.m_PredictFreeze) return false; - if(Seconds <= 0 || m_Core.m_Super || m_FreezeTime > Seconds * GameWorld()->GameTickSpeed()) + if(Seconds <= 0 || m_Core.m_Super || m_Core.m_Invincible || m_FreezeTime > Seconds * GameWorld()->GameTickSpeed()) return false; if(m_Core.m_FreezeStart < GameWorld()->GameTick() - GameWorld()->GameTickSpeed()) { diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 214228809..fa1d620ef 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -199,6 +199,8 @@ void CCharacter::SetInvincible(bool Invincible) SetSuper(false); m_Core.m_Invincible = Invincible; + if(Invincible) + UnFreeze(); } void CCharacter::SetLiveFrozen(bool Active) @@ -1377,7 +1379,7 @@ void CCharacter::HandleSkippableTiles(int Index) Collision()->GetFCollisionAt(m_Pos.x + GetProximityRadius() / 3.f, m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH || Collision()->GetFCollisionAt(m_Pos.x - GetProximityRadius() / 3.f, m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH || Collision()->GetFCollisionAt(m_Pos.x - GetProximityRadius() / 3.f, m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH) && - !m_Core.m_Super && !(Team() && Teams()->TeeFinished(m_pPlayer->GetCid()))) + !m_Core.m_Super && !m_Core.m_Invincible && !(Team() && Teams()->TeeFinished(m_pPlayer->GetCid()))) { Die(m_pPlayer->GetCid(), WEAPON_WORLD); return; @@ -1507,7 +1509,7 @@ void CCharacter::HandleTiles(int Index) return; // freeze - if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Core.m_Super && !m_Core.m_DeepFrozen) + if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible && !m_Core.m_DeepFrozen) { Freeze(); } @@ -1515,17 +1517,17 @@ void CCharacter::HandleTiles(int Index) UnFreeze(); // deep freeze - if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Core.m_Super && !m_Core.m_DeepFrozen) + if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible && !m_Core.m_DeepFrozen) m_Core.m_DeepFrozen = true; - else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Core.m_Super && m_Core.m_DeepFrozen) + else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible && m_Core.m_DeepFrozen) m_Core.m_DeepFrozen = false; // live freeze - if(((m_TileIndex == TILE_LFREEZE) || (m_TileFIndex == TILE_LFREEZE)) && !m_Core.m_Super) + if(((m_TileIndex == TILE_LFREEZE) || (m_TileFIndex == TILE_LFREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible) { m_Core.m_LiveFrozen = true; } - else if(((m_TileIndex == TILE_LUNFREEZE) || (m_TileFIndex == TILE_LUNFREEZE)) && !m_Core.m_Super) + else if(((m_TileIndex == TILE_LUNFREEZE) || (m_TileFIndex == TILE_LUNFREEZE)) && !m_Core.m_Super && !m_Core.m_Invincible) { m_Core.m_LiveFrozen = false; } @@ -1706,31 +1708,31 @@ void CCharacter::HandleTiles(int Index) Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aType[Team()] = TILE_SWITCHCLOSE; Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aLastUpdateTick[Team()] = Server()->Tick(); } - else if(Collision()->GetSwitchType(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) { Freeze(Collision()->GetSwitchDelay(MapIndex)); } } - else if(Collision()->GetSwitchType(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) m_Core.m_DeepFrozen = true; } - else if(Collision()->GetSwitchType(MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) m_Core.m_DeepFrozen = false; } - else if(Collision()->GetSwitchType(MapIndex) == TILE_LFREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_LFREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) { m_Core.m_LiveFrozen = true; } } - else if(Collision()->GetSwitchType(MapIndex) == TILE_LUNFREEZE && Team() != TEAM_SUPER) + else if(Collision()->GetSwitchType(MapIndex) == TILE_LUNFREEZE && Team() != TEAM_SUPER && !m_Core.m_Invincible) { if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()]) { @@ -1862,7 +1864,7 @@ void CCharacter::HandleTiles(int Index) int z = Collision()->IsTeleport(MapIndex); if(!g_Config.m_SvOldTeleportHook && !g_Config.m_SvOldTeleportWeapons && z && !Collision()->TeleOuts(z - 1).empty()) { - if(m_Core.m_Super) + if(m_Core.m_Super || m_Core.m_Invincible) return; int TeleOut = GameWorld()->m_Core.RandomOr0(Collision()->TeleOuts(z - 1).size()); m_Core.m_Pos = Collision()->TeleOuts(z - 1)[TeleOut]; @@ -1877,7 +1879,7 @@ void CCharacter::HandleTiles(int Index) int evilz = Collision()->IsEvilTeleport(MapIndex); if(evilz && !Collision()->TeleOuts(evilz - 1).empty()) { - if(m_Core.m_Super) + if(m_Core.m_Super || m_Core.m_Invincible) return; int TeleOut = GameWorld()->m_Core.RandomOr0(Collision()->TeleOuts(evilz - 1).size()); m_Core.m_Pos = Collision()->TeleOuts(evilz - 1)[TeleOut]; @@ -1899,7 +1901,7 @@ void CCharacter::HandleTiles(int Index) } if(Collision()->IsCheckEvilTeleport(MapIndex)) { - if(m_Core.m_Super) + if(m_Core.m_Super || m_Core.m_Invincible) return; // first check if there is a TeleCheckOut for the current recorded checkpoint, if not check previous checkpoints for(int k = m_TeleCheckpoint - 1; k >= 0; k--) @@ -1936,7 +1938,7 @@ void CCharacter::HandleTiles(int Index) } if(Collision()->IsCheckTeleport(MapIndex)) { - if(m_Core.m_Super) + if(m_Core.m_Super || m_Core.m_Invincible) return; // first check if there is a TeleCheckOut for the current recorded checkpoint, if not check previous checkpoints for(int k = m_TeleCheckpoint - 1; k >= 0; k--) @@ -2082,7 +2084,7 @@ void CCharacter::DDRaceTick() if(m_Input.m_Direction != 0 || m_Input.m_Jump != 0) m_LastMove = Server()->Tick(); - if(m_Core.m_LiveFrozen && !m_Core.m_Super) + if(m_Core.m_LiveFrozen && !m_Core.m_Super && !m_Core.m_Invincible) { m_Input.m_Direction = 0; m_Input.m_Jump = 0; @@ -2136,7 +2138,7 @@ void CCharacter::DDRacePostCoreTick() m_FrozenLastTick = false; - if(m_Core.m_DeepFrozen && !m_Core.m_Super) + if(m_Core.m_DeepFrozen && !m_Core.m_Super && !m_Core.m_Invincible) Freeze(); // following jump rules can be overridden by tiles, like Refill Jumps, Stopper and Wall Jump @@ -2161,9 +2163,9 @@ void CCharacter::DDRacePostCoreTick() m_Core.m_Jumped = 1; } - if((m_Core.m_Super || m_Core.m_EndlessJump) && m_Core.m_Jumped > 1) + if((m_Core.m_Super || m_Core.m_Invincible || m_Core.m_EndlessJump) && m_Core.m_Jumped > 1) { - // Super players and players with infinite jumps always have light feet + // Super players, invincible players and players with infinite jumps always have light feet m_Core.m_Jumped = 1; } @@ -2208,7 +2210,7 @@ void CCharacter::DDRacePostCoreTick() bool CCharacter::Freeze(int Seconds) { - if(Seconds <= 0 || m_Core.m_Super || m_FreezeTime > Seconds * Server()->TickSpeed()) + if(Seconds <= 0 || m_Core.m_Super || m_Core.m_Invincible || m_FreezeTime > Seconds * Server()->TickSpeed()) return false; if(m_FreezeTime == 0 || m_Core.m_FreezeStart < Server()->Tick() - Server()->TickSpeed()) { @@ -2382,7 +2384,7 @@ void CCharacter::DDRaceInit() void CCharacter::Rescue() { - if(m_SetSavePos[GetPlayer()->m_RescueMode] && !m_Core.m_Super) + if(m_SetSavePos[GetPlayer()->m_RescueMode] && !m_Core.m_Super && !m_Core.m_Invincible) { if(m_LastRescue + (int64_t)g_Config.m_SvRescueDelay * Server()->TickSpeed() > Server()->Tick()) { From c91be7b19f43f51649cde61317df3718f1789910 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Thu, 26 Sep 2024 19:07:37 +0200 Subject: [PATCH 4/5] Add practice command /invincible to toggle invincible mode --- src/game/server/ddracechat.cpp | 7 +++++++ src/game/server/gamecontext.cpp | 1 + src/game/server/gamecontext.h | 1 + 3 files changed, 9 insertions(+) diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 63c9137d7..63db4bdff 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -2167,6 +2167,13 @@ void CGameContext::ConPracticeUnNinja(IConsole::IResult *pResult, void *pUserDat ConUnNinja(pResult, pUserData); } +void CGameContext::ConPracticeToggleInvincible(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + if(pSelf->GetPracticeCharacter(pResult)) + ConToggleInvincible(pResult, pUserData); +} + void CGameContext::ConPracticeAddWeapon(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 73606fcac..55164d835 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -3821,6 +3821,7 @@ void CGameContext::RegisterChatCommands() Console()->Register("unweapons", "", CFGFLAG_CHAT | CMDFLAG_PRACTICE, ConPracticeUnWeapons, this, "Removes all weapons from you"); Console()->Register("ninja", "", CFGFLAG_CHAT | CMDFLAG_PRACTICE, ConPracticeNinja, this, "Makes you a ninja"); Console()->Register("unninja", "", CFGFLAG_CHAT | CMDFLAG_PRACTICE, ConPracticeUnNinja, this, "Removes ninja from you"); + Console()->Register("invincible", "?i['0'|'1']", CFGFLAG_CHAT | CMDFLAG_PRACTICE, ConPracticeToggleInvincible, this, "Toggles invincible mode"); Console()->Register("kill", "", CFGFLAG_CHAT | CFGFLAG_SERVER, ConProtectedKill, this, "Kill yourself when kill-protected during a long game (use f1, kill for regular kill)"); } diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index dce2499dc..4d402175d 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -493,6 +493,7 @@ private: static void ConPracticeUnWeapons(IConsole::IResult *pResult, void *pUserData); static void ConPracticeNinja(IConsole::IResult *pResult, void *pUserData); static void ConPracticeUnNinja(IConsole::IResult *pResult, void *pUserData); + static void ConPracticeToggleInvincible(IConsole::IResult *pResult, void *pUserData); static void ConPracticeAddWeapon(IConsole::IResult *pResult, void *pUserData); static void ConPracticeRemoveWeapon(IConsole::IResult *pResult, void *pUserData); From 5457e8fc3be0ff95edcf72c82fe18c42c653349a Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Fri, 4 Oct 2024 19:29:43 +0200 Subject: [PATCH 5/5] Render sparkling trail for invincible players --- data/extras.png | Bin 4799 -> 6821 bytes datasrc/content.py | 1 + src/game/client/components/effects.cpp | 20 ++++++++++++++++++++ src/game/client/components/effects.h | 1 + src/game/client/components/particles.cpp | 12 ++++++++---- src/game/client/components/particles.h | 4 ++++ src/game/client/components/players.cpp | 6 ++++++ src/game/client/gameclient.cpp | 8 ++++++++ src/game/client/gameclient.h | 4 +++- src/game/client/render.h | 1 + 10 files changed, 52 insertions(+), 5 deletions(-) diff --git a/data/extras.png b/data/extras.png index b2c6deea1980885c9eae7c5fb003f4969901d86f..14d320e1e6c4a2a9504fd7e406eee7493591b82a 100644 GIT binary patch literal 6821 zcmeHL_gj-m+kJvjL{waLRlpTj6ELfYfD(EvEAUXl8U&=HG^vpi5D0{_tLs`f2_=N$ zD-fgzN(%u)5fwvK>7fKA^b!aJ2nop--~ZwJ?VVqqxt_V^J~Lz*^`!5?VzJ-bwP z0RXTEwD=tZ0CKXf9I$hT?6Z2eXA=Nag23M|UB8>TFcMAn_KQ{<x zJw2`GuWwvD@ksj(X<%UA?k5YLr}gxpR<|@p8+V8Xj_?x9ZY@7}uuXsYK|+FpLG>Gm z>YG!0eYN+#Kl1*O-k-2T9lJB!XD6SQ71h;lI`qEEUQC>c&kkR_CScO*d41DQ*O?q= zAJrZlbHc~LzG}i}Z&6z5Xze>+zk(X!?Kz|3S%YqrB5-~>2$yKLLuyCNtlTq!&c`j~ zxD8f(u+K^5_EaPL1;zB57$CCP-`$?xd;vT}GS2Pjz`0?gts1XmC2hKCuxKe)luB9* zqwYUUq!_X^hEP!8Xq~1FK*L)#oM8DdyVR`t^1*WhMX=}Yczev#ojo|O@lPmQJGsvb zA#W=JdTYv`UEdD?PwEN}B+!m~*!Na8Nr|uq-KF(j&@_X4GC1ev^{il0a?G;*ICi>a z6#OZv^&QenBkq?&*Jq|^y5SP%o0{<}T$e-apP_(Du{LDjBP7b6 zh=eg-e`9Zv#Az^FOX_Ea0?tjt%5HBS^%c?h40zVMikH;x>gqGGfU8B?NJ66EGmjQ_ z>1z$Gx0le!~}rWF{vM7WBGj6vhgPq?Q3IM8{j!Rw&N&A$?0@y)au>YK5M}ZI35jJqq4_ z;}cmE*piwPWrTkSfDTTM5gb~9h60dA<+pv{tW1HfT1T@AGtYx*IBn^vNx(yaG zuG2ZlwwyZM(@2;-66`fr^sGw%sfi$I{q3PhNuyAQFfn>&F-MH@l(g?VV17}OBC0f6@Sx}*#rNYWZ0H9S(SV7izo9CVc<4>}X@aXY$9#fY^ zR~Is1_tnYK^OH1Z7ilpl2@#Zm73+q&@cO5wha7kd4vI4l%CYSxu|c>N#J}4-?+-l5 zPcQ;a$y^5Fw0KV~!bi}+Ln|LXH{C~e0&I?Db5p=$X;tu;>W>4d35HItX;HhkvJ(R} zF2D|$m#H|eykqZ947fS`IM`nSc>Zc308#^E;SejE_qWAi_9xy%>SY?4u!=Fh+wr1Q zlabf7)PxTy-u}(YP4~h(notNkz+5fxQp4=oyjfrFo2h^LP*<%0AUH@Jq7P?X!A!28 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 pK6S$YB4{zA42?b1?;=gTV*xei`-Ce*t5m)W`q; diff --git a/datasrc/content.py b/datasrc/content.py index 52353c491..7cb5fbe1a 100644 --- a/datasrc/content.py +++ b/datasrc/content.py @@ -452,6 +452,7 @@ container.sprites.Add(Sprite("hud_lock_mode", set_hud, 10,6,2,2)) container.sprites.Add(Sprite("hud_team0_mode", set_hud, 12,6,2,2)) container.sprites.Add(Sprite("part_snowflake", set_extras, 0,0,2,2)) +container.sprites.Add(Sprite("part_sparkle", set_extras, 2,0,2,2)) anim = Animation("base") diff --git a/src/game/client/components/effects.cpp b/src/game/client/components/effects.cpp index d5c7fd608..ae2a1bdb4 100644 --- a/src/game/client/components/effects.cpp +++ b/src/game/client/components/effects.cpp @@ -103,6 +103,26 @@ void CEffects::FreezingFlakes(vec2 Pos, vec2 Size, float Alpha) m_pClient->m_Particles.Add(CParticles::GROUP_EXTRA, &p); } +void CEffects::SparkleTrail(vec2 Pos, float Alpha) +{ + if(!m_Add50hz) + return; + + CParticle p; + p.SetDefault(); + p.m_Spr = SPRITE_PART_SPARKLE; + p.m_Pos = Pos + random_direction() * random_float(40.0f); + p.m_Vel = vec2(0, 0); + p.m_LifeSpan = 0.5f; + p.m_StartSize = 0.0f; + p.m_EndSize = random_float(20.0f, 30.0f); + p.m_UseAlphaFading = true; + p.m_StartAlpha = Alpha; + p.m_EndAlpha = std::min(0.2f, Alpha); + p.m_Collides = false; + m_pClient->m_Particles.Add(CParticles::GROUP_TRAIL_EXTRA, &p); +} + void CEffects::SmokeTrail(vec2 Pos, vec2 Vel, float Alpha, float TimePassed) { if(!m_Add50hz && TimePassed < 0.001f) diff --git a/src/game/client/components/effects.h b/src/game/client/components/effects.h index de75e63c9..703eff397 100644 --- a/src/game/client/components/effects.h +++ b/src/game/client/components/effects.h @@ -30,6 +30,7 @@ public: void PlayerDeath(vec2 Pos, int ClientId, float Alpha = 1.0f); void PowerupShine(vec2 Pos, vec2 Size, float Alpha = 1.0f); void FreezingFlakes(vec2 Pos, vec2 Size, float Alpha = 1.0f); + void SparkleTrail(vec2 Pos, float Alpha = 1.0f); void Confetti(vec2 Pos, float Alpha = 1.0f); void Update(); diff --git a/src/game/client/components/particles.cpp b/src/game/client/components/particles.cpp index fef6e886d..21ef0ce35 100644 --- a/src/game/client/components/particles.cpp +++ b/src/game/client/components/particles.cpp @@ -14,6 +14,7 @@ CParticles::CParticles() { OnReset(); m_RenderTrail.m_pParts = this; + m_RenderTrailExtra.m_pParts = this; m_RenderExplosions.m_pParts = this; m_RenderExtra.m_pParts = this; m_RenderGeneral.m_pParts = this; @@ -181,9 +182,12 @@ void CParticles::OnInit() m_ExtraParticleQuadContainerIndex = Graphics()->CreateQuadContainer(false); - // TODO: Use a loop similar to the one for m_ParticleQuadContainerIndex if you add more additional particles - Graphics()->QuadsSetSubset(0, 0, 1, 1); - RenderTools()->QuadContainerAddSprite(m_ExtraParticleQuadContainerIndex, 1.f); + for(int i = 0; i <= (SPRITE_PART_SPARKLE - SPRITE_PART_SNOWFLAKE); ++i) + { + Graphics()->QuadsSetSubset(0, 0, 1, 1); + RenderTools()->QuadContainerAddSprite(m_ExtraParticleQuadContainerIndex, 1.f); + } + Graphics()->QuadContainerUpload(m_ExtraParticleQuadContainerIndex); } @@ -207,7 +211,7 @@ void CParticles::RenderGroup(int Group) IGraphics::CTextureHandle *aParticles = GameClient()->m_ParticlesSkin.m_aSpriteParticles; int FirstParticleOffset = SPRITE_PART_SLICE; int ParticleQuadContainerIndex = m_ParticleQuadContainerIndex; - if(Group == GROUP_EXTRA) + if(Group == GROUP_EXTRA || Group == GROUP_TRAIL_EXTRA) { aParticles = GameClient()->m_ExtrasSkin.m_aSpriteParticles; FirstParticleOffset = SPRITE_PART_SNOWFLAKE; diff --git a/src/game/client/components/particles.h b/src/game/client/components/particles.h index 0d2f4937d..16c06b5d9 100644 --- a/src/game/client/components/particles.h +++ b/src/game/client/components/particles.h @@ -68,6 +68,7 @@ public: enum { GROUP_PROJECTILE_TRAIL = 0, + GROUP_TRAIL_EXTRA, GROUP_EXPLOSIONS, GROUP_EXTRA, GROUP_GENERAL, @@ -111,7 +112,10 @@ private: virtual void OnRender() override { m_pParts->RenderGroup(TGROUP); } }; + // behind players CRenderGroup m_RenderTrail; + CRenderGroup m_RenderTrailExtra; + // in front of players CRenderGroup m_RenderExplosions; CRenderGroup m_RenderExtra; CRenderGroup m_RenderGeneral; diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 85ea937ba..92609c65f 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -742,6 +742,10 @@ void CPlayers::RenderPlayer( { GameClient()->m_Effects.FreezingFlakes(BodyPos, vec2(32, 32), Alpha); } + if(RenderInfo.m_TeeRenderFlags & TEE_EFFECT_SPARKLE) + { + GameClient()->m_Effects.SparkleTrail(BodyPos, Alpha); + } if(ClientId < 0) return; @@ -833,6 +837,8 @@ void CPlayers::OnRender() aRenderInfo[i].m_TeeRenderFlags |= TEE_EFFECT_FROZEN | TEE_NO_WEAPON; if(m_pClient->m_aClients[i].m_LiveFrozen) aRenderInfo[i].m_TeeRenderFlags |= TEE_EFFECT_FROZEN; + if(m_pClient->m_aClients[i].m_Invincible) + aRenderInfo[i].m_TeeRenderFlags |= TEE_EFFECT_SPARKLE; const CGameClient::CSnapState::CCharacterInfo &CharacterInfo = m_pClient->m_Snap.m_aCharacters[i]; const bool Frozen = CharacterInfo.m_HasExtendedData && CharacterInfo.m_ExtendedData.m_FreezeEnd != 0; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 9aa732795..396ffcebd 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -128,6 +128,7 @@ void CGameClient::OnConsoleInit() &m_Background, // render instead of m_MapLayersBackground when g_Config.m_ClOverlayEntities == 100 &m_MapLayersBackground, // first to render &m_Particles.m_RenderTrail, + &m_Particles.m_RenderTrailExtra, &m_Items, &m_Ghost, &m_Players, @@ -1683,6 +1684,7 @@ void CGameClient::OnNewSnapshot() pClient->m_ShotgunHitDisabled = pCharacterData->m_Flags & CHARACTERFLAG_SHOTGUN_HIT_DISABLED; pClient->m_HookHitDisabled = pCharacterData->m_Flags & CHARACTERFLAG_HOOK_HIT_DISABLED; pClient->m_Super = pCharacterData->m_Flags & CHARACTERFLAG_SUPER; + pClient->m_Invincible = pCharacterData->m_Flags & CHARACTERFLAG_INVINCIBLE; // Endless pClient->m_EndlessHook = pCharacterData->m_Flags & CHARACTERFLAG_ENDLESS_HOOK; @@ -2457,6 +2459,7 @@ void CGameClient::CClientData::Reset() m_ShotgunHitDisabled = false; m_HookHitDisabled = false; m_Super = false; + m_Invincible = false; m_HasTelegunGun = false; m_HasTelegunGrenade = false; m_HasTelegunLaser = false; @@ -3697,6 +3700,7 @@ void CGameClient::LoadExtrasSkin(const char *pPath, bool AsDir) if(m_ExtrasSkinLoaded) { Graphics()->UnloadTexture(&m_ExtrasSkin.m_SpriteParticleSnowflake); + Graphics()->UnloadTexture(&m_ExtrasSkin.m_SpriteParticleSparkle); for(auto &SpriteParticle : m_ExtrasSkin.m_aSpriteParticles) SpriteParticle = IGraphics::CTextureHandle(); @@ -3731,7 +3735,11 @@ void CGameClient::LoadExtrasSkin(const char *pPath, bool AsDir) else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_PART_SNOWFLAKE].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_PART_SNOWFLAKE].m_pSet->m_Gridy, true) && Graphics()->IsImageFormatRgba(aPath, ImgInfo)) { m_ExtrasSkin.m_SpriteParticleSnowflake = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_SNOWFLAKE]); + m_ExtrasSkin.m_SpriteParticleSparkle = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_SPARKLE]); + m_ExtrasSkin.m_aSpriteParticles[0] = m_ExtrasSkin.m_SpriteParticleSnowflake; + m_ExtrasSkin.m_aSpriteParticles[1] = m_ExtrasSkin.m_SpriteParticleSparkle; + m_ExtrasSkinLoaded = true; } ImgInfo.Free(); diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index bf0f9071d..ebaca0dd4 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -394,6 +394,7 @@ public: bool m_ShotgunHitDisabled; bool m_HookHitDisabled; bool m_Super; + bool m_Invincible; bool m_HasTelegunGun; bool m_HasTelegunGrenade; bool m_HasTelegunLaser; @@ -768,7 +769,8 @@ public: struct SClientExtrasSkin { IGraphics::CTextureHandle m_SpriteParticleSnowflake; - IGraphics::CTextureHandle m_aSpriteParticles[1]; + IGraphics::CTextureHandle m_SpriteParticleSparkle; + IGraphics::CTextureHandle m_aSpriteParticles[2]; }; SClientExtrasSkin m_ExtrasSkin; diff --git a/src/game/client/render.h b/src/game/client/render.h index 17d8ccfa7..11b7255b9 100644 --- a/src/game/client/render.h +++ b/src/game/client/render.h @@ -123,6 +123,7 @@ enum { TEE_EFFECT_FROZEN = 1, TEE_NO_WEAPON = 2, + TEE_EFFECT_SPARKLE = 4, }; // sprite renderings