Skip to content

Commit 5c1a25d

Browse files
authored
Predict all sounds and particles (ddnet#11360)
2 parents 51647f0 + e333184 commit 5c1a25d

File tree

14 files changed

+279
-16
lines changed

14 files changed

+279
-16
lines changed

datasrc/network.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
]
2525
GameInfoFlags2 = [
2626
"ALLOW_X_SKINS", "GAMETYPE_CITY", "GAMETYPE_FDDRACE", "ENTITIES_FDDRACE", "HUD_HEALTH_ARMOR", "HUD_AMMO",
27-
"HUD_DDRACE", "NO_WEAK_HOOK", "NO_SKIN_CHANGE_FOR_FROZEN", "DDRACE_TEAM"
27+
"HUD_DDRACE", "NO_WEAK_HOOK", "NO_SKIN_CHANGE_FOR_FROZEN", "DDRACE_TEAM", "PREDICT_EVENTS"
2828
]
2929
ExPlayerFlags = ["AFK", "PAUSED", "SPEC"]
3030
LegacyProjectileFlags = [f"CLIENTID_BIT{i}" for i in range(8)] + [
@@ -77,7 +77,7 @@
7777
7878
enum
7979
{
80-
GAMEINFO_CURVERSION=10,
80+
GAMEINFO_CURVERSION=11,
8181
};
8282
'''
8383

src/engine/shared/config_variables.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// client
1515
MACRO_CONFIG_INT(ClPredict, cl_predict, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict client movements")
1616
MACRO_CONFIG_INT(ClPredictDummy, cl_predict_dummy, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict dummy movements")
17+
MACRO_CONFIG_INT(ClPredictEvents, cl_predict_events, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict events (sounds, particles)")
1718
MACRO_CONFIG_INT(ClAntiPingLimit, cl_antiping_limit, 0, 0, 500, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Adds delay to antiping (0 to disable)")
1819
MACRO_CONFIG_INT(ClAntiPingPercent, cl_antiping_percent, 100, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How far ahead antiping predicts, ignored when antiping limit is used")
1920
MACRO_CONFIG_INT(ClAntiPing, cl_antiping, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable antiping, i. e. more aggressive prediction.")

src/game/client/gameclient.cpp

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ void CGameClient::OnConnected()
582582
m_Layers.Init(Kernel()->RequestInterface<IMap>(), false);
583583
m_Collision.Init(Layers());
584584
m_GameWorld.m_Core.InitSwitchers(m_Collision.m_HighestSwitchNumber);
585+
m_GameWorld.m_PredictedEvents.clear();
585586
m_RaceHelper.Init(this);
586587

587588
// render loading before going through all components
@@ -1386,17 +1387,32 @@ void CGameClient::ProcessEvents()
13861387
if(Item.m_Type == NETEVENTTYPE_DAMAGEIND)
13871388
{
13881389
const CNetEvent_DamageInd *pEvent = (const CNetEvent_DamageInd *)Item.m_pData;
1389-
m_Effects.DamageIndicator(vec2(pEvent->m_X, pEvent->m_Y), direction(pEvent->m_Angle / 256.0f), Alpha);
1390+
1391+
vec2 DamageIndPos = vec2(pEvent->m_X, pEvent->m_Y);
1392+
if(!m_PredictedWorld.CheckPredictedEventHandled(CGameWorld::CPredictedEvent(Item.m_Type, DamageIndPos, -1, Client()->GameTick(g_Config.m_ClDummy), pEvent->m_Angle)))
1393+
{
1394+
m_Effects.DamageIndicator(vec2(pEvent->m_X, pEvent->m_Y), direction(pEvent->m_Angle / 256.0f), Alpha);
1395+
}
13901396
}
13911397
else if(Item.m_Type == NETEVENTTYPE_EXPLOSION)
13921398
{
13931399
const CNetEvent_Explosion *pEvent = (const CNetEvent_Explosion *)Item.m_pData;
1394-
m_Effects.Explosion(vec2(pEvent->m_X, pEvent->m_Y), Alpha);
1400+
1401+
vec2 ExplosionPos = vec2(pEvent->m_X, pEvent->m_Y);
1402+
if(!m_PredictedWorld.CheckPredictedEventHandled(CGameWorld::CPredictedEvent(Item.m_Type, ExplosionPos, -1, Client()->GameTick(g_Config.m_ClDummy))))
1403+
{
1404+
m_Effects.Explosion(ExplosionPos, Alpha);
1405+
}
13951406
}
13961407
else if(Item.m_Type == NETEVENTTYPE_HAMMERHIT)
13971408
{
13981409
const CNetEvent_HammerHit *pEvent = (const CNetEvent_HammerHit *)Item.m_pData;
1399-
m_Effects.HammerHit(vec2(pEvent->m_X, pEvent->m_Y), Alpha, Volume);
1410+
1411+
vec2 HammerHitPos = vec2(pEvent->m_X, pEvent->m_Y);
1412+
if(!m_PredictedWorld.CheckPredictedEventHandled(CGameWorld::CPredictedEvent(Item.m_Type, HammerHitPos, -1, Client()->GameTick(g_Config.m_ClDummy))))
1413+
{
1414+
m_Effects.HammerHit(HammerHitPos, Alpha, Volume);
1415+
}
14001416
}
14011417
else if(Item.m_Type == NETEVENTTYPE_BIRTHDAY)
14021418
{
@@ -1427,7 +1443,11 @@ void CGameClient::ProcessEvents()
14271443
if(m_GameInfo.m_RaceSounds && ((pEvent->m_SoundId == SOUND_GUN_FIRE && !g_Config.m_SndGun) || (pEvent->m_SoundId == SOUND_PLAYER_PAIN_LONG && !g_Config.m_SndLongPain)))
14281444
continue;
14291445

1430-
m_Sounds.PlayAt(CSounds::CHN_WORLD, pEvent->m_SoundId, 1.0f, vec2(pEvent->m_X, pEvent->m_Y));
1446+
vec2 SoundPos = vec2(pEvent->m_X, pEvent->m_Y);
1447+
if(!m_PredictedWorld.CheckPredictedEventHandled(CGameWorld::CPredictedEvent(Item.m_Type, SoundPos, -1, Client()->GameTick(g_Config.m_ClDummy), pEvent->m_SoundId)))
1448+
{
1449+
m_Sounds.PlayAt(CSounds::CHN_WORLD, pEvent->m_SoundId, 1.0f, SoundPos);
1450+
}
14311451
}
14321452
else if(Item.m_Type == NETEVENTTYPE_MAPSOUNDWORLD)
14331453
{
@@ -1545,6 +1565,7 @@ static CGameInfo GetGameInfo(const CNetObj_GameInfoEx *pInfoEx, int InfoExSize,
15451565
Info.m_NoWeakHookAndBounce = false;
15461566
Info.m_NoSkinChangeForFrozen = false;
15471567
Info.m_DDRaceTeam = false;
1568+
Info.m_PredictEvents = DDRace || Vanilla;
15481569

15491570
if(Version >= 0)
15501571
{
@@ -1608,6 +1629,10 @@ static CGameInfo GetGameInfo(const CNetObj_GameInfoEx *pInfoEx, int InfoExSize,
16081629
{
16091630
Info.m_DDRaceTeam = Flags2 & GAMEINFOFLAG2_DDRACE_TEAM;
16101631
}
1632+
if(Version >= 11)
1633+
{
1634+
Info.m_PredictEvents = Flags2 & GAMEINFOFLAG2_PREDICT_EVENTS;
1635+
}
16111636

16121637
return Info;
16131638
}
@@ -2518,6 +2543,9 @@ void CGameClient::OnPredict()
25182543

25192544
// init
25202545
bool Dummy = g_Config.m_ClDummy ^ m_IsDummySwapping;
2546+
2547+
// PredictedEvents are only handled in predicted world, so update them here
2548+
m_GameWorld.m_PredictedEvents = m_PredictedWorld.m_PredictedEvents;
25212549
m_PredictedWorld.CopyWorld(&m_GameWorld);
25222550

25232551
// don't predict inactive players, or entities from other teams
@@ -2592,6 +2620,8 @@ void CGameClient::OnPredict()
25922620

25932621
m_PredictedWorld.Tick();
25942622

2623+
HandlePredictedEvents(Tick);
2624+
25952625
// fetch the current characters
25962626
if(Tick == PredictionTick)
25972627
{
@@ -2636,6 +2666,10 @@ void CGameClient::OnPredict()
26362666
m_Sounds.PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, Pos);
26372667
if(Events & COREEVENT_HOOK_HIT_NOHOOK)
26382668
m_Sounds.PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, Pos);
2669+
if(Events & COREEVENT_HOOK_ATTACH_PLAYER)
2670+
{
2671+
m_PredictedWorld.CreatePredictedSound(Pos, SOUND_HOOK_ATTACH_PLAYER, pLocalChar->GetCid());
2672+
}
26392673
}
26402674
}
26412675

@@ -3388,6 +3422,7 @@ void CGameClient::UpdatePrediction()
33883422
m_GameWorld.m_WorldConfig.m_PredictWeapons = AntiPingWeapons();
33893423
m_GameWorld.m_WorldConfig.m_BugDDRaceInput = m_GameInfo.m_BugDDRaceInput;
33903424
m_GameWorld.m_WorldConfig.m_NoWeakHookAndBounce = m_GameInfo.m_NoWeakHookAndBounce;
3425+
m_GameWorld.m_WorldConfig.m_PredictEvents = m_GameInfo.m_PredictEvents;
33913426

33923427
if(!m_Snap.m_pLocalCharacter)
33933428
{
@@ -3706,6 +3741,54 @@ void CGameClient::UpdateRenderedCharacters()
37063741
}
37073742
}
37083743

3744+
void CGameClient::HandlePredictedEvents(const int Tick)
3745+
{
3746+
const float Alpha = 1.0f;
3747+
const float Volume = 1.0f;
3748+
3749+
auto EventsIterator = m_PredictedWorld.m_PredictedEvents.begin();
3750+
while(EventsIterator != m_PredictedWorld.m_PredictedEvents.end())
3751+
{
3752+
if(!EventsIterator->m_Handled && EventsIterator->m_Tick <= Tick)
3753+
{
3754+
if(EventsIterator->m_EventId == NETEVENTTYPE_SOUNDWORLD)
3755+
{
3756+
if(m_GameInfo.m_RaceSounds && ((EventsIterator->m_ExtraInfo == SOUND_GUN_FIRE && !g_Config.m_SndGun) || (EventsIterator->m_ExtraInfo == SOUND_PLAYER_PAIN_LONG && !g_Config.m_SndLongPain)))
3757+
{
3758+
EventsIterator = m_PredictedWorld.m_PredictedEvents.erase(EventsIterator);
3759+
continue;
3760+
}
3761+
m_Sounds.PlayAt(CSounds::CHN_WORLD, EventsIterator->m_ExtraInfo, 1.0f, EventsIterator->m_Pos);
3762+
}
3763+
else if(EventsIterator->m_EventId == NETEVENTTYPE_EXPLOSION)
3764+
{
3765+
m_Effects.Explosion(EventsIterator->m_Pos, Alpha);
3766+
}
3767+
else if(EventsIterator->m_EventId == NETEVENTTYPE_HAMMERHIT)
3768+
{
3769+
m_Effects.HammerHit(EventsIterator->m_Pos, Alpha, Volume);
3770+
}
3771+
else if(EventsIterator->m_EventId == NETEVENTTYPE_DAMAGEIND)
3772+
{
3773+
m_Effects.DamageIndicator(EventsIterator->m_Pos, direction(EventsIterator->m_ExtraInfo / 256.0f), Alpha);
3774+
}
3775+
3776+
EventsIterator->m_Handled = true;
3777+
++EventsIterator;
3778+
continue;
3779+
}
3780+
else if(Tick - EventsIterator->m_Tick > 3 * Client()->GameTickSpeed()) // 3 seconds
3781+
{
3782+
// remove too old events
3783+
EventsIterator = m_PredictedWorld.m_PredictedEvents.erase(EventsIterator);
3784+
}
3785+
else
3786+
{
3787+
++EventsIterator;
3788+
}
3789+
}
3790+
}
3791+
37093792
void CGameClient::DetectStrongHook()
37103793
{
37113794
// attempt to detect strong/weak between players

src/game/client/gameclient.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ class CGameInfo
117117
bool m_NoSkinChangeForFrozen;
118118

119119
bool m_DDRaceTeam;
120+
121+
bool m_PredictEvents;
120122
};
121123

122124
class CSnapEntities
@@ -901,6 +903,7 @@ class CGameClient : public IGameClient
901903
void UpdatePrediction();
902904
void UpdateSpectatorCursor();
903905
void UpdateRenderedCharacters();
906+
void HandlePredictedEvents(int Tick);
904907

905908
int m_aLastUpdateTick[MAX_CLIENTS] = {0};
906909
void DetectStrongHook();

src/game/client/prediction/entities/character.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ void CCharacter::SetWeapon(int Weapon)
2222
m_LastWeapon = m_Core.m_ActiveWeapon;
2323
m_QueuedWeapon = -1;
2424
SetActiveWeapon(Weapon);
25+
26+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_WEAPON_SWITCH, GetCid());
2527
}
2628

2729
void CCharacter::SetSolo(bool Solo)
@@ -174,6 +176,7 @@ void CCharacter::HandleNinja()
174176
continue;
175177

176178
// Hit a player, give them damage and stuffs...
179+
GameWorld()->CreatePredictedSound(pChr->m_Pos, SOUND_NINJA_HIT, GetCid());
177180
// set his velocity to fast upward (for now)
178181
dbg_assert(m_NumObjectsHit < MAX_CLIENTS, "m_aHitObjects overflow");
179182
m_aHitObjects[m_NumObjectsHit++] = ClientId;
@@ -285,6 +288,17 @@ void CCharacter::FireWeapon()
285288
if(!WillFire)
286289
return;
287290

291+
if(m_FreezeTime)
292+
{
293+
// Timer stuff to avoid shrieking orchestra caused by unfreeze-plasma
294+
if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1))
295+
{
296+
m_PainSoundTimer = 1 * GameWorld()->GameTickSpeed();
297+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_PLAYER_PAIN_LONG, GetCid());
298+
}
299+
return;
300+
}
301+
288302
// check for ammo
289303
if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime)
290304
{
@@ -300,6 +314,8 @@ void CCharacter::FireWeapon()
300314
if(m_Core.m_HammerHitDisabled)
301315
break;
302316

317+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_HAMMER_FIRE, GetCid());
318+
303319
CEntity *apEnts[MAX_CLIENTS];
304320
int Hits = 0;
305321
int Num = GameWorld()->FindEntities(ProjStartPos, m_ProximityRadius * 0.5f, apEnts,
@@ -313,6 +329,10 @@ void CCharacter::FireWeapon()
313329
continue;
314330

315331
// set his velocity to fast upward (for now)
332+
if(length(pTarget->m_Pos - ProjStartPos) > 0.0f)
333+
GameWorld()->CreatePredictedHammerHitEvent(pTarget->m_Pos - normalize(pTarget->m_Pos - ProjStartPos) * GetProximityRadius() * 0.5f, GetCid());
334+
else
335+
GameWorld()->CreatePredictedHammerHitEvent(ProjStartPos, GetCid());
316336

317337
vec2 Dir;
318338
if(length(pTarget->m_Pos - m_Pos) > 0.0f)
@@ -378,6 +398,8 @@ void CCharacter::FireWeapon()
378398
0, //Force
379399
-1 //SoundImpact
380400
);
401+
402+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_GUN_FIRE, GetCid()); // NOLINT(clang-analyzer-unix.Malloc)
381403
}
382404
}
383405
break;
@@ -413,6 +435,8 @@ void CCharacter::FireWeapon()
413435

414436
new CLaser(GameWorld(), m_Pos, Direction, LaserReach, GetCid(), WEAPON_SHOTGUN);
415437
}
438+
439+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_SHOTGUN_FIRE, GetCid());
416440
}
417441
break;
418442

@@ -431,6 +455,8 @@ void CCharacter::FireWeapon()
431455
true, //Explosive
432456
SOUND_GRENADE_EXPLODE //SoundImpact
433457
); //SoundImpact
458+
459+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_GRENADE_FIRE, GetCid());
434460
}
435461
break;
436462

@@ -439,6 +465,7 @@ void CCharacter::FireWeapon()
439465
float LaserReach = GetTuning(GetOverriddenTuneZone())->m_LaserReach;
440466

441467
new CLaser(GameWorld(), m_Pos, Direction, LaserReach, GetCid(), WEAPON_LASER);
468+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_LASER_FIRE, GetCid());
442469
}
443470
break;
444471

@@ -452,6 +479,8 @@ void CCharacter::FireWeapon()
452479

453480
// clamp to prevent massive MoveBox calculation lag with SG bug
454481
m_Core.m_Ninja.m_OldVelAmount = std::clamp(length(m_Core.m_Vel), 0.0f, 6000.0f);
482+
483+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_NINJA_FIRE, GetCid());
455484
}
456485
break;
457486
}
@@ -473,6 +502,9 @@ void CCharacter::HandleWeapons()
473502
HandleNinja();
474503
HandleJetpack();
475504

505+
if(m_PainSoundTimer > 0)
506+
m_PainSoundTimer--;
507+
476508
// check reload timer
477509
if(m_ReloadTimer)
478510
{
@@ -493,6 +525,9 @@ void CCharacter::GiveNinja()
493525
if(m_Core.m_ActiveWeapon != WEAPON_NINJA)
494526
m_LastWeapon = m_Core.m_ActiveWeapon;
495527
SetActiveWeapon(WEAPON_NINJA);
528+
529+
if(GameWorld()->m_WorldConfig.m_IsVanilla)
530+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_PICKUP_NINJA, GetCid());
496531
}
497532

498533
void CCharacter::OnPredictedInput(const CNetObj_PlayerInput *pNewInput)

src/game/client/prediction/entities/character.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ class CCharacter : public CEntity
148148

149149
int m_MoveRestrictions;
150150

151+
int m_PainSoundTimer;
152+
151153
// these are non-heldback inputs
152154
CNetObj_PlayerInput m_LatestPrevInput;
153155
CNetObj_PlayerInput m_LatestInput;

src/game/client/prediction/entities/laser.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ void CLaser::DoBounce()
148148

149149
if(m_Bounces > BounceNum)
150150
m_Energy = -1;
151+
152+
GameWorld()->CreatePredictedSound(m_Pos, SOUND_LASER_BOUNCE, m_Id);
151153
}
152154
}
153155
else

0 commit comments

Comments
 (0)