diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fdd677cc..6b9528c36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,7 +443,10 @@ elseif(TARGET_OS STREQUAL "mac") set(PLATFORM_LIBS ${CARBON} ${SECURITY}) else() set(PLATFORM_CLIENT) - set(PLATFORM_CLIENT_LIBS GL GLU X11) + find_package(OpenGL) + find_package(X11) + set(PLATFORM_CLIENT_LIBS ${OPENGL_gl_LIBRARY} ${OPENGL_glu_LIBRARY} ${X11_X11_LIB}) + set(PLATFORM_CLIENT_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR} ${X11_X11_INCLUDE_PATH}) if(TARGET_OS STREQUAL "linux") set(PLATFORM_LIBS rt) # clock_gettime for glibc < 2.17 else() @@ -875,6 +878,18 @@ if(CLIENT) gameclient.h lineinput.cpp lineinput.h + prediction/entities/character.cpp + prediction/entities/character.h + prediction/entities/laser.cpp + prediction/entities/laser.h + prediction/entities/pickup.cpp + prediction/entities/pickup.h + prediction/entities/projectile.cpp + prediction/entities/projectile.h + prediction/entity.cpp + prediction/entity.h + prediction/gameworld.cpp + prediction/gameworld.h race.cpp race.h render.cpp diff --git a/src/base/math.h b/src/base/math.h index 07b063965..5a4cf473e 100644 --- a/src/base/math.h +++ b/src/base/math.h @@ -67,4 +67,7 @@ template inline T min(T a, T b) { return a inline T max(T a, T b) { return a>b?a:b; } template inline T absolute(T a) { return a inline T in_range(T a, T lower, T upper) { return lower <= a && a <= upper; } +template inline T in_range(T a, T upper) { return in_range(a, 0, upper); } + #endif // BASE_MATH_H diff --git a/src/base/vmath.h b/src/base/vmath.h index 022e2c75c..0ca16c7e7 100644 --- a/src/base/vmath.h +++ b/src/base/vmath.h @@ -42,6 +42,8 @@ public: bool operator !=(const vector2_base &v) const { return x != v.x || y != v.y; } operator const T* () { return &x; } + + T &operator[](const int index) { return index ? y : x; } }; diff --git a/src/engine/client.h b/src/engine/client.h index d8507b260..1f77f8774 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -38,6 +38,8 @@ protected: float m_RenderFrameTime; int m_GameTickSpeed; + + float m_FrameTimeAvg; public: char m_aNews[3000]; int64 m_ReconnectTime; @@ -84,6 +86,7 @@ public: // other time access inline float RenderFrameTime() const { return m_RenderFrameTime; } inline float LocalTime() const { return m_LocalTime; } + inline float FrameTimeAvg() const { return m_FrameTimeAvg; } // actions virtual void Connect(const char *pAddress, const char *pPassword = NULL) = 0; @@ -124,7 +127,7 @@ public: // input virtual int *GetInput(int Tick) = 0; - virtual bool InputExists(int Tick) = 0; + virtual int *GetDirectInput(int Tick) = 0; // remote console virtual void RconAuth(const char *pUsername, const char *pPassword) = 0; @@ -196,6 +199,8 @@ public: virtual void GenerateTimeoutSeed() = 0; virtual IFriends* Foes() = 0; + + virtual void GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount) = 0; }; class IGameClient : public IInterface diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 6df862895..760a9c3d4 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -357,6 +357,8 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta) m_ReconnectTime = 0; m_GenerateTimeoutSeed = true; + + m_FrameTimeAvg = 0.0001f; } // ----- send functions ----- @@ -553,12 +555,12 @@ int *CClient::GetInput(int Tick) return 0; } -bool CClient::InputExists(int Tick) +int *CClient::GetDirectInput(int Tick) { for(int i = 0; i < 200; i++) if(m_aInputs[g_Config.m_ClDummy][i].m_Tick == Tick) - return true; - return false; + return (int *)m_aInputs[g_Config.m_ClDummy][i].m_aData; + return 0; } // ------ state handling ----- @@ -2960,6 +2962,8 @@ void CClient::Run() m_RenderFrameTimeHigh = m_RenderFrameTime; m_FpsGraph.Add(1.0f/m_RenderFrameTime, 1,1,1); + m_FrameTimeAvg = m_FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f; + // keep the overflow time - it's used to make sure the gfx refreshrate is reached int64 AdditionalTime = g_Config.m_GfxRefreshRate ? ((Now - LastRenderTime) - (time_freq() / (int64)g_Config.m_GfxRefreshRate)) : 0; // if the value is over a second time loose, reset the additional time (drop the frames, that are lost already) @@ -3882,3 +3886,13 @@ int CClient::GetPredictionTime() int64 Now = time_get(); return (int)((m_PredictedTime.Get(Now)-m_GameTime[g_Config.m_ClDummy].Get(Now))*1000/(float)time_freq()); } + +void CClient::GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount) +{ + int64 GameTime = m_GameTime[g_Config.m_ClDummy].Get(time_get()); + int64 PredTime = m_PredictedTime.Get(time_get()); + int64 SmoothTime = clamp(GameTime + (int64)(MixAmount * (PredTime - GameTime)), GameTime, PredTime); + + *pSmoothTick = (int)(SmoothTime*50/time_freq())+1; + *pSmoothIntraTick = (SmoothTime - (*pSmoothTick-1)*time_freq()/50) / (float)(time_freq()/50); +} diff --git a/src/engine/client/client.h b/src/engine/client/client.h index bc4daa05c..c0b291dd4 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -253,7 +253,7 @@ public: // TODO: OPT: do this a lot smarter! virtual int *GetInput(int Tick); - virtual bool InputExists(int Tick); + virtual int *GetDirectInput(int Tick); const char *LatestVersion(); @@ -417,5 +417,7 @@ public: bool EditorHasUnsavedData() { return m_pEditor->HasUnsavedData(); } virtual IFriends* Foes() {return &m_Foes; } + + void GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount); }; #endif diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h index 85157d2cf..09695dd54 100644 --- a/src/engine/serverbrowser.h +++ b/src/engine/serverbrowser.h @@ -71,6 +71,7 @@ bool IsRace(const CServerInfo *pInfo); bool IsFastCap(const CServerInfo *pInfo); bool IsDDRace(const CServerInfo *pInfo); bool IsDDNet(const CServerInfo *pInfo); +bool IsBlockWorlds(const CServerInfo *pInfo); bool Is64Player(const CServerInfo *pInfo); bool IsPlus(const CServerInfo *pInfo); diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 223fc7b14..b7293b68f 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -355,6 +355,7 @@ MACRO_CONFIG_STR(SvConnLoggingServer, sv_conn_logging_server, 128, "", CFGFLAG_S MACRO_CONFIG_INT(ClUnpredictedShadow, cl_unpredicted_shadow, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show unpredicted shadow tee to estimate your delay") MACRO_CONFIG_INT(ClPredictDDRace, cl_predict_ddrace, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Predict some DDRace tiles") +MACRO_CONFIG_INT(ClPredictFreeze, cl_predict_freeze, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Predict freeze tiles") MACRO_CONFIG_INT(ClShowNinja, cl_show_ninja, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show ninja skin") MACRO_CONFIG_INT(ClShowHookCollOther, cl_show_hook_coll_other, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show other players' hook collision line") MACRO_CONFIG_INT(ClShowHookCollOwn, cl_show_hook_coll_own, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show own players' hook collision line") diff --git a/src/engine/shared/serverbrowser.cpp b/src/engine/shared/serverbrowser.cpp index 20855dd0b..dab567ae5 100644 --- a/src/engine/shared/serverbrowser.cpp +++ b/src/engine/shared/serverbrowser.cpp @@ -52,12 +52,17 @@ bool IsBlockInfectionZ(const CServerInfo *pInfo) || str_find_nocase(pInfo->m_aGameType, "infectionZ"); } +bool IsBlockWorlds(const CServerInfo *pInfo) +{ + return (str_comp_nocase_num(pInfo->m_aGameType, "bw ", 4) == 0) + || (str_comp_nocase(pInfo->m_aGameType, "bw") == 0); +} + bool IsDDNet(const CServerInfo *pInfo) { return (str_find_nocase(pInfo->m_aGameType, "ddracenet") || str_find_nocase(pInfo->m_aGameType, "ddnet") - || (str_comp_nocase_num(pInfo->m_aGameType, "bw ", 4) == 0) - || (str_comp_nocase(pInfo->m_aGameType, "bw") == 0)) + || IsBlockWorlds(pInfo)) && !IsBlockInfectionZ(pInfo); } diff --git a/src/game/client/components/effects.cpp b/src/game/client/components/effects.cpp index 3a23f87bc..2d0c3735f 100644 --- a/src/game/client/components/effects.cpp +++ b/src/game/client/components/effects.cpp @@ -82,9 +82,9 @@ void CEffects::PowerupShine(vec2 Pos, vec2 size) m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); } -void CEffects::SmokeTrail(vec2 Pos, vec2 Vel) +void CEffects::SmokeTrail(vec2 Pos, vec2 Vel, float TimePassed) { - if(!m_Add50hz) + if(!m_Add50hz && TimePassed < 0.001f) return; CParticle p; @@ -97,7 +97,7 @@ void CEffects::SmokeTrail(vec2 Pos, vec2 Vel) p.m_EndSize = 0; p.m_Friction = 0.7f; p.m_Gravity = frandom()*-500.0f; - m_pClient->m_pParticles->Add(CParticles::GROUP_PROJECTILE_TRAIL, &p); + m_pClient->m_pParticles->Add(CParticles::GROUP_PROJECTILE_TRAIL, &p, TimePassed); } @@ -120,9 +120,9 @@ void CEffects::SkidTrail(vec2 Pos, vec2 Vel) m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p); } -void CEffects::BulletTrail(vec2 Pos) +void CEffects::BulletTrail(vec2 Pos, float TimePassed) { - if(!m_Add100hz) + if(!m_Add100hz && TimePassed < 0.001f) return; CParticle p; @@ -133,7 +133,7 @@ void CEffects::BulletTrail(vec2 Pos) p.m_StartSize = 8.0f; p.m_EndSize = 0; p.m_Friction = 0.7f; - m_pClient->m_pParticles->Add(CParticles::GROUP_PROJECTILE_TRAIL, &p); + m_pClient->m_pParticles->Add(CParticles::GROUP_PROJECTILE_TRAIL, &p, TimePassed); } void CEffects::PlayerSpawn(vec2 Pos) diff --git a/src/game/client/components/effects.h b/src/game/client/components/effects.h index 2b43c614b..5832f1db7 100644 --- a/src/game/client/components/effects.h +++ b/src/game/client/components/effects.h @@ -13,8 +13,8 @@ public: virtual void OnRender(); - void BulletTrail(vec2 Pos); - void SmokeTrail(vec2 Pos, vec2 Vel); + void BulletTrail(vec2 Pos, float TimePassed = 0.f); + void SmokeTrail(vec2 Pos, vec2 Vel, float TimePassed = 0.f); void SkidTrail(vec2 Pos, vec2 Vel); void Explosion(vec2 Pos); void HammerHit(vec2 Pos); diff --git a/src/game/client/components/ghost.cpp b/src/game/client/components/ghost.cpp index e46a21cd7..968a34d13 100644 --- a/src/game/client/components/ghost.cpp +++ b/src/game/client/components/ghost.cpp @@ -345,8 +345,8 @@ void CGhost::OnRender() Player.m_AttackTick += Client()->GameTick() - GhostTick; - m_pClient->m_pPlayers->RenderHook(&Prev, &Player, &pGhost->m_RenderInfo , -2, vec2(), vec2(), IntraTick); - m_pClient->m_pPlayers->RenderPlayer(&Prev, &Player, &pGhost->m_RenderInfo, -2, vec2(), IntraTick); + m_pClient->m_pPlayers->RenderHook(&Prev, &Player, &pGhost->m_RenderInfo , -2, IntraTick); + m_pClient->m_pPlayers->RenderPlayer(&Prev, &Player, &pGhost->m_RenderInfo, -2, IntraTick); } } diff --git a/src/game/client/components/items.cpp b/src/game/client/components/items.cpp index 86ac93cb1..3a0afd3d8 100644 --- a/src/game/client/components/items.cpp +++ b/src/game/client/components/items.cpp @@ -43,29 +43,20 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID) Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunSpeed; } - // bool LocalPlayerInGame = false; if(m_pClient->m_Snap.m_pLocalInfo) LocalPlayerInGame = m_pClient->m_aClients[m_pClient->m_Snap.m_pLocalInfo->m_ClientID].m_Team != -1; - // static float s_LastGameTickTime = Client()->GameTickTime(); if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) s_LastGameTickTime = Client()->GameTickTime(); - int PrevTick = Client()->PrevGameTick(); - + float Ct; if(m_pClient->AntiPingGrenade() && LocalPlayerInGame && !(Client()->State() == IClient::STATE_DEMOPLAYBACK)) - { - // calc predicted game tick - static int Offset = 0; - Offset = (int)(0.8f * (float)Offset + 0.2f * (float)(Client()->PredGameTick() - Client()->GameTick())); - - PrevTick += Offset; - } - - float Ct = (PrevTick-pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime; + Ct = ((float)(Client()->PredGameTick() - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick())/(float)SERVER_TICK_SPEED; + else + Ct = (Client()->PrevGameTick() - pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime; if(Ct < 0) return; // projectile haven't been shot yet @@ -77,7 +68,6 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID) vec2 Pos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct); vec2 PrevPos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct-0.001f); - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); @@ -86,7 +76,6 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID) vec2 Vel = Pos-PrevPos; //vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), Client()->IntraGameTick()); - // add particle for this projectile if(pCurrent->m_Type == WEAPON_GRENADE) { @@ -197,31 +186,34 @@ void CItems::RenderFlag(const CNetObj_Flag *pPrev, const CNetObj_Flag *pCurrent, if(pCurGameData) { + int FlagCarrier = (pCurrent->m_Team == TEAM_RED) ? pCurGameData->m_FlagCarrierRed : pCurGameData->m_FlagCarrierBlue; + // use the flagcarriers position if available + if(FlagCarrier >= 0 && m_pClient->m_Snap.m_aCharacters[FlagCarrier].m_Active) + Pos = m_pClient->m_aClients[FlagCarrier].m_RenderPos; + // make sure that the flag isn't interpolated between capture and return if(pPrevGameData && ((pCurrent->m_Team == TEAM_RED && pPrevGameData->m_FlagCarrierRed != pCurGameData->m_FlagCarrierRed) || (pCurrent->m_Team == TEAM_BLUE && pPrevGameData->m_FlagCarrierBlue != pCurGameData->m_FlagCarrierBlue))) Pos = vec2(pCurrent->m_X, pCurrent->m_Y); - - // make sure to use predicted position if we are the carrier - if(m_pClient->m_Snap.m_pLocalInfo && - ((pCurrent->m_Team == TEAM_RED && pCurGameData->m_FlagCarrierRed == m_pClient->m_Snap.m_LocalClientID) || - (pCurrent->m_Team == TEAM_BLUE && pCurGameData->m_FlagCarrierBlue == m_pClient->m_Snap.m_LocalClientID))) - Pos = m_pClient->m_LocalCharacterPos; } Graphics()->RenderQuadContainerAsSprite(m_ItemsQuadContainerIndex, QuadOffset, Pos.x, Pos.y-Size*0.75f); } -void CItems::RenderLaser(const struct CNetObj_Laser *pCurrent) +void CItems::RenderLaser(const struct CNetObj_Laser *pCurrent, bool IsPredicted) { vec3 RGB; vec2 Pos = vec2(pCurrent->m_X, pCurrent->m_Y); vec2 From = vec2(pCurrent->m_FromX, pCurrent->m_FromY); vec2 Dir = normalize(Pos-From); - float Ticks = Client()->GameTick() - pCurrent->m_StartTick + Client()->IntraGameTick(); + float Ticks; + if(IsPredicted) + Ticks = (float)(Client()->PredGameTick() - pCurrent->m_StartTick) + Client()->PredIntraGameTick(); + else + Ticks = (float)(Client()->GameTick() - pCurrent->m_StartTick) + Client()->IntraGameTick(); float Ms = (Ticks/50.0f) * 1000.0f; float a = Ms / m_pClient->m_Tuning[g_Config.m_ClDummy].m_LaserBounceDelay; a = clamp(a, 0.0f, 1.0f); @@ -278,6 +270,38 @@ void CItems::OnRender() if(Client()->State() < IClient::STATE_ONLINE) return; + bool UsePredicted = (GameClient()->Predict() && GameClient()->AntiPingWeapons() && GameClient()->AntiPingGrenade()); + if(UsePredicted) + { + for(auto *pProj = (CProjectile*) GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile*) pProj->NextEntity()) + { + CNetObj_Projectile Data; + if(pProj->m_Weapon != WEAPON_SHOTGUN || pProj->m_Explosive || pProj->m_Freeze) + pProj->FillExtraInfo(&Data); + else + pProj->FillInfo(&Data); + RenderProjectile(&Data, pProj->ID()); + } + for(auto *pLaser = (CLaser*) GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_LASER); pLaser; pLaser = (CLaser*) pLaser->NextEntity()) + { + if(pLaser->GetOwner() < 0 || (pLaser->GetOwner() >= 0 && !GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal)) + continue; + CNetObj_Laser Data; + pLaser->FillInfo(&Data); + RenderLaser(&Data, true); + } + for(auto *pPickup = (CPickup*) GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PICKUP); pPickup; pPickup = (CPickup*) pPickup->NextEntity()) + { + if(auto *pPrev = (CPickup*) GameClient()->m_PrevPredictedWorld.GetEntity(pPickup->ID(), CGameWorld::ENTTYPE_PICKUP)) + { + CNetObj_Pickup Data, Prev; + pPickup->FillInfo(&Data); + pPrev->FillInfo(&Prev); + RenderPickup(&Prev, &Data); + } + } + } + int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT); for(int i = 0; i < Num; i++) { @@ -286,16 +310,40 @@ void CItems::OnRender() if(Item.m_Type == NETOBJTYPE_PROJECTILE) { + if(UsePredicted) + { + if(auto *pProj = (CProjectile*) GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData)) + { + if(pProj->m_LastRenderTick <= 0 + && (pProj->m_Weapon != WEAPON_SHOTGUN || (!pProj->m_Freeze && !pProj->m_Explosive)) // skip ddrace shotgun bullets + && (pProj->m_Weapon == WEAPON_SHOTGUN || fabs(length(pProj->m_Direction) - 1.f) < 0.02) // workaround to skip grenades on ball mod + && (pProj->GetOwner() < 0 || !GameClient()->m_aClients[pProj->GetOwner()].m_IsPredictedLocal) // skip locally predicted projectiles + && !Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID)) + { + ReconstructSmokeTrail((const CNetObj_Projectile *)pData, Item.m_ID, pProj->m_DestroyTick, pProj->m_LifeSpan); + } + pProj->m_LastRenderTick = Client()->GameTick(); + continue; + } + } RenderProjectile((const CNetObj_Projectile *)pData, Item.m_ID); } else if(Item.m_Type == NETOBJTYPE_PICKUP) { + if(UsePredicted) + continue; const void *pPrev = Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID); if(pPrev) RenderPickup((const CNetObj_Pickup *)pPrev, (const CNetObj_Pickup *)pData); } else if(Item.m_Type == NETOBJTYPE_LASER) { + if(UsePredicted) + { + auto *pLaser = (CLaser*) GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData); + if(pLaser && pLaser->GetOwner() >= 0 && GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal) + continue; + } RenderLaser((const CNetObj_Laser *)pData); } } @@ -326,7 +374,7 @@ void CItems::OnRender() m_aExtraProjectiles[i] = m_aExtraProjectiles[m_NumExtraProjectiles-1]; m_NumExtraProjectiles--; } - else + else if(!UsePredicted) RenderProjectile(&m_aExtraProjectiles[i], 0); } @@ -384,3 +432,68 @@ void CItems::AddExtraProjectile(CNetObj_Projectile *pProj) m_NumExtraProjectiles++; } } + +void CItems::ReconstructSmokeTrail(const CNetObj_Projectile *pCurrent, int ItemID, int DestroyTick, int LifeSpan) +{ + bool LocalPlayerInGame = false; + + if(m_pClient->m_Snap.m_pLocalInfo) + LocalPlayerInGame = m_pClient->m_aClients[m_pClient->m_Snap.m_pLocalInfo->m_ClientID].m_Team != -1; + if(!m_pClient->AntiPingGrenade() || !m_pClient->AntiPingWeapons() || !LocalPlayerInGame) + return; + if(Client()->PredGameTick() == pCurrent->m_StartTick) + return; + + // get positions + float Curvature = 0; + float Speed = 0; + if(pCurrent->m_Type == WEAPON_GRENADE) + { + Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeCurvature; + Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeSpeed; + } + else if(pCurrent->m_Type == WEAPON_SHOTGUN) + { + Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunCurvature; + Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunSpeed; + } + else if(pCurrent->m_Type == WEAPON_GUN) + { + Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunCurvature; + Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunSpeed; + } + + float Pt = ((float)(Client()->PredGameTick() - pCurrent->m_StartTick) + Client()->PredIntraGameTick())/(float)SERVER_TICK_SPEED; + if(Pt < 0) + return; // projectile haven't been shot yet + + float Gt = (Client()->PrevGameTick() - pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + Client()->GameTickTime(); + + vec2 StartPos; + vec2 StartVel; + + ExtractInfo(pCurrent, &StartPos, &StartVel); + + float T = Pt; + if(DestroyTick >= 0) + T = min(Pt, ((float)(DestroyTick - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick())/(float)SERVER_TICK_SPEED); + T = min(T, LifeSpan/(float)SERVER_TICK_SPEED); + + float MinTrailSpan = 0.4f * ((pCurrent->m_Type == WEAPON_GRENADE) ? 0.5f : 0.25f); + float Step = max(Client()->FrameTimeAvg(), (pCurrent->m_Type == WEAPON_GRENADE) ? 0.02f : 0.01f); + for(int i = 1+(int)(Gt/Step); i < (int)(T/Step); i++) + { + float t = Step * (float)i + 0.4f * Step * (frandom() - 0.5f); + vec2 Pos = CalcPos(StartPos, StartVel, Curvature, Speed, t); + vec2 PrevPos = CalcPos(StartPos, StartVel, Curvature, Speed, t-0.001f); + vec2 Vel = Pos-PrevPos; + float TimePassed = Pt-t; + if(Pt - MinTrailSpan > 0.01f) + TimePassed = min(TimePassed, (TimePassed-MinTrailSpan)/(Pt - MinTrailSpan)*(MinTrailSpan * 0.5f) + MinTrailSpan); + // add particle for this projectile + if(pCurrent->m_Type == WEAPON_GRENADE) + m_pClient->m_pEffects->SmokeTrail(Pos, Vel*-1, TimePassed); + else + m_pClient->m_pEffects->BulletTrail(Pos, TimePassed); + } +} diff --git a/src/game/client/components/items.h b/src/game/client/components/items.h index 38221ef72..8a619b0f1 100644 --- a/src/game/client/components/items.h +++ b/src/game/client/components/items.h @@ -17,7 +17,7 @@ class CItems : public CComponent void RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID); void RenderPickup(const CNetObj_Pickup *pPrev, const CNetObj_Pickup *pCurrent); void RenderFlag(const CNetObj_Flag *pPrev, const CNetObj_Flag *pCurrent, const CNetObj_GameData *pPrevGameData, const CNetObj_GameData *pCurGameData); - void RenderLaser(const struct CNetObj_Laser *pCurrent); + void RenderLaser(const struct CNetObj_Laser *pCurrent, bool IsPredicted = false); int m_ItemsQuadContainerIndex; public: @@ -26,6 +26,8 @@ public: virtual void OnInit(); void AddExtraProjectile(CNetObj_Projectile *pProj); + + void ReconstructSmokeTrail(const CNetObj_Projectile *pCurrent, int ItemID, int DestroyTick, int LifeSpan); }; #endif diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 90a74a67d..3d97e10f2 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -163,69 +164,71 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) } // handle keyboard shortcuts independent of active menu - - // increase/decrease speed - if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP) || Input()->KeyPress(KEY_UP)) + if(m_pClient->m_pGameConsole->IsClosed()) { - DemoPlayer()->SetSpeedIndex(+1); - LastSpeedChange = time_get(); - } - else if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN) || Input()->KeyPress(KEY_DOWN)) - { - DemoPlayer()->SetSpeedIndex(-1); - LastSpeedChange = time_get(); - } - - // pause/unpause - if(Input()->KeyPress(KEY_SPACE) || Input()->KeyPress(KEY_RETURN) || Input()->KeyPress(KEY_K)) - { - if(pInfo->m_Paused) + // increase/decrease speed + if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP) || Input()->KeyPress(KEY_UP)) { - DemoPlayer()->Unpause(); + DemoPlayer()->SetSpeedIndex(+1); + LastSpeedChange = time_get(); } - else + else if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN) || Input()->KeyPress(KEY_DOWN)) { - DemoPlayer()->Pause(); + DemoPlayer()->SetSpeedIndex(-1); + LastSpeedChange = time_get(); } - } - // seek backward/forward 10/5 seconds - if(Input()->KeyPress(KEY_J)) - { - DemoPlayer()->SeekTime(-10.0f); - } - else if(Input()->KeyPress(KEY_L)) - { - DemoPlayer()->SeekTime(10.0f); - } - else if(Input()->KeyPress(KEY_LEFT)) - { - DemoPlayer()->SeekTime(-5.0f); - } - else if(Input()->KeyPress(KEY_RIGHT)) - { - DemoPlayer()->SeekTime(5.0f); - } - - // seek to 0-90% - const int SeekPercentKeys[] = {KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9}; - for(unsigned i = 0; i < sizeof(SeekPercentKeys) / sizeof(SeekPercentKeys[0]); i++) - { - if(Input()->KeyPress(SeekPercentKeys[i])) + // pause/unpause + if(Input()->KeyPress(KEY_SPACE) || Input()->KeyPress(KEY_RETURN) || Input()->KeyPress(KEY_K)) { - DemoPlayer()->SeekPercent(i * 0.1f); - break; + if(pInfo->m_Paused) + { + DemoPlayer()->Unpause(); + } + else + { + DemoPlayer()->Pause(); + } } - } - // seek to the beginning/end - if(Input()->KeyPress(KEY_HOME)) - { - DemoPlayer()->SeekPercent(0.0f); - } - else if(Input()->KeyPress(KEY_END)) - { - DemoPlayer()->SeekPercent(1.0f); + // seek backward/forward 10/5 seconds + if(Input()->KeyPress(KEY_J)) + { + DemoPlayer()->SeekTime(-10.0f); + } + else if(Input()->KeyPress(KEY_L)) + { + DemoPlayer()->SeekTime(10.0f); + } + else if(Input()->KeyPress(KEY_LEFT)) + { + DemoPlayer()->SeekTime(-5.0f); + } + else if(Input()->KeyPress(KEY_RIGHT)) + { + DemoPlayer()->SeekTime(5.0f); + } + + // seek to 0-90% + const int SeekPercentKeys[] = {KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9}; + for(unsigned i = 0; i < sizeof(SeekPercentKeys) / sizeof(SeekPercentKeys[0]); i++) + { + if(Input()->KeyPress(SeekPercentKeys[i])) + { + DemoPlayer()->SeekPercent(i * 0.1f); + break; + } + } + + // seek to the beginning/end + if(Input()->KeyPress(KEY_HOME)) + { + DemoPlayer()->SeekPercent(0.0f); + } + else if(Input()->KeyPress(KEY_END)) + { + DemoPlayer()->SeekPercent(1.0f); + } } TotalHeight = SeekBarHeight+ButtonbarHeight+NameBarHeight+Margins*3; diff --git a/src/game/client/components/nameplates.cpp b/src/game/client/components/nameplates.cpp index c289b83da..ac73c394f 100644 --- a/src/game/client/components/nameplates.cpp +++ b/src/game/client/components/nameplates.cpp @@ -27,23 +27,14 @@ void CNamePlates::RenderNameplate( const CNetObj_PlayerInfo *pPlayerInfo ) { - float IntraTick = Client()->IntraGameTick(); int ClientID = pPlayerInfo->m_ClientID; bool Local = m_pClient->m_Snap.m_LocalClientID == ClientID; vec2 Position; - if((!m_pClient->AntiPingPlayers() && !pPlayerInfo->m_Local) || m_pClient->m_Snap.m_SpecInfo.m_Active) - { - Position = mix(vec2(pPrevChar->m_X, pPrevChar->m_Y), vec2(pPlayerChar->m_X, pPlayerChar->m_Y), IntraTick); - } - else if(!m_pClient->AntiPingPlayers() && pPlayerInfo->m_Local) - { - Position = vec2(m_pClient->m_LocalCharacterPos.x, m_pClient->m_LocalCharacterPos.y); - } + if(ClientID >= 0 && ClientID < MAX_CLIENTS) + Position = m_pClient->m_aClients[ClientID].m_RenderPos; else - { - Position = m_pPlayers->m_CurPredictedPos[ClientID]; - } + Position = mix(vec2(pPrevChar->m_X, pPrevChar->m_Y), vec2(pPlayerChar->m_X, pPlayerChar->m_Y), Client()->IntraGameTick()); bool OtherTeam; @@ -51,15 +42,11 @@ void CNamePlates::RenderNameplate( OtherTeam = false; else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) OtherTeam = m_pClient->m_Teams.Team(pPlayerInfo->m_ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID); + else if((m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Solo || m_pClient->m_aClients[ClientID].m_Solo) && !Local) + OtherTeam = true; else OtherTeam = m_pClient->m_Teams.Team(pPlayerInfo->m_ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_LocalClientID); - if(m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Solo && !Local) - OtherTeam = true; - - if(!Local && m_pClient->m_aClients[pPlayerInfo->m_ClientID].m_Solo) - OtherTeam = true; - float FontSize = 18.0f + 20.0f * g_Config.m_ClNameplatesSize / 100.0f; float FontSizeClan = 18.0f + 20.0f * g_Config.m_ClNameplatesClanSize / 100.0f; // render name plate diff --git a/src/game/client/components/particles.cpp b/src/game/client/components/particles.cpp index 87e456944..d6512178c 100644 --- a/src/game/client/components/particles.cpp +++ b/src/game/client/components/particles.cpp @@ -35,7 +35,7 @@ void CParticles::OnReset() m_aFirstPart[i] = -1; } -void CParticles::Add(int Group, CParticle *pPart) +void CParticles::Add(int Group, CParticle *pPart, float TimePassed) { if(Client()->State() == IClient::STATE_DEMOPLAYBACK) { @@ -69,7 +69,7 @@ void CParticles::Add(int Group, CParticle *pPart) m_aFirstPart[Group] = Id; // set some parameters - m_aParticles[Id].m_Life = 0; + m_aParticles[Id].m_Life = TimePassed; } void CParticles::Update(float TimePassed) diff --git a/src/game/client/components/particles.h b/src/game/client/components/particles.h index b27a2e26d..40a9c9bd6 100644 --- a/src/game/client/components/particles.h +++ b/src/game/client/components/particles.h @@ -62,7 +62,7 @@ public: CParticles(); - void Add(int Group, CParticle *pPart); + void Add(int Group, CParticle *pPart, float TimePassed = 0.f); virtual void OnReset(); virtual void OnRender(); diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 962266a0e..03d8a00fd 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -77,141 +77,11 @@ inline float AngularApproach(float Src, float Dst, float Amount) return n; } -void CPlayers::Predict( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPrevInfo, - const CNetObj_PlayerInfo *pPlayerInfo, - vec2 &PrevPredPos, - vec2 &SmoothPos, - int &MoveCnt, - vec2 &Position - ) -{ - CNetObj_Character Prev; - CNetObj_Character Player; - Prev = *pPrevChar; - Player = *pPlayerChar; - - CNetObj_PlayerInfo pInfo = *pPlayerInfo; - - - // set size - - float IntraTick = Client()->IntraGameTick(); - - - //float angle = 0; - - if(pInfo.m_Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - // just use the direct input if it's local player we are rendering - } - else - { - /* - float mixspeed = Client()->FrameTime()*2.5f; - if(player.attacktick != prev.attacktick) // shooting boosts the mixing speed - mixspeed *= 15.0f; - - // move the delta on a constant speed on a x^2 curve - float current = g_GameClient.m_aClients[info.cid].angle; - float target = player.angle/256.0f; - float delta = angular_distance(current, target); - float sign = delta < 0 ? -1 : 1; - float new_delta = delta - 2*mixspeed*sqrt(delta*sign)*sign + mixspeed*mixspeed; - - // make sure that it doesn't vibrate when it's still - if(fabs(delta) < 2/256.0f) - angle = target; - else - angle = angular_approach(current, target, fabs(delta-new_delta)); - - g_GameClient.m_aClients[info.cid].angle = angle;*/ - } - - vec2 NonPredPos = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - - // use preditect players if needed - if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // apply predicted results - m_pClient->m_aClients[pInfo.m_ClientID].m_Predicted.Write(&Player); - m_pClient->m_aClients[pInfo.m_ClientID].m_PrevPredicted.Write(&Prev); - - IntraTick = Client()->PredIntraGameTick(); - } - } - - Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - - - static double s_Ping = 0; - - if(pInfo.m_Local) { - s_Ping = mix(s_Ping, (double)pInfo.m_Latency, 0.1); - } - - if(!pInfo.m_Local) - { - /* - for ping = 260, usual missprediction distances: - - move = 120-140 - jump = 130 - dj = 250 - - normalized: - move = 0.461 - 0.538 - jump = 0.5 - dj = .961 - - */ - //printf("%d\n", m_pClient->m_Snap.m_pLocalInfo->m_Latency); - - - if(m_pClient->m_Snap.m_pLocalInfo) - s_Ping = mix(s_Ping, (double)m_pClient->m_Snap.m_pLocalInfo->m_Latency, 0.1); - - double d = length(PrevPredPos - Position)/s_Ping; - - if((d > 0.4) && (d < 5.)) - { -// if(MoveCnt == 0) -// printf("[\n"); - if(MoveCnt == 0) - SmoothPos = NonPredPos; - - MoveCnt = 10; -// SmoothPos = PrevPredPos; -// SmoothPos = mix(NonPredPos, Position, 0.6); - } - - PrevPredPos = Position; - - if(MoveCnt > 0) - { -// Position = mix(mix(NonPredPos, Position, 0.5), SmoothPos, (((float)MoveCnt))/15); -// Position = mix(mix(NonPredPos, Position, 0.5), SmoothPos, 0.5); - Position = mix(NonPredPos, Position, 0.5); - - SmoothPos = Position; - MoveCnt--; -// if(MoveCnt == 0) -// printf("]\n\n"); - } - } -} - void CPlayers::RenderHook( const CNetObj_Character *pPrevChar, const CNetObj_Character *pPlayerChar, const CTeeRenderInfo *pRenderInfo, int ClientID, - const vec2 &parPosition, - const vec2 &PositionTo, float Intra ) { @@ -222,19 +92,15 @@ void CPlayers::RenderHook( CTeeRenderInfo RenderInfo = *pRenderInfo; - bool AntiPingPlayers = m_pClient->AntiPingPlayers(); bool Local = m_pClient->m_Snap.m_LocalClientID == ClientID; // don't render hooks to not active character cores if(pPlayerChar->m_HookedPlayer != -1 && !m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Active) return; - float IntraTick = Client()->IntraGameTick(); - if(ClientID < 0) - { - IntraTick = Intra; - AntiPingPlayers = false; - } + float IntraTick = Intra; + if(ClientID >= 0) + IntraTick = (m_pClient->m_aClients[ClientID].m_IsPredicted) ? Client()->PredIntraGameTick() : Client()->IntraGameTick(); bool OtherTeam; @@ -242,15 +108,11 @@ void CPlayers::RenderHook( OtherTeam = false; else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) OtherTeam = m_pClient->m_Teams.Team(ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID); + else if((m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Solo || m_pClient->m_aClients[ClientID].m_Solo) && !Local) + OtherTeam = true; else OtherTeam = m_pClient->m_Teams.Team(ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_LocalClientID); - if(m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Solo && !Local) - OtherTeam = true; - - if(!Local && m_pClient->m_aClients[ClientID].m_Solo) - OtherTeam = true; - if(OtherTeam) { RenderInfo.m_ColorBody.a = g_Config.m_ClShowOthersAlpha / 100.0f; @@ -259,43 +121,11 @@ void CPlayers::RenderHook( RenderInfo.m_Size = 64.0f; - if(!AntiPingPlayers) - { - // use predicted players if needed - if(Local && g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(!m_pClient->m_Snap.m_pLocalCharacter || (m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - } - else - { - // apply predicted results - m_pClient->m_PredictedChar.Write(&Player); - m_pClient->m_PredictedPrevChar.Write(&Prev); - IntraTick = Client()->PredIntraGameTick(); - } - } - } - else - { - if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // apply predicted results - m_pClient->m_aClients[ClientID].m_Predicted.Write(&Player); - m_pClient->m_aClients[ClientID].m_PrevPredicted.Write(&Prev); - - IntraTick = Client()->PredIntraGameTick(); - } - } - } - vec2 Position; - if(!AntiPingPlayers) - Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); + if(in_range(ClientID, MAX_CLIENTS-1)) + Position = m_pClient->m_aClients[ClientID].m_RenderPos; else - Position = parPosition; + Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); // draw hook if(Prev.m_HookState>0 && Player.m_HookState>0) @@ -309,39 +139,10 @@ void CPlayers::RenderHook( vec2 Pos = Position; vec2 HookPos; - if(!AntiPingPlayers) - { - if(pPlayerChar->m_HookedPlayer != -1) - { - if(m_pClient->m_Snap.m_LocalClientID != -1 && pPlayerChar->m_HookedPlayer == m_pClient->m_Snap.m_LocalClientID && !m_pClient->m_Snap.m_SpecInfo.m_Active) - { - if(Client()->State() == IClient::STATE_DEMOPLAYBACK) // only use prediction if needed - HookPos = vec2(m_pClient->m_LocalCharacterPos.x, m_pClient->m_LocalCharacterPos.y); - else - HookPos = mix(vec2(m_pClient->m_PredictedPrevChar.m_Pos.x, m_pClient->m_PredictedPrevChar.m_Pos.y), - vec2(m_pClient->m_PredictedChar.m_Pos.x, m_pClient->m_PredictedChar.m_Pos.y), Client()->PredIntraGameTick()); - } - else if(Local) - { - HookPos = mix(vec2(m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Prev.m_X, - m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Prev.m_Y), - vec2(m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Cur.m_X, - m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Cur.m_Y), - Client()->IntraGameTick()); - } - else - HookPos = mix(vec2(pPrevChar->m_HookX, pPrevChar->m_HookY), vec2(pPlayerChar->m_HookX, pPlayerChar->m_HookY), Client()->IntraGameTick()); - } - else - HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), IntraTick); - } + if(in_range(pPlayerChar->m_HookedPlayer, MAX_CLIENTS-1)) + HookPos = m_pClient->m_aClients[pPlayerChar->m_HookedPlayer].m_RenderPos; else - { - if(pPrevChar->m_HookedPlayer != -1) - HookPos = PositionTo; - else - HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), IntraTick); - } + HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), IntraTick); float d = distance(Pos, HookPos); vec2 Dir = normalize(Pos-HookPos); @@ -381,12 +182,8 @@ void CPlayers::RenderPlayer( const CNetObj_Character *pPlayerChar, const CTeeRenderInfo *pRenderInfo, int ClientID, - const vec2 &parPosition, float Intra -/* vec2 &PrevPos, - vec2 &SmoothPos, - int &MoveCnt -*/ ) + ) { CNetObj_Character Prev; CNetObj_Character Player; @@ -395,35 +192,25 @@ void CPlayers::RenderPlayer( CTeeRenderInfo RenderInfo = *pRenderInfo; - bool AntiPingPlayers = m_pClient->AntiPingPlayers(); bool Local = m_pClient->m_Snap.m_LocalClientID == ClientID; - bool NewTick = m_pClient->m_NewTick; - bool OtherTeam; if((m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Team == TEAM_SPECTATORS && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) || ClientID < 0) OtherTeam = false; else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) OtherTeam = m_pClient->m_Teams.Team(ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID); + else if((m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Solo || m_pClient->m_aClients[ClientID].m_Solo) && !Local) + OtherTeam = true; else OtherTeam = m_pClient->m_Teams.Team(ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_LocalClientID); - if(m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Solo && !Local) - OtherTeam = true; - - if(!Local && m_pClient->m_aClients[ClientID].m_Solo) - OtherTeam = true; - // set size RenderInfo.m_Size = 64.0f; - float IntraTick = Client()->IntraGameTick(); - if(ClientID < 0) - { - IntraTick = Intra; - AntiPingPlayers = false; - } + float IntraTick = Intra; + if(ClientID >= 0) + IntraTick = m_pClient->m_aClients[ClientID].m_IsPredicted ? Client()->PredIntraGameTick() : Client()->IntraGameTick(); float Angle; if(Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) @@ -433,82 +220,33 @@ void CPlayers::RenderPlayer( } else { + float AngleIntraTick = IntraTick; + // using unpredicted angle when rendering other players in-game + if(ClientID >= 0) + AngleIntraTick = Client()->IntraGameTick(); // If the player moves their weapon through top, then change // the end angle by 2*Pi, so that the mix function will use the // short path and not the long one. if(Player.m_Angle > (256.0f * pi) && Prev.m_Angle < 0) - { Player.m_Angle -= 256.0f * 2 * pi; - Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, IntraTick) / 256.0f; - } else if(Player.m_Angle < 0 && Prev.m_Angle > (256.0f * pi)) - { Player.m_Angle += 256.0f * 2 * pi; - Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, IntraTick) / 256.0f; - } - else - { - // No special cases? Just use mix(): - Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, IntraTick)/256.0f; - } - } - - // use preditect players if needed - if(!AntiPingPlayers) - { - if(Local && g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(!m_pClient->m_Snap.m_pLocalCharacter || (m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - } - else - { - // apply predicted results - m_pClient->m_PredictedChar.Write(&Player); - m_pClient->m_PredictedPrevChar.Write(&Prev); - IntraTick = Client()->PredIntraGameTick(); - NewTick = m_pClient->m_NewPredictedTick; - } - } - } - else - { - if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - { - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // apply predicted results - m_pClient->m_aClients[ClientID].m_Predicted.Write(&Player); - m_pClient->m_aClients[ClientID].m_PrevPredicted.Write(&Prev); - - IntraTick = Client()->PredIntraGameTick(); - NewTick = m_pClient->m_NewPredictedTick; - } - } + Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, AngleIntraTick)/256.0f; } vec2 Direction = GetDirection((int)(Angle*256.0f)); vec2 Position; - if(!AntiPingPlayers) - Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); + if(in_range(ClientID, MAX_CLIENTS-1)) + Position = m_pClient->m_aClients[ClientID].m_RenderPos; else - Position = parPosition; + Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); vec2 Vel = mix(vec2(Prev.m_VelX/256.0f, Prev.m_VelY/256.0f), vec2(Player.m_VelX/256.0f, Player.m_VelY/256.0f), IntraTick); m_pClient->m_pFlow->Add(Position, Vel*100.0f, 10.0f); RenderInfo.m_GotAirJump = Player.m_Jumped&2?0:1; - - // detect events - if(NewTick) - { - // detect air jump - if(!RenderInfo.m_GotAirJump && !(Prev.m_Jumped&2)) - m_pClient->m_pEffects->AirJump(Position); - } - bool Stationary = Player.m_VelX <= 1 && Player.m_VelX >= -1; bool InAir = !Collision()->CheckPoint(Player.m_X, Player.m_Y+16); bool WantOtherDir = (Player.m_Direction == -1 && Vel.x > 0) || (Player.m_Direction == 1 && Vel.x < 0); @@ -798,7 +536,13 @@ void CPlayers::RenderPlayer( // render the "shadow" tee if(Local && (g_Config.m_Debug || g_Config.m_ClUnpredictedShadow)) { - vec2 GhostPosition = mix(vec2(pPrevChar->m_X, pPrevChar->m_Y), vec2(pPlayerChar->m_X, pPlayerChar->m_Y), Client()->IntraGameTick()); + vec2 GhostPosition = Position; + if(ClientID >= 0) + GhostPosition = mix( + vec2(m_pClient->m_Snap.m_aCharacters[ClientID].m_Prev.m_X, m_pClient->m_Snap.m_aCharacters[ClientID].m_Prev.m_Y), + vec2(m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_X, m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_Y), + Client()->IntraGameTick()); + CTeeRenderInfo Ghost = RenderInfo; Ghost.m_ColorBody.a = 0.5f; Ghost.m_ColorFeet.a = 0.5f; @@ -925,56 +669,6 @@ void CPlayers::OnRender() } } - static vec2 PrevPos[MAX_CLIENTS]; - static vec2 SmoothPos[MAX_CLIENTS]; - static int MoveCnt[MAX_CLIENTS] = {0}; - - static int predcnt = 0; - - if(m_pClient->AntiPingPlayers()) - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!m_pClient->m_Snap.m_aCharacters[i].m_Active) - continue; - const void *pPrevInfo = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_PLAYERINFO, i); - const void *pInfo = Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_PLAYERINFO, i); - - if(pPrevInfo && pInfo) - { - CNetObj_Character PrevChar = m_pClient->m_Snap.m_aCharacters[i].m_Prev; - CNetObj_Character CurChar = m_pClient->m_Snap.m_aCharacters[i].m_Cur; - - Predict( - &PrevChar, - &CurChar, - (const CNetObj_PlayerInfo *)pPrevInfo, - (const CNetObj_PlayerInfo *)pInfo, - PrevPos[i], - SmoothPos[i], - MoveCnt[i], - m_CurPredictedPos[i] - ); - } - } - - if(m_pClient->AntiPingPlayers() && g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK) - if(m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) - { - // double ping = m_pClient->m_Snap.m_pLocalInfo->m_Latency; - // static double fps; - // fps = mix(fps, (1. / Client()->RenderFrameTime()), 0.1); - - // int predmax = (fps * ping / 1000.); - - int predmax = 19; - // if( 0 <= predmax && predmax <= 100) - predcnt = (predcnt + 1) % predmax; - // else - // predcnt = (predcnt + 1) % 2; - } - } - // render other players in two passes, first pass we render the other, second pass we render our self for(int p = 0; p < 4; p++) { @@ -994,29 +688,17 @@ void CPlayers::OnRender() if((p % 2) == 0 && Local) continue; if((p % 2) == 1 && !Local) continue; - CNetObj_Character PrevChar = m_pClient->m_Snap.m_aCharacters[i].m_Prev; - CNetObj_Character CurChar = m_pClient->m_Snap.m_aCharacters[i].m_Cur; + CNetObj_Character PrevChar = m_pClient->m_aClients[i].m_RenderPrev; + CNetObj_Character CurChar = m_pClient->m_aClients[i].m_RenderCur; if(p<2) { - if(PrevChar.m_HookedPlayer != -1) - RenderHook( - &PrevChar, - &CurChar, - &m_aRenderInfo[i], - i, - m_CurPredictedPos[i], - m_CurPredictedPos[PrevChar.m_HookedPlayer] - ); - else - RenderHook( - &PrevChar, - &CurChar, - &m_aRenderInfo[i], - i, - m_CurPredictedPos[i], - m_CurPredictedPos[i] - ); + RenderHook( + &PrevChar, + &CurChar, + &m_aRenderInfo[i], + i + ); } else { @@ -1024,8 +706,7 @@ void CPlayers::OnRender() &PrevChar, &CurChar, &m_aRenderInfo[i], - i, - m_CurPredictedPos[i] + i ); } } diff --git a/src/game/client/components/players.h b/src/game/client/components/players.h index 0e3c82c8a..cbd1eae3f 100644 --- a/src/game/client/components/players.h +++ b/src/game/client/components/players.h @@ -15,34 +15,16 @@ class CPlayers : public CComponent const CNetObj_Character *pPlayerChar, const CTeeRenderInfo *pRenderInfo, int ClientID, - const vec2 &Position, float Intra = 0.f -/* vec2 &PrevPredPos, - vec2 &SmoothPos, - int &MoveCnt -*/ ); void RenderHook( const CNetObj_Character *pPrevChar, const CNetObj_Character *pPlayerChar, const CTeeRenderInfo *pRenderInfo, int ClientID, - const vec2 &Position, - const vec2 &PositionTo, float Intra = 0.f ); - void Predict( - const CNetObj_Character *pPrevChar, - const CNetObj_Character *pPlayerChar, - const CNetObj_PlayerInfo *pPrevInfo, - const CNetObj_PlayerInfo *pPlayerInfo, - vec2 &PrevPredPos, - vec2 &SmoothPos, - int &MoveCnt, - vec2 &Position - ); - int m_WeaponEmoteQuadContainerIndex; int m_DirectionQuadContainerIndex; int m_WeaponSpriteMuzzleQuadContainerIndex[NUM_WEAPONS]; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 9274dda71..023482d5d 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -346,6 +346,10 @@ void CGameClient::OnInit() int64 End = time_get(); 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_pCollision = Collision(); + m_GameWorld.m_pTeams = &m_TeamsPredicted; } void CGameClient::OnUpdate() @@ -476,6 +480,11 @@ void CGameClient::OnConnected() // people at start as the other info 64 packet is only sent after the first // snap Client()->Rcon("crashmeplx"); + + m_GameWorld.Clear(); + m_GameWorld.m_WorldConfig.m_InfiniteAmmo = true; + for(int i = 0; i < MAX_CLIENTS; i++) + m_aLastWorldCharacters[i].m_Alive = false; } void CGameClient::OnReset() @@ -504,9 +513,6 @@ void CGameClient::OnReset() m_DDRaceMsgSent[1] = false; m_ShowOthers[0] = -1; m_ShowOthers[1] = -1; - - for(int i = 0; i < 150; i++) - m_aWeaponData[i].m_Tick = -1; } @@ -542,20 +548,6 @@ void CGameClient::UpdatePositions() vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y), Client()->IntraGameTick()); } - if(AntiPingPlayers()) - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!m_Snap.m_aCharacters[i].m_Active) - continue; - - if(m_Snap.m_pLocalCharacter && m_Snap.m_pLocalPrevCharacter && g_Config.m_ClPredict /* && g_Config.m_AntiPing */ && !(m_Snap.m_LocalClientID == -1 || !m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Active)) - m_Snap.m_aCharacters[i].m_Position = mix(m_aClients[i].m_PrevPredicted.m_Pos, m_aClients[i].m_Predicted.m_Pos, Client()->PredIntraGameTick()); - else - m_Snap.m_aCharacters[i].m_Position = mix(vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y), vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y), Client()->IntraGameTick()); - } - } - // spectator position if(m_Snap.m_SpecInfo.m_Active) { @@ -577,6 +569,8 @@ void CGameClient::UpdatePositions() m_Snap.m_SpecInfo.m_UsePosition = true; } } + + UpdateRenderedCharacters(); } @@ -594,7 +588,7 @@ static void Evolve(CNetObj_Character *pCharacter, int Tick) while(pCharacter->m_Tick < Tick) { pCharacter->m_Tick++; - TempCore.Tick(false, true); + TempCore.Tick(false); TempCore.Move(); TempCore.Quantize(); } @@ -699,33 +693,6 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, bool IsDummy) return; g_GameClient.m_pItems->AddExtraProjectile(&Proj); - - if(AntiPingWeapons() && Proj.m_Type == WEAPON_GRENADE && !UseExtraInfo(&Proj)) - { - vec2 StartPos; - vec2 Direction; - ExtractInfo(&Proj, &StartPos, &Direction); - if(CWeaponData *pCurrentData = GetWeaponData(Proj.m_StartTick)) - { - if(CWeaponData *pMatchingData = FindWeaponData(Proj.m_StartTick)) - { - if(distance(pMatchingData->m_Direction, Direction) < 0.015) - Direction = pMatchingData->m_Direction; - else if(int *pData = Client()->GetInput(Proj.m_StartTick+2)) - { - CNetObj_PlayerInput *pNextInput = (CNetObj_PlayerInput*) pData; - vec2 NextDirection = normalize(vec2(pNextInput->m_TargetX, pNextInput->m_TargetY)); - if(distance(NextDirection, Direction) < 0.015) - Direction = NextDirection; - } - if(distance(pMatchingData->StartPos(), StartPos) < 1) - StartPos = pMatchingData->StartPos(); - } - pCurrentData->m_Tick = Proj.m_StartTick; - pCurrentData->m_Direction = Direction; - pCurrentData->m_Pos = StartPos - Direction * 28.0f * 0.75f; - } - } } return; @@ -854,6 +821,17 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, bool IsDummy) CNetMsg_Sv_PlayerTime *pMsg = (CNetMsg_Sv_PlayerTime *)pRawMsg; m_aClients[pMsg->m_ClientID].m_Score = pMsg->m_Time; } + else if(MsgId == NETMSGTYPE_SV_KILLMSG) + { + CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg; + // reset character prediction + if(!(m_GameWorld.m_WorldConfig.m_IsFNG && pMsg->m_Weapon == WEAPON_RIFLE)) + { + m_CharOrder.GiveWeak(pMsg->m_Victim); + m_aLastWorldCharacters[pMsg->m_Victim].m_Alive = false; + m_GameWorld.ReleaseHooked(pMsg->m_Victim); + } + } } void CGameClient::OnStateChange(int NewState, int OldState) @@ -1120,6 +1098,9 @@ void CGameClient::OnNewSnapshot() { const CNetObj_DDNetCharacter *pCharacterData = (const CNetObj_DDNetCharacter *)pData; + m_Snap.m_aCharacters[Item.m_ID].m_ExtendedData = *pCharacterData; + m_Snap.m_aCharacters[Item.m_ID].m_HasExtendedData = true; + // Collision m_aClients[Item.m_ID].m_Solo = m_aClients[Item.m_ID].m_Predicted.m_Solo = pCharacterData->m_Flags & CHARACTERFLAG_SOLO; @@ -1378,11 +1359,25 @@ void CGameClient::OnNewSnapshot() m_pGhost->OnNewSnapshot(); m_pRaceDemo->OnNewSnapshot(); + + // detect air jump for unpredicted players + for(int i = 0; i < MAX_CLIENTS; i++) + if(m_Snap.m_aCharacters[i].m_Active && (m_Snap.m_aCharacters[i].m_Cur.m_Jumped&2) && !(m_Snap.m_aCharacters[i].m_Prev.m_Jumped&2)) + if(!Predict() || (!AntiPingPlayers() && i != m_Snap.m_LocalClientID)) + { + vec2 Pos = mix(vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y), + vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y), + Client()->IntraGameTick()); + m_pEffects->AirJump(Pos); + } + + // update prediction data + if(Client()->State() != IClient::STATE_DEMOPLAYBACK) + UpdatePrediction(); } void CGameClient::OnPredict() { - // store the previous values so we can detect prediction errors CCharacterCore BeforePrevChar = m_PredictedPrevChar; CCharacterCore BeforeChar = m_PredictedChar; @@ -1407,397 +1402,155 @@ void CGameClient::OnPredict() return; } - static bool IsWeaker[2][MAX_CLIENTS] = {{0}}; - if(AntiPingPlayers()) - FindWeaker(IsWeaker); - - // repredict character - CWorldCore World; - World.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; - - // search for players + vec2 aBeforeRender[MAX_CLIENTS]; for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!m_Snap.m_aCharacters[i].m_Active || !m_Snap.m_paPlayerInfos[i]) - continue; + aBeforeRender[i] = GetSmoothPos(i); - g_GameClient.m_aClients[i].m_Predicted.Init(&World, Collision(), &m_Teams); - World.m_apCharacters[i] = &g_GameClient.m_aClients[i].m_Predicted; - World.m_apCharacters[i]->m_Id = m_Snap.m_paPlayerInfos[i]->m_ClientID; - g_GameClient.m_aClients[i].m_Predicted.Read(&m_Snap.m_aCharacters[i].m_Cur); - g_GameClient.m_aClients[i].m_Predicted.m_ActiveWeapon = m_Snap.m_aCharacters[i].m_Cur.m_Weapon; - } + // init + m_PredictedWorld.CopyWorld(&m_GameWorld); - CServerInfo Info; - Client()->GetServerInfo(&Info); - const int MaxProjectiles = 128; - class CLocalProjectile PredictedProjectiles[MaxProjectiles]; - int NumProjectiles = 0; - int ReloadTimer = 0; + // don't predict inactive players + for(int i = 0; i < MAX_CLIENTS; i++) + if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i)) + if(!m_Snap.m_aCharacters[i].m_Active && pChar->m_SnapTicks > 10) + pChar->Destroy(); - if(AntiPingWeapons()) - { - for(int Index = 0; Index < MaxProjectiles; Index++) - PredictedProjectiles[Index].Deactivate(); - - int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT); - for(int Index = 0; Index < Num && NumProjectiles < MaxProjectiles; Index++) - { - IClient::CSnapItem Item; - const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item); - if(Item.m_Type == NETOBJTYPE_PROJECTILE) - { - CNetObj_Projectile* pProj = (CNetObj_Projectile*) pData; - if(pProj->m_Type == WEAPON_GRENADE || (pProj->m_Type == WEAPON_SHOTGUN && UseExtraInfo(pProj))) - { - CLocalProjectile NewProj; - NewProj.Init(this, &World, Collision(), pProj); - if(fabs(1.0f - length(NewProj.m_Direction)) < 0.015) - { - if(!NewProj.m_ExtraInfo) - { - if(CWeaponData *pData = FindWeaponData(NewProj.m_StartTick)) - { - NewProj.m_Pos = pData->StartPos(); - NewProj.m_Direction = pData->m_Direction; - NewProj.m_Owner = m_Snap.m_LocalClientID; - } - } - PredictedProjectiles[NumProjectiles] = NewProj; - NumProjectiles++; - } - } - } - } - - int AttackTick = m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Cur.m_AttackTick; - if(World.m_apCharacters[m_Snap.m_LocalClientID]->m_ActiveWeapon == WEAPON_HAMMER) - { - CWeaponData *pWeaponData = GetWeaponData(AttackTick); - if(pWeaponData && pWeaponData->m_Tick == AttackTick) - ReloadTimer = SERVER_TICK_SPEED / 3 - (Client()->GameTick() - AttackTick); - else - ReloadTimer = 0; - } - else - ReloadTimer = g_pData->m_Weapons.m_aId[World.m_apCharacters[m_Snap.m_LocalClientID]->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000 - (Client()->GameTick() - AttackTick); - ReloadTimer = max(ReloadTimer, 0); - } + CCharacter *pLocalChar = m_PredictedWorld.GetCharacterByID(m_Snap.m_LocalClientID); + if(!pLocalChar) + return; // predict - for(int Tick = Client()->GameTick()+1; Tick <= Client()->PredGameTick(); Tick++) + for(int Tick = Client()->GameTick() + 1; Tick <= Client()->PredGameTick(); Tick++) { - // fetch the local - if(Tick == Client()->PredGameTick() && World.m_apCharacters[m_Snap.m_LocalClientID]) - m_PredictedPrevChar = *World.m_apCharacters[m_Snap.m_LocalClientID]; - - for(int c = 0; c < MAX_CLIENTS; c++) + // fetch the previous characters + if(Tick == Client()->PredGameTick()) { - if(!World.m_apCharacters[c]) - continue; - - if(AntiPingPlayers() && Tick == Client()->PredGameTick()) - g_GameClient.m_aClients[c].m_PrevPredicted = *World.m_apCharacters[c]; + m_PrevPredictedWorld.CopyWorld(&m_PredictedWorld); + m_PredictedPrevChar = pLocalChar->GetCore(); + for(int i = 0; i < MAX_CLIENTS; i++) + if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i)) + m_aClients[i].m_PrevPredicted = pChar->GetCore(); } - // input - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; + // apply inputs and tick + CNetObj_PlayerInput *pInputData = (CNetObj_PlayerInput*) Client()->GetDirectInput(Tick); + if(pInputData) + pLocalChar->OnDirectInput(pInputData); + m_PredictedWorld.m_GameTick = Tick; + if(pInputData) + pLocalChar->OnPredictedInput(pInputData); + m_PredictedWorld.Tick(); - mem_zero(&World.m_apCharacters[c]->m_Input, sizeof(World.m_apCharacters[c]->m_Input)); - if(m_Snap.m_LocalClientID == c) - { - // apply player input - int *pInput = Client()->GetInput(Tick); - if(pInput) - World.m_apCharacters[c]->m_Input = *((CNetObj_PlayerInput*)pInput); - } + // fetch the current characters + if(Tick == Client()->PredGameTick()) + { + m_PredictedChar = pLocalChar->GetCore(); + for(int i = 0; i < MAX_CLIENTS; i++) + if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i)) + m_aClients[i].m_Predicted = pChar->GetCore(); } - if(AntiPingWeapons()) - { - const float ProximityRadius = 28.0f; - CNetObj_PlayerInput Input; - CNetObj_PlayerInput PrevInput; - mem_zero(&Input, sizeof(Input)); - mem_zero(&PrevInput, sizeof(PrevInput)); - int *pInput = Client()->GetInput(Tick); - if(pInput) - Input = *((CNetObj_PlayerInput*)pInput); - int *pPrevInput = Client()->GetInput(Tick-1); - if(pPrevInput) - PrevInput = *((CNetObj_PlayerInput*)pPrevInput); - - CCharacterCore *Local = World.m_apCharacters[m_Snap.m_LocalClientID]; - vec2 Direction = normalize(vec2(Input.m_TargetX, Input.m_TargetY)); - vec2 Pos = Local->m_Pos; - vec2 ProjStartPos = Pos + Direction * ProximityRadius * 0.75f; - - bool WeaponFired = false; - bool NewPresses = false; - - // handle weapons - - if(!ReloadTimer && World.m_apCharacters[m_Snap.m_LocalClientID] && (pInput && pPrevInput)) + for(int i = 0; i < MAX_CLIENTS; i++) + if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i)) { - bool FullAuto = false; - if(Local->m_ActiveWeapon == WEAPON_GRENADE || Local->m_ActiveWeapon == WEAPON_SHOTGUN || Local->m_ActiveWeapon == WEAPON_RIFLE) - FullAuto = true; - - bool WillFire = false; - if(CountInput(PrevInput.m_Fire, Input.m_Fire).m_Presses) - { - WillFire = true; - NewPresses = true; - } - - if(FullAuto && (Input.m_Fire & 1)) - WillFire = true; - - if(WillFire && ((IsRace(&Info) || m_Snap.m_pLocalCharacter->m_AmmoCount) || Local->m_ActiveWeapon == WEAPON_HAMMER)) { - int ExpectedStartTick = Tick - 1; - ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000; - bool DirectInput = Client()->InputExists(Tick); - if(!DirectInput) - { - ReloadTimer++; - ExpectedStartTick++; - } - switch(Local->m_ActiveWeapon) - { - case WEAPON_RIFLE: - case WEAPON_SHOTGUN: - case WEAPON_GUN: - { - WeaponFired = true; - } break; - case WEAPON_GRENADE: - { - if(NumProjectiles >= MaxProjectiles) - break; - PredictedProjectiles[NumProjectiles].Init( - this, &World, Collision(), - Direction, //StartDir - ProjStartPos, //StartPos - ExpectedStartTick, //StartTick - WEAPON_GRENADE, //Type - m_Snap.m_LocalClientID, //Owner - WEAPON_GRENADE, //Weapon - 1, 0, 0, 1); //Explosive, Bouncing, Freeze, ExtraInfo - NumProjectiles++; - WeaponFired = true; - } break; - case WEAPON_HAMMER: - { - vec2 ProjPos = ProjStartPos; - float Radius = ProximityRadius*0.5f; - - int Hits = 0; - bool OwnerCanProbablyHitOthers = (m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_Tuning[g_Config.m_ClDummy].m_PlayerHooking); - if(!OwnerCanProbablyHitOthers) - break; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!World.m_apCharacters[i]) - continue; - if(i == m_Snap.m_LocalClientID) - continue; - if(distance(World.m_apCharacters[i]->m_Pos, ProjPos) >= Radius + ProximityRadius) - continue; - - CCharacterCore *pTarget = World.m_apCharacters[i]; - - if(m_aClients[i].m_Active && !m_Teams.CanCollide(i, m_Snap.m_LocalClientID)) - continue; - - vec2 Dir; - if(length(pTarget->m_Pos - Pos) > 0.0f) - Dir = normalize(pTarget->m_Pos - Pos); - else - Dir = vec2(0.f, -1.f); - - float Strength; - Strength = World.m_Tuning[g_Config.m_ClDummy].m_HammerStrength; - - vec2 Temp = pTarget->m_Vel + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f; - - pTarget->LimitForce(&Temp); - - Temp -= pTarget->m_Vel; - pTarget->ApplyForce((vec2(0.f, -1.0f) + Temp) * Strength); - Hits++; - } - // if we Hit anything, we have to wait for the reload - if(Hits) - { - ReloadTimer = SERVER_TICK_SPEED/3; - WeaponFired = true; - } - } break; - } - if(!ReloadTimer) - { - ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000; - if(!DirectInput) - ReloadTimer++; - } - } + m_aClients[i].m_PredPos[Tick % 200] = pChar->Core()->m_Pos; + m_aClients[i].m_PredTick[Tick % 200] = Tick; } - if(ReloadTimer) - ReloadTimer--; - - if(Tick > Client()->GameTick()+1) - if(CWeaponData *pWeaponData = GetWeaponData(Tick-1)) - pWeaponData->m_Pos = Pos; - if(WeaponFired) - { - if(CWeaponData *pWeaponData = GetWeaponData(Tick-1)) - { - pWeaponData->m_Direction = Direction; - pWeaponData->m_Tick = Tick-1; - } - if(NewPresses) - { - if(CWeaponData *pWeaponData = GetWeaponData(Tick-2)) - { - pWeaponData->m_Direction = Direction; - pWeaponData->m_Tick = Tick-2; - } - if(CWeaponData *pWeaponData = GetWeaponData(Tick)) - { - pWeaponData->m_Direction = Direction; - pWeaponData->m_Tick = Tick; - } - } - } - - // projectiles - for(int g = 0; g < MaxProjectiles; g++) - if(PredictedProjectiles[g].m_Active) - PredictedProjectiles[g].Tick(Tick, Client()->GameTickSpeed(), m_Snap.m_LocalClientID); - } - - // calculate where everyone should move - if(AntiPingPlayers()) - { - //first apply Tick to weaker players (players that the local client has strong hook against), then local, then stronger players - for(int h = 0; h < 3; h++) - { - if(h == 1) - { - if(World.m_apCharacters[m_Snap.m_LocalClientID]) - World.m_apCharacters[m_Snap.m_LocalClientID]->Tick(true, true); - } - else - for(int c = 0; c < MAX_CLIENTS; c++) - if(c != m_Snap.m_LocalClientID && World.m_apCharacters[c] && ((h == 0 && IsWeaker[g_Config.m_ClDummy][c]) || (h == 2 && !IsWeaker[g_Config.m_ClDummy][c]))) - World.m_apCharacters[c]->Tick(false, true); - } - } - else - { - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; - World.m_apCharacters[c]->Tick(m_Snap.m_LocalClientID == c, true); - } - } - - // move all players and quantize their data - if(AntiPingPlayers()) - { - // Everyone with weaker hook - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(c != m_Snap.m_LocalClientID && World.m_apCharacters[c] && IsWeaker[g_Config.m_ClDummy][c]) - { - World.m_apCharacters[c]->Move(); - World.m_apCharacters[c]->Quantize(); - } - } - - // Us - if(World.m_apCharacters[m_Snap.m_LocalClientID]) - { - World.m_apCharacters[m_Snap.m_LocalClientID]->Move(); - World.m_apCharacters[m_Snap.m_LocalClientID]->Quantize(); - } - - // Everyone with stronger hook - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(c != m_Snap.m_LocalClientID && World.m_apCharacters[c] && !IsWeaker[g_Config.m_ClDummy][c]) - { - World.m_apCharacters[c]->Move(); - World.m_apCharacters[c]->Quantize(); - } - } - } - else - { - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; - World.m_apCharacters[c]->Move(); - World.m_apCharacters[c]->Quantize(); - } - } - // check if we want to trigger effects if(Tick > m_LastNewPredictedTick[g_Config.m_ClDummy]) { m_LastNewPredictedTick[g_Config.m_ClDummy] = Tick; m_NewPredictedTick = true; - - if(m_Snap.m_LocalClientID != -1 && World.m_apCharacters[m_Snap.m_LocalClientID]) + vec2 Pos = pLocalChar->Core()->m_Pos; + int Events = pLocalChar->Core()->m_TriggeredEvents; + if(g_Config.m_ClPredict) + if(Events&COREEVENT_AIR_JUMP) + m_pEffects->AirJump(Pos); + if(g_Config.m_SndGame) { - vec2 Pos = World.m_apCharacters[m_Snap.m_LocalClientID]->m_Pos; - int Events = World.m_apCharacters[m_Snap.m_LocalClientID]->m_TriggeredEvents; if(Events&COREEVENT_GROUND_JUMP) - if(g_Config.m_SndGame) - g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, Pos); - - /*if(events&COREEVENT_AIR_JUMP) - { - GameClient.effects->air_jump(pos); - GameClient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 1.0f, pos); - }*/ - - //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); - //if(events&COREEVENT_HOOK_ATTACH_PLAYER) snd_play_random(CHN_WORLD, SOUND_HOOK_ATTACH_PLAYER, 1.0f, pos); + m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, Pos); if(Events&COREEVENT_HOOK_ATTACH_GROUND) - if(g_Config.m_SndGame) - g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, Pos); + m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, Pos); if(Events&COREEVENT_HOOK_HIT_NOHOOK) - if(g_Config.m_SndGame) - g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, Pos); - //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); + m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, Pos); } } + } + // detect mispredictions of other players and make corrections smoother when possible + static vec2 s_aLastPos[MAX_CLIENTS] = {{0,0}}; + static bool s_aLastActive[MAX_CLIENTS] = {0}; - if(Tick == Client()->PredGameTick() && World.m_apCharacters[m_Snap.m_LocalClientID]) + if(g_Config.m_ClAntiPingSmooth && Predict() && AntiPingPlayers() && m_NewTick && abs(m_PredictedTick - Client()->PredGameTick()) <= 1 && abs(Client()->GameTick() - Client()->PrevGameTick()) <= 2) + { + int PredTime = clamp(Client()->GetPredictionTime(), 0, 800); + float SmoothPace = 4 - 1.5f * PredTime/800.f; // smoothing pace (a lower value will make the smoothing quicker) + int64 Len = 1000 * PredTime * SmoothPace; + + for(int i = 0; i < MAX_CLIENTS; i++) { - m_PredictedChar = *World.m_apCharacters[m_Snap.m_LocalClientID]; - - if(AntiPingPlayers()) + if(!m_Snap.m_aCharacters[i].m_Active || i == m_Snap.m_LocalClientID || !s_aLastActive[i]) + continue; + vec2 NewPos = (m_PredictedTick == Client()->PredGameTick()) ? m_aClients[i].m_Predicted.m_Pos : m_aClients[i].m_PrevPredicted.m_Pos; + vec2 PredErr = (s_aLastPos[i] - NewPos)/(float)min(Client()->GetPredictionTime(), 200); + if(in_range(length(PredErr), 0.05f, 5.f)) { - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(!World.m_apCharacters[c]) - continue; + vec2 PredPos = mix(m_aClients[i].m_PrevPredicted.m_Pos, m_aClients[i].m_Predicted.m_Pos, Client()->PredIntraGameTick()); + vec2 CurPos = mix( + vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y), + vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y), + Client()->IntraGameTick()); + vec2 RenderDiff = PredPos - aBeforeRender[i]; + vec2 PredDiff = PredPos - CurPos; - g_GameClient.m_aClients[c].m_Predicted = *World.m_apCharacters[c]; + float MixAmount[2]; + for(int j = 0; j < 2; j++) + { + MixAmount[j] = 1.0; + if(fabs(PredErr[j]) > 0.05f) + { + MixAmount[j] = 0.0; + if(fabs(RenderDiff[j]) > 0.01f) + { + MixAmount[j] = 1.f - clamp(RenderDiff[j] / PredDiff[j], 0.f, 1.f); + MixAmount[j] = 1.f - powf(1.f - MixAmount[j], 1/1.2f); + } + } + int64 TimePassed = time_get() - m_aClients[i].m_SmoothStart[j]; + if(in_range(TimePassed, (int64)0, Len-1)) + MixAmount[j] = min(MixAmount[j], (float)(TimePassed/(double)Len)); + + } + for(int j = 0; j < 2; j++) + if(fabs(RenderDiff[j]) < 0.01f && fabs(PredDiff[j]) < 0.01f && fabs(m_aClients[i].m_PrevPredicted.m_Pos[j] - m_aClients[i].m_Predicted.m_Pos[j]) < 0.01f && MixAmount[j] > MixAmount[j^1]) + MixAmount[j] = MixAmount[j^1]; + for(int j = 0; j < 2; j++) + { + int64 Remaining = min((1.f-MixAmount[j])*Len, min(time_freq()*0.700f, (1.f-MixAmount[j^1])*Len + time_freq()*0.300f)); // don't smooth for longer than 700ms, or more than 300ms longer along one axis than the other axis + int64 Start = time_get() - (Len - Remaining); + if(!in_range(Start + Len, m_aClients[i].m_SmoothStart[j], m_aClients[i].m_SmoothStart[j] + Len)) + { + m_aClients[i].m_SmoothStart[j] = Start; + m_aClients[i].m_SmoothLen[j] = Len; + } } } } } + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_Snap.m_aCharacters[i].m_Active) + { + s_aLastPos[i] = m_aClients[i].m_Predicted.m_Pos; + s_aLastActive[i] = true; + } + else + s_aLastActive[i] = false; + } + if(g_Config.m_Debug && g_Config.m_ClPredict && m_PredictedTick == Client()->PredGameTick()) { CNetObj_CharacterCore Before = {0}, Now = {0}, BeforePrev = {0}, NowPrev = {0}; @@ -2152,112 +1905,109 @@ int CGameClient::IntersectCharacter(vec2 HookPos, vec2 NewPos, vec2& NewPos2, in return ClosestID; } - -int CGameClient::IntersectCharacter(vec2 OldPos, vec2 NewPos, float Radius, vec2 *NewPos2, int ownID, CWorldCore *World) +vec3 CalculateNameColor(vec3 TextColorHSL) { - float PhysSize = 28.0f; - float Distance = 0.0f; - int ClosestID = -1; - - if(!World) - return ClosestID; - - CClientData OwncData = m_aClients[ownID]; - - for(int i=0; im_apCharacters[i]) - continue; - CClientData cData = m_aClients[i]; - - bool IsOneSuper = cData.m_Super || OwncData.m_Super; - bool IsOneSolo = cData.m_Solo || OwncData.m_Solo; - - if(!cData.m_Active || i == ownID || (!IsOneSuper && (!m_Teams.CanCollide(i, ownID) || IsOneSolo))) - continue; - vec2 Position = World->m_apCharacters[i]->m_Pos; - vec2 ClosestPoint = closest_point_on_line(OldPos, NewPos, Position); - if(distance(Position, ClosestPoint) < PhysSize+Radius) - { - if(ClosestID == -1 || distance(OldPos, Position) < Distance) - { - *NewPos2 = ClosestPoint; - ClosestID = i; - Distance = distance(OldPos, Position); - } - } - } - return ClosestID; + return HslToRgb(vec3(TextColorHSL.h, TextColorHSL.s * 0.68f, TextColorHSL.l * 0.81f)); } -void CLocalProjectile::Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, const CNetObj_Projectile *pProj) +void CGameClient::UpdatePrediction() { - m_Active = 1; - m_pGameClient = pGameClient; - m_pWorld = pWorld; - m_pCollision = pCollision; - m_StartTick = pProj->m_StartTick; - m_Type = pProj->m_Type; - m_Weapon = m_Type; - - ExtractInfo(pProj, &m_Pos, &m_Direction); - - if(UseExtraInfo(pProj)) + if(!m_Snap.m_pLocalCharacter) { - ExtractExtraInfo(pProj, &m_Owner, &m_Explosive, &m_Bouncing, &m_Freeze); - m_ExtraInfo = true; + if(CCharacter *pLocalChar = m_GameWorld.GetCharacterByID(m_Snap.m_LocalClientID)) + pLocalChar->Destroy(); + return; + } + + m_TeamsPredicted = m_Teams; + + CServerInfo CurrentServerInfo; + Client()->GetServerInfo(&CurrentServerInfo); + + m_GameWorld.m_WorldConfig.m_IsVanilla = IsVanilla(&CurrentServerInfo); + m_GameWorld.m_WorldConfig.m_IsDDRace = IsDDRace(&CurrentServerInfo); + m_GameWorld.m_WorldConfig.m_IsFNG = IsFNG(&CurrentServerInfo); + m_GameWorld.m_WorldConfig.m_PredictTiles = g_Config.m_ClPredictDDRace && m_GameWorld.m_WorldConfig.m_IsDDRace && !IsBlockWorlds(&CurrentServerInfo); + m_GameWorld.m_WorldConfig.m_PredictFreeze = g_Config.m_ClPredictFreeze; + m_GameWorld.m_WorldConfig.m_PredictWeapons = AntiPingWeapons(); + m_GameWorld.m_Core.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; + if(m_Snap.m_pLocalCharacter->m_AmmoCount > 0 && m_Snap.m_pLocalCharacter->m_Weapon != WEAPON_NINJA) + m_GameWorld.m_WorldConfig.m_InfiniteAmmo = false; + + // restore characters from previously saved ones if they temporarily left the snapshot + for(int i = 0; i < MAX_CLIENTS; i++) + if(m_aLastWorldCharacters[i].IsAlive() && m_Snap.m_aCharacters[i].m_Active && !m_GameWorld.GetCharacterByID(i)) + if(CCharacter *pCopy = new CCharacter(m_aLastWorldCharacters[i])) + { + m_GameWorld.InsertEntity(pCopy); + if(pCopy->m_FreezeTime > 0) + pCopy->m_FreezeTime = 0; + if(pCopy->Core()->m_HookedPlayer > 0) + { + pCopy->Core()->m_HookedPlayer = -1; + pCopy->Core()->m_HookState = HOOK_IDLE; + } + } + + CCharacter *pLocalChar = m_GameWorld.GetCharacterByID(m_Snap.m_LocalClientID); + + // update strong and weak hook + if(pLocalChar && AntiPingPlayers()) + DetectStrongHook(); + for(int i : m_CharOrder.m_IDs) + if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i)) + { + m_GameWorld.RemoveEntity(pChar); + m_GameWorld.InsertEntity(pChar); + } + + // advance the gameworld to the current gametick + if(pLocalChar && abs(m_GameWorld.GameTick() - Client()->GameTick()) < SERVER_TICK_SPEED) + { + for(int Tick = m_GameWorld.GameTick() + 1; Tick <= Client()->GameTick(); Tick++) + { + CNetObj_PlayerInput *pInput = (CNetObj_PlayerInput*) Client()->GetDirectInput(Tick); + if(pInput) + pLocalChar->OnDirectInput(pInput); + m_GameWorld.m_GameTick = Tick; + if(pInput) + pLocalChar->OnPredictedInput(pInput); + m_GameWorld.Tick(); + + for(int i = 0; i < MAX_CLIENTS; i++) + if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i)) + { + m_aClients[i].m_PredPos[Tick % 200] = pChar->Core()->m_Pos; + m_aClients[i].m_PredTick[Tick % 200] = Tick; + } + } } else { - bool StandardVel = (fabs(1.0f - length(m_Direction)) < 0.015); - m_Owner = -1; - m_Explosive = m_Type == WEAPON_GRENADE && StandardVel; - m_Bouncing = 0; - m_Freeze = 0; - m_ExtraInfo = false; + // skip to current gametick + m_GameWorld.m_GameTick = Client()->GameTick(); + if(pLocalChar) + if(CNetObj_PlayerInput *pInput = (CNetObj_PlayerInput*) Client()->GetInput(Client()->GameTick())) + pLocalChar->SetInput(pInput); } -} -void CLocalProjectile::Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, vec2 Direction, vec2 Pos, int StartTick, int Type, int Owner, int Weapon, bool Explosive, int Bouncing, bool Freeze, bool ExtraInfo) -{ - m_Active = 1; - m_pGameClient = pGameClient; - m_pWorld = pWorld; - m_pCollision = pCollision; - m_Direction = Direction; - m_Pos = Pos; - m_StartTick = StartTick; - m_Type = Type; - m_Weapon = Weapon; - m_Owner = Owner; - m_Explosive = Explosive; - m_Bouncing = Bouncing; - m_Freeze = Freeze; - m_ExtraInfo = ExtraInfo; -} + for(int i = 0; i < MAX_CLIENTS; i++) + if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i)) + { + m_aClients[i].m_PredPos[Client()->GameTick() % 200] = pChar->Core()->m_Pos; + m_aClients[i].m_PredTick[Client()->GameTick() % 200] = Client()->GameTick(); + } -vec2 CLocalProjectile::GetPos(float Time) -{ - float Curvature = 0; - float Speed = 0; - - switch(m_Type) + // update the local gameworld with the new snapshot + m_GameWorld.NetObjBegin(); + int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT); + for(int Index = 0; Index < Num; Index++) { - case WEAPON_GRENADE: - Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GrenadeCurvature; - Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GrenadeSpeed; - break; - - case WEAPON_SHOTGUN: - Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ShotgunCurvature; - Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ShotgunSpeed; - break; - - case WEAPON_GUN: - Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GunCurvature; - Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GunSpeed; - break; + IClient::CSnapItem Item; + const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item); + m_GameWorld.NetObjAdd(Item.m_ID, Item.m_Type, pData); } +<<<<<<< HEAD return CalcPos(m_Pos, m_Direction, Curvature, Speed, Time); } @@ -2299,143 +2049,171 @@ void CLocalProjectile::Tick(int CurrentTick, int GameTickSpeed, int LocalClientI if(m_Explosive && (Target < 0 || (Target >= 0 && (!m_Freeze || (m_Weapon == WEAPON_SHOTGUN && Collide))))) CreateExplosion(ColPos, m_Owner); if(Collide && m_Bouncing != 0) +======= + for(int i = 0; i < MAX_CLIENTS; i++) + if(m_Snap.m_aCharacters[i].m_Active) +>>>>>>> upstream/master { - m_StartTick = CurrentTick; - m_Pos = NewPos+(-(m_Direction*4)); - if(m_Bouncing == 1) - m_Direction.x = -m_Direction.x; - else if(m_Bouncing == 2) - m_Direction.y = -m_Direction.y; - if(fabs(m_Direction.x) < 1e-6) - m_Direction.x = 0; - if(fabs(m_Direction.y) < 1e-6) - m_Direction.y = 0; - m_Pos += m_Direction; + bool IsLocal = (i == m_Snap.m_LocalClientID); + int GameTeam = (m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) ? m_aClients[i].m_Team : i; + m_GameWorld.NetCharAdd(i, &m_Snap.m_aCharacters[i].m_Cur, + m_Snap.m_aCharacters[i].m_HasExtendedData ? &m_Snap.m_aCharacters[i].m_ExtendedData : 0, + GameTeam, IsLocal); } - else if(!m_Bouncing) - Deactivate(); - } + m_GameWorld.NetObjEnd(m_Snap.m_LocalClientID); - if(!m_Bouncing) - { - int Lifetime = 0; - if(m_Weapon == WEAPON_GRENADE) - Lifetime = (int)(m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED); - else if(m_Weapon == WEAPON_GUN) - Lifetime = (int)(m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED); - else if(m_Weapon == WEAPON_SHOTGUN) - Lifetime = (int)(m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunLifetime * SERVER_TICK_SPEED); - int LifeSpan = Lifetime - (CurrentTick - m_StartTick); - if(LifeSpan == -1) + // save the characters that are currently active + for(int i = 0; i < MAX_CLIENTS; i++) + if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i)) { - if(m_Explosive) - CreateExplosion(ColPos, LocalClientID); - Deactivate(); + m_aLastWorldCharacters[i] = *pChar; + m_aLastWorldCharacters[i].DetachFromGameWorld(); } +} + +void CGameClient::UpdateRenderedCharacters() +{ + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(!m_Snap.m_aCharacters[i].m_Active) + continue; + m_aClients[i].m_RenderCur = m_Snap.m_aCharacters[i].m_Cur; + m_aClients[i].m_RenderPrev = m_Snap.m_aCharacters[i].m_Prev; + m_aClients[i].m_IsPredicted = false; + vec2 UnpredPos = mix( + vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y), + vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y), + Client()->IntraGameTick()); + vec2 Pos = UnpredPos; + + if(Predict() && (i == m_Snap.m_LocalClientID || AntiPingPlayers())) + { + m_aClients[i].m_Predicted.Write(&m_aClients[i].m_RenderCur); + m_aClients[i].m_PrevPredicted.Write(&m_aClients[i].m_RenderPrev); + m_aClients[i].m_IsPredicted = true; + Pos = mix( + vec2(m_aClients[i].m_RenderPrev.m_X, m_aClients[i].m_RenderPrev.m_Y), + vec2(m_aClients[i].m_RenderCur.m_X, m_aClients[i].m_RenderCur.m_Y), + m_aClients[i].m_IsPredicted ? Client()->PredIntraGameTick() : Client()->IntraGameTick()); + + if(i == m_Snap.m_LocalClientID) + m_aClients[i].m_IsPredictedLocal = true; + else + { + // use unpredicted values for other players + m_aClients[i].m_RenderPrev.m_Angle = m_Snap.m_aCharacters[i].m_Prev.m_Angle; + m_aClients[i].m_RenderPrev.m_AttackTick = m_Snap.m_aCharacters[i].m_Prev.m_AttackTick; + m_aClients[i].m_RenderCur.m_Angle = m_Snap.m_aCharacters[i].m_Cur.m_Angle; + m_aClients[i].m_RenderCur.m_AttackTick = m_Snap.m_aCharacters[i].m_Cur.m_AttackTick; + + if(g_Config.m_ClAntiPingSmooth) + Pos = GetSmoothPos(i); + } + } + m_Snap.m_aCharacters[i].m_Position = Pos; + m_aClients[i].m_RenderPos = Pos; + if(Predict() && i == m_Snap.m_LocalClientID) + m_LocalCharacterPos = Pos; } } -void CLocalProjectile::CreateExplosion(vec2 Pos, int LocalClientID) +void CGameClient::DetectStrongHook() { - if(!m_pWorld) - return; - float Radius = 135.0f; - float InnerRadius = 48.0f; - - bool OwnerCanProbablyHitOthers = (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking); - - for(int c = 0; c < MAX_CLIENTS; c++) + static int s_LastUpdateTick[MAX_CLIENTS] = {0}; + // attempt to detect strong/weak between players + for(int FromPlayer = 0; FromPlayer < MAX_CLIENTS; FromPlayer++) { - if(!m_pWorld->m_apCharacters[c]) + if(!m_Snap.m_aCharacters[FromPlayer].m_Active) continue; - if(m_Owner >= 0 && c >= 0) - if(m_pGameClient->m_aClients[c].m_Active && !m_pGameClient->m_Teams.CanCollide(c, m_Owner)) - continue; - if(c != LocalClientID && !OwnerCanProbablyHitOthers) + 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; - vec2 Diff = m_pWorld->m_apCharacters[c]->m_Pos - Pos; - vec2 ForceDir(0,1); - float l = length(Diff); - if(l) - ForceDir = normalize(Diff); - l = 1-clamp((l-InnerRadius)/(Radius-InnerRadius), 0.0f, 1.0f); + if(abs(min(s_LastUpdateTick[ToPlayer], s_LastUpdateTick[FromPlayer]) - Client()->GameTick()) < SERVER_TICK_SPEED/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; + s_LastUpdateTick[ToPlayer] = s_LastUpdateTick[FromPlayer] = Client()->GameTick(); - float Strength = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ExplosionStrength; - float Dmg = Strength * l; - - if((int)Dmg) - m_pWorld->m_apCharacters[c]->ApplyForce(ForceDir*Dmg*2); - } -} - -CWeaponData *CGameClient::FindWeaponData(int TargetTick) -{ - CWeaponData *pData; - int TickDiff[3] = {0, -1, 1}; - for(unsigned int i = 0; i < sizeof(TickDiff)/sizeof(int); i++) - if((pData = GetWeaponData(TargetTick + TickDiff[i]))) - if(pData->m_Tick == TargetTick + TickDiff[i]) - return GetWeaponData(TargetTick + TickDiff[i]); - return NULL; -} - -void CGameClient::FindWeaker(bool IsWeaker[2][MAX_CLIENTS]) -{ - // attempts to detect strong/weak against the player we are hooking - static int DirAccumulated[2][MAX_CLIENTS] = {{0}}; - if(!m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Active || !m_Snap.m_paPlayerInfos[m_Snap.m_LocalClientID]) - return; - int HookedPlayer = m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Prev.m_HookedPlayer; - if(HookedPlayer >= 0 && m_Snap.m_aCharacters[HookedPlayer].m_Active && m_Snap.m_paPlayerInfos[HookedPlayer]) - { - CCharacterCore OtherCharCur; - OtherCharCur.Read(&m_Snap.m_aCharacters[HookedPlayer].m_Cur); float PredictErr[2]; + CCharacterCore ToCharCur; + ToCharCur.Read(&m_Snap.m_aCharacters[ToPlayer].m_Cur); + + CWorldCore World; + World.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; + CCharacterCore ToChar; + CCharacterCore FromChar; for(int dir = 0; dir < 2; dir++) { - CWorldCore World; - World.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; + ToChar.Init(&World, Collision(), &m_Teams); + World.m_apCharacters[ToPlayer] = &ToChar; + ToChar.Read(&m_Snap.m_aCharacters[ToPlayer].m_Prev); - CCharacterCore OtherChar; - OtherChar.Init(&World, Collision(), &m_Teams); - World.m_apCharacters[HookedPlayer] = &OtherChar; - OtherChar.Read(&m_Snap.m_aCharacters[HookedPlayer].m_Prev); - - CCharacterCore LocalChar; - LocalChar.Init(&World, Collision(), &m_Teams); - World.m_apCharacters[m_Snap.m_LocalClientID] = &LocalChar; - LocalChar.Read(&m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Prev); + FromChar.Init(&World, Collision(), &m_Teams); + World.m_apCharacters[FromPlayer] = &FromChar; + FromChar.Read(&m_Snap.m_aCharacters[FromPlayer].m_Prev); for(int Tick = Client()->PrevGameTick(); Tick < Client()->GameTick(); Tick++) { if(dir == 0) { - LocalChar.Tick(false, true); - OtherChar.Tick(false, true); + FromChar.Tick(false); + ToChar.Tick(false); } else { - OtherChar.Tick(false, true); - LocalChar.Tick(false, true); + ToChar.Tick(false); + FromChar.Tick(false); } - LocalChar.Move(); - LocalChar.Quantize(); - OtherChar.Move(); - OtherChar.Quantize(); + FromChar.Move(); + FromChar.Quantize(); + ToChar.Move(); + ToChar.Quantize(); } - PredictErr[dir] = distance(OtherChar.m_Vel, OtherCharCur.m_Vel); + PredictErr[dir] = distance(ToChar.m_Vel, ToCharCur.m_Vel); } - static const float LOW = 0.0001f; - static const float HIGH = 0.07f; + const float LOW = 0.0001f; + const float HIGH = 0.07f; if(PredictErr[1] < LOW && PredictErr[0] > HIGH) - DirAccumulated[g_Config.m_ClDummy][HookedPlayer] = SaturatedAdd(-1, 2, DirAccumulated[g_Config.m_ClDummy][HookedPlayer], 1); + { + if(m_CharOrder.HasStrongAgainst(ToPlayer, FromPlayer)) + { + if(ToPlayer != m_Snap.m_LocalClientID) + m_CharOrder.GiveWeak(ToPlayer); + else + m_CharOrder.GiveStrong(FromPlayer); + } + } else if(PredictErr[0] < LOW && PredictErr[1] > HIGH) - DirAccumulated[g_Config.m_ClDummy][HookedPlayer] = SaturatedAdd(-1, 2, DirAccumulated[g_Config.m_ClDummy][HookedPlayer], -1); - IsWeaker[g_Config.m_ClDummy][HookedPlayer] = (DirAccumulated[g_Config.m_ClDummy][HookedPlayer] > 0); + { + if(m_CharOrder.HasStrongAgainst(FromPlayer, ToPlayer)) + { + if(ToPlayer != m_Snap.m_LocalClientID) + m_CharOrder.GiveStrong(ToPlayer); + else + m_CharOrder.GiveWeak(FromPlayer); + } + } } } -vec3 CalculateNameColor(vec3 TextColorHSL) +vec2 CGameClient::GetSmoothPos(int ClientID) { - return HslToRgb(vec3(TextColorHSL.h, TextColorHSL.s * 0.68f, TextColorHSL.l * 0.81f)); + vec2 Pos = mix(m_aClients[ClientID].m_PrevPredicted.m_Pos, m_aClients[ClientID].m_Predicted.m_Pos, Client()->PredIntraGameTick()); + int64 Now = time_get(); + for(int i = 0; i < 2; i++) + { + int64 Len = clamp(m_aClients[ClientID].m_SmoothLen[i], (int64) 1, time_freq()); + int64 TimePassed = Now - m_aClients[ClientID].m_SmoothStart[i]; + if(in_range(TimePassed, (int64)0, Len-1)) + { + float MixAmount = 1.f - powf(1.f - TimePassed/(float)Len, 1.2f); + int SmoothTick; + float SmoothIntra; + Client()->GetSmoothTick(&SmoothTick, &SmoothIntra, MixAmount); + if(SmoothTick > 0 && m_aClients[ClientID].m_PredTick[(SmoothTick-1) % 200] >= Client()->PrevGameTick() && m_aClients[ClientID].m_PredTick[SmoothTick % 200] <= Client()->PredGameTick()) + Pos[i] = mix(m_aClients[ClientID].m_PredPos[(SmoothTick-1) % 200][i], m_aClients[ClientID].m_PredPos[SmoothTick % 200][i], SmoothIntra); + } + } + return Pos; } diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 2a87ed799..1ca7bfb88 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -13,6 +13,11 @@ #include +#include +#include +#include +#include + #define MIN3(x,y,z) ((y) <= (z) ? \ ((x) <= (y) ? (x) : (y)) \ : \ @@ -23,46 +28,6 @@ : \ ((x) >= (z) ? (x) : (z))) -class CGameClient; - -class CWeaponData -{ -public: - int m_Tick; - vec2 m_Pos; - vec2 m_Direction; - vec2 StartPos() { return m_Pos + m_Direction * 28.0f * 0.75f; } -}; - -class CLocalProjectile -{ -public: - int m_Active; - CGameClient *m_pGameClient; - CWorldCore *m_pWorld; - CCollision *m_pCollision; - - vec2 m_Direction; - vec2 m_Pos; - int m_StartTick; - int m_Type; - - int m_Owner; - int m_Weapon; - bool m_Explosive; - int m_Bouncing; - bool m_Freeze; - bool m_ExtraInfo; - - vec2 GetPos(float Time); - void CreateExplosion(vec2 Pos, int LocalClientID); - void Tick(int CurrentTick, int GameTickSpeed, int LocalClientID); - void Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, const CNetObj_Projectile *pProj); - void Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, vec2 Vel, vec2 Pos, int StartTick, int Type, int Owner, int Weapon, bool Explosive, int Bouncing, bool Freeze, bool ExtraInfo); - bool GameLayerClipped(vec2 CheckPos); - void Deactivate() { m_Active = 0; } -}; - class CGameClient : public IGameClient { class CStack @@ -217,6 +182,9 @@ public: CNetObj_Character m_Prev; CNetObj_Character m_Cur; + CNetObj_DDNetCharacter m_ExtendedData; + bool m_HasExtendedData; + // interpolated position vec2 m_Position; }; @@ -281,6 +249,17 @@ public: // DDRace int m_Score; + + // rendered characters + CNetObj_Character m_RenderCur; + CNetObj_Character m_RenderPrev; + vec2 m_RenderPos; + bool m_IsPredicted; + bool m_IsPredictedLocal; + int64 m_SmoothStart[2]; + int64 m_SmoothLen[2]; + vec2 m_PredPos[200]; + int m_PredTick[200]; }; CClientData m_aClients[MAX_CLIENTS]; @@ -397,23 +376,30 @@ public: class CTeamsCore m_Teams; int IntersectCharacter(vec2 Pos0, vec2 Pos1, vec2& NewPos, int ownID); - int IntersectCharacter(vec2 OldPos, vec2 NewPos, float Radius, vec2* NewPos2, int ownID, CWorldCore *World); - - CWeaponData m_aWeaponData[150]; - CWeaponData *GetWeaponData(int Tick) { return &m_aWeaponData[((Tick%150)+150)%150]; } - CWeaponData *FindWeaponData(int TargetTick); virtual int GetLastRaceTick(); - void FindWeaker(bool IsWeaker[2][MAX_CLIENTS]); - bool AntiPingPlayers() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingPlayers && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK && (m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_Tuning[g_Config.m_ClDummy].m_PlayerHooking); } bool AntiPingGrenade() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingGrenade && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK; } bool AntiPingWeapons() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingWeapons && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK; } + bool Predict() { return g_Config.m_ClPredict && !(m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER) && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK && m_Snap.m_pLocalCharacter; } + + CGameWorld m_GameWorld; + CGameWorld m_PredictedWorld; + CGameWorld m_PrevPredictedWorld; private: bool m_DDRaceMsgSent[2]; int m_ShowOthers[2]; + + void UpdatePrediction(); + void UpdateRenderedCharacters(); + void DetectStrongHook(); + vec2 GetSmoothPos(int ClientID); + + CCharOrder m_CharOrder; + class CCharacter m_aLastWorldCharacters[MAX_CLIENTS]; + class CTeamsCore m_TeamsPredicted; }; diff --git a/src/game/client/prediction/entities/character.cpp b/src/game/client/prediction/entities/character.cpp new file mode 100644 index 000000000..1b54fe0dd --- /dev/null +++ b/src/game/client/prediction/entities/character.cpp @@ -0,0 +1,1187 @@ +/* (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 "character.h" +#include "projectile.h" +#include "laser.h" + +#include +#include + +// Character, "physical" player's part + +void CCharacter::SetWeapon(int W) +{ + if(W == m_Core.m_ActiveWeapon) + return; + + m_LastWeapon = m_Core.m_ActiveWeapon; + m_QueuedWeapon = -1; + m_Core.m_ActiveWeapon = W; + + if(m_Core.m_ActiveWeapon < 0 || m_Core.m_ActiveWeapon >= NUM_WEAPONS) + m_Core.m_ActiveWeapon = 0; +} + +void CCharacter::SetSolo(bool Solo) +{ + TeamsCore()->SetSolo(GetCID(), Solo); +} + +bool CCharacter::IsGrounded() +{ + if(Collision()->CheckPoint(m_Pos.x+m_ProximityRadius/2, m_Pos.y+m_ProximityRadius/2+5)) + return true; + if(Collision()->CheckPoint(m_Pos.x-m_ProximityRadius/2, m_Pos.y+m_ProximityRadius/2+5)) + return true; + + int index = Collision()->GetPureMapIndex(vec2(m_Pos.x, m_Pos.y+m_ProximityRadius/2+4)); + int tile = Collision()->GetTileIndex(index); + int flags = Collision()->GetTileFlags(index); + if(tile == TILE_STOPA || (tile == TILE_STOP && flags == ROTATION_0) || (tile ==TILE_STOPS && (flags == ROTATION_0 || flags == ROTATION_180))) + return true; + tile = Collision()->GetFTileIndex(index); + flags = Collision()->GetFTileFlags(index); + if(tile == TILE_STOPA || (tile == TILE_STOP && flags == ROTATION_0) || (tile ==TILE_STOPS && (flags == ROTATION_0 || flags == ROTATION_180))) + return true; + + return false; +} + +void CCharacter::HandleJetpack() +{ + if(m_NumInputs < 2) + return; + + vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY)); + + bool FullAuto = false; + if(m_Core.m_ActiveWeapon == WEAPON_GRENADE || m_Core.m_ActiveWeapon == WEAPON_SHOTGUN || m_Core.m_ActiveWeapon == WEAPON_RIFLE) + FullAuto = true; + if (m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN) + FullAuto = true; + + // check if we gonna fire + bool WillFire = false; + if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses) + WillFire = true; + + if(FullAuto && (m_LatestInput.m_Fire&1) && m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + WillFire = true; + + if(!WillFire) + return; + + // check for ammo + if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + { + return; + } + + switch(m_Core.m_ActiveWeapon) + { + case WEAPON_GUN: + { + if (m_Jetpack) + { + float Strength = m_LastJetpackStrength; + TakeDamage(Direction * -1.0f * (Strength / 100.0f / 6.11f), g_pData->m_Weapons.m_Hammer.m_pBase->m_Damage, GetCID(), m_Core.m_ActiveWeapon); + } + } + } +} + +void CCharacter::RemoveNinja() +{ + m_Ninja.m_CurrentMoveTime = 0; + m_aWeapons[WEAPON_NINJA].m_Got = false; + m_Core.m_ActiveWeapon = m_LastWeapon; + + SetWeapon(m_Core.m_ActiveWeapon); +} + +void CCharacter::HandleNinja() +{ + if(m_Core.m_ActiveWeapon != WEAPON_NINJA) + return; + + if ((GameWorld()->GameTick() - m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * GameWorld()->GameTickSpeed() / 1000)) + { + // time's up, return + RemoveNinja(); + return; + } + + // force ninja Weapon + SetWeapon(WEAPON_NINJA); + + m_Ninja.m_CurrentMoveTime--; + + if (m_Ninja.m_CurrentMoveTime == 0) + { + // reset velocity + m_Core.m_Vel = m_Ninja.m_ActivationDir*m_Ninja.m_OldVelAmount; + } + + if (m_Ninja.m_CurrentMoveTime > 0) + { + // Set velocity + m_Core.m_Vel = m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity; + vec2 OldPos = m_Pos; + Collision()->MoveBox(&m_Core.m_Pos, &m_Core.m_Vel, vec2(m_ProximityRadius, m_ProximityRadius), 0.f); + + // reset velocity so the client doesn't predict stuff + m_Core.m_Vel = vec2(0.f, 0.f); + + // check if we Hit anything along the way + { + CCharacter *aEnts[MAX_CLIENTS]; + vec2 Dir = m_Pos - OldPos; + float Radius = m_ProximityRadius * 2.0f; + vec2 Center = OldPos + Dir * 0.5f; + int Num = GameWorld()->FindEntities(Center, Radius, (CEntity**)aEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER); + + // check that we're not in solo part + if (TeamsCore()->GetSolo(GetCID())) + return; + + for (int i = 0; i < Num; ++i) + { + if (aEnts[i] == this) + continue; + + // Don't hit players in other teams + if (Team() != aEnts[i]->Team()) + continue; + + // Don't hit players in solo parts + if (TeamsCore()->GetSolo(aEnts[i]->GetCID())) + return; + + // make sure we haven't Hit this object before + bool bAlreadyHit = false; + for (int j = 0; j < m_NumObjectsHit; j++) + { + if (m_aHitObjects[j] == aEnts[i]->GetCID()) + bAlreadyHit = true; + } + if (bAlreadyHit) + continue; + + // check so we are sufficiently close + if (distance(aEnts[i]->m_Pos, m_Pos) > (m_ProximityRadius * 2.0f)) + continue; + + // Hit a player, give him damage and stuffs... + // set his velocity to fast upward (for now) + if(m_NumObjectsHit < 10) + m_aHitObjects[m_NumObjectsHit++] = aEnts[i]->GetCID(); + + CCharacter *pChar = GameWorld()->GetCharacterByID(aEnts[i]->GetCID()); + if(pChar) + pChar->TakeDamage(vec2(0, -10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, GetCID(), WEAPON_NINJA); + } + } + + return; + } + + return; +} + +void CCharacter::DoWeaponSwitch() +{ + // make sure we can switch + if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_aWeapons[WEAPON_NINJA].m_Got) + return; + + // switch Weapon + SetWeapon(m_QueuedWeapon); +} + +void CCharacter::HandleWeaponSwitch() +{ + if(m_NumInputs < 2) + return; + + int WantedWeapon = m_Core.m_ActiveWeapon; + if(m_QueuedWeapon != -1) + WantedWeapon = m_QueuedWeapon; + + bool Anything = false; + for(int i = 0; i < NUM_WEAPONS - 1; ++i) + if(m_aWeapons[i].m_Got) + Anything = true; + if(!Anything) + return; + // select Weapon + int Next = CountInput(m_LatestPrevInput.m_NextWeapon, m_LatestInput.m_NextWeapon).m_Presses; + int Prev = CountInput(m_LatestPrevInput.m_PrevWeapon, m_LatestInput.m_PrevWeapon).m_Presses; + + if(Next < 128) // make sure we only try sane stuff + { + while(Next) // Next Weapon selection + { + WantedWeapon = (WantedWeapon+1)%NUM_WEAPONS; + if(m_aWeapons[WantedWeapon].m_Got) + Next--; + } + } + + if(Prev < 128) // make sure we only try sane stuff + { + while(Prev) // Prev Weapon selection + { + WantedWeapon = (WantedWeapon-1)<0?NUM_WEAPONS-1:WantedWeapon-1; + if(m_aWeapons[WantedWeapon].m_Got) + Prev--; + } + } + + // Direct Weapon selection + if(m_LatestInput.m_WantedWeapon) + WantedWeapon = m_Input.m_WantedWeapon-1; + + // check for insane values + if(WantedWeapon >= 0 && WantedWeapon < NUM_WEAPONS && WantedWeapon != m_Core.m_ActiveWeapon && m_aWeapons[WantedWeapon].m_Got) + m_QueuedWeapon = WantedWeapon; + + DoWeaponSwitch(); +} + +void CCharacter::FireWeapon() +{ + if(m_NumInputs < 2) + return; + + if(!GameWorld()->m_WorldConfig.m_PredictWeapons) + return; + + if(m_ReloadTimer != 0) + return; + + DoWeaponSwitch(); + vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY)); + + bool FullAuto = false; + if(m_Core.m_ActiveWeapon == WEAPON_GRENADE || m_Core.m_ActiveWeapon == WEAPON_SHOTGUN || m_Core.m_ActiveWeapon == WEAPON_RIFLE) + FullAuto = true; + if (m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN) + FullAuto = true; + + // don't fire non auto weapons when player is deep and sv_deepfly is disabled + if(!g_Config.m_SvDeepfly && !FullAuto && m_DeepFreeze) + return; + + // check if we gonna fire + bool WillFire = false; + if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses) + WillFire = true; + + if(FullAuto && (m_LatestInput.m_Fire&1) && m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + WillFire = true; + + if(!WillFire) + return; + + // check for ammo + if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo) + { + return; + } + + vec2 ProjStartPos = m_Pos+Direction*m_ProximityRadius*0.75f; + + switch(m_Core.m_ActiveWeapon) + { + case WEAPON_HAMMER: + { + // reset objects Hit + m_NumObjectsHit = 0; + + if (m_Hit&DISABLE_HIT_HAMMER) break; + + CCharacter *apEnts[MAX_CLIENTS]; + int Hits = 0; + int Num = GameWorld()->FindEntities(ProjStartPos, m_ProximityRadius*0.5f, (CEntity**)apEnts, + MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER); + + for (int i = 0; i < Num; ++i) + { + CCharacter *pTarget = apEnts[i]; + + if((pTarget == this || (pTarget->IsAlive() && !CanCollide(pTarget->GetCID())))) + continue; + + // set his velocity to fast upward (for now) + + vec2 Dir; + if (length(pTarget->m_Pos - m_Pos) > 0.0f) + Dir = normalize(pTarget->m_Pos - m_Pos); + else + Dir = vec2(0.f, -1.f); + + float Strength = Tuning()->m_HammerStrength; + + vec2 Temp = pTarget->m_Core.m_Vel + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f; + pTarget->Core()->LimitForce(&Temp); + Temp -= pTarget->m_Core.m_Vel; + + vec2 Force = vec2(0.f, -1.0f) + Temp; + + if(GameWorld()->m_WorldConfig.m_IsFNG) + { + if(m_GameTeam == pTarget->m_GameTeam && pTarget->m_LastSnapWeapon == WEAPON_NINJA) // melt hammer + { + Force.x *= 50 * 0.01f; + Force.y *= 50 * 0.01f; + } + else + { + Force.x *= 320 * 0.01f; + Force.y *= 120 * 0.01f; + } + } + else + Force *= Strength; + + pTarget->TakeDamage(Force, g_pData->m_Weapons.m_Hammer.m_pBase->m_Damage, + GetCID(), m_Core.m_ActiveWeapon); + pTarget->UnFreeze(); + + Hits++; + } + + // if we Hit anything, we have to wait for the reload + if(Hits) + m_ReloadTimer = GameWorld()->GameTickSpeed()/3; + + } break; + + case WEAPON_GUN: + { + if (!m_Jetpack) + { + int Lifetime; + Lifetime = (int)(GameWorld()->GameTickSpeed()*Tuning()->m_GunLifetime); + new CProjectile + ( + GameWorld(), + WEAPON_GUN,//Type + GetCID(),//Owner + ProjStartPos,//Pos + Direction,//Dir + Lifetime,//Span + 0,//Freeze + 0,//Explosive + 0,//Force + -1,//SoundImpact + WEAPON_GUN//Weapon + ); + } + } break; + + case WEAPON_SHOTGUN: + { + if(GameWorld()->m_WorldConfig.m_IsVanilla) + { + + int ShotSpread = 2; + for(int i = -ShotSpread; i <= ShotSpread; ++i) + { + float Spreading[] = {-0.185f, -0.070f, 0, 0.070f, 0.185f}; + float a = GetAngle(Direction); + a += Spreading[i+2]; + float v = 1-(absolute(i)/(float)ShotSpread); + float Speed = mix((float)GameWorld()->Tuning()->m_ShotgunSpeeddiff, 1.0f, v); + new CProjectile + ( + GameWorld(), + WEAPON_SHOTGUN,//Type + GetCID(),//Owner + ProjStartPos,//Pos + vec2(cosf(a), sinf(a))*Speed,//Dir + (int)(GameWorld()->GameTickSpeed()*GameWorld()->Tuning()->m_ShotgunLifetime),//Span + 0,//Freeze + 0,//Explosive + 0,//Force + -1,//SoundImpact + WEAPON_SHOTGUN//Weapon + ); + } + } + else if(GameWorld()->m_WorldConfig.m_IsDDRace) + { + float LaserReach = Tuning()->m_LaserReach; + new CLaser(GameWorld(), m_Pos, Direction, LaserReach, GetCID(), WEAPON_SHOTGUN); + } + } break; + + case WEAPON_GRENADE: + { + int Lifetime = (int)(GameWorld()->GameTickSpeed()*Tuning()->m_GrenadeLifetime); + new CProjectile + ( + GameWorld(), + WEAPON_GRENADE,//Type + GetCID(),//Owner + ProjStartPos,//Pos + Direction,//Dir + Lifetime,//Span + 0,//Freeze + true,//Explosive + 0,//Force + SOUND_GRENADE_EXPLODE,//SoundImpact + WEAPON_GRENADE//Weapon + );//SoundImpact + } break; + + case WEAPON_RIFLE: + { + float LaserReach = Tuning()->m_LaserReach; + new CLaser(GameWorld(), m_Pos, Direction, LaserReach, GetCID(), WEAPON_RIFLE); + } break; + + case WEAPON_NINJA: + { + // reset Hit objects + m_NumObjectsHit = 0; + + m_Ninja.m_ActivationDir = Direction; + m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * GameWorld()->GameTickSpeed() / 1000; + m_Ninja.m_OldVelAmount = length(m_Core.m_Vel); + } break; + + } + + if(!m_ReloadTimer) + { + float FireDelay; + Tuning()->Get(38 + m_Core.m_ActiveWeapon, &FireDelay); + m_ReloadTimer = FireDelay * GameWorld()->GameTickSpeed() / 1000; + } +} + +void CCharacter::HandleWeapons() +{ + //ninja + HandleNinja(); + HandleJetpack(); + + // check reload timer + if(m_ReloadTimer) + { + m_ReloadTimer--; + return; + } + + // fire Weapon, if wanted + FireWeapon(); + + return; +} + +bool CCharacter::GiveWeapon(int Weapon, int Ammo) +{ + if(m_aWeapons[Weapon].m_Ammo < g_pData->m_Weapons.m_aId[Weapon].m_Maxammo || !m_aWeapons[Weapon].m_Got) + { + m_aWeapons[Weapon].m_Got = true; + if(!m_FreezeTime) + m_aWeapons[Weapon].m_Ammo = min(g_pData->m_Weapons.m_aId[Weapon].m_Maxammo, Ammo); + return true; + } + return false; +} + +void CCharacter::GiveNinja() +{ + m_Ninja.m_ActivationTick = GameWorld()->GameTick(); + m_aWeapons[WEAPON_NINJA].m_Got = true; + if (!m_FreezeTime) + m_aWeapons[WEAPON_NINJA].m_Ammo = -1; + if (m_Core.m_ActiveWeapon != WEAPON_NINJA) + m_LastWeapon = m_Core.m_ActiveWeapon; + m_Core.m_ActiveWeapon = WEAPON_NINJA; +} + +void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput) +{ + // copy new input + mem_copy(&m_SavedInput, pNewInput, sizeof(m_SavedInput)); + mem_copy(&m_Input, pNewInput, sizeof(m_Input)); + //m_NumInputs++; + + // it is not allowed to aim in the center + if(m_Input.m_TargetX == 0 && m_Input.m_TargetY == 0) + m_Input.m_TargetY = -1; +} + +void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput) +{ + m_NumInputs++; + mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput)); + mem_copy(&m_LatestInput, pNewInput, sizeof(m_LatestInput)); + + // it is not allowed to aim in the center + if(m_LatestInput.m_TargetX == 0 && m_LatestInput.m_TargetY == 0) + m_LatestInput.m_TargetY = -1; + + if(m_NumInputs > 2 && Team() != TEAM_SPECTATORS) + { + HandleWeaponSwitch(); + FireWeapon(); + } + + mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput)); +} + +void CCharacter::Tick() +{ + DDRaceTick(); + + m_Core.m_Input = m_Input; + m_Core.Tick(true); + + // handle Weapons + HandleWeapons(); + + DDRacePostCoreTick(); + + // Previnput + m_PrevInput = m_Input; + + m_PrevPos = m_Core.m_Pos; + return; +} + +void CCharacter::TickDefered() +{ + m_Core.Move(); + m_Core.Quantize(); + m_Pos = m_Core.m_Pos; +} + +bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon) +{ + m_Core.ApplyForce(Force); + return true; +} + +// DDRace + +bool CCharacter::CanCollide(int ClientID) +{ + return TeamsCore()->CanCollide(GetCID(), ClientID); +} + +bool CCharacter::SameTeam(int ClientID) +{ + return TeamsCore()->SameTeam(GetCID(), ClientID); +} + +int CCharacter::Team() +{ + return TeamsCore()->Team(GetCID()); +} + +void CCharacter::HandleSkippableTiles(int Index) +{ + if(Index < 0) + return; + + // handle speedup tiles + if(Collision()->IsSpeedup(Index)) + { + vec2 Direction, MaxVel, TempVel = m_Core.m_Vel; + int Force, MaxSpeed = 0; + float TeeAngle, SpeederAngle, DiffAngle, SpeedLeft, TeeSpeed; + Collision()->GetSpeedup(Index, &Direction, &Force, &MaxSpeed); + if(Force == 255 && MaxSpeed) + { + m_Core.m_Vel = Direction * (MaxSpeed/5); + } + else + { + if(MaxSpeed > 0 && MaxSpeed < 5) MaxSpeed = 5; + if(MaxSpeed > 0) + { + if(Direction.x > 0.0000001f) + SpeederAngle = -atan(Direction.y / Direction.x); + else if(Direction.x < 0.0000001f) + SpeederAngle = atan(Direction.y / Direction.x) + 2.0f * asin(1.0f); + else if(Direction.y > 0.0000001f) + SpeederAngle = asin(1.0f); + else + SpeederAngle = asin(-1.0f); + + if(SpeederAngle < 0) + SpeederAngle = 4.0f * asin(1.0f) + SpeederAngle; + + if(TempVel.x > 0.0000001f) + TeeAngle = -atan(TempVel.y / TempVel.x); + else if(TempVel.x < 0.0000001f) + TeeAngle = atan(TempVel.y / TempVel.x) + 2.0f * asin(1.0f); + else if(TempVel.y > 0.0000001f) + TeeAngle = asin(1.0f); + else + TeeAngle = asin(-1.0f); + + if(TeeAngle < 0) + TeeAngle = 4.0f * asin(1.0f) + TeeAngle; + + TeeSpeed = sqrt(pow(TempVel.x, 2) + pow(TempVel.y, 2)); + + DiffAngle = SpeederAngle - TeeAngle; + SpeedLeft = MaxSpeed / 5.0f - cos(DiffAngle) * TeeSpeed; + if(abs((int)SpeedLeft) > Force && SpeedLeft > 0.0000001f) + TempVel += Direction * Force; + else if(abs((int)SpeedLeft) > Force) + TempVel += Direction * -Force; + else + TempVel += Direction * SpeedLeft; + } + else + TempVel += Direction * Force; + m_Core.LimitForce(&TempVel); + m_Core.m_Vel = TempVel; + } + } +} + +void CCharacter::HandleTiles(int Index) +{ + int MapIndex = Index; + float Offset = 4.0f; + int MapIndexL = Collision()->GetPureMapIndex(vec2(m_Pos.x + (m_ProximityRadius / 2) + Offset, m_Pos.y)); + int MapIndexR = Collision()->GetPureMapIndex(vec2(m_Pos.x - (m_ProximityRadius / 2) - Offset, m_Pos.y)); + int MapIndexT = Collision()->GetPureMapIndex(vec2(m_Pos.x, m_Pos.y + (m_ProximityRadius / 2) + Offset)); + int MapIndexB = Collision()->GetPureMapIndex(vec2(m_Pos.x, m_Pos.y - (m_ProximityRadius / 2) - Offset)); + m_TileIndex = Collision()->GetTileIndex(MapIndex); + m_TileFlags = Collision()->GetTileFlags(MapIndex); + m_TileIndexL = Collision()->GetTileIndex(MapIndexL); + m_TileFlagsL = Collision()->GetTileFlags(MapIndexL); + m_TileIndexR = Collision()->GetTileIndex(MapIndexR); + m_TileFlagsR = Collision()->GetTileFlags(MapIndexR); + m_TileIndexB = Collision()->GetTileIndex(MapIndexB); + m_TileFlagsB = Collision()->GetTileFlags(MapIndexB); + m_TileIndexT = Collision()->GetTileIndex(MapIndexT); + m_TileFlagsT = Collision()->GetTileFlags(MapIndexT); + m_TileFIndex = Collision()->GetFTileIndex(MapIndex); + m_TileFFlags = Collision()->GetFTileFlags(MapIndex); + m_TileFIndexL = Collision()->GetFTileIndex(MapIndexL); + m_TileFFlagsL = Collision()->GetFTileFlags(MapIndexL); + m_TileFIndexR = Collision()->GetFTileIndex(MapIndexR); + m_TileFFlagsR = Collision()->GetFTileFlags(MapIndexR); + m_TileFIndexB = Collision()->GetFTileIndex(MapIndexB); + m_TileFFlagsB = Collision()->GetFTileFlags(MapIndexB); + m_TileFIndexT = Collision()->GetFTileIndex(MapIndexT); + m_TileFFlagsT = Collision()->GetFTileFlags(MapIndexT);// + m_TileSIndex = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndex)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileIndex(MapIndex) : 0 : 0; + m_TileSFlags = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndex)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileFlags(MapIndex) : 0 : 0; + m_TileSIndexL = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndexL)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileIndex(MapIndexL) : 0 : 0; + m_TileSFlagsL = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndexL)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileFlags(MapIndexL) : 0 : 0; + m_TileSIndexR = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndexR)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileIndex(MapIndexR) : 0 : 0; + m_TileSFlagsR = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndexR)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileFlags(MapIndexR) : 0 : 0; + m_TileSIndexB = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndexB)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileIndex(MapIndexB) : 0 : 0; + m_TileSFlagsB = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndexB)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileFlags(MapIndexB) : 0 : 0; + m_TileSIndexT = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndexT)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileIndex(MapIndexT) : 0 : 0; + m_TileSFlagsT = (Collision()->m_pSwitchers && Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndexT)].m_Status[Team()])?(Team() != TEAM_SUPER)? Collision()->GetDTileFlags(MapIndexT) : 0 : 0; + if(Index < 0) + { + m_LastRefillJumps = false; + return; + } + + // freeze + if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Super && !m_DeepFreeze) + { + Freeze(); + } + else if(((m_TileIndex == TILE_UNFREEZE) || (m_TileFIndex == TILE_UNFREEZE)) && !m_DeepFreeze) + { + UnFreeze(); + } + + // deep freeze + if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Super && !m_DeepFreeze) + { + m_DeepFreeze = true; + } + else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Super && m_DeepFreeze) + { + m_DeepFreeze = false; + } + + // endless hook + if(((m_TileIndex == TILE_EHOOK_START) || (m_TileFIndex == TILE_EHOOK_START)) && !m_EndlessHook) + { + m_EndlessHook = true; + } + else if(((m_TileIndex == TILE_EHOOK_END) || (m_TileFIndex == TILE_EHOOK_END)) && m_EndlessHook) + { + m_EndlessHook = false; + } + + // collide with others + if(((m_TileIndex == TILE_NPC_END) || (m_TileFIndex == TILE_NPC_END)) && m_Core.m_Collision) + { + m_Core.m_Collision = false; + } + else if(((m_TileIndex == TILE_NPC_START) || (m_TileFIndex == TILE_NPC_START)) && !m_Core.m_Collision) + { + m_Core.m_Collision = true; + } + + // hook others + if(((m_TileIndex == TILE_NPH_END) || (m_TileFIndex == TILE_NPH_END)) && m_Core.m_Hook) + { + m_Core.m_Hook = false; + } + else if(((m_TileIndex == TILE_NPH_START) || (m_TileFIndex == TILE_NPH_START)) && !m_Core.m_Hook) + { + m_Core.m_Hook = true; + } + + // unlimited air jumps + if(((m_TileIndex == TILE_SUPER_START) || (m_TileFIndex == TILE_SUPER_START)) && !m_SuperJump) + { + m_SuperJump = true; + } + else if(((m_TileIndex == TILE_SUPER_END) || (m_TileFIndex == TILE_SUPER_END)) && m_SuperJump) + { + m_SuperJump = false; + } + + // walljump + if((m_TileIndex == TILE_WALLJUMP) || (m_TileFIndex == TILE_WALLJUMP)) + { + if(m_Core.m_Vel.y > 0 && m_Core.m_Colliding && m_Core.m_LeftWall) + { + m_Core.m_LeftWall = false; + m_Core.m_JumpedTotal = m_Core.m_Jumps - 1; + m_Core.m_Jumped = 1; + } + } + + // jetpack gun + if(((m_TileIndex == TILE_JETPACK_START) || (m_TileFIndex == TILE_JETPACK_START)) && !m_Jetpack) + { + m_Jetpack = true; + } + else if(((m_TileIndex == TILE_JETPACK_END) || (m_TileFIndex == TILE_JETPACK_END)) && m_Jetpack) + { + m_Jetpack = false; + } + + // solo part + if(((m_TileIndex == TILE_SOLO_START) || (m_TileFIndex == TILE_SOLO_START)) && !TeamsCore()->GetSolo(GetCID())) + { + SetSolo(true); + } + else if(((m_TileIndex == TILE_SOLO_END) || (m_TileFIndex == TILE_SOLO_END)) && TeamsCore()->GetSolo(GetCID())) + { + SetSolo(false); + } + + // refill jumps + if(((m_TileIndex == TILE_REFILL_JUMPS) || (m_TileFIndex == TILE_REFILL_JUMPS)) && !m_LastRefillJumps) + { + m_Core.m_JumpedTotal = 0; + m_Core.m_Jumped = 0; + m_LastRefillJumps = true; + } + if((m_TileIndex != TILE_REFILL_JUMPS) && (m_TileFIndex != TILE_REFILL_JUMPS)) + { + m_LastRefillJumps = false; + } + + // stopper + if(((m_TileIndex == TILE_STOP && m_TileFlags == ROTATION_270) || (m_TileIndexL == TILE_STOP && m_TileFlagsL == ROTATION_270) || (m_TileIndexL == TILE_STOPS && (m_TileFlagsL == ROTATION_90 || m_TileFlagsL ==ROTATION_270)) || (m_TileIndexL == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_270) || (m_TileFIndexL == TILE_STOP && m_TileFFlagsL == ROTATION_270) || (m_TileFIndexL == TILE_STOPS && (m_TileFFlagsL == ROTATION_90 || m_TileFFlagsL == ROTATION_270)) || (m_TileFIndexL == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_270) || (m_TileSIndexL == TILE_STOP && m_TileSFlagsL == ROTATION_270) || (m_TileSIndexL == TILE_STOPS && (m_TileSFlagsL == ROTATION_90 || m_TileSFlagsL == ROTATION_270)) || (m_TileSIndexL == TILE_STOPA)) && m_Core.m_Vel.x > 0) + { + if((int)Collision()->GetPos(MapIndexL).x) + if((int)Collision()->GetPos(MapIndexL).x < (int)m_Core.m_Pos.x) + m_Core.m_Pos = m_PrevPos; + m_Core.m_Vel.x = 0; + } + if(((m_TileIndex == TILE_STOP && m_TileFlags == ROTATION_90) || (m_TileIndexR == TILE_STOP && m_TileFlagsR == ROTATION_90) || (m_TileIndexR == TILE_STOPS && (m_TileFlagsR == ROTATION_90 || m_TileFlagsR == ROTATION_270)) || (m_TileIndexR == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_90) || (m_TileFIndexR == TILE_STOP && m_TileFFlagsR == ROTATION_90) || (m_TileFIndexR == TILE_STOPS && (m_TileFFlagsR == ROTATION_90 || m_TileFFlagsR == ROTATION_270)) || (m_TileFIndexR == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_90) || (m_TileSIndexR == TILE_STOP && m_TileSFlagsR == ROTATION_90) || (m_TileSIndexR == TILE_STOPS && (m_TileSFlagsR == ROTATION_90 || m_TileSFlagsR == ROTATION_270)) || (m_TileSIndexR == TILE_STOPA)) && m_Core.m_Vel.x < 0) + { + if((int)Collision()->GetPos(MapIndexR).x) + if((int)Collision()->GetPos(MapIndexR).x > (int)m_Core.m_Pos.x) + m_Core.m_Pos = m_PrevPos; + m_Core.m_Vel.x = 0; + } + if(((m_TileIndex == TILE_STOP && m_TileFlags == ROTATION_180) || (m_TileIndexB == TILE_STOP && m_TileFlagsB == ROTATION_180) || (m_TileIndexB == TILE_STOPS && (m_TileFlagsB == ROTATION_0 || m_TileFlagsB == ROTATION_180)) || (m_TileIndexB == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_180) || (m_TileFIndexB == TILE_STOP && m_TileFFlagsB == ROTATION_180) || (m_TileFIndexB == TILE_STOPS && (m_TileFFlagsB == ROTATION_0 || m_TileFFlagsB == ROTATION_180)) || (m_TileFIndexB == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_180) || (m_TileSIndexB == TILE_STOP && m_TileSFlagsB == ROTATION_180) || (m_TileSIndexB == TILE_STOPS && (m_TileSFlagsB == ROTATION_0 || m_TileSFlagsB == ROTATION_180)) || (m_TileSIndexB == TILE_STOPA)) && m_Core.m_Vel.y < 0) + { + if((int)Collision()->GetPos(MapIndexB).y) + if((int)Collision()->GetPos(MapIndexB).y > (int)m_Core.m_Pos.y) + m_Core.m_Pos = m_PrevPos; + m_Core.m_Vel.y = 0; + } + if(((m_TileIndex == TILE_STOP && m_TileFlags == ROTATION_0) || (m_TileIndexT == TILE_STOP && m_TileFlagsT == ROTATION_0) || (m_TileIndexT == TILE_STOPS && (m_TileFlagsT == ROTATION_0 || m_TileFlagsT == ROTATION_180)) || (m_TileIndexT == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_0) || (m_TileFIndexT == TILE_STOP && m_TileFFlagsT == ROTATION_0) || (m_TileFIndexT == TILE_STOPS && (m_TileFFlagsT == ROTATION_0 || m_TileFFlagsT == ROTATION_180)) || (m_TileFIndexT == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_0) || (m_TileSIndexT == TILE_STOP && m_TileSFlagsT == ROTATION_0) || (m_TileSIndexT == TILE_STOPS && (m_TileSFlagsT == ROTATION_0 || m_TileSFlagsT == ROTATION_180)) || (m_TileSIndexT == TILE_STOPA)) && m_Core.m_Vel.y > 0) + { + if((int)Collision()->GetPos(MapIndexT).y) + if((int)Collision()->GetPos(MapIndexT).y < (int)m_Core.m_Pos.y) + m_Core.m_Pos = m_PrevPos; + m_Core.m_Vel.y = 0; + m_Core.m_Jumped = 0; + m_Core.m_JumpedTotal = 0; + } + + // handle switch tiles + if(Collision()->IsSwitch(MapIndex) == TILE_SWITCHOPEN && Team() != TEAM_SUPER) + { + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = true; + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = 0; + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHOPEN; + } + else if(Collision()->IsSwitch(MapIndex) == TILE_SWITCHTIMEDOPEN && Team() != TEAM_SUPER) + { + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = true; + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = GameWorld()->GameTick() + 1 + Collision()->GetSwitchDelay(MapIndex)*GameWorld()->GameTickSpeed() ; + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHTIMEDOPEN; + } + else if(Collision()->IsSwitch(MapIndex) == TILE_SWITCHTIMEDCLOSE && Team() != TEAM_SUPER) + { + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = false; + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = GameWorld()->GameTick() + 1 + Collision()->GetSwitchDelay(MapIndex)*GameWorld()->GameTickSpeed(); + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHTIMEDCLOSE; + } + else if(Collision()->IsSwitch(MapIndex) == TILE_SWITCHCLOSE && Team() != TEAM_SUPER) + { + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = false; + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = 0; + Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHCLOSE; + } + else if(Collision()->IsSwitch(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER) + { + if(Collision()->GetSwitchNumber(MapIndex) == 0 || Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()]) + Freeze(Collision()->GetSwitchDelay(MapIndex)); + } + else if(Collision()->IsSwitch(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER && Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()]) + { + m_DeepFreeze = true; + } + else if(Collision()->IsSwitch(MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER && Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()]) + { + m_DeepFreeze = false; + } + else if(Collision()->IsSwitch(MapIndex) == TILE_JUMP) + { + int newJumps = Collision()->GetSwitchDelay(MapIndex); + + if (newJumps != m_Core.m_Jumps) + { + m_Core.m_Jumps = newJumps; + } + } +} + +void CCharacter::DDRaceTick() +{ + if(!GameWorld()->m_WorldConfig.m_PredictTiles || !GameWorld()->m_WorldConfig.m_PredictWeapons) + return; + mem_copy(&m_Input, &m_SavedInput, sizeof(m_Input)); + if(m_FreezeTime > 0 || m_FreezeTime == -1) + { + if(m_FreezeTime > 0) + m_FreezeTime--; + else + m_Ninja.m_ActivationTick = GameWorld()->GameTick(); + m_Input.m_Direction = 0; + m_Input.m_Jump = 0; + m_Input.m_Hook = 0; + if (m_FreezeTime == 1) + UnFreeze(); + } +} + +void CCharacter::DDRacePostCoreTick() +{ + if(!GameWorld()->m_WorldConfig.m_PredictTiles) + return; + + if (m_EndlessHook) + m_Core.m_HookTick = 0; + + if (m_DeepFreeze && !m_Super) + Freeze(); + + if (m_Core.m_Jumps == 0 && !m_Super) + m_Core.m_Jumped = 3; + else if (m_Core.m_Jumps == 1 && m_Core.m_Jumped > 0) + m_Core.m_Jumped = 3; + else if (m_Core.m_JumpedTotal < m_Core.m_Jumps - 1 && m_Core.m_Jumped > 1) + m_Core.m_Jumped = 1; + + if ((m_Super || m_SuperJump) && m_Core.m_Jumped > 1) + m_Core.m_Jumped = 1; + + int CurrentIndex = Collision()->GetMapIndex(m_Pos); + HandleSkippableTiles(CurrentIndex); + + // handle Anti-Skip tiles + std::list < int > Indices = Collision()->GetMapIndices(m_PrevPos, m_Pos); + if(!Indices.empty()) + for(std::list < int >::iterator i = Indices.begin(); i != Indices.end(); i++) + HandleTiles(*i); + else + { + HandleTiles(CurrentIndex); + } +} + +bool CCharacter::Freeze(int Seconds) +{ + if(!GameWorld()->m_WorldConfig.m_PredictFreeze) + return false; + if ((Seconds <= 0 || m_Super || m_FreezeTime == -1 || m_FreezeTime > Seconds * GameWorld()->GameTickSpeed()) && Seconds != -1) + return false; + if (m_FreezeTick < GameWorld()->GameTick() - GameWorld()->GameTickSpeed() || Seconds == -1) + { + for(int i = 0; i < NUM_WEAPONS; i++) + if(m_aWeapons[i].m_Got) + { + m_aWeapons[i].m_Ammo = 0; + } + m_FreezeTime = Seconds == -1 ? Seconds : Seconds * GameWorld()->GameTickSpeed(); + m_FreezeTick = GameWorld()->GameTick(); + return true; + } + return false; +} + +bool CCharacter::Freeze() +{ + return Freeze(g_Config.m_SvFreezeDelay); +} + +bool CCharacter::UnFreeze() +{ + if (m_FreezeTime > 0) + { + for(int i=0;im_X, pChar->m_Y); + m_PrevPos = m_Pos; + m_Core.Reset(); + m_Core.Init(&GameWorld()->m_Core, GameWorld()->Collision(), GameWorld()->Teams()); + m_Core.m_Id = ID; + SetSolo(false); + m_EndlessHook = false; + m_Hit = HIT_ALL; + m_SuperJump = false; + m_Jetpack = false; + m_Core.m_Jumps = 2; + mem_zero(&m_Ninja, sizeof(m_Ninja)); + mem_zero(&m_SavedInput, sizeof(m_SavedInput)); + m_LatestInput = m_LatestPrevInput = m_PrevInput = m_Input = m_SavedInput; + m_ProximityRadius = ms_PhysSize; + m_Core.m_LeftWall = 1; + m_NumInputs = 0; + m_FreezeTime = 0; + m_FreezeTick = 0; + m_DeepFreeze = 0; + m_ReloadTimer = 0; + //GiveAllWeapons(); + //GiveWeapon(WEAPON_HAMMER, -1); + m_NumObjectsHit = 0; + m_LastRefillJumps = false; + m_LastJetpackStrength = 400.0; + m_Super = false; + m_Alive = true; + + Read(pChar, pExtended, false); + + GameWorld()->InsertEntity(this); +} + +void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal) +{ + vec2 PosBefore = m_Pos; + + m_Core.Read((CNetObj_CharacterCore*) pChar); + m_Pos = m_Core.m_Pos; + + if(distance(PosBefore, m_Pos) > 2.f) // misprediction, don't use prevpos + m_PrevPos = m_Pos; + + if(distance(m_PrevPos, m_Pos) > 10.f * 32.f) // reset prevpos if the distance is high + m_PrevPos = m_Pos; + + // remove weapons that are unavailable. if the current weapon is ninja just set ammo to zero in case the player is frozen + if(pChar->m_Weapon != m_Core.m_ActiveWeapon) + { + if(pChar->m_Weapon == WEAPON_NINJA) + m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo = 0; + else + { + if(m_Core.m_ActiveWeapon == WEAPON_NINJA) + { + SetNinjaActivationDir(vec2(0,0)); + SetNinjaActivationTick(-500); + SetNinjaCurrentMoveTime(0); + } + if(pChar->m_Weapon == m_LastSnapWeapon) + m_aWeapons[m_Core.m_ActiveWeapon].m_Got = false; + } + } + m_LastSnapWeapon = pChar->m_Weapon; + + // add weapons + if(pChar->m_Weapon != WEAPON_NINJA) + { + m_aWeapons[pChar->m_Weapon].m_Got = true; + m_aWeapons[pChar->m_Weapon].m_Ammo = (GameWorld()->m_WorldConfig.m_InfiniteAmmo || GameWorld()->m_WorldConfig.m_IsDDRace || pChar->m_Weapon == WEAPON_HAMMER) ? -1 : pChar->m_AmmoCount; + SetActiveWeapon(pChar->m_Weapon); + } + + if(!pExtended) + { + if(pChar->m_Emote != EMOTE_PAIN && pChar->m_Emote != EMOTE_NORMAL) + m_DeepFreeze = false; + if(pChar->m_Weapon != WEAPON_NINJA || pChar->m_AttackTick > m_FreezeTick || absolute(pChar->m_VelX) == 256*10) + { + m_DeepFreeze = false; + UnFreeze(); + } + } + if(!GameWorld()->m_WorldConfig.m_PredictFreeze) + { + m_DeepFreeze = false; + UnFreeze(); + } + + if(GameWorld()->m_WorldConfig.m_PredictWeapons && Tuning()->m_JetpackStrength > 0) + { + m_LastJetpackStrength = Tuning()->m_JetpackStrength; + m_Jetpack = true; + } + else if(pChar->m_Weapon != WEAPON_NINJA) + m_Jetpack = false; + + if(GameWorld()->m_WorldConfig.m_PredictTiles) + { + if(pChar->m_Jumped&2) + { + m_SuperJump = false; + if(m_Core.m_Jumps > m_Core.m_JumpedTotal && m_Core.m_JumpedTotal > 0 && m_Core.m_Jumps > 2) + m_Core.m_Jumps = m_Core.m_JumpedTotal + 1; + else + m_Core.m_JumpedTotal = m_Core.m_Jumps; + } + else + { + if(m_Core.m_Jumps < 2) + m_Core.m_Jumps = m_Core.m_JumpedTotal + 2; + } + if(Tuning()->m_AirJumpImpulse == 0) + { + m_Core.m_Jumps = 0; + m_Core.m_Jumped = 3; + } + } + + if(m_Core.m_HookTick != 0) + m_EndlessHook = false; + + // reset player collision + if(!pExtended) + SetSolo(!Tuning()->m_PlayerCollision && !Tuning()->m_PlayerHooking); + m_Core.m_Collision = Tuning()->m_PlayerCollision; + m_Core.m_Hook = Tuning()->m_PlayerHooking; + + // reset all input except direction and hook for non-local players (as in vanilla prediction) + if(!IsLocal) + { + mem_zero(&m_Input, sizeof(m_Input)); + mem_zero(&m_SavedInput, sizeof(m_SavedInput)); + m_Input.m_Direction = m_SavedInput.m_Direction = m_Core.m_Direction; + m_Input.m_Hook = m_SavedInput.m_Hook = (m_Core.m_HookState == HOOK_GRABBED); + m_Input.m_TargetX = cosf(pChar->m_Angle/256.0f); + m_Input.m_TargetY = sinf(pChar->m_Angle/256.0f); + } + + m_Alive = true; + + if(pExtended) + { + m_Super = pExtended->m_Flags & CHARACTERFLAG_SUPER; + + SetSolo(pExtended->m_Flags & CHARACTERFLAG_SOLO); + m_Super = pExtended->m_Flags & CHARACTERFLAG_SUPER; + if(m_Super) + TeamsCore()->Team(GetCID(), TEAM_SUPER); + m_EndlessHook = pExtended->m_Flags & CHARACTERFLAG_ENDLESS_HOOK; + m_Core.m_Collision = !(pExtended->m_Flags & CHARACTERFLAG_NO_COLLISION); + m_Core.m_Hook = !(pExtended->m_Flags & CHARACTERFLAG_NO_HOOK); + m_SuperJump = pExtended->m_Flags & CHARACTERFLAG_ENDLESS_JUMP; + m_Jetpack = pExtended->m_Flags & CHARACTERFLAG_JETPACK; + + if(GameWorld()->m_WorldConfig.m_PredictFreeze) + m_DeepFreeze = pExtended->m_Flags & CHARACTERFLAG_DEEP_FROZEN; + + bool Frozen = pExtended->m_Flags & CHARACTERFLAG_FROZEN; + if(m_FreezeTime > 0 && !Frozen) + UnFreeze(); + + m_Hit = HIT_ALL; + if(pExtended->m_Flags & CHARACTERFLAG_NO_GRENADE_HIT) + m_Hit |= DISABLE_HIT_GRENADE; + if(pExtended->m_Flags & CHARACTERFLAG_NO_HAMMER_HIT) + m_Hit |= DISABLE_HIT_HAMMER; + if(pExtended->m_Flags & CHARACTERFLAG_NO_RIFLE_HIT) + m_Hit |= DISABLE_HIT_RIFLE; + if(pExtended->m_Flags & CHARACTERFLAG_NO_SHOTGUN_HIT) + m_Hit |= DISABLE_HIT_SHOTGUN; + } +} + +void CCharacter::SetCoreWorld(CGameWorld *pGameWorld) +{ + m_Core.m_pWorld = &pGameWorld->m_Core; + m_Core.m_pCollision = pGameWorld->Collision(); + m_Core.m_pTeams = pGameWorld->Teams(); +} + +bool CCharacter::Match(CCharacter *pChar) +{ + if(distance(pChar->m_Core.m_Pos, m_Core.m_Pos) > 32.f) + return false; + return true; +} diff --git a/src/game/client/prediction/entities/character.h b/src/game/client/prediction/entities/character.h new file mode 100644 index 000000000..b081258a9 --- /dev/null +++ b/src/game/client/prediction/entities/character.h @@ -0,0 +1,212 @@ +/* (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_PREDICTION_ENTITIES_CHARACTER_H +#define GAME_CLIENT_PREDICTION_ENTITIES_CHARACTER_H + +#include +#include "projectile.h" + +#include +#include + +enum +{ + WEAPON_GAME = -3, // team switching etc + WEAPON_SELF = -2, // console kill command + WEAPON_WORLD = -1, // death tiles etc +}; + +enum +{ + FAKETUNE_FREEZE = 1, + FAKETUNE_SOLO = 2, + FAKETUNE_NOJUMP = 4, + FAKETUNE_NOCOLL = 8, + FAKETUNE_NOHOOK = 16, + FAKETUNE_JETPACK = 32, + FAKETUNE_NOHAMMER = 64, + +}; + +class CCharacter : public CEntity +{ + friend class CGameWorld; +public: + //character's size + static const int ms_PhysSize = 28; + + virtual void Tick(); + virtual void TickDefered(); + + bool IsGrounded(); + + void SetWeapon(int W); + void SetSolo(bool Solo); + void HandleWeaponSwitch(); + void DoWeaponSwitch(); + + void HandleWeapons(); + void HandleNinja(); + void HandleJetpack(); + + void OnPredictedInput(CNetObj_PlayerInput *pNewInput); + void OnDirectInput(CNetObj_PlayerInput *pNewInput); + void FireWeapon(); + + bool TakeDamage(vec2 Force, int Dmg, int From, int Weapon); + + bool GiveWeapon(int Weapon, int Ammo); + void GiveNinja(); + void RemoveNinja(); + + bool IsAlive() { return m_Alive; } + + bool m_Alive; + + CTeamsCore* TeamsCore(); + bool Freeze(int Time); + bool Freeze(); + bool UnFreeze(); + void GiveAllWeapons(); + int Team(); + bool CanCollide(int ClientID); + bool SameTeam(int ClientID); + bool m_Super; + bool m_SuperJump; + bool m_Jetpack; + int m_FreezeTime; + int m_FreezeTick; + bool m_DeepFreeze; + bool m_EndlessHook; + enum + { + HIT_ALL=0, + DISABLE_HIT_HAMMER=1, + DISABLE_HIT_SHOTGUN=2, + DISABLE_HIT_GRENADE=4, + DISABLE_HIT_RIFLE=8 + }; + int m_Hit; + vec2 m_PrevPos; + + int m_TileIndex; + int m_TileFlags; + int m_TileFIndex; + int m_TileFFlags; + int m_TileSIndex; + int m_TileSFlags; + int m_TileIndexL; + int m_TileFlagsL; + int m_TileFIndexL; + int m_TileFFlagsL; + int m_TileSIndexL; + int m_TileSFlagsL; + int m_TileIndexR; + int m_TileFlagsR; + int m_TileFIndexR; + int m_TileFFlagsR; + int m_TileSIndexR; + int m_TileSFlagsR; + int m_TileIndexT; + int m_TileFlagsT; + int m_TileFIndexT; + int m_TileFFlagsT; + int m_TileSIndexT; + int m_TileSFlagsT; + int m_TileIndexB; + int m_TileFlagsB; + int m_TileFIndexB; + int m_TileFFlagsB; + int m_TileSIndexB; + int m_TileSFlagsB; + bool m_LastRefillJumps; + + // Setters/Getters because i don't want to modify vanilla vars access modifiers + int GetLastWeapon() { return m_LastWeapon; }; + void SetLastWeapon(int LastWeap) {m_LastWeapon = LastWeap; }; + int GetActiveWeapon() { return m_Core.m_ActiveWeapon; }; + void SetActiveWeapon(int ActiveWeap) {m_Core.m_ActiveWeapon = ActiveWeap; }; + CCharacterCore GetCore() { return m_Core; }; + void SetCore(CCharacterCore Core) {m_Core = Core; }; + CCharacterCore* Core() { return &m_Core; }; + bool GetWeaponGot(int Type) { return m_aWeapons[Type].m_Got; }; + void SetWeaponGot(int Type, bool Value) { m_aWeapons[Type].m_Got = Value; }; + int GetWeaponAmmo(int Type) { return m_aWeapons[Type].m_Ammo; }; + void SetWeaponAmmo(int Type, int Value) { m_aWeapons[Type].m_Ammo = Value; }; + void SetNinjaActivationDir(vec2 ActivationDir) { m_Ninja.m_ActivationDir = ActivationDir; }; + void SetNinjaActivationTick(int ActivationTick) { m_Ninja.m_ActivationTick = ActivationTick; }; + void SetNinjaCurrentMoveTime(int CurrentMoveTime) { m_Ninja.m_CurrentMoveTime = CurrentMoveTime; }; + int GetCID() { return m_ID; } + void SetInput(CNetObj_PlayerInput *pNewInput) { m_LatestInput = m_Input = *pNewInput; }; + int GetJumped() { return m_Core.m_Jumped; } + + CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended = 0); + void Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal); + void SetCoreWorld(CGameWorld *pGameWorld); + + int m_LastSnapWeapon; + int m_LastJetpackStrength; + bool m_KeepHooked; + int m_GameTeam; + + bool Match(CCharacter *pChar); + CCharacter() { m_Alive = false; } + +private: + // weapon info + int m_aHitObjects[10]; + int m_NumObjectsHit; + + struct WeaponStat + { + int m_AmmoRegenStart; + int m_Ammo; + int m_Ammocost; + bool m_Got; + } m_aWeapons[NUM_WEAPONS]; + + int m_LastWeapon; + int m_QueuedWeapon; + + int m_ReloadTimer; + + // these are non-heldback inputs + CNetObj_PlayerInput m_LatestPrevInput; + CNetObj_PlayerInput m_LatestInput; + + // input + CNetObj_PlayerInput m_PrevInput; + CNetObj_PlayerInput m_Input; + CNetObj_PlayerInput m_SavedInput; + + int m_NumInputs; + + // ninja + struct NinjaStat + { + vec2 m_ActivationDir; + int m_ActivationTick; + int m_CurrentMoveTime; + int m_OldVelAmount; + } m_Ninja; + + // the player core for the physics + CCharacterCore m_Core; + + // DDRace + + void HandleTiles(int Index); + void HandleSkippableTiles(int Index); + void DDRaceTick(); + void DDRacePostCoreTick(); +}; + +enum +{ + DDRACE_NONE = 0, + DDRACE_STARTED, + DDRACE_CHEAT, // no time and won't start again unless ordered by a mod or death + DDRACE_FINISHED +}; + +#endif diff --git a/src/game/client/prediction/entities/laser.cpp b/src/game/client/prediction/entities/laser.cpp new file mode 100644 index 000000000..02b0fba44 --- /dev/null +++ b/src/game/client/prediction/entities/laser.cpp @@ -0,0 +1,204 @@ +/* (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 "character.h" +#include "laser.h" + +#include + +CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEnergy, int Owner, int Type) +: CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER) +{ + m_Pos = Pos; + m_Owner = Owner; + m_Energy = StartEnergy; + if(pGameWorld->m_WorldConfig.m_IsFNG && m_Energy < 10.f) + m_Energy = 800.0f; + m_Dir = Direction; + m_Bounces = 0; + m_EvalTick = 0; + m_TelePos = vec2(0,0); + m_WasTele = false; + m_Type = Type; + GameWorld()->InsertEntity(this); + DoBounce(); +} + +bool CLaser::HitCharacter(vec2 From, vec2 To) +{ + vec2 At; + CCharacter *pOwnerChar = GameWorld()->GetCharacterByID(m_Owner); + CCharacter *pHit; + bool pDontHitSelf = g_Config.m_SvOldLaser || (m_Bounces == 0 && !m_WasTele); + + if(pOwnerChar ? (!(pOwnerChar->m_Hit&CCharacter::DISABLE_HIT_RIFLE) && m_Type == WEAPON_RIFLE) || (!(pOwnerChar->m_Hit&CCharacter::DISABLE_HIT_SHOTGUN) && m_Type == WEAPON_SHOTGUN) : g_Config.m_SvHit) + pHit = GameWorld()->IntersectCharacter(m_Pos, To, 0.f, At, pDontHitSelf ? pOwnerChar : 0, m_Owner); + else + pHit = GameWorld()->IntersectCharacter(m_Pos, To, 0.f, At, pDontHitSelf ? pOwnerChar : 0, m_Owner, pOwnerChar); + + if(!pHit || (pHit == pOwnerChar && g_Config.m_SvOldLaser) || (pHit != pOwnerChar && pOwnerChar ? (pOwnerChar->m_Hit&CCharacter::DISABLE_HIT_RIFLE && m_Type == WEAPON_RIFLE) || (pOwnerChar->m_Hit&CCharacter::DISABLE_HIT_SHOTGUN && m_Type == WEAPON_SHOTGUN) : !g_Config.m_SvHit)) + return false; + m_From = From; + m_Pos = At; + m_Energy = -1; + if (m_Type == WEAPON_SHOTGUN) + { + vec2 Temp; + + float Strength = GameWorld()->Tuning()->m_ShotgunStrength;; + + if(!g_Config.m_SvOldLaser) + Temp = pHit->Core()->m_Vel + normalize(m_PrevPos - pHit->Core()->m_Pos) * Strength; + else + Temp = pHit->Core()->m_Vel + normalize(pOwnerChar->Core()->m_Pos - pHit->Core()->m_Pos) * Strength; + if(Temp.x > 0 && ((pHit->m_TileIndex == TILE_STOP && pHit->m_TileFlags == ROTATION_270) || (pHit->m_TileIndexL == TILE_STOP && pHit->m_TileFlagsL == ROTATION_270) || (pHit->m_TileIndexL == TILE_STOPS && (pHit->m_TileFlagsL == ROTATION_90 || pHit->m_TileFlagsL ==ROTATION_270)) || (pHit->m_TileIndexL == TILE_STOPA) || (pHit->m_TileFIndex == TILE_STOP && pHit->m_TileFFlags == ROTATION_270) || (pHit->m_TileFIndexL == TILE_STOP && pHit->m_TileFFlagsL == ROTATION_270) || (pHit->m_TileFIndexL == TILE_STOPS && (pHit->m_TileFFlagsL == ROTATION_90 || pHit->m_TileFFlagsL == ROTATION_270)) || (pHit->m_TileFIndexL == TILE_STOPA) || (pHit->m_TileSIndex == TILE_STOP && pHit->m_TileSFlags == ROTATION_270) || (pHit->m_TileSIndexL == TILE_STOP && pHit->m_TileSFlagsL == ROTATION_270) || (pHit->m_TileSIndexL == TILE_STOPS && (pHit->m_TileSFlagsL == ROTATION_90 || pHit->m_TileSFlagsL == ROTATION_270)) || (pHit->m_TileSIndexL == TILE_STOPA))) + Temp.x = 0; + if(Temp.x < 0 && ((pHit->m_TileIndex == TILE_STOP && pHit->m_TileFlags == ROTATION_90) || (pHit->m_TileIndexR == TILE_STOP && pHit->m_TileFlagsR == ROTATION_90) || (pHit->m_TileIndexR == TILE_STOPS && (pHit->m_TileFlagsR == ROTATION_90 || pHit->m_TileFlagsR == ROTATION_270)) || (pHit->m_TileIndexR == TILE_STOPA) || (pHit->m_TileFIndex == TILE_STOP && pHit->m_TileFFlags == ROTATION_90) || (pHit->m_TileFIndexR == TILE_STOP && pHit->m_TileFFlagsR == ROTATION_90) || (pHit->m_TileFIndexR == TILE_STOPS && (pHit->m_TileFFlagsR == ROTATION_90 || pHit->m_TileFFlagsR == ROTATION_270)) || (pHit->m_TileFIndexR == TILE_STOPA) || (pHit->m_TileSIndex == TILE_STOP && pHit->m_TileSFlags == ROTATION_90) || (pHit->m_TileSIndexR == TILE_STOP && pHit->m_TileSFlagsR == ROTATION_90) || (pHit->m_TileSIndexR == TILE_STOPS && (pHit->m_TileSFlagsR == ROTATION_90 || pHit->m_TileSFlagsR == ROTATION_270)) || (pHit->m_TileSIndexR == TILE_STOPA))) + Temp.x = 0; + if(Temp.y < 0 && ((pHit->m_TileIndex == TILE_STOP && pHit->m_TileFlags == ROTATION_180) || (pHit->m_TileIndexB == TILE_STOP && pHit->m_TileFlagsB == ROTATION_180) || (pHit->m_TileIndexB == TILE_STOPS && (pHit->m_TileFlagsB == ROTATION_0 || pHit->m_TileFlagsB == ROTATION_180)) || (pHit->m_TileIndexB == TILE_STOPA) || (pHit->m_TileFIndex == TILE_STOP && pHit->m_TileFFlags == ROTATION_180) || (pHit->m_TileFIndexB == TILE_STOP && pHit->m_TileFFlagsB == ROTATION_180) || (pHit->m_TileFIndexB == TILE_STOPS && (pHit->m_TileFFlagsB == ROTATION_0 || pHit->m_TileFFlagsB == ROTATION_180)) || (pHit->m_TileFIndexB == TILE_STOPA) || (pHit->m_TileSIndex == TILE_STOP && pHit->m_TileSFlags == ROTATION_180) || (pHit->m_TileSIndexB == TILE_STOP && pHit->m_TileSFlagsB == ROTATION_180) || (pHit->m_TileSIndexB == TILE_STOPS && (pHit->m_TileSFlagsB == ROTATION_0 || pHit->m_TileSFlagsB == ROTATION_180)) || (pHit->m_TileSIndexB == TILE_STOPA))) + Temp.y = 0; + if(Temp.y > 0 && ((pHit->m_TileIndex == TILE_STOP && pHit->m_TileFlags == ROTATION_0) || (pHit->m_TileIndexT == TILE_STOP && pHit->m_TileFlagsT == ROTATION_0) || (pHit->m_TileIndexT == TILE_STOPS && (pHit->m_TileFlagsT == ROTATION_0 || pHit->m_TileFlagsT == ROTATION_180)) || (pHit->m_TileIndexT == TILE_STOPA) || (pHit->m_TileFIndex == TILE_STOP && pHit->m_TileFFlags == ROTATION_0) || (pHit->m_TileFIndexT == TILE_STOP && pHit->m_TileFFlagsT == ROTATION_0) || (pHit->m_TileFIndexT == TILE_STOPS && (pHit->m_TileFFlagsT == ROTATION_0 || pHit->m_TileFFlagsT == ROTATION_180)) || (pHit->m_TileFIndexT == TILE_STOPA) || (pHit->m_TileSIndex == TILE_STOP && pHit->m_TileSFlags == ROTATION_0) || (pHit->m_TileSIndexT == TILE_STOP && pHit->m_TileSFlagsT == ROTATION_0) || (pHit->m_TileSIndexT == TILE_STOPS && (pHit->m_TileSFlagsT == ROTATION_0 || pHit->m_TileSFlagsT == ROTATION_180)) || (pHit->m_TileSIndexT == TILE_STOPA))) + Temp.y = 0; + pHit->Core()->m_Vel = Temp; + } + else if (m_Type == WEAPON_RIFLE) + { + pHit->UnFreeze(); + } + return true; +} + +void CLaser::DoBounce() +{ + m_EvalTick = GameWorld()->GameTick(); + + if(m_Energy < 0) + { + GameWorld()->DestroyEntity(this); + return; + } + m_PrevPos = m_Pos; + vec2 Coltile; + + 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); + + if(Res) + { + if(!HitCharacter(m_Pos, To)) + { + // intersected + m_From = m_Pos; + m_Pos = To; + + vec2 TempPos = m_Pos; + vec2 TempDir = m_Dir * 4.0f; + + int f = 0; + if(Res == -1) + { + f = Collision()->GetTile(round_to_int(Coltile.x), round_to_int(Coltile.y)); + Collision()->SetCollisionAt(round_to_int(Coltile.x), round_to_int(Coltile.y), TILE_SOLID); + } + Collision()->MovePoint(&TempPos, &TempDir, 1.0f, 0); + if(Res == -1) + { + Collision()->SetCollisionAt(round_to_int(Coltile.x), round_to_int(Coltile.y), f); + } + m_Pos = TempPos; + m_Dir = normalize(TempDir); + + m_Energy -= distance(m_From, m_Pos) + GameWorld()->Tuning()->m_LaserBounceCost; + + m_Bounces++; + m_WasTele = false; + + int BounceNum = GameWorld()->Tuning()->m_LaserBounceNum; + + if(m_Bounces > BounceNum) + m_Energy = -1; + } + } + else + { + if(!HitCharacter(m_Pos, To)) + { + m_From = m_Pos; + m_Pos = To; + m_Energy = -1; + } + } +} + +void CLaser::Tick() +{ + float Delay = GameWorld()->Tuning()->m_LaserBounceDelay; + + if(GameWorld()->m_WorldConfig.m_IsVanilla) // predict old physics on vanilla 0.6 servers + { + if(GameWorld()->GameTick() > m_EvalTick+(GameWorld()->GameTickSpeed()*Delay/1000.0f)) + DoBounce(); + } + else + { + if((GameWorld()->GameTick() - m_EvalTick) > (GameWorld()->GameTickSpeed()*Delay/1000.0f)) + DoBounce(); + } +} + +CLaser::CLaser(CGameWorld *pGameWorld, int ID, CNetObj_Laser *pLaser) +: CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER) +{ + m_Pos.x = pLaser->m_X; + m_Pos.y = pLaser->m_Y; + m_From.x = pLaser->m_FromX; + m_From.y = pLaser->m_FromY; + m_EvalTick = pLaser->m_StartTick; + m_Owner = -2; + m_Energy = pGameWorld->Tuning()->m_LaserReach; + if(pGameWorld->m_WorldConfig.m_IsFNG && m_Energy < 10.f) + m_Energy = 800.0f; + + m_Dir = m_Pos - m_From; + if(length(m_Pos - m_From) > 0.001) + m_Dir = normalize(m_Dir); + else + m_Energy = 0; + m_Type = WEAPON_RIFLE; + m_PrevPos = m_From; + m_ID = ID; +} + +void CLaser::FillInfo(CNetObj_Laser *pLaser) +{ + pLaser->m_X = (int)m_Pos.x; + pLaser->m_Y = (int)m_Pos.y; + pLaser->m_FromX = (int)m_From.x; + pLaser->m_FromY = (int)m_From.y; + pLaser->m_StartTick = m_EvalTick; +} + +bool CLaser::Match(CLaser *pLaser) +{ + if(pLaser->m_EvalTick != m_EvalTick) + return false; + if(distance(pLaser->m_From, m_From) > 2.f) + return false; + const vec2 ThisDiff = m_Pos - m_From; + const vec2 OtherDiff = pLaser->m_Pos - pLaser->m_From; + const float DirError = distance(normalize(OtherDiff)*length(ThisDiff), ThisDiff); + if(DirError > 2.f) + return false; + return true; +} diff --git a/src/game/client/prediction/entities/laser.h b/src/game/client/prediction/entities/laser.h new file mode 100644 index 000000000..d1017eb3e --- /dev/null +++ b/src/game/client/prediction/entities/laser.h @@ -0,0 +1,43 @@ +/* (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_PREDICTION_ENTITIES_LASER_H +#define GAME_CLIENT_PREDICTION_ENTITIES_LASER_H + +#include + +class CLaser : public CEntity +{ + friend class CGameWorld; +public: + CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEnergy, int Owner, int Type); + + virtual void Tick(); + + const vec2 &GetFrom() { return m_From; } + const int &GetOwner() { return m_Owner; } + const int &GetEvalTick() { return m_EvalTick; } + CLaser(CGameWorld *pGameWorld, int ID, CNetObj_Laser *pLaser); + void FillInfo(CNetObj_Laser *pLaser); + bool Match(CLaser *pLaser); + +protected: + bool HitCharacter(vec2 From, vec2 To); + void DoBounce(); + +private: + vec2 m_From; + vec2 m_Dir; + vec2 m_TelePos; + bool m_WasTele; + float m_Energy; + int m_Bounces; + int m_EvalTick; + int m_Owner; + + // DDRace + + vec2 m_PrevPos; + int m_Type; +}; + +#endif diff --git a/src/game/client/prediction/entities/pickup.cpp b/src/game/client/prediction/entities/pickup.cpp new file mode 100644 index 000000000..aedef585f --- /dev/null +++ b/src/game/client/prediction/entities/pickup.cpp @@ -0,0 +1,108 @@ +/* (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 "character.h" +#include "pickup.h" + +void CPickup::Tick() +{ + Move(); + // Check if a player intersected us + CCharacter *apEnts[MAX_CLIENTS]; + int Num = GameWorld()->FindEntities(m_Pos, 20.0f, (CEntity**)apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER); + for(int i = 0; i < Num; ++i) { + CCharacter * pChr = apEnts[i]; + if(pChr && pChr->IsAlive()) + { + if(m_Layer == LAYER_SWITCH && !Collision()->m_pSwitchers[m_Number].m_Status[pChr->Team()]) continue; + bool sound = false; + // player picked us up, is someone was hooking us, let them go + switch (m_Type) + { + case POWERUP_HEALTH: + //pChr->Freeze(); + break; + + case POWERUP_ARMOR: + if(pChr->Team() == TEAM_SUPER) continue; + for(int i = WEAPON_SHOTGUN; i < NUM_WEAPONS; i++) + { + if(pChr->GetWeaponGot(i)) + { + if(!(pChr->m_FreezeTime && i == WEAPON_NINJA)) + { + pChr->SetWeaponGot(i, false); + pChr->SetWeaponAmmo(i, 0); + sound = true; + } + } + } + pChr->SetNinjaActivationDir(vec2(0,0)); + pChr->SetNinjaActivationTick(-500); + pChr->SetNinjaCurrentMoveTime(0); + if (sound) + pChr->SetLastWeapon(WEAPON_GUN); + if(!pChr->m_FreezeTime && pChr->GetActiveWeapon() >= WEAPON_SHOTGUN) + pChr->SetActiveWeapon(WEAPON_HAMMER); + break; + + case POWERUP_WEAPON: + + if(m_Subtype >= 0 && m_Subtype < NUM_WEAPONS && (!pChr->GetWeaponGot(m_Subtype) || (pChr->GetWeaponAmmo(m_Subtype) != -1 && !pChr->m_FreezeTime))) + pChr->GiveWeapon(m_Subtype, -1); + break; + + case POWERUP_NINJA: + { + // activate ninja on target player + pChr->GiveNinja(); + break; + } + default: + break; + }; + } + } +} + +void CPickup::Move() +{ + if (GameWorld()->GameTick()%int(GameWorld()->GameTickSpeed() * 0.15f) == 0) + { + int Flags; + int index = Collision()->IsMover(m_Pos.x,m_Pos.y, &Flags); + if (index) + { + m_Core=Collision()->CpSpeed(index, Flags); + } + m_Pos += m_Core; + } +} + +CPickup::CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup) +: CEntity(pGameWorld, CGameWorld::ENTTYPE_PICKUP) +{ + m_Pos.x = pPickup->m_X; + m_Pos.y = pPickup->m_Y; + m_Type = pPickup->m_Type; + m_Subtype = pPickup->m_Subtype; + m_Core = vec2(0.f, 0.f); + m_ID = ID; +} + +void CPickup::FillInfo(CNetObj_Pickup *pPickup) +{ + pPickup->m_X = (int)m_Pos.x; + pPickup->m_Y = (int)m_Pos.y; + pPickup->m_Type = m_Type; + pPickup->m_Subtype = m_Subtype; +} + +bool CPickup::Match(CPickup *pPickup) +{ + if(pPickup->m_Type != m_Type || pPickup->m_Subtype != m_Subtype) + return false; + if(distance(pPickup->m_Pos, m_Pos) > 2.0f) + return false; + return true; +} diff --git a/src/game/client/prediction/entities/pickup.h b/src/game/client/prediction/entities/pickup.h new file mode 100644 index 000000000..5041a6cd1 --- /dev/null +++ b/src/game/client/prediction/entities/pickup.h @@ -0,0 +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. */ +#ifndef GAME_CLIENT_PREDICTION_ENTITIES_PICKUP_H +#define GAME_CLIENT_PREDICTION_ENTITIES_PICKUP_H + +#include + +const int PickupPhysSize = 14; + +class CPickup : public CEntity +{ +public: + virtual void Tick(); + + CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup); + void FillInfo(CNetObj_Pickup *pPickup); + bool Match(CPickup *pPickup); + +private: + int m_Type; + int m_Subtype; + + // DDRace + + void Move(); + vec2 m_Core; +}; + +#endif diff --git a/src/game/client/prediction/entities/projectile.cpp b/src/game/client/prediction/entities/projectile.cpp new file mode 100644 index 000000000..688b16928 --- /dev/null +++ b/src/game/client/prediction/entities/projectile.cpp @@ -0,0 +1,243 @@ +/* (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 "projectile.h" + +#include + +CProjectile::CProjectile + ( + CGameWorld *pGameWorld, + int Type, + int Owner, + vec2 Pos, + vec2 Dir, + int Span, + bool Freeze, + bool Explosive, + float Force, + int SoundImpact, + int Weapon, + int Layer, + int Number + ) +: CEntity(pGameWorld, CGameWorld::ENTTYPE_PROJECTILE) +{ + m_Type = Type; + m_Pos = Pos; + m_Direction = Dir; + m_LifeSpan = Span; + m_Owner = Owner; + m_Force = Force; + m_SoundImpact = SoundImpact; + m_Weapon = Weapon; + m_StartTick = GameWorld()->GameTick(); + m_Explosive = Explosive; + + m_Layer = Layer; + m_Number = Number; + m_Freeze = Freeze; + + GameWorld()->InsertEntity(this); +} + +vec2 CProjectile::GetPos(float Time) +{ + float Curvature = 0; + float Speed = 0; + + switch(m_Type) + { + case WEAPON_GRENADE: + Curvature = Tuning()->m_GrenadeCurvature; + Speed = Tuning()->m_GrenadeSpeed; + break; + + case WEAPON_SHOTGUN: + Curvature = Tuning()->m_ShotgunCurvature; + Speed = Tuning()->m_ShotgunSpeed; + break; + + case WEAPON_GUN: + Curvature = Tuning()->m_GunCurvature; + Speed = Tuning()->m_GunSpeed; + break; + } + + return CalcPos(m_Pos, m_Direction, Curvature, Speed, Time); +} + + +void CProjectile::Tick() +{ + float Pt = (GameWorld()->GameTick()-m_StartTick-1)/(float)GameWorld()->GameTickSpeed(); + float Ct = (GameWorld()->GameTick()-m_StartTick)/(float)GameWorld()->GameTickSpeed(); + vec2 PrevPos = GetPos(Pt); + vec2 CurPos = GetPos(Ct); + vec2 ColPos; + vec2 NewPos; + int Collide = Collision()->IntersectLine(PrevPos, CurPos, &ColPos, &NewPos); + CCharacter *pOwnerChar = GameWorld()->GetCharacterByID(m_Owner); + + CCharacter *pTargetChr = GameWorld()->IntersectCharacter(PrevPos, ColPos, m_Freeze ? 1.0f : 6.0f, ColPos, pOwnerChar, m_Owner); + + if(m_LifeSpan > -1) + m_LifeSpan--; + + int64_t TeamMask = -1LL; + bool isWeaponCollide = false; + if + ( + pOwnerChar && + pTargetChr && + pOwnerChar->IsAlive() && + pTargetChr->IsAlive() && + !pTargetChr->CanCollide(m_Owner) + ) + { + isWeaponCollide = true; + } + + if( ((pTargetChr && (pOwnerChar ? !(pOwnerChar->m_Hit&CCharacter::DISABLE_HIT_GRENADE) : g_Config.m_SvHit || m_Owner == -1 || pTargetChr == pOwnerChar)) || Collide || GameLayerClipped(CurPos)) && !isWeaponCollide) + { + if(m_Explosive && (!pTargetChr || (pTargetChr && (!m_Freeze || (m_Weapon == WEAPON_SHOTGUN && Collide))))) + { + GameWorld()->CreateExplosion(ColPos, m_Owner, m_Weapon, m_Owner == -1, (!pTargetChr ? -1 : pTargetChr->Team()), + (m_Owner != -1)? TeamMask : -1LL); + } + else if(pTargetChr && m_Freeze && ((m_Layer == LAYER_SWITCH && Collision()->m_pSwitchers[m_Number].m_Status[pTargetChr->Team()]) || m_Layer != LAYER_SWITCH)) + pTargetChr->Freeze(); + if(Collide && m_Bouncing != 0) + { + m_StartTick = GameWorld()->GameTick(); + m_Pos = NewPos+(-(m_Direction*4)); + if (m_Bouncing == 1) + m_Direction.x = -m_Direction.x; + else if(m_Bouncing == 2) + m_Direction.y = -m_Direction.y; + if (fabs(m_Direction.x) < 1e-6) + m_Direction.x = 0; + if (fabs(m_Direction.y) < 1e-6) + m_Direction.y = 0; + m_Pos += m_Direction; + } + else if (m_Weapon == WEAPON_GUN) + { + GameWorld()->DestroyEntity(this); + } + else + if (!m_Freeze) + GameWorld()->DestroyEntity(this); + } + if(m_LifeSpan == -1) + { + if(m_Explosive) + { + if(m_Owner >= 0) + pOwnerChar = GameWorld()->GetCharacterByID(m_Owner); + + int64_t TeamMask = -1LL; + + GameWorld()->CreateExplosion(ColPos, m_Owner, m_Weapon, m_Owner == -1, (!pOwnerChar ? -1 : pOwnerChar->Team()), + (m_Owner != -1)? TeamMask : -1LL); + } + GameWorld()->DestroyEntity(this); + } +} + +// DDRace + +void CProjectile::SetBouncing(int Value) +{ + m_Bouncing = Value; +} + +CProjectile::CProjectile(CGameWorld *pGameWorld, int ID, CNetObj_Projectile *pProj) +: CEntity(pGameWorld, CGameWorld::ENTTYPE_PROJECTILE) +{ + ExtractInfo(pProj, &m_Pos, &m_Direction); + if(UseExtraInfo(pProj)) + ExtractExtraInfo(pProj, &m_Owner, &m_Explosive, &m_Bouncing, &m_Freeze); + else + { + m_Owner = -1; + m_Bouncing = m_Freeze = 0; + m_Explosive = (pProj->m_Type == WEAPON_GRENADE) && (fabs(1.0f - length(m_Direction)) < 0.015f); + } + m_Type = m_Weapon = pProj->m_Type; + m_StartTick = pProj->m_StartTick; + + int Lifetime = 20 * GameWorld()->GameTickSpeed(); + m_SoundImpact = -1; + if(m_Weapon == WEAPON_GRENADE) + { + Lifetime = pGameWorld->Tuning()->m_GrenadeLifetime * GameWorld()->GameTickSpeed(); + m_SoundImpact = SOUND_GRENADE_EXPLODE; + } + else if(m_Weapon == WEAPON_GUN) + Lifetime = pGameWorld->Tuning()->m_GunLifetime * GameWorld()->GameTickSpeed(); + else if(m_Weapon == WEAPON_SHOTGUN && !GameWorld()->m_WorldConfig.m_IsDDRace) + Lifetime = pGameWorld->Tuning()->m_ShotgunLifetime * GameWorld()->GameTickSpeed(); + m_LifeSpan = Lifetime - (pGameWorld->GameTick() - m_StartTick); + m_ID = ID; +} + +void CProjectile::FillInfo(CNetObj_Projectile *pProj) +{ + pProj->m_X = (int)m_Pos.x; + pProj->m_Y = (int)m_Pos.y; + pProj->m_VelX = (int)(m_Direction.x*100.0f); + pProj->m_VelY = (int)(m_Direction.y*100.0f); + pProj->m_StartTick = m_StartTick; + pProj->m_Type = m_Type; +} + +void CProjectile::FillExtraInfo(CNetObj_Projectile *pProj) +{ + const int MaxPos = 0x7fffffff/100; + if(abs((int)m_Pos.y)+1 >= MaxPos || abs((int)m_Pos.x)+1 >= MaxPos) + { + //If the modified data would be too large to fit in an integer, send normal data instead + FillInfo(pProj); + return; + } + //Send additional/modified info, by modifiying the fields of the netobj + float Angle = -atan2f(m_Direction.x, m_Direction.y); + + int Data = 0; + Data |= (abs(m_Owner) & 255)<<0; + if(m_Owner < 0) + Data |= 1<<8; + Data |= 1<<9; //This bit tells the client to use the extra info + Data |= (m_Bouncing & 3)<<10; + if(m_Explosive) + Data |= 1<<12; + if(m_Freeze) + Data |= 1<<13; + + pProj->m_X = (int)(m_Pos.x * 100.0f); + pProj->m_Y = (int)(m_Pos.y * 100.0f); + pProj->m_VelX = (int)(Angle * 1000000.0f); + pProj->m_VelY = Data; + pProj->m_StartTick = m_StartTick; + pProj->m_Type = m_Type; +} + +bool CProjectile::Match(CProjectile *pProj) +{ + if(pProj->m_Weapon != m_Weapon) + return false; + if(pProj->m_StartTick != m_StartTick) + return false; + if(distance(pProj->m_Pos, m_Pos) > 2.f) + return false; + if(distance(pProj->m_Direction, m_Direction) > 2.f) + return false; + return true; +} + +int CProjectile::NetworkClipped(vec2 ViewPos) +{ + float Ct = (GameWorld()->GameTick()-m_StartTick)/(float)GameWorld()->GameTickSpeed(); + return ((CEntity*) this)->NetworkClipped(GetPos(Ct), ViewPos); +} diff --git a/src/game/client/prediction/entities/projectile.h b/src/game/client/prediction/entities/projectile.h new file mode 100644 index 000000000..1023138b9 --- /dev/null +++ b/src/game/client/prediction/entities/projectile.h @@ -0,0 +1,64 @@ +/* (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_PREDICTION_ENTITIES_PROJECTILE_H +#define GAME_CLIENT_PREDICTION_ENTITIES_PROJECTILE_H + +#include +#include "character.h" +#include + +class CProjectile : public CEntity +{ + friend class CGameWorld; + friend class CItems; +public: + CProjectile + ( + CGameWorld *pGameWorld, + int Type, + int Owner, + vec2 Pos, + vec2 Dir, + int Span, + bool Freeeze, + bool Explosive, + float Force, + int SoundImpact, + int Weapon, + int Layer = 0, + int Number = 0 + ); + + vec2 GetPos(float Time); + void FillInfo(CNetObj_Projectile *pProj); + + virtual void Tick(); + + bool Match(CProjectile *pProj); + void SetBouncing(int Value); + void FillExtraInfo(CNetObj_Projectile *pProj); + + const vec2 &GetDirection() { return m_Direction; } + const int &GetOwner() { return m_Owner; } + const int &GetStartTick() { return m_StartTick; } + CProjectile(CGameWorld *pGameWorld, int ID, CNetObj_Projectile *pProj); + virtual int NetworkClipped(vec2 ViewPos); + +private: + vec2 m_Direction; + int m_LifeSpan; + int m_Owner; + int m_Type; + int m_SoundImpact; + int m_Weapon; + float m_Force; + int m_StartTick; + bool m_Explosive; + + // DDRace + + int m_Bouncing; + bool m_Freeze; +}; + +#endif diff --git a/src/game/client/prediction/entity.cpp b/src/game/client/prediction/entity.cpp new file mode 100644 index 000000000..94723c6b8 --- /dev/null +++ b/src/game/client/prediction/entity.cpp @@ -0,0 +1,58 @@ +/* (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 "entity.h" + +////////////////////////////////////////////////// +// Entity +////////////////////////////////////////////////// +CEntity::CEntity(CGameWorld *pGameWorld, int ObjType) +{ + m_pGameWorld = pGameWorld; + + m_ObjType = ObjType; + m_Pos = vec2(0,0); + m_ProximityRadius = 0; + + m_MarkedForDestroy = false; + m_ID = -1; + + m_pPrevTypeEntity = 0; + m_pNextTypeEntity = 0; + m_SnapTicks = -1; + + // DDRace + m_pParent = 0; + m_DestroyTick = -1; + m_LastRenderTick = -1; +} + +CEntity::~CEntity() +{ + if(GameWorld()) + GameWorld()->RemoveEntity(this); +} + +int CEntity::NetworkClipped(vec2 ViewPos) +{ + return NetworkClipped(m_Pos, ViewPos); +} + +int CEntity::NetworkClipped(vec2 CheckPos, vec2 ViewPos) +{ + float dx = ViewPos.x-CheckPos.x; + float dy = ViewPos.y-CheckPos.y; + + if(absolute(dx) > 1000.0f || absolute(dy) > 800.0f) + return 1; + + if(distance(ViewPos, CheckPos) > 4000.0f) + return 1; + return 0; +} + +bool CEntity::GameLayerClipped(vec2 CheckPos) +{ + return round_to_int(CheckPos.x)/32 < -200 || round_to_int(CheckPos.x)/32 > Collision()->GetWidth()+200 || + round_to_int(CheckPos.y)/32 < -200 || round_to_int(CheckPos.y)/32 > Collision()->GetHeight()+200 ? true : false; +} diff --git a/src/game/client/prediction/entity.h b/src/game/client/prediction/entity.h new file mode 100644 index 000000000..46dcbc4e4 --- /dev/null +++ b/src/game/client/prediction/entity.h @@ -0,0 +1,71 @@ +/* (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_PREDICTION_ENTITY_H +#define GAME_CLIENT_PREDICTION_ENTITY_H + +#include +#include +#include "gameworld.h" + +#define MACRO_ALLOC_HEAP() \ + public: \ + void *operator new(size_t Size) \ + { \ + void *p = malloc(Size); \ + /*dbg_msg("", "++ %p %d", p, size);*/ \ + mem_zero(p, Size); \ + return p; \ + } \ + void operator delete(void *pPtr) \ + { \ + /*dbg_msg("", "-- %p", p);*/ \ + free(pPtr); \ + } \ + private: + +class CEntity +{ + MACRO_ALLOC_HEAP() + friend class CGameWorld; // entity list handling + CEntity *m_pPrevTypeEntity; + CEntity *m_pNextTypeEntity; +protected: + class CGameWorld *m_pGameWorld; + bool m_MarkedForDestroy; + int m_ID; + int m_ObjType; +public: + CEntity(CGameWorld *pGameWorld, int Objtype); + virtual ~CEntity(); + + class CGameWorld *GameWorld() { return m_pGameWorld; } + CTuningParams *Tuning() { return GameWorld()->Tuning(); } + class CCollision *Collision() { return GameWorld()->Collision(); } + CEntity *TypeNext() { return m_pNextTypeEntity; } + CEntity *TypePrev() { return m_pPrevTypeEntity; } + + virtual void Destroy() { delete this; } + virtual void Tick() {} + virtual void TickDefered() {} + + bool GameLayerClipped(vec2 CheckPos); + virtual int NetworkClipped(vec2 ViewPos); + virtual int NetworkClipped(vec2 CheckPos, vec2 ViewPos); + float m_ProximityRadius; + vec2 m_Pos; + int m_Number; + int m_Layer; + + int m_SnapTicks; + int m_DestroyTick; + int m_LastRenderTick; + CEntity *m_pParent; + CEntity *NextEntity() { return m_pNextTypeEntity; } + int ID() { return m_ID; } + void Keep() { m_SnapTicks = 0; m_MarkedForDestroy = false; } + void DetachFromGameWorld() { m_pGameWorld = 0; } + + CEntity() { m_ID = -1; m_pGameWorld = 0; } +}; + +#endif diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp new file mode 100644 index 000000000..7231879a8 --- /dev/null +++ b/src/game/client/prediction/gameworld.cpp @@ -0,0 +1,557 @@ +/* (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 "gameworld.h" +#include "entity.h" +#include "entities/character.h" +#include "entities/projectile.h" +#include "entities/laser.h" +#include "entities/pickup.h" +#include +#include +#include + +////////////////////////////////////////////////// +// game world +////////////////////////////////////////////////// +CGameWorld::CGameWorld() +{ + for(int i = 0; i < NUM_ENTTYPES; i++) + m_apFirstEntityTypes[i] = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + m_apCharacters[i] = 0; + m_pCollision = 0; + m_pTeams = 0; + m_GameTick = 0; + m_pParent = 0; + m_pChild = 0; +} + +CGameWorld::~CGameWorld() +{ + // delete all entities + for(int i = 0; i < NUM_ENTTYPES; i++) + while(m_apFirstEntityTypes[i]) + delete m_apFirstEntityTypes[i]; + if(m_pChild && m_pChild->m_pParent == this) + { + OnModified(); + m_pChild->m_pParent = 0; + } + if(m_pParent && m_pParent->m_pChild == this) + m_pParent->m_pChild = 0; +} + +CEntity *CGameWorld::FindFirst(int Type) +{ + return Type < 0 || Type >= NUM_ENTTYPES ? 0 : m_apFirstEntityTypes[Type]; +} + +CEntity *CGameWorld::FindLast(int Type) +{ + CEntity *pLast = FindFirst(Type); + if(pLast) + while(pLast->TypeNext()) + pLast = pLast->TypeNext(); + return pLast; +} + +int CGameWorld::FindEntities(vec2 Pos, float Radius, CEntity **ppEnts, int Max, int Type) +{ + if(Type < 0 || Type >= NUM_ENTTYPES) + return 0; + + int Num = 0; + for(CEntity *pEnt = m_apFirstEntityTypes[Type]; pEnt; pEnt = pEnt->m_pNextTypeEntity) + { + if(distance(pEnt->m_Pos, Pos) < Radius+pEnt->m_ProximityRadius) + { + if(ppEnts) + ppEnts[Num] = pEnt; + Num++; + if(Num == Max) + break; + } + } + + return Num; +} + +void CGameWorld::InsertEntity(CEntity *pEnt, bool Last) +{ + pEnt->m_pGameWorld = this; + pEnt->m_pNextTypeEntity = 0x0; + pEnt->m_pPrevTypeEntity = 0x0; + + // insert it + if(!Last) + { + if(m_apFirstEntityTypes[pEnt->m_ObjType]) + m_apFirstEntityTypes[pEnt->m_ObjType]->m_pPrevTypeEntity = pEnt; + pEnt->m_pNextTypeEntity = m_apFirstEntityTypes[pEnt->m_ObjType]; + pEnt->m_pPrevTypeEntity = 0x0; + m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt; + } + else + { + // insert it at the end of the list + CEntity *pLast = m_apFirstEntityTypes[pEnt->m_ObjType]; + if(pLast) + { + while(pLast->m_pNextTypeEntity) + pLast = pLast->m_pNextTypeEntity; + pLast->m_pNextTypeEntity = pEnt; + } + else + m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt; + pEnt->m_pPrevTypeEntity = pLast; + pEnt->m_pNextTypeEntity = 0x0; + } + + if(pEnt->m_ObjType == ENTTYPE_CHARACTER) + { + auto *pChar = (CCharacter*) pEnt; + int ID = pChar->GetCID(); + if(ID >= 0 && ID < MAX_CLIENTS) + { + m_apCharacters[ID] = pChar; + m_Core.m_apCharacters[ID] = pChar->Core(); + } + pChar->SetCoreWorld(this); + } +} + +void CGameWorld::DestroyEntity(CEntity *pEnt) +{ + pEnt->m_MarkedForDestroy = true; +} + +void CGameWorld::RemoveEntity(CEntity *pEnt) +{ + // not in the list + if(!pEnt->m_pNextTypeEntity && !pEnt->m_pPrevTypeEntity && m_apFirstEntityTypes[pEnt->m_ObjType] != pEnt) + return; + + // remove + if(pEnt->m_pPrevTypeEntity) + pEnt->m_pPrevTypeEntity->m_pNextTypeEntity = pEnt->m_pNextTypeEntity; + else + m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt->m_pNextTypeEntity; + if(pEnt->m_pNextTypeEntity) + pEnt->m_pNextTypeEntity->m_pPrevTypeEntity = pEnt->m_pPrevTypeEntity; + + // keep list traversing valid + if(m_pNextTraverseEntity == pEnt) + m_pNextTraverseEntity = pEnt->m_pNextTypeEntity; + + pEnt->m_pNextTypeEntity = 0; + pEnt->m_pPrevTypeEntity = 0; + + if(pEnt->m_ObjType == ENTTYPE_CHARACTER) + { + CCharacter *pChar = (CCharacter*) pEnt; + int ID = pChar->GetCID(); + if(ID >= 0 && ID < MAX_CLIENTS) + { + m_apCharacters[ID] = 0; + m_Core.m_apCharacters[ID] = 0; + } + } + pEnt->m_pParent = 0; + + if(m_IsValidCopy && m_pParent && m_pParent->m_pChild == this) + if(pEnt->m_pParent) + pEnt->m_pParent->m_DestroyTick = GameTick(); +} + +void CGameWorld::RemoveEntities() +{ + // destroy objects marked for destruction + for(int i = 0; i < NUM_ENTTYPES; i++) + for(CEntity *pEnt = m_apFirstEntityTypes[i]; pEnt; ) + { + m_pNextTraverseEntity = pEnt->m_pNextTypeEntity; + if(pEnt->m_MarkedForDestroy) + { + RemoveEntity(pEnt); + pEnt->Destroy(); + } + pEnt = m_pNextTraverseEntity; + } +} + +bool distCompare(std::pair a, std::pair b) +{ + return (a.first < b.first); +} + +void CGameWorld::Tick() +{ + // update all objects + for(int i = 0; i < NUM_ENTTYPES; i++) + for(CEntity *pEnt = m_apFirstEntityTypes[i]; pEnt; ) + { + m_pNextTraverseEntity = pEnt->m_pNextTypeEntity; + pEnt->Tick(); + pEnt = m_pNextTraverseEntity; + } + + for(int i = 0; i < NUM_ENTTYPES; i++) + for(CEntity *pEnt = m_apFirstEntityTypes[i]; pEnt; ) + { + m_pNextTraverseEntity = pEnt->m_pNextTypeEntity; + pEnt->TickDefered(); + pEnt->m_SnapTicks++; + pEnt = m_pNextTraverseEntity; + } + + RemoveEntities(); + + OnModified(); +} + + +// TODO: should be more general +CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, vec2& NewPos, CCharacter *pNotThis, int CollideWith, class CCharacter *pThisOnly) +{ + // Find other players + float ClosestLen = distance(Pos0, Pos1) * 100.0f; + CCharacter *pClosest = 0; + + CCharacter *p = (CCharacter *)FindFirst(ENTTYPE_CHARACTER); + for(; p; p = (CCharacter *)p->TypeNext()) + { + if(p == pNotThis) + continue; + + if(CollideWith != -1 && !p->CanCollide(CollideWith)) + continue; + + vec2 IntersectPos = closest_point_on_line(Pos0, Pos1, p->m_Pos); + float Len = distance(p->m_Pos, IntersectPos); + if(Len < p->m_ProximityRadius+Radius) + { + Len = distance(Pos0, IntersectPos); + if(Len < ClosestLen) + { + NewPos = IntersectPos; + ClosestLen = Len; + pClosest = p; + } + } + } + + return pClosest; +} + +std::list CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, class CEntity *pNotThis) +{ + std::list< CCharacter * > listOfChars; + + CCharacter *pChr = (CCharacter *)FindFirst(CGameWorld::ENTTYPE_CHARACTER); + for(; pChr; pChr = (CCharacter *)pChr->TypeNext()) + { + if(pChr == pNotThis) + continue; + + vec2 IntersectPos = closest_point_on_line(Pos0, Pos1, pChr->m_Pos); + float Len = distance(pChr->m_Pos, IntersectPos); + if(Len < pChr->m_ProximityRadius+Radius) + { + listOfChars.push_back(pChr); + } + } + return listOfChars; +} + +void CGameWorld::ReleaseHooked(int ClientID) +{ + CCharacter *pChr = (CCharacter *)CGameWorld::FindFirst(CGameWorld::ENTTYPE_CHARACTER); + for(; pChr; pChr = (CCharacter *)pChr->TypeNext()) + { + CCharacterCore* Core = pChr->Core(); + if(Core->m_HookedPlayer == ClientID) + { + Core->m_HookedPlayer = -1; + Core->m_HookState = HOOK_RETRACTED; + Core->m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; + Core->m_HookState = HOOK_RETRACTED; + } + } +} + +CTuningParams *CGameWorld::Tuning() +{ + return &m_Core.m_Tuning[g_Config.m_ClDummy]; +} + +CEntity *CGameWorld::GetEntity(int ID, int EntType) +{ + for(CEntity *pEnt = m_apFirstEntityTypes[EntType]; pEnt; pEnt = pEnt->m_pNextTypeEntity) + if(pEnt->m_ID == ID) + return pEnt; + return 0; +} + +void CGameWorld::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage, int ActivatedTeam, int64_t Mask) +{ + // deal damage + CCharacter *apEnts[MAX_CLIENTS]; + float Radius = 135.0f; + float InnerRadius = 48.0f; + int Num = FindEntities(Pos, Radius, (CEntity**)apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER); + for(int i = 0; i < Num; i++) + { + vec2 Diff = apEnts[i]->m_Pos - Pos; + vec2 ForceDir(0,1); + float l = length(Diff); + if(l) + ForceDir = normalize(Diff); + l = 1-clamp((l-InnerRadius)/(Radius-InnerRadius), 0.0f, 1.0f); + float Strength; + if(Owner == -1 || !GetCharacterByID(Owner)) + Strength = Tuning()->m_ExplosionStrength; + else + Strength = GetCharacterByID(Owner)->Tuning()->m_ExplosionStrength; + + float Dmg = Strength * l; + if((int)Dmg) + if((GetCharacterByID(Owner) ? !(GetCharacterByID(Owner)->m_Hit&CCharacter::DISABLE_HIT_GRENADE) : g_Config.m_SvHit || NoDamage) || Owner == apEnts[i]->GetCID()) + { + if(Owner != -1 && apEnts[i]->IsAlive() && !apEnts[i]->CanCollide(Owner)) + continue; + if(Owner == -1 && ActivatedTeam != -1 && apEnts[i]->IsAlive() && apEnts[i]->Team() != ActivatedTeam) + continue; + apEnts[i]->TakeDamage(ForceDir*Dmg*2, (int)Dmg, Owner, Weapon); + if(GetCharacterByID(Owner) ? GetCharacterByID(Owner)->m_Hit&CCharacter::DISABLE_HIT_GRENADE : !g_Config.m_SvHit || NoDamage) + break; + } + } +} + +void CGameWorld::NetObjBegin() +{ + for(int i = 0; i < NUM_ENTTYPES; i++) + for(CEntity *pEnt = FindFirst(i); pEnt; pEnt = pEnt->TypeNext()) + { + pEnt->m_MarkedForDestroy = true; + if(i == ENTTYPE_CHARACTER) + ((CCharacter*)pEnt)->m_KeepHooked = false; + } + OnModified(); +} + +void CGameWorld::NetCharAdd(int ObjID, CNetObj_Character *pCharObj, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal) +{ + CCharacter *pChar; + if((pChar = (CCharacter*) GetEntity(ObjID, ENTTYPE_CHARACTER))) + { + pChar->Read(pCharObj, pExtended, IsLocal); + pChar->Keep(); + } + else + pChar = new CCharacter(this, ObjID, pCharObj, pExtended); + + if(pChar) + pChar->m_GameTeam = GameTeam; +} + +void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData) +{ + if(ObjType == NETOBJTYPE_PROJECTILE && m_WorldConfig.m_PredictWeapons) + { + CProjectile NetProj = CProjectile(this, ObjID, (CNetObj_Projectile*) pObjData); + + if(NetProj.m_Type != WEAPON_SHOTGUN && fabs(length(NetProj.m_Direction) - 1.f) > 0.02f) // workaround to skip grenades on ball mod + return; + + if(CProjectile *pProj = (CProjectile*) GetEntity(ObjID, ENTTYPE_PROJECTILE)) + { + if(NetProj.Match(pProj)) + { + pProj->Keep(); + if(pProj->m_Type == WEAPON_SHOTGUN && m_WorldConfig.m_IsDDRace) + pProj->m_LifeSpan = 20 * GameTickSpeed() - (GameTick() - pProj->m_StartTick); + return; + } + } + for(CProjectile *pProj = (CProjectile*) FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile*) pProj->TypeNext()) + { + if(pProj->m_ID == -1 && NetProj.GetOwner() < 0 && NetProj.Match(pProj)) + { + pProj->m_ID = ObjID; + pProj->Keep(); + return; + } + } + CEntity *pEnt = new CProjectile(NetProj); + InsertEntity(pEnt); + } + else if(ObjType == NETOBJTYPE_PICKUP && m_WorldConfig.m_PredictWeapons) + { + CPickup NetPickup = CPickup(this, ObjID, (CNetObj_Pickup*) pObjData); + if(CPickup *pPickup = (CPickup*) GetEntity(ObjID, ENTTYPE_PICKUP)) + { + if(NetPickup.Match(pPickup)) + { + pPickup->m_Pos = NetPickup.m_Pos; + pPickup->Keep(); + return; + } + } + CEntity *pEnt = new CPickup(NetPickup); + InsertEntity(pEnt, true); + } + else if(ObjType == NETOBJTYPE_LASER && m_WorldConfig.m_PredictWeapons) + { + CLaser NetLaser = CLaser(this, ObjID, (CNetObj_Laser*) pObjData); + if(CLaser *pLaser = (CLaser*) GetEntity(ObjID, ENTTYPE_LASER)) + { + if(NetLaser.Match(pLaser)) + { + pLaser->Keep(); + if(distance(pLaser->m_Pos, NetLaser.m_Pos) > 2.f) + { + pLaser->m_Energy = 0.f; + pLaser->m_Pos = NetLaser.m_Pos; + } + } + } + for(CLaser *pLaser = (CLaser*) FindFirst(CGameWorld::ENTTYPE_LASER); pLaser; pLaser = (CLaser*) pLaser->TypeNext()) + { + if(pLaser->m_ID == -1 && NetLaser.Match(pLaser)) + { + pLaser->m_ID = ObjID; + pLaser->Keep(); + return; + } + } + CEntity *pEnt = new CLaser(NetLaser); + InsertEntity(pEnt, true); + } +} + +void CGameWorld::NetObjEnd(int LocalID) +{ + // keep predicting hooked characters, based on hook position + for(int i = 0; i < MAX_CLIENTS; i++) + if(CCharacter *pChar = GetCharacterByID(i)) + if(!pChar->m_MarkedForDestroy) + if(CCharacter *pHookedChar = GetCharacterByID(pChar->m_Core.m_HookedPlayer)) + if(pHookedChar->m_MarkedForDestroy) + { + pHookedChar->m_Pos = pHookedChar->m_Core.m_Pos = pChar->m_Core.m_HookPos; + pHookedChar->m_Core.m_Vel = vec2(0,0); + mem_zero(&pHookedChar->m_SavedInput, sizeof(pHookedChar->m_SavedInput)); + pHookedChar->m_KeepHooked = true; + pHookedChar->m_MarkedForDestroy = false; + } + // keep entities that are out of view for a short while, for some entity types + if(CCharacter *pLocal = GetCharacterByID(LocalID)) + for(int i : {ENTTYPE_CHARACTER, ENTTYPE_PROJECTILE, ENTTYPE_LASER}) + for(CEntity *pEnt = FindFirst(i); pEnt; pEnt=pEnt->TypeNext()) + if(pEnt->m_MarkedForDestroy) + if(pEnt->m_SnapTicks < 2 * SERVER_TICK_SPEED || (i != ENTTYPE_CHARACTER && pEnt->m_SnapTicks < 5 * SERVER_TICK_SPEED)) + if(pEnt->NetworkClipped(pLocal->m_Core.m_Pos)) + pEnt->m_MarkedForDestroy = false; + RemoveEntities(); + + // Update character IDs and pointers + for(int i = 0; i < MAX_CLIENTS; i++) + { + m_apCharacters[i] = 0; + m_Core.m_apCharacters[i] = 0; + } + for(CCharacter *pChar = (CCharacter*) FindFirst(ENTTYPE_CHARACTER); pChar; pChar = (CCharacter*) pChar->TypeNext()) + { + int ID = pChar->GetCID(); + if(ID >= 0 && ID < MAX_CLIENTS) + { + m_apCharacters[ID] = pChar; + m_Core.m_apCharacters[ID] = pChar->Core(); + } + } +} + +void CGameWorld::CopyWorld(CGameWorld *pFrom) +{ + if(pFrom == this || !pFrom) + return; + m_IsValidCopy = false; + m_pParent = pFrom; + if(m_pParent && m_pParent->m_pChild && m_pParent->m_pChild != this) + m_pParent->m_pChild->m_IsValidCopy = false; + 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++) + m_Core.m_Tuning[i] = pFrom->m_Core.m_Tuning[i]; + m_pTeams = pFrom->m_pTeams; + // delete the previous entities + for(int i = 0; i < NUM_ENTTYPES; i++) + while(m_apFirstEntityTypes[i]) + delete m_apFirstEntityTypes[i]; + for(int i = 0; i < MAX_CLIENTS; i++) + { + m_apCharacters[i] = 0; + m_Core.m_apCharacters[i] = 0; + } + // copy and add the new entities + for(int Type = 0; Type < NUM_ENTTYPES; Type++) + { + for(CEntity *pEnt = pFrom->FindLast(Type); pEnt; pEnt = pEnt->TypePrev()) + { + CEntity *pCopy = 0; + if(Type == ENTTYPE_PROJECTILE) + pCopy = new CProjectile(*((CProjectile*)pEnt)); + else if(Type == ENTTYPE_LASER) + pCopy = new CLaser(*((CLaser*)pEnt)); + else if(Type == ENTTYPE_CHARACTER) + pCopy = new CCharacter(*((CCharacter*)pEnt)); + else if(Type == ENTTYPE_PICKUP) + pCopy = new CPickup(*((CPickup*)pEnt)); + if(pCopy) + { + pCopy->m_pParent = pEnt; + this->InsertEntity(pCopy); + } + } + } + m_IsValidCopy = true; +} + +CEntity *CGameWorld::FindMatch(int ObjID, int ObjType, const void *pObjData) +{ +#define FindType(EntType, EntClass, ObjClass) \ + { \ + CEntity *pEnt = GetEntity(ObjID, EntType); \ + if(pEnt && EntClass(this, ObjID, (ObjClass*) pObjData).Match((EntClass*)pEnt)) \ + return pEnt; \ + return 0; \ + } + switch(ObjType) + { + case NETOBJTYPE_CHARACTER: FindType(ENTTYPE_CHARACTER, CCharacter, CNetObj_Character); + case NETOBJTYPE_PROJECTILE: FindType(ENTTYPE_PROJECTILE, CProjectile, CNetObj_Projectile); + case NETOBJTYPE_LASER: FindType(ENTTYPE_LASER, CLaser, CNetObj_Laser); + case NETOBJTYPE_PICKUP: FindType(ENTTYPE_PICKUP, CPickup, CNetObj_Pickup); + } + return 0; +} + +void CGameWorld::OnModified() +{ + if(m_pChild) + m_pChild->m_IsValidCopy = false; +} + +void CGameWorld::Clear() +{ + // delete all entities + for(int i = 0; i < NUM_ENTTYPES; i++) + while(m_apFirstEntityTypes[i]) + delete m_apFirstEntityTypes[i]; +} diff --git a/src/game/client/prediction/gameworld.h b/src/game/client/prediction/gameworld.h new file mode 100644 index 000000000..28012abf3 --- /dev/null +++ b/src/game/client/prediction/gameworld.h @@ -0,0 +1,136 @@ +/* (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_PREDICTION_GAMEWORLD_H +#define GAME_CLIENT_PREDICTION_GAMEWORLD_H + +#include + +#include + +class CEntity; +class CCharacter; + +class CGameWorld +{ + friend class CCharacter; +public: + enum + { + ENTTYPE_PROJECTILE = 0, + ENTTYPE_LASER, + ENTTYPE_PICKUP, + ENTTYPE_FLAG, + ENTTYPE_CHARACTER, + NUM_ENTTYPES + }; + + CWorldCore m_Core; + + CGameWorld(); + ~CGameWorld(); + + CEntity *FindFirst(int Type); + CEntity *FindLast(int Type); + int FindEntities(vec2 Pos, float Radius, CEntity **ppEnts, int Max, int Type); + class CCharacter *IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, class CCharacter *pNotThis = 0, int CollideWith = -1, class CCharacter *pThisOnly = 0); + void InsertEntity(CEntity *pEntity, bool Last = false); + void RemoveEntity(CEntity *pEntity); + void DestroyEntity(CEntity *pEntity); + void Tick(); + + // DDRace + + std::list IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, class CEntity *pNotThis); + void ReleaseHooked(int ClientID); + std::list IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, class CEntity *pNotThis = 0); + + int m_GameTick; + int m_GameTickSpeed; + CCollision *m_pCollision; + CTeamsCore *m_pTeams; + + // getter for server variables + int GameTick() { return m_GameTick; } + int GameTickSpeed() { return m_GameTickSpeed; } + class CCollision *Collision() { return m_pCollision; } + CTeamsCore *Teams() { return m_pTeams; } + CTuningParams *Tuning(); + CEntity *GetEntity(int ID, int EntityType); + class CCharacter *GetCharacterByID(int ID) { return (ID >= 0 && ID < MAX_CLIENTS) ? m_apCharacters[ID] : 0; } + + // from gamecontext + void CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage, int ActivatedTeam, int64_t Mask); + + // for client side prediction + struct + { + bool m_IsDDRace; + bool m_IsVanilla; + bool m_IsFNG; + bool m_InfiniteAmmo; + bool m_PredictTiles; + bool m_PredictFreeze; + bool m_PredictWeapons; + } m_WorldConfig; + + bool m_IsValidCopy; + CGameWorld *m_pParent; + CGameWorld *m_pChild; + + void OnModified(); + void NetObjBegin(); + void NetCharAdd(int ObjID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal); + void NetObjAdd(int ObjID, int ObjType, const void *pObjData); + void NetObjEnd(int LocalID); + void CopyWorld(CGameWorld *pFrom); + CEntity *FindMatch(int ObjID, int ObjType, const void *pObjData); + void Clear(); + +private: + void RemoveEntities(); + + CEntity *m_pNextTraverseEntity; + CEntity *m_apFirstEntityTypes[NUM_ENTTYPES]; + + class CCharacter *m_apCharacters[MAX_CLIENTS]; +}; + +class CCharOrder +{ +public: + std::list m_IDs; // reverse of the order in the gameworld, since entities will be inserted in reverse + CCharOrder() + { + for(int i = 0; i < MAX_CLIENTS; i++) + m_IDs.push_back(i); + } + void GiveStrong(int c) + { + if(0 <= c && c < MAX_CLIENTS) + { + m_IDs.remove(c); + m_IDs.push_front(c); + } + } + void GiveWeak(int c) + { + if(0 <= c && c < MAX_CLIENTS) + { + m_IDs.remove(c); + m_IDs.push_back(c); + } + } + bool HasStrongAgainst(int From, int To) + { + for(int i : m_IDs) + { + if(i == To) + return false; + else if(i == From) + return true; + } + return false; + } +}; + +#endif diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 8f34763b9..3f5bb8180 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -43,12 +43,6 @@ enum DIALOG_FILE, }; -struct CEntity -{ - CPoint m_Position; - int m_Type; -}; - class CEnvelope { public: diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index bdeabbc6c..a6d172f0c 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -121,7 +121,7 @@ void CCharacterCore::Reset() m_DeepFrozen = false; } -void CCharacterCore::Tick(bool UseInput, bool IsClient) +void CCharacterCore::Tick(bool UseInput) { float PhysSize = 28.0f; int MapIndex = Collision()->GetPureMapIndex(m_Pos); @@ -161,8 +161,6 @@ void CCharacterCore::Tick(bool UseInput, bool IsClient) m_TileSFlagsT = (UseInput && IsRightTeam(MapIndexT))?Collision()->GetDTileFlags(MapIndexT):0; m_TriggeredEvents = 0; - vec2 PrevPos = m_Pos; - // get ground state bool Grounded = false; if(m_pCollision->CheckPoint(m_Pos.x+PhysSize/2, m_Pos.y+PhysSize/2+5)) @@ -366,7 +364,7 @@ void CCharacterCore::Tick(bool UseInput, bool IsClient) if(m_HookedPlayer != -1) { CCharacterCore *pCharCore = m_pWorld->m_apCharacters[m_HookedPlayer]; - if(pCharCore && (IsClient || m_pTeams->CanKeepHook(m_Id, pCharCore->m_Id))) + if(pCharCore && m_pTeams->CanKeepHook(m_Id, pCharCore->m_Id)) m_HookPos = pCharCore->m_Pos; else { @@ -498,113 +496,6 @@ void CCharacterCore::Tick(bool UseInput, bool IsClient) { m_NewHook = false; } - - int Index = MapIndex; - if(g_Config.m_ClPredictDDRace && IsClient && m_pCollision->IsSpeedup(Index)) - { - vec2 Direction, MaxVel, TempVel = m_Vel; - int Force, MaxSpeed = 0; - float TeeAngle, SpeederAngle, DiffAngle, SpeedLeft, TeeSpeed; - m_pCollision->GetSpeedup(Index, &Direction, &Force, &MaxSpeed); - if(Force == 255 && MaxSpeed) - { - m_Vel = Direction * (MaxSpeed/5); - } - else - { - if(MaxSpeed > 0 && MaxSpeed < 5) MaxSpeed = 5; - if(MaxSpeed > 0) - { - if(Direction.x > 0.0000001f) - SpeederAngle = -atan(Direction.y / Direction.x); - else if(Direction.x < 0.0000001f) - SpeederAngle = atan(Direction.y / Direction.x) + 2.0f * asin(1.0f); - else if(Direction.y > 0.0000001f) - SpeederAngle = asin(1.0f); - else - SpeederAngle = asin(-1.0f); - - if(SpeederAngle < 0) - SpeederAngle = 4.0f * asin(1.0f) + SpeederAngle; - - if(TempVel.x > 0.0000001f) - TeeAngle = -atan(TempVel.y / TempVel.x); - else if(TempVel.x < 0.0000001f) - TeeAngle = atan(TempVel.y / TempVel.x) + 2.0f * asin(1.0f); - else if(TempVel.y > 0.0000001f) - TeeAngle = asin(1.0f); - else - TeeAngle = asin(-1.0f); - - if(TeeAngle < 0) - TeeAngle = 4.0f * asin(1.0f) + TeeAngle; - - TeeSpeed = sqrt(pow(TempVel.x, 2) + pow(TempVel.y, 2)); - - DiffAngle = SpeederAngle - TeeAngle; - SpeedLeft = MaxSpeed / 5.0f - cos(DiffAngle) * TeeSpeed; - if(abs((int)SpeedLeft) > Force && SpeedLeft > 0.0000001f) - TempVel += Direction * Force; - else if(abs((int)SpeedLeft) > Force) - TempVel += Direction * -Force; - else - TempVel += Direction * SpeedLeft; - } - else - TempVel += Direction * Force; - - - if(TempVel.x > 0 && ((this->m_TileIndex == TILE_STOP && this->m_TileFlags == ROTATION_270) || (this->m_TileIndexL == TILE_STOP && this->m_TileFlagsL == ROTATION_270) || (this->m_TileIndexL == TILE_STOPS && (this->m_TileFlagsL == ROTATION_90 || this->m_TileFlagsL ==ROTATION_270)) || (this->m_TileIndexL == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_270) || (m_TileFIndexL == TILE_STOP && m_TileFFlagsL == ROTATION_270) || (m_TileFIndexL == TILE_STOPS && (m_TileFFlagsL == ROTATION_90 || m_TileFFlagsL == ROTATION_270)) || (m_TileFIndexL == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_270) || (m_TileSIndexL == TILE_STOP && m_TileSFlagsL == ROTATION_270) || (m_TileSIndexL == TILE_STOPS && (m_TileSFlagsL == ROTATION_90 || m_TileSFlagsL == ROTATION_270)) || (m_TileSIndexL == TILE_STOPA))) - TempVel.x = 0; - if(TempVel.x < 0 && ((this->m_TileIndex == TILE_STOP && this->m_TileFlags == ROTATION_90) || (this->m_TileIndexR == TILE_STOP && this->m_TileFlagsR == ROTATION_90) || (this->m_TileIndexR == TILE_STOPS && (this->m_TileFlagsR == ROTATION_90 || this->m_TileFlagsR == ROTATION_270)) || (this->m_TileIndexR == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_90) || (m_TileFIndexR == TILE_STOP && m_TileFFlagsR == ROTATION_90) || (m_TileFIndexR == TILE_STOPS && (m_TileFFlagsR == ROTATION_90 || m_TileFFlagsR == ROTATION_270)) || (m_TileFIndexR == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_90) || (m_TileSIndexR == TILE_STOP && m_TileSFlagsR == ROTATION_90) || (m_TileSIndexR == TILE_STOPS && (m_TileSFlagsR == ROTATION_90 || m_TileSFlagsR == ROTATION_270)) || (m_TileSIndexR == TILE_STOPA))) - TempVel.x = 0; - if(TempVel.y < 0 && ((this->m_TileIndex == TILE_STOP && this->m_TileFlags == ROTATION_180) || (this->m_TileIndexB == TILE_STOP && this->m_TileFlagsB == ROTATION_180) || (this->m_TileIndexB == TILE_STOPS && (this->m_TileFlagsB == ROTATION_0 || this->m_TileFlagsB == ROTATION_180)) || (this->m_TileIndexB == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_180) || (m_TileFIndexB == TILE_STOP && m_TileFFlagsB == ROTATION_180) || (m_TileFIndexB == TILE_STOPS && (m_TileFFlagsB == ROTATION_0 || m_TileFFlagsB == ROTATION_180)) || (m_TileFIndexB == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_180) || (m_TileSIndexB == TILE_STOP && m_TileSFlagsB == ROTATION_180) || (m_TileSIndexB == TILE_STOPS && (m_TileSFlagsB == ROTATION_0 || m_TileSFlagsB == ROTATION_180)) || (m_TileSIndexB == TILE_STOPA))) - TempVel.y = 0; - if(TempVel.y > 0 && ((this->m_TileIndex == TILE_STOP && this->m_TileFlags == ROTATION_0) || (this->m_TileIndexT == TILE_STOP && this->m_TileFlagsT == ROTATION_0) || (this->m_TileIndexT == TILE_STOPS && (this->m_TileFlagsT == ROTATION_0 || this->m_TileFlagsT == ROTATION_180)) || (this->m_TileIndexT == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_0) || (m_TileFIndexT == TILE_STOP && m_TileFFlagsT == ROTATION_0) || (m_TileFIndexT == TILE_STOPS && (m_TileFFlagsT == ROTATION_0 || m_TileFFlagsT == ROTATION_180)) || (m_TileFIndexT == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_0) || (m_TileSIndexT == TILE_STOP && m_TileSFlagsT == ROTATION_0) || (m_TileSIndexT == TILE_STOPS && (m_TileSFlagsT == ROTATION_0 || m_TileSFlagsT == ROTATION_180)) || (m_TileSIndexT == TILE_STOPA))) - TempVel.y = 0; - - - m_Vel = TempVel; - } - } - - // jetpack and ninjajetpack prediction - if(IsClient && UseInput && (m_Input.m_Fire&1) && (m_ActiveWeapon == WEAPON_GUN || m_ActiveWeapon == WEAPON_NINJA)) { - m_Vel += TargetDirection * -1.0f * (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_JetpackStrength / 100.0f / 6.11f); - } - - if(g_Config.m_ClPredictDDRace && IsClient) - { - if(((m_TileIndex == TILE_STOP && m_TileFlags == ROTATION_270) || (m_TileIndexL == TILE_STOP && m_TileFlagsL == ROTATION_270) || (m_TileIndexL == TILE_STOPS && (m_TileFlagsL == ROTATION_90 || m_TileFlagsL ==ROTATION_270)) || (m_TileIndexL == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_270) || (m_TileFIndexL == TILE_STOP && m_TileFFlagsL == ROTATION_270) || (m_TileFIndexL == TILE_STOPS && (m_TileFFlagsL == ROTATION_90 || m_TileFFlagsL == ROTATION_270)) || (m_TileFIndexL == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_270) || (m_TileSIndexL == TILE_STOP && m_TileSFlagsL == ROTATION_270) || (m_TileSIndexL == TILE_STOPS && (m_TileSFlagsL == ROTATION_90 || m_TileSFlagsL == ROTATION_270)) || (m_TileSIndexL == TILE_STOPA)) && m_Vel.x > 0) - { - if((int)m_pCollision->GetPos(MapIndexL).x < (int)m_Pos.x) - m_Pos = PrevPos; - m_Vel.x = 0; - } - if(((m_TileIndex == TILE_STOP && m_TileFlags == ROTATION_90) || (m_TileIndexR == TILE_STOP && m_TileFlagsR == ROTATION_90) || (m_TileIndexR == TILE_STOPS && (m_TileFlagsR == ROTATION_90 || m_TileFlagsR == ROTATION_270)) || (m_TileIndexR == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_90) || (m_TileFIndexR == TILE_STOP && m_TileFFlagsR == ROTATION_90) || (m_TileFIndexR == TILE_STOPS && (m_TileFFlagsR == ROTATION_90 || m_TileFFlagsR == ROTATION_270)) || (m_TileFIndexR == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_90) || (m_TileSIndexR == TILE_STOP && m_TileSFlagsR == ROTATION_90) || (m_TileSIndexR == TILE_STOPS && (m_TileSFlagsR == ROTATION_90 || m_TileSFlagsR == ROTATION_270)) || (m_TileSIndexR == TILE_STOPA)) && m_Vel.x < 0) - { - if((int)m_pCollision->GetPos(MapIndexR).x) - if((int)m_pCollision->GetPos(MapIndexR).x < (int)m_Pos.x) - m_Pos = PrevPos; - m_Vel.x = 0; - } - if(((m_TileIndex == TILE_STOP && m_TileFlags == ROTATION_180) || (m_TileIndexB == TILE_STOP && m_TileFlagsB == ROTATION_180) || (m_TileIndexB == TILE_STOPS && (m_TileFlagsB == ROTATION_0 || m_TileFlagsB == ROTATION_180)) || (m_TileIndexB == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_180) || (m_TileFIndexB == TILE_STOP && m_TileFFlagsB == ROTATION_180) || (m_TileFIndexB == TILE_STOPS && (m_TileFFlagsB == ROTATION_0 || m_TileFFlagsB == ROTATION_180)) || (m_TileFIndexB == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_180) || (m_TileSIndexB == TILE_STOP && m_TileSFlagsB == ROTATION_180) || (m_TileSIndexB == TILE_STOPS && (m_TileSFlagsB == ROTATION_0 || m_TileSFlagsB == ROTATION_180)) || (m_TileSIndexB == TILE_STOPA)) && m_Vel.y < 0) - { - if((int)m_pCollision->GetPos(MapIndexB).y) - if((int)m_pCollision->GetPos(MapIndexB).y < (int)m_Pos.y) - m_Pos = PrevPos; - m_Vel.y = 0; - } - if(((m_TileIndex == TILE_STOP && m_TileFlags == ROTATION_0) || (m_TileIndexT == TILE_STOP && m_TileFlagsT == ROTATION_0) || (m_TileIndexT == TILE_STOPS && (m_TileFlagsT == ROTATION_0 || m_TileFlagsT == ROTATION_180)) || (m_TileIndexT == TILE_STOPA) || (m_TileFIndex == TILE_STOP && m_TileFFlags == ROTATION_0) || (m_TileFIndexT == TILE_STOP && m_TileFFlagsT == ROTATION_0) || (m_TileFIndexT == TILE_STOPS && (m_TileFFlagsT == ROTATION_0 || m_TileFFlagsT == ROTATION_180)) || (m_TileFIndexT == TILE_STOPA) || (m_TileSIndex == TILE_STOP && m_TileSFlags == ROTATION_0) || (m_TileSIndexT == TILE_STOP && m_TileSFlagsT == ROTATION_0) || (m_TileSIndexT == TILE_STOPS && (m_TileSFlagsT == ROTATION_0 || m_TileSFlagsT == ROTATION_180)) || (m_TileSIndexT == TILE_STOPA)) && m_Vel.y > 0) - { - if((int)m_pCollision->GetPos(MapIndexT).y) - if((int)m_pCollision->GetPos(MapIndexT).y < (int)m_Pos.y) - m_Pos = PrevPos; - m_Vel.y = 0; - m_Jumped = 0; - m_JumpedTotal = 0; - } - } } // clamp the velocity to something sane diff --git a/src/game/gamecore.h b/src/game/gamecore.h index d90539117..bdfbda7f6 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -214,7 +214,7 @@ public: void Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams); void Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams, std::map > *pTeleOuts); void Reset(); - void Tick(bool UseInput, bool IsClient); + void Tick(bool UseInput); void Move(); void Read(const CNetObj_CharacterCore *pObjCore); diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index b1b243f2c..d33e35f2e 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -748,7 +748,7 @@ void CCharacter::Tick() DDRaceTick(); m_Core.m_Input = m_Input; - m_Core.Tick(true, false); + m_Core.Tick(true); // handle Weapons HandleWeapons(); @@ -769,7 +769,7 @@ void CCharacter::TickDefered() CWorldCore TempWorld; m_ReckoningCore.Init(&TempWorld, GameServer()->Collision(), &((CGameControllerDDRace*)GameServer()->m_pController)->m_Teams.m_Core, &((CGameControllerDDRace*)GameServer()->m_pController)->m_TeleOuts); m_ReckoningCore.m_Id = m_pPlayer->GetCID(); - m_ReckoningCore.Tick(false, false); + m_ReckoningCore.Tick(false); m_ReckoningCore.Move(); m_ReckoningCore.Quantize(); } diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp index 2e6a2dae3..b4207605a 100644 --- a/src/game/server/entities/laser.cpp +++ b/src/game/server/entities/laser.cpp @@ -168,7 +168,7 @@ void CLaser::DoBounce() } CCharacter *pOwnerChar = GameServer()->GetPlayerChar(m_Owner); - if (m_Owner >= 0 && m_Energy <= 0 && m_Pos && !m_TeleportCancelled && pOwnerChar && + if (m_Owner >= 0 && m_Energy <= 0 && m_Pos && !m_TeleportCancelled && pOwnerChar && pOwnerChar->IsAlive() && pOwnerChar->m_HasTeleLaser && m_Type == WEAPON_RIFLE) { vec2 PossiblePos; @@ -238,6 +238,15 @@ void CLaser::Reset() void CLaser::Tick() { + if(g_Config.m_SvDestroyLasersOnDeath && m_Owner >= 0) + { + CCharacter *pOwnerChar = GameServer()->GetPlayerChar(m_Owner); + if(!(pOwnerChar && pOwnerChar->IsAlive())) + { + Reset(); + } + } + float Delay; if (m_TuneZone) Delay = GameServer()->TuningList()[m_TuneZone].m_LaserBounceDelay; diff --git a/src/game/server/entities/projectile.cpp b/src/game/server/entities/projectile.cpp index 7624f737a..a590aea9b 100644 --- a/src/game/server/entities/projectile.cpp +++ b/src/game/server/entities/projectile.cpp @@ -145,7 +145,7 @@ void CProjectile::Tick() { TeamMask = pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner); } - else if (m_Owner >= 0) + else if (m_Owner >= 0 && (m_Weapon != WEAPON_GRENADE || g_Config.m_SvDestroyBulletsOnDeath)) { GameServer()->m_World.DestroyEntity(this); return; diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index f3f8bfee6..47344ed7e 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -23,7 +23,7 @@ void CSaveTee::save(CCharacter *pChr) m_NeededFaketuning = pChr->m_NeededFaketuning; m_TeeFinished = pChr->Teams()->TeeFinished(pChr->m_pPlayer->GetCID()); - m_IsSolo = pChr->Teams()->m_Core.GetSolo(pChr->m_pPlayer->GetCID()); + m_IsSolo = pChr->m_Solo; for(int i = 0; i< NUM_WEAPONS; i++) { @@ -101,7 +101,6 @@ void CSaveTee::load(CCharacter *pChr, int Team) pChr->m_NeededFaketuning = m_NeededFaketuning; pChr->Teams()->SetForceCharacterTeam(pChr->m_pPlayer->GetCID(), Team); - pChr->Teams()->m_Core.SetSolo(pChr->m_pPlayer->GetCID(), m_IsSolo); pChr->Teams()->SetFinished(pChr->m_pPlayer->GetCID(), m_TeeFinished); for(int i = 0; i< NUM_WEAPONS; i++) @@ -177,7 +176,7 @@ void CSaveTee::load(CCharacter *pChr, int Team) pChr->m_Core.m_HookState = m_HookState; } - pChr->GameServer()->SendTuningParams(pChr->m_pPlayer->GetCID(), m_TuneZone); + pChr->SetSolo(m_IsSolo); } char* CSaveTee::GetString() diff --git a/src/game/variables.h b/src/game/variables.h index 85c65df83..f6a1fd8a7 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -11,6 +11,8 @@ MACRO_CONFIG_INT(ClAntiPing, cl_antiping, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, 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(ClNameplates, cl_nameplates, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show name plates") 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") @@ -146,6 +148,8 @@ MACRO_CONFIG_INT(SvOldTeleportHook, sv_old_teleport_hook, 0, 0, 1, CFGFLAG_SERVE 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")