mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Rewrite of prediction code, with additional prediction
This commit is contained in:
parent
fc68f3add1
commit
0b3f3b03b5
|
@ -875,6 +875,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
|
||||
|
|
|
@ -67,4 +67,7 @@ template <typename T> inline T min(T a, T b) { return a<b?a:b; }
|
|||
template <typename T> inline T max(T a, T b) { return a>b?a:b; }
|
||||
template <typename T> inline T absolute(T a) { return a<T(0)?-a:a; }
|
||||
|
||||
template <typename T> inline T in_range(T a, T lower, T upper) { return lower <= a && a <= upper; }
|
||||
template <typename T> inline T in_range(T a, T upper) { return in_range(a, 0, upper); }
|
||||
|
||||
#endif // BASE_MATH_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; }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -123,8 +126,8 @@ public:
|
|||
virtual int MapDownloadTotalsize() = 0;
|
||||
|
||||
// input
|
||||
virtual int *GetInput(int Tick) = 0;
|
||||
virtual bool InputExists(int Tick) = 0;
|
||||
virtual int *GetInput(int Tick, int IsDummy = 0) = 0;
|
||||
virtual int *GetDirectInput(int Tick, int IsDummy = 0) = 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
|
||||
|
|
|
@ -357,6 +357,8 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta)
|
|||
m_ReconnectTime = 0;
|
||||
|
||||
m_GenerateTimeoutSeed = true;
|
||||
|
||||
m_FrameTimeAvg = 0.0001f;
|
||||
}
|
||||
|
||||
// ----- send functions -----
|
||||
|
@ -509,10 +511,10 @@ void CClient::SendInput()
|
|||
// pack input
|
||||
CMsgPacker Msg(NETMSG_INPUT);
|
||||
Msg.AddInt(m_AckGameTick[i]);
|
||||
Msg.AddInt(m_PredTick[i]);
|
||||
Msg.AddInt(m_PredTick[g_Config.m_ClDummy]);
|
||||
Msg.AddInt(Size);
|
||||
|
||||
m_aInputs[i][m_CurrentInput[i]].m_Tick = m_PredTick[i];
|
||||
m_aInputs[i][m_CurrentInput[i]].m_Tick = m_PredTick[g_Config.m_ClDummy];
|
||||
m_aInputs[i][m_CurrentInput[i]].m_PredictedTime = m_PredictedTime.Get(Now);
|
||||
m_aInputs[i][m_CurrentInput[i]].m_Time = Now;
|
||||
|
||||
|
@ -539,12 +541,13 @@ const char *CClient::LatestVersion()
|
|||
}
|
||||
|
||||
// TODO: OPT: do this a lot smarter!
|
||||
int *CClient::GetInput(int Tick)
|
||||
int *CClient::GetInput(int Tick, int IsDummy)
|
||||
{
|
||||
int Best = -1;
|
||||
const int d = IsDummy ^ g_Config.m_ClDummy;
|
||||
for(int i = 0; i < 200; i++)
|
||||
{
|
||||
if(m_aInputs[g_Config.m_ClDummy][i].m_Tick <= Tick && (Best == -1 || m_aInputs[g_Config.m_ClDummy][Best].m_Tick < m_aInputs[g_Config.m_ClDummy][i].m_Tick))
|
||||
if(m_aInputs[d][i].m_Tick <= Tick && (Best == -1 || m_aInputs[d][Best].m_Tick < m_aInputs[d][i].m_Tick))
|
||||
Best = i;
|
||||
}
|
||||
|
||||
|
@ -553,12 +556,13 @@ int *CClient::GetInput(int Tick)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool CClient::InputExists(int Tick)
|
||||
int *CClient::GetDirectInput(int Tick, int IsDummy)
|
||||
{
|
||||
const int d = IsDummy ^ g_Config.m_ClDummy;
|
||||
for(int i = 0; i < 200; i++)
|
||||
if(m_aInputs[g_Config.m_ClDummy][i].m_Tick == Tick)
|
||||
return true;
|
||||
return false;
|
||||
if(m_aInputs[d][i].m_Tick == Tick)
|
||||
return (int *)m_aInputs[d][i].m_aData;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ------ state handling -----
|
||||
|
@ -2960,6 +2964,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 +3888,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);
|
||||
}
|
||||
|
|
|
@ -252,8 +252,8 @@ public:
|
|||
void SendInput();
|
||||
|
||||
// TODO: OPT: do this a lot smarter!
|
||||
virtual int *GetInput(int Tick);
|
||||
virtual bool InputExists(int Tick);
|
||||
virtual int *GetInput(int Tick, int IsDummy);
|
||||
virtual int *GetDirectInput(int Tick, int IsDummy);
|
||||
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,36 @@ 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)
|
||||
if(pProj->m_Weapon != WEAPON_SHOTGUN || (!pProj->m_Freeze && !pProj->m_Explosive)) // skip ddrace shotgun bullets
|
||||
if(pProj->m_Weapon == WEAPON_SHOTGUN || fabs(length(pProj->m_Direction) - 1.f) < 0.02) // workaround to skip grenades on ball mod
|
||||
if(pProj->GetOwner() < 0 || !GameClient()->m_aClients[pProj->GetOwner()].m_IsPredictedLocal) // skip locally predicted projectiles
|
||||
if(!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)
|
||||
if(auto *pLaser = (CLaser*) GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData))
|
||||
if(pLaser->GetOwner() >= 0 && GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal)
|
||||
continue;
|
||||
RenderLaser((const CNetObj_Laser *)pData);
|
||||
}
|
||||
}
|
||||
|
@ -326,7 +370,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 +428,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -259,43 +125,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 +143,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 +186,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,11 +196,8 @@ 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)
|
||||
|
@ -418,12 +216,9 @@ void CPlayers::RenderPlayer(
|
|||
// 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,83 +228,32 @@ 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_Predicted.m_Solo = m_pClient->m_aClients[ClientID].m_Solo;
|
||||
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 +542,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 +675,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 +694,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 +712,7 @@ void CPlayers::OnRender()
|
|||
&PrevChar,
|
||||
&CurChar,
|
||||
&m_aRenderInfo[i],
|
||||
i,
|
||||
m_CurPredictedPos[i]
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -13,6 +13,11 @@
|
|||
|
||||
#include <game/teamscore.h>
|
||||
|
||||
#include <game/client/prediction/gameworld.h>
|
||||
#include <game/client/prediction/entities/character.h>
|
||||
#include <game/client/prediction/entities/laser.h>
|
||||
#include <game/client/prediction/entities/pickup.h>
|
||||
|
||||
#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
|
||||
|
@ -281,6 +246,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,19 +373,27 @@ 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; }
|
||||
bool PredictDummy() { return AntiPingPlayers() && Client()->DummyConnected() && m_Snap.m_LocalClientID >= 0 && m_PredictedDummyID >= 0; }
|
||||
|
||||
void UpdatePrediction();
|
||||
void UpdateRenderedCharacters();
|
||||
void DetectStrongHook();
|
||||
vec2 GetSmoothPos(int ClientID);
|
||||
|
||||
CGameWorld m_GameWorld;
|
||||
CGameWorld m_PredictedWorld;
|
||||
CGameWorld m_PrevPredictedWorld;
|
||||
int m_PredictedDummyID;
|
||||
int m_IsDummySwapping;
|
||||
CCharOrder m_CharOrder;
|
||||
class CCharacter m_aLastWorldCharacters[MAX_CLIENTS];
|
||||
|
||||
private:
|
||||
bool m_DDRaceMsgSent[2];
|
||||
|
|
1145
src/game/client/prediction/entities/character.cpp
Normal file
1145
src/game/client/prediction/entities/character.cpp
Normal file
File diff suppressed because it is too large
Load diff
212
src/game/client/prediction/entities/character.h
Normal file
212
src/game/client/prediction/entities/character.h
Normal file
|
@ -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 <game/client/prediction/entity.h>
|
||||
#include "projectile.h"
|
||||
|
||||
#include <game/gamecore.h>
|
||||
#include <game/generated/client_data.h>
|
||||
|
||||
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;
|
||||
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();
|
||||
|
||||
public:
|
||||
CTeamsCore* TeamsCore();
|
||||
bool Freeze(int Time);
|
||||
bool Freeze();
|
||||
bool UnFreeze();
|
||||
void GiveAllWeapons();
|
||||
int Team();
|
||||
bool CanCollide(int ClientID);
|
||||
bool SameTeam(int ClientID);
|
||||
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);
|
||||
void Read(CNetObj_Character *pChar, 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; }
|
||||
};
|
||||
|
||||
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
|
204
src/game/client/prediction/entities/laser.cpp
Normal file
204
src/game/client/prediction/entities/laser.cpp
Normal file
|
@ -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 <game/generated/protocol.h>
|
||||
#include "character.h"
|
||||
#include "laser.h"
|
||||
|
||||
#include <engine/shared/config.h>
|
||||
|
||||
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;
|
||||
}
|
44
src/game/client/prediction/entities/laser.h
Normal file
44
src/game/client/prediction/entities/laser.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* (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 <game/client/prediction/entity.h>
|
||||
|
||||
class CLaser : public CEntity
|
||||
{
|
||||
friend class CGameWorld;
|
||||
public:
|
||||
CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEnergy, int Owner, int Type);
|
||||
|
||||
virtual void Tick();
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
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);
|
||||
};
|
||||
|
||||
#endif
|
108
src/game/client/prediction/entities/pickup.cpp
Normal file
108
src/game/client/prediction/entities/pickup.cpp
Normal file
|
@ -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 <game/generated/protocol.h>
|
||||
#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;
|
||||
}
|
32
src/game/client/prediction/entities/pickup.h
Normal file
32
src/game/client/prediction/entities/pickup.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* (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 <game/client/prediction/entity.h>
|
||||
|
||||
const int PickupPhysSize = 14;
|
||||
|
||||
class CPickup : public CEntity
|
||||
{
|
||||
public:
|
||||
virtual void Tick();
|
||||
|
||||
private:
|
||||
|
||||
int m_Type;
|
||||
int m_Subtype;
|
||||
|
||||
// DDRace
|
||||
|
||||
void Move();
|
||||
vec2 m_Core;
|
||||
|
||||
public:
|
||||
|
||||
CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup);
|
||||
void FillInfo(CNetObj_Pickup *pPickup);
|
||||
bool Match(CPickup *pPickup);
|
||||
};
|
||||
|
||||
#endif
|
243
src/game/client/prediction/entities/projectile.cpp
Normal file
243
src/game/client/prediction/entities/projectile.cpp
Normal file
|
@ -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 <game/generated/protocol.h>
|
||||
#include "projectile.h"
|
||||
|
||||
#include <engine/shared/config.h>
|
||||
|
||||
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);
|
||||
}
|
65
src/game/client/prediction/entities/projectile.h
Normal file
65
src/game/client/prediction/entities/projectile.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/* (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 <game/client/prediction/entity.h>
|
||||
#include "character.h"
|
||||
#include <game/extrainfo.h>
|
||||
|
||||
class CProjectile : public CEntity
|
||||
{
|
||||
friend class CGameWorld;
|
||||
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();
|
||||
|
||||
//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;
|
||||
|
||||
public:
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
#endif
|
58
src/game/client/prediction/entity.cpp
Normal file
58
src/game/client/prediction/entity.cpp
Normal file
|
@ -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;
|
||||
}
|
71
src/game/client/prediction/entity.h
Normal file
71
src/game/client/prediction/entity.h
Normal file
|
@ -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 <new>
|
||||
#include <base/vmath.h>
|
||||
#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
|
557
src/game/client/prediction/gameworld.cpp
Normal file
557
src/game/client/prediction/gameworld.cpp
Normal file
|
@ -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 <algorithm>
|
||||
#include <utility>
|
||||
#include <engine/shared/config.h>
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// 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<float,int> a, std::pair<float,int> 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<class CCharacter *> 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, int GameTeam, bool IsLocal)
|
||||
{
|
||||
CCharacter *pChar;
|
||||
if((pChar = (CCharacter*) GetEntity(ObjID, ENTTYPE_CHARACTER)))
|
||||
{
|
||||
pChar->Read(pCharObj, IsLocal);
|
||||
pChar->Keep();
|
||||
}
|
||||
else
|
||||
pChar = new CCharacter(this, ObjID, pCharObj);
|
||||
|
||||
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];
|
||||
}
|
137
src/game/client/prediction/gameworld.h
Normal file
137
src/game/client/prediction/gameworld.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
/* (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 <game/gamecore.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
class CEntity;
|
||||
class CCharacter;
|
||||
|
||||
class CGameWorld
|
||||
{
|
||||
friend class CCharacter;
|
||||
public:
|
||||
enum
|
||||
{
|
||||
ENTTYPE_PROJECTILE = 0,
|
||||
ENTTYPE_LASER,
|
||||
ENTTYPE_PICKUP,
|
||||
ENTTYPE_FLAG,
|
||||
ENTTYPE_CHARACTER,
|
||||
NUM_ENTTYPES
|
||||
};
|
||||
|
||||
private:
|
||||
void RemoveEntities();
|
||||
|
||||
CEntity *m_pNextTraverseEntity;
|
||||
CEntity *m_apFirstEntityTypes[NUM_ENTTYPES];
|
||||
|
||||
class CCharacter *m_apCharacters[MAX_CLIENTS];
|
||||
|
||||
public:
|
||||
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<class CCharacter *> IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, class CEntity *pNotThis);
|
||||
void ReleaseHooked(int ClientID);
|
||||
std::list<class CCharacter *> 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, 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();
|
||||
};
|
||||
|
||||
class CCharOrder
|
||||
{
|
||||
public:
|
||||
std::list<int> 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
|
|
@ -43,12 +43,6 @@ enum
|
|||
DIALOG_FILE,
|
||||
};
|
||||
|
||||
struct CEntity
|
||||
{
|
||||
CPoint m_Position;
|
||||
int m_Type;
|
||||
};
|
||||
|
||||
class CEnvelope
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -488,113 +486,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
|
||||
|
|
|
@ -214,7 +214,7 @@ public:
|
|||
void Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams);
|
||||
void Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams, std::map<int, std::vector<vec2> > *pTeleOuts);
|
||||
void Reset();
|
||||
void Tick(bool UseInput, bool IsClient);
|
||||
void Tick(bool UseInput);
|
||||
void Move();
|
||||
|
||||
void Read(const CNetObj_CharacterCore *pObjCore);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue