1620: Rewrite of prediction code, with additional prediction (updated) r=def- a=trml

This is a reworked attempt at a rewrite of the prediction code (#464), to allow for more client side prediction. It doesn't fix the duplication of server code, but the client code should otherwise be cleaner. This includes separating prediction code out of gamecore/gameclient, and a refactor of the rendering of predicted characters.

There is also prediction for some new things, and some other changes:
- prediction of laser, shotgun, ninja, fng hammer, pickups and ddrace tiles (freeze/unfreeze tiles with cl_predict_freeze)
- laser and bullets are rendered when you fire them and bullets don't go through walls (when both cl_antiping_grenade and cl_antiping_weapons are enabled)
- antiping for flags
- prediction of dummy input
- an additional smoothing option that attempts to make antiping less jumpy (cl_antiping_smooth)

Co-authored-by: trml <trml@users.noreply.github.com>
Co-authored-by: trml <trml@noreply.github.com>
This commit is contained in:
bors[bot] 2019-04-23 17:11:21 +00:00
commit 526af034dd
38 changed files with 3572 additions and 1225 deletions

View file

@ -878,6 +878,18 @@ if(CLIENT)
gameclient.h
lineinput.cpp
lineinput.h
prediction/entities/character.cpp
prediction/entities/character.h
prediction/entities/laser.cpp
prediction/entities/laser.h
prediction/entities/pickup.cpp
prediction/entities/pickup.h
prediction/entities/projectile.cpp
prediction/entities/projectile.h
prediction/entity.cpp
prediction/entity.h
prediction/gameworld.cpp
prediction/gameworld.h
race.cpp
race.h
render.cpp

View file

@ -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

View file

@ -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; }
};

View file

@ -38,6 +38,8 @@ protected:
float m_RenderFrameTime;
int m_GameTickSpeed;
float m_FrameTimeAvg;
public:
char m_aNews[3000];
int64 m_ReconnectTime;
@ -84,6 +86,7 @@ public:
// other time access
inline float RenderFrameTime() const { return m_RenderFrameTime; }
inline float LocalTime() const { return m_LocalTime; }
inline float FrameTimeAvg() const { return m_FrameTimeAvg; }
// actions
virtual void Connect(const char *pAddress, const char *pPassword = NULL) = 0;
@ -124,7 +127,7 @@ public:
// input
virtual int *GetInput(int Tick) = 0;
virtual bool InputExists(int Tick) = 0;
virtual int *GetDirectInput(int Tick) = 0;
// remote console
virtual void RconAuth(const char *pUsername, const char *pPassword) = 0;
@ -196,6 +199,8 @@ public:
virtual void GenerateTimeoutSeed() = 0;
virtual IFriends* Foes() = 0;
virtual void GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount) = 0;
};
class IGameClient : public IInterface

View file

@ -357,6 +357,8 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta)
m_ReconnectTime = 0;
m_GenerateTimeoutSeed = true;
m_FrameTimeAvg = 0.0001f;
}
// ----- send functions -----
@ -553,12 +555,12 @@ int *CClient::GetInput(int Tick)
return 0;
}
bool CClient::InputExists(int Tick)
int *CClient::GetDirectInput(int Tick)
{
for(int i = 0; i < 200; i++)
if(m_aInputs[g_Config.m_ClDummy][i].m_Tick == Tick)
return true;
return false;
return (int *)m_aInputs[g_Config.m_ClDummy][i].m_aData;
return 0;
}
// ------ state handling -----
@ -2960,6 +2962,8 @@ void CClient::Run()
m_RenderFrameTimeHigh = m_RenderFrameTime;
m_FpsGraph.Add(1.0f/m_RenderFrameTime, 1,1,1);
m_FrameTimeAvg = m_FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f;
// keep the overflow time - it's used to make sure the gfx refreshrate is reached
int64 AdditionalTime = g_Config.m_GfxRefreshRate ? ((Now - LastRenderTime) - (time_freq() / (int64)g_Config.m_GfxRefreshRate)) : 0;
// if the value is over a second time loose, reset the additional time (drop the frames, that are lost already)
@ -3882,3 +3886,13 @@ int CClient::GetPredictionTime()
int64 Now = time_get();
return (int)((m_PredictedTime.Get(Now)-m_GameTime[g_Config.m_ClDummy].Get(Now))*1000/(float)time_freq());
}
void CClient::GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount)
{
int64 GameTime = m_GameTime[g_Config.m_ClDummy].Get(time_get());
int64 PredTime = m_PredictedTime.Get(time_get());
int64 SmoothTime = clamp(GameTime + (int64)(MixAmount * (PredTime - GameTime)), GameTime, PredTime);
*pSmoothTick = (int)(SmoothTime*50/time_freq())+1;
*pSmoothIntraTick = (SmoothTime - (*pSmoothTick-1)*time_freq()/50) / (float)(time_freq()/50);
}

View file

@ -253,7 +253,7 @@ public:
// TODO: OPT: do this a lot smarter!
virtual int *GetInput(int Tick);
virtual bool InputExists(int Tick);
virtual int *GetDirectInput(int Tick);
const char *LatestVersion();
@ -417,5 +417,7 @@ public:
bool EditorHasUnsavedData() { return m_pEditor->HasUnsavedData(); }
virtual IFriends* Foes() {return &m_Foes; }
void GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount);
};
#endif

View file

@ -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);

View file

@ -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")

View file

@ -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);
}

View file

@ -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)

View file

@ -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);

View file

@ -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);
}
}

View file

@ -43,29 +43,20 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunSpeed;
}
//
bool LocalPlayerInGame = false;
if(m_pClient->m_Snap.m_pLocalInfo)
LocalPlayerInGame = m_pClient->m_aClients[m_pClient->m_Snap.m_pLocalInfo->m_ClientID].m_Team != -1;
//
static float s_LastGameTickTime = Client()->GameTickTime();
if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
s_LastGameTickTime = Client()->GameTickTime();
int PrevTick = Client()->PrevGameTick();
float Ct;
if(m_pClient->AntiPingGrenade() && LocalPlayerInGame && !(Client()->State() == IClient::STATE_DEMOPLAYBACK))
{
// calc predicted game tick
static int Offset = 0;
Offset = (int)(0.8f * (float)Offset + 0.2f * (float)(Client()->PredGameTick() - Client()->GameTick()));
PrevTick += Offset;
}
float Ct = (PrevTick-pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime;
Ct = ((float)(Client()->PredGameTick() - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick())/(float)SERVER_TICK_SPEED;
else
Ct = (Client()->PrevGameTick() - pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime;
if(Ct < 0)
return; // projectile haven't been shot yet
@ -77,7 +68,6 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
vec2 Pos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct);
vec2 PrevPos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct-0.001f);
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
@ -86,7 +76,6 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
vec2 Vel = Pos-PrevPos;
//vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), Client()->IntraGameTick());
// add particle for this projectile
if(pCurrent->m_Type == WEAPON_GRENADE)
{
@ -197,31 +186,34 @@ void CItems::RenderFlag(const CNetObj_Flag *pPrev, const CNetObj_Flag *pCurrent,
if(pCurGameData)
{
int FlagCarrier = (pCurrent->m_Team == TEAM_RED) ? pCurGameData->m_FlagCarrierRed : pCurGameData->m_FlagCarrierBlue;
// use the flagcarriers position if available
if(FlagCarrier >= 0 && m_pClient->m_Snap.m_aCharacters[FlagCarrier].m_Active)
Pos = m_pClient->m_aClients[FlagCarrier].m_RenderPos;
// make sure that the flag isn't interpolated between capture and return
if(pPrevGameData &&
((pCurrent->m_Team == TEAM_RED && pPrevGameData->m_FlagCarrierRed != pCurGameData->m_FlagCarrierRed) ||
(pCurrent->m_Team == TEAM_BLUE && pPrevGameData->m_FlagCarrierBlue != pCurGameData->m_FlagCarrierBlue)))
Pos = vec2(pCurrent->m_X, pCurrent->m_Y);
// make sure to use predicted position if we are the carrier
if(m_pClient->m_Snap.m_pLocalInfo &&
((pCurrent->m_Team == TEAM_RED && pCurGameData->m_FlagCarrierRed == m_pClient->m_Snap.m_LocalClientID) ||
(pCurrent->m_Team == TEAM_BLUE && pCurGameData->m_FlagCarrierBlue == m_pClient->m_Snap.m_LocalClientID)))
Pos = m_pClient->m_LocalCharacterPos;
}
Graphics()->RenderQuadContainerAsSprite(m_ItemsQuadContainerIndex, QuadOffset, Pos.x, Pos.y-Size*0.75f);
}
void CItems::RenderLaser(const struct CNetObj_Laser *pCurrent)
void CItems::RenderLaser(const struct CNetObj_Laser *pCurrent, bool IsPredicted)
{
vec3 RGB;
vec2 Pos = vec2(pCurrent->m_X, pCurrent->m_Y);
vec2 From = vec2(pCurrent->m_FromX, pCurrent->m_FromY);
vec2 Dir = normalize(Pos-From);
float Ticks = Client()->GameTick() - pCurrent->m_StartTick + Client()->IntraGameTick();
float Ticks;
if(IsPredicted)
Ticks = (float)(Client()->PredGameTick() - pCurrent->m_StartTick) + Client()->PredIntraGameTick();
else
Ticks = (float)(Client()->GameTick() - pCurrent->m_StartTick) + Client()->IntraGameTick();
float Ms = (Ticks/50.0f) * 1000.0f;
float a = Ms / m_pClient->m_Tuning[g_Config.m_ClDummy].m_LaserBounceDelay;
a = clamp(a, 0.0f, 1.0f);
@ -278,6 +270,38 @@ void CItems::OnRender()
if(Client()->State() < IClient::STATE_ONLINE)
return;
bool UsePredicted = (GameClient()->Predict() && GameClient()->AntiPingWeapons() && GameClient()->AntiPingGrenade());
if(UsePredicted)
{
for(auto *pProj = (CProjectile*) GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile*) pProj->NextEntity())
{
CNetObj_Projectile Data;
if(pProj->m_Weapon != WEAPON_SHOTGUN || pProj->m_Explosive || pProj->m_Freeze)
pProj->FillExtraInfo(&Data);
else
pProj->FillInfo(&Data);
RenderProjectile(&Data, pProj->ID());
}
for(auto *pLaser = (CLaser*) GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_LASER); pLaser; pLaser = (CLaser*) pLaser->NextEntity())
{
if(pLaser->GetOwner() < 0 || (pLaser->GetOwner() >= 0 && !GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal))
continue;
CNetObj_Laser Data;
pLaser->FillInfo(&Data);
RenderLaser(&Data, true);
}
for(auto *pPickup = (CPickup*) GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PICKUP); pPickup; pPickup = (CPickup*) pPickup->NextEntity())
{
if(auto *pPrev = (CPickup*) GameClient()->m_PrevPredictedWorld.GetEntity(pPickup->ID(), CGameWorld::ENTTYPE_PICKUP))
{
CNetObj_Pickup Data, Prev;
pPickup->FillInfo(&Data);
pPrev->FillInfo(&Prev);
RenderPickup(&Prev, &Data);
}
}
}
int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT);
for(int i = 0; i < Num; i++)
{
@ -286,16 +310,40 @@ void CItems::OnRender()
if(Item.m_Type == NETOBJTYPE_PROJECTILE)
{
if(UsePredicted)
{
if(auto *pProj = (CProjectile*) GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData))
{
if(pProj->m_LastRenderTick <= 0
&& (pProj->m_Weapon != WEAPON_SHOTGUN || (!pProj->m_Freeze && !pProj->m_Explosive)) // skip ddrace shotgun bullets
&& (pProj->m_Weapon == WEAPON_SHOTGUN || fabs(length(pProj->m_Direction) - 1.f) < 0.02) // workaround to skip grenades on ball mod
&& (pProj->GetOwner() < 0 || !GameClient()->m_aClients[pProj->GetOwner()].m_IsPredictedLocal) // skip locally predicted projectiles
&& !Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID))
{
ReconstructSmokeTrail((const CNetObj_Projectile *)pData, Item.m_ID, pProj->m_DestroyTick, pProj->m_LifeSpan);
}
pProj->m_LastRenderTick = Client()->GameTick();
continue;
}
}
RenderProjectile((const CNetObj_Projectile *)pData, Item.m_ID);
}
else if(Item.m_Type == NETOBJTYPE_PICKUP)
{
if(UsePredicted)
continue;
const void *pPrev = Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID);
if(pPrev)
RenderPickup((const CNetObj_Pickup *)pPrev, (const CNetObj_Pickup *)pData);
}
else if(Item.m_Type == NETOBJTYPE_LASER)
{
if(UsePredicted)
{
auto *pLaser = (CLaser*) GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData);
if(pLaser && pLaser->GetOwner() >= 0 && GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal)
continue;
}
RenderLaser((const CNetObj_Laser *)pData);
}
}
@ -326,7 +374,7 @@ void CItems::OnRender()
m_aExtraProjectiles[i] = m_aExtraProjectiles[m_NumExtraProjectiles-1];
m_NumExtraProjectiles--;
}
else
else if(!UsePredicted)
RenderProjectile(&m_aExtraProjectiles[i], 0);
}
@ -384,3 +432,68 @@ void CItems::AddExtraProjectile(CNetObj_Projectile *pProj)
m_NumExtraProjectiles++;
}
}
void CItems::ReconstructSmokeTrail(const CNetObj_Projectile *pCurrent, int ItemID, int DestroyTick, int LifeSpan)
{
bool LocalPlayerInGame = false;
if(m_pClient->m_Snap.m_pLocalInfo)
LocalPlayerInGame = m_pClient->m_aClients[m_pClient->m_Snap.m_pLocalInfo->m_ClientID].m_Team != -1;
if(!m_pClient->AntiPingGrenade() || !m_pClient->AntiPingWeapons() || !LocalPlayerInGame)
return;
if(Client()->PredGameTick() == pCurrent->m_StartTick)
return;
// get positions
float Curvature = 0;
float Speed = 0;
if(pCurrent->m_Type == WEAPON_GRENADE)
{
Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeCurvature;
Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeSpeed;
}
else if(pCurrent->m_Type == WEAPON_SHOTGUN)
{
Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunCurvature;
Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunSpeed;
}
else if(pCurrent->m_Type == WEAPON_GUN)
{
Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunCurvature;
Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunSpeed;
}
float Pt = ((float)(Client()->PredGameTick() - pCurrent->m_StartTick) + Client()->PredIntraGameTick())/(float)SERVER_TICK_SPEED;
if(Pt < 0)
return; // projectile haven't been shot yet
float Gt = (Client()->PrevGameTick() - pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + Client()->GameTickTime();
vec2 StartPos;
vec2 StartVel;
ExtractInfo(pCurrent, &StartPos, &StartVel);
float T = Pt;
if(DestroyTick >= 0)
T = min(Pt, ((float)(DestroyTick - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick())/(float)SERVER_TICK_SPEED);
T = min(T, LifeSpan/(float)SERVER_TICK_SPEED);
float MinTrailSpan = 0.4f * ((pCurrent->m_Type == WEAPON_GRENADE) ? 0.5f : 0.25f);
float Step = max(Client()->FrameTimeAvg(), (pCurrent->m_Type == WEAPON_GRENADE) ? 0.02f : 0.01f);
for(int i = 1+(int)(Gt/Step); i < (int)(T/Step); i++)
{
float t = Step * (float)i + 0.4f * Step * (frandom() - 0.5f);
vec2 Pos = CalcPos(StartPos, StartVel, Curvature, Speed, t);
vec2 PrevPos = CalcPos(StartPos, StartVel, Curvature, Speed, t-0.001f);
vec2 Vel = Pos-PrevPos;
float TimePassed = Pt-t;
if(Pt - MinTrailSpan > 0.01f)
TimePassed = min(TimePassed, (TimePassed-MinTrailSpan)/(Pt - MinTrailSpan)*(MinTrailSpan * 0.5f) + MinTrailSpan);
// add particle for this projectile
if(pCurrent->m_Type == WEAPON_GRENADE)
m_pClient->m_pEffects->SmokeTrail(Pos, Vel*-1, TimePassed);
else
m_pClient->m_pEffects->BulletTrail(Pos, TimePassed);
}
}

View file

@ -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

View file

@ -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;

View file

@ -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)

View file

@ -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();

View file

@ -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());
}
if(in_range(pPlayerChar->m_HookedPlayer, MAX_CLIENTS-1))
HookPos = m_pClient->m_aClients[pPlayerChar->m_HookedPlayer].m_RenderPos;
else
HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), IntraTick);
}
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);
}
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,28 +694,16 @@ 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]
i
);
}
else
@ -1024,8 +712,7 @@ void CPlayers::OnRender()
&PrevChar,
&CurChar,
&m_aRenderInfo[i],
i,
m_CurPredictedPos[i]
i
);
}
}

View file

@ -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

View file

@ -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
@ -217,6 +182,9 @@ public:
CNetObj_Character m_Prev;
CNetObj_Character m_Cur;
CNetObj_DDNetCharacter m_ExtendedData;
bool m_HasExtendedData;
// interpolated position
vec2 m_Position;
};
@ -281,6 +249,17 @@ public:
// DDRace
int m_Score;
// rendered characters
CNetObj_Character m_RenderCur;
CNetObj_Character m_RenderPrev;
vec2 m_RenderPos;
bool m_IsPredicted;
bool m_IsPredictedLocal;
int64 m_SmoothStart[2];
int64 m_SmoothLen[2];
vec2 m_PredPos[200];
int m_PredTick[200];
};
CClientData m_aClients[MAX_CLIENTS];
@ -397,23 +376,30 @@ public:
class CTeamsCore m_Teams;
int IntersectCharacter(vec2 Pos0, vec2 Pos1, vec2& NewPos, int ownID);
int IntersectCharacter(vec2 OldPos, vec2 NewPos, float Radius, vec2* NewPos2, int ownID, CWorldCore *World);
CWeaponData m_aWeaponData[150];
CWeaponData *GetWeaponData(int Tick) { return &m_aWeaponData[((Tick%150)+150)%150]; }
CWeaponData *FindWeaponData(int TargetTick);
virtual int GetLastRaceTick();
void FindWeaker(bool IsWeaker[2][MAX_CLIENTS]);
bool AntiPingPlayers() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingPlayers && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK && (m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_Tuning[g_Config.m_ClDummy].m_PlayerHooking); }
bool AntiPingGrenade() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingGrenade && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK; }
bool AntiPingWeapons() { return g_Config.m_ClAntiPing && g_Config.m_ClAntiPingWeapons && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK; }
bool Predict() { return g_Config.m_ClPredict && !(m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER) && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK && m_Snap.m_pLocalCharacter; }
CGameWorld m_GameWorld;
CGameWorld m_PredictedWorld;
CGameWorld m_PrevPredictedWorld;
private:
bool m_DDRaceMsgSent[2];
int m_ShowOthers[2];
void UpdatePrediction();
void UpdateRenderedCharacters();
void DetectStrongHook();
vec2 GetSmoothPos(int ClientID);
CCharOrder m_CharOrder;
class CCharacter m_aLastWorldCharacters[MAX_CLIENTS];
class CTeamsCore m_TeamsPredicted;
};

File diff suppressed because it is too large Load diff

View 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;
CTeamsCore* TeamsCore();
bool Freeze(int Time);
bool Freeze();
bool UnFreeze();
void GiveAllWeapons();
int Team();
bool CanCollide(int ClientID);
bool SameTeam(int ClientID);
bool m_Super;
bool m_SuperJump;
bool m_Jetpack;
int m_FreezeTime;
int m_FreezeTick;
bool m_DeepFreeze;
bool m_EndlessHook;
enum
{
HIT_ALL=0,
DISABLE_HIT_HAMMER=1,
DISABLE_HIT_SHOTGUN=2,
DISABLE_HIT_GRENADE=4,
DISABLE_HIT_RIFLE=8
};
int m_Hit;
vec2 m_PrevPos;
int m_TileIndex;
int m_TileFlags;
int m_TileFIndex;
int m_TileFFlags;
int m_TileSIndex;
int m_TileSFlags;
int m_TileIndexL;
int m_TileFlagsL;
int m_TileFIndexL;
int m_TileFFlagsL;
int m_TileSIndexL;
int m_TileSFlagsL;
int m_TileIndexR;
int m_TileFlagsR;
int m_TileFIndexR;
int m_TileFFlagsR;
int m_TileSIndexR;
int m_TileSFlagsR;
int m_TileIndexT;
int m_TileFlagsT;
int m_TileFIndexT;
int m_TileFFlagsT;
int m_TileSIndexT;
int m_TileSFlagsT;
int m_TileIndexB;
int m_TileFlagsB;
int m_TileFIndexB;
int m_TileFFlagsB;
int m_TileSIndexB;
int m_TileSFlagsB;
bool m_LastRefillJumps;
// Setters/Getters because i don't want to modify vanilla vars access modifiers
int GetLastWeapon() { return m_LastWeapon; };
void SetLastWeapon(int LastWeap) {m_LastWeapon = LastWeap; };
int GetActiveWeapon() { return m_Core.m_ActiveWeapon; };
void SetActiveWeapon(int ActiveWeap) {m_Core.m_ActiveWeapon = ActiveWeap; };
CCharacterCore GetCore() { return m_Core; };
void SetCore(CCharacterCore Core) {m_Core = Core; };
CCharacterCore* Core() { return &m_Core; };
bool GetWeaponGot(int Type) { return m_aWeapons[Type].m_Got; };
void SetWeaponGot(int Type, bool Value) { m_aWeapons[Type].m_Got = Value; };
int GetWeaponAmmo(int Type) { return m_aWeapons[Type].m_Ammo; };
void SetWeaponAmmo(int Type, int Value) { m_aWeapons[Type].m_Ammo = Value; };
void SetNinjaActivationDir(vec2 ActivationDir) { m_Ninja.m_ActivationDir = ActivationDir; };
void SetNinjaActivationTick(int ActivationTick) { m_Ninja.m_ActivationTick = ActivationTick; };
void SetNinjaCurrentMoveTime(int CurrentMoveTime) { m_Ninja.m_CurrentMoveTime = CurrentMoveTime; };
int GetCID() { return m_ID; }
void SetInput(CNetObj_PlayerInput *pNewInput) { m_LatestInput = m_Input = *pNewInput; };
int GetJumped() { return m_Core.m_Jumped; }
CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended = 0);
void Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal);
void SetCoreWorld(CGameWorld *pGameWorld);
int m_LastSnapWeapon;
int m_LastJetpackStrength;
bool m_KeepHooked;
int m_GameTeam;
bool Match(CCharacter *pChar);
CCharacter() { m_Alive = false; }
private:
// weapon info
int m_aHitObjects[10];
int m_NumObjectsHit;
struct WeaponStat
{
int m_AmmoRegenStart;
int m_Ammo;
int m_Ammocost;
bool m_Got;
} m_aWeapons[NUM_WEAPONS];
int m_LastWeapon;
int m_QueuedWeapon;
int m_ReloadTimer;
// these are non-heldback inputs
CNetObj_PlayerInput m_LatestPrevInput;
CNetObj_PlayerInput m_LatestInput;
// input
CNetObj_PlayerInput m_PrevInput;
CNetObj_PlayerInput m_Input;
CNetObj_PlayerInput m_SavedInput;
int m_NumInputs;
// ninja
struct NinjaStat
{
vec2 m_ActivationDir;
int m_ActivationTick;
int m_CurrentMoveTime;
int m_OldVelAmount;
} m_Ninja;
// the player core for the physics
CCharacterCore m_Core;
// DDRace
void HandleTiles(int Index);
void HandleSkippableTiles(int Index);
void DDRaceTick();
void DDRacePostCoreTick();
};
enum
{
DDRACE_NONE = 0,
DDRACE_STARTED,
DDRACE_CHEAT, // no time and won't start again unless ordered by a mod or death
DDRACE_FINISHED
};
#endif

View 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;
}

View file

@ -0,0 +1,43 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_PREDICTION_ENTITIES_LASER_H
#define GAME_CLIENT_PREDICTION_ENTITIES_LASER_H
#include <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();
const vec2 &GetFrom() { return m_From; }
const int &GetOwner() { return m_Owner; }
const int &GetEvalTick() { return m_EvalTick; }
CLaser(CGameWorld *pGameWorld, int ID, CNetObj_Laser *pLaser);
void FillInfo(CNetObj_Laser *pLaser);
bool Match(CLaser *pLaser);
protected:
bool HitCharacter(vec2 From, vec2 To);
void DoBounce();
private:
vec2 m_From;
vec2 m_Dir;
vec2 m_TelePos;
bool m_WasTele;
float m_Energy;
int m_Bounces;
int m_EvalTick;
int m_Owner;
// DDRace
vec2 m_PrevPos;
int m_Type;
};
#endif

View 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;
}

View file

@ -0,0 +1,29 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_PREDICTION_ENTITIES_PICKUP_H
#define GAME_CLIENT_PREDICTION_ENTITIES_PICKUP_H
#include <game/client/prediction/entity.h>
const int PickupPhysSize = 14;
class CPickup : public CEntity
{
public:
virtual void Tick();
CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup);
void FillInfo(CNetObj_Pickup *pPickup);
bool Match(CPickup *pPickup);
private:
int m_Type;
int m_Subtype;
// DDRace
void Move();
vec2 m_Core;
};
#endif

View 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);
}

View file

@ -0,0 +1,64 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_PREDICTION_ENTITIES_PROJECTILE_H
#define GAME_CLIENT_PREDICTION_ENTITIES_PROJECTILE_H
#include <game/client/prediction/entity.h>
#include "character.h"
#include <game/extrainfo.h>
class CProjectile : public CEntity
{
friend class CGameWorld;
friend class CItems;
public:
CProjectile
(
CGameWorld *pGameWorld,
int Type,
int Owner,
vec2 Pos,
vec2 Dir,
int Span,
bool Freeeze,
bool Explosive,
float Force,
int SoundImpact,
int Weapon,
int Layer = 0,
int Number = 0
);
vec2 GetPos(float Time);
void FillInfo(CNetObj_Projectile *pProj);
virtual void Tick();
bool Match(CProjectile *pProj);
void SetBouncing(int Value);
void FillExtraInfo(CNetObj_Projectile *pProj);
const vec2 &GetDirection() { return m_Direction; }
const int &GetOwner() { return m_Owner; }
const int &GetStartTick() { return m_StartTick; }
CProjectile(CGameWorld *pGameWorld, int ID, CNetObj_Projectile *pProj);
virtual int NetworkClipped(vec2 ViewPos);
private:
vec2 m_Direction;
int m_LifeSpan;
int m_Owner;
int m_Type;
int m_SoundImpact;
int m_Weapon;
float m_Force;
int m_StartTick;
bool m_Explosive;
// DDRace
int m_Bouncing;
bool m_Freeze;
};
#endif

View 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;
}

View 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

View 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, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal)
{
CCharacter *pChar;
if((pChar = (CCharacter*) GetEntity(ObjID, ENTTYPE_CHARACTER)))
{
pChar->Read(pCharObj, pExtended, IsLocal);
pChar->Keep();
}
else
pChar = new CCharacter(this, ObjID, pCharObj, pExtended);
if(pChar)
pChar->m_GameTeam = GameTeam;
}
void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData)
{
if(ObjType == NETOBJTYPE_PROJECTILE && m_WorldConfig.m_PredictWeapons)
{
CProjectile NetProj = CProjectile(this, ObjID, (CNetObj_Projectile*) pObjData);
if(NetProj.m_Type != WEAPON_SHOTGUN && fabs(length(NetProj.m_Direction) - 1.f) > 0.02f) // workaround to skip grenades on ball mod
return;
if(CProjectile *pProj = (CProjectile*) GetEntity(ObjID, ENTTYPE_PROJECTILE))
{
if(NetProj.Match(pProj))
{
pProj->Keep();
if(pProj->m_Type == WEAPON_SHOTGUN && m_WorldConfig.m_IsDDRace)
pProj->m_LifeSpan = 20 * GameTickSpeed() - (GameTick() - pProj->m_StartTick);
return;
}
}
for(CProjectile *pProj = (CProjectile*) FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile*) pProj->TypeNext())
{
if(pProj->m_ID == -1 && NetProj.GetOwner() < 0 && NetProj.Match(pProj))
{
pProj->m_ID = ObjID;
pProj->Keep();
return;
}
}
CEntity *pEnt = new CProjectile(NetProj);
InsertEntity(pEnt);
}
else if(ObjType == NETOBJTYPE_PICKUP && m_WorldConfig.m_PredictWeapons)
{
CPickup NetPickup = CPickup(this, ObjID, (CNetObj_Pickup*) pObjData);
if(CPickup *pPickup = (CPickup*) GetEntity(ObjID, ENTTYPE_PICKUP))
{
if(NetPickup.Match(pPickup))
{
pPickup->m_Pos = NetPickup.m_Pos;
pPickup->Keep();
return;
}
}
CEntity *pEnt = new CPickup(NetPickup);
InsertEntity(pEnt, true);
}
else if(ObjType == NETOBJTYPE_LASER && m_WorldConfig.m_PredictWeapons)
{
CLaser NetLaser = CLaser(this, ObjID, (CNetObj_Laser*) pObjData);
if(CLaser *pLaser = (CLaser*) GetEntity(ObjID, ENTTYPE_LASER))
{
if(NetLaser.Match(pLaser))
{
pLaser->Keep();
if(distance(pLaser->m_Pos, NetLaser.m_Pos) > 2.f)
{
pLaser->m_Energy = 0.f;
pLaser->m_Pos = NetLaser.m_Pos;
}
}
}
for(CLaser *pLaser = (CLaser*) FindFirst(CGameWorld::ENTTYPE_LASER); pLaser; pLaser = (CLaser*) pLaser->TypeNext())
{
if(pLaser->m_ID == -1 && NetLaser.Match(pLaser))
{
pLaser->m_ID = ObjID;
pLaser->Keep();
return;
}
}
CEntity *pEnt = new CLaser(NetLaser);
InsertEntity(pEnt, true);
}
}
void CGameWorld::NetObjEnd(int LocalID)
{
// keep predicting hooked characters, based on hook position
for(int i = 0; i < MAX_CLIENTS; i++)
if(CCharacter *pChar = GetCharacterByID(i))
if(!pChar->m_MarkedForDestroy)
if(CCharacter *pHookedChar = GetCharacterByID(pChar->m_Core.m_HookedPlayer))
if(pHookedChar->m_MarkedForDestroy)
{
pHookedChar->m_Pos = pHookedChar->m_Core.m_Pos = pChar->m_Core.m_HookPos;
pHookedChar->m_Core.m_Vel = vec2(0,0);
mem_zero(&pHookedChar->m_SavedInput, sizeof(pHookedChar->m_SavedInput));
pHookedChar->m_KeepHooked = true;
pHookedChar->m_MarkedForDestroy = false;
}
// keep entities that are out of view for a short while, for some entity types
if(CCharacter *pLocal = GetCharacterByID(LocalID))
for(int i : {ENTTYPE_CHARACTER, ENTTYPE_PROJECTILE, ENTTYPE_LASER})
for(CEntity *pEnt = FindFirst(i); pEnt; pEnt=pEnt->TypeNext())
if(pEnt->m_MarkedForDestroy)
if(pEnt->m_SnapTicks < 2 * SERVER_TICK_SPEED || (i != ENTTYPE_CHARACTER && pEnt->m_SnapTicks < 5 * SERVER_TICK_SPEED))
if(pEnt->NetworkClipped(pLocal->m_Core.m_Pos))
pEnt->m_MarkedForDestroy = false;
RemoveEntities();
// Update character IDs and pointers
for(int i = 0; i < MAX_CLIENTS; i++)
{
m_apCharacters[i] = 0;
m_Core.m_apCharacters[i] = 0;
}
for(CCharacter *pChar = (CCharacter*) FindFirst(ENTTYPE_CHARACTER); pChar; pChar = (CCharacter*) pChar->TypeNext())
{
int ID = pChar->GetCID();
if(ID >= 0 && ID < MAX_CLIENTS)
{
m_apCharacters[ID] = pChar;
m_Core.m_apCharacters[ID] = pChar->Core();
}
}
}
void CGameWorld::CopyWorld(CGameWorld *pFrom)
{
if(pFrom == this || !pFrom)
return;
m_IsValidCopy = false;
m_pParent = pFrom;
if(m_pParent && m_pParent->m_pChild && m_pParent->m_pChild != this)
m_pParent->m_pChild->m_IsValidCopy = false;
pFrom->m_pChild = this;
m_GameTick = pFrom->m_GameTick;
m_GameTickSpeed = pFrom->m_GameTickSpeed;
m_pCollision = pFrom->m_pCollision;
m_WorldConfig = pFrom->m_WorldConfig;
for(int i = 0; i < 2; i++)
m_Core.m_Tuning[i] = pFrom->m_Core.m_Tuning[i];
m_pTeams = pFrom->m_pTeams;
// delete the previous entities
for(int i = 0; i < NUM_ENTTYPES; i++)
while(m_apFirstEntityTypes[i])
delete m_apFirstEntityTypes[i];
for(int i = 0; i < MAX_CLIENTS; i++)
{
m_apCharacters[i] = 0;
m_Core.m_apCharacters[i] = 0;
}
// copy and add the new entities
for(int Type = 0; Type < NUM_ENTTYPES; Type++)
{
for(CEntity *pEnt = pFrom->FindLast(Type); pEnt; pEnt = pEnt->TypePrev())
{
CEntity *pCopy = 0;
if(Type == ENTTYPE_PROJECTILE)
pCopy = new CProjectile(*((CProjectile*)pEnt));
else if(Type == ENTTYPE_LASER)
pCopy = new CLaser(*((CLaser*)pEnt));
else if(Type == ENTTYPE_CHARACTER)
pCopy = new CCharacter(*((CCharacter*)pEnt));
else if(Type == ENTTYPE_PICKUP)
pCopy = new CPickup(*((CPickup*)pEnt));
if(pCopy)
{
pCopy->m_pParent = pEnt;
this->InsertEntity(pCopy);
}
}
}
m_IsValidCopy = true;
}
CEntity *CGameWorld::FindMatch(int ObjID, int ObjType, const void *pObjData)
{
#define FindType(EntType, EntClass, ObjClass) \
{ \
CEntity *pEnt = GetEntity(ObjID, EntType); \
if(pEnt && EntClass(this, ObjID, (ObjClass*) pObjData).Match((EntClass*)pEnt)) \
return pEnt; \
return 0; \
}
switch(ObjType)
{
case NETOBJTYPE_CHARACTER: FindType(ENTTYPE_CHARACTER, CCharacter, CNetObj_Character);
case NETOBJTYPE_PROJECTILE: FindType(ENTTYPE_PROJECTILE, CProjectile, CNetObj_Projectile);
case NETOBJTYPE_LASER: FindType(ENTTYPE_LASER, CLaser, CNetObj_Laser);
case NETOBJTYPE_PICKUP: FindType(ENTTYPE_PICKUP, CPickup, CNetObj_Pickup);
}
return 0;
}
void CGameWorld::OnModified()
{
if(m_pChild)
m_pChild->m_IsValidCopy = false;
}
void CGameWorld::Clear()
{
// delete all entities
for(int i = 0; i < NUM_ENTTYPES; i++)
while(m_apFirstEntityTypes[i])
delete m_apFirstEntityTypes[i];
}

View file

@ -0,0 +1,136 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_PREDICTION_GAMEWORLD_H
#define GAME_CLIENT_PREDICTION_GAMEWORLD_H
#include <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
};
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, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal);
void NetObjAdd(int ObjID, int ObjType, const void *pObjData);
void NetObjEnd(int LocalID);
void CopyWorld(CGameWorld *pFrom);
CEntity *FindMatch(int ObjID, int ObjType, const void *pObjData);
void Clear();
private:
void RemoveEntities();
CEntity *m_pNextTraverseEntity;
CEntity *m_apFirstEntityTypes[NUM_ENTTYPES];
class CCharacter *m_apCharacters[MAX_CLIENTS];
};
class CCharOrder
{
public:
std::list<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

View file

@ -43,12 +43,6 @@ enum
DIALOG_FILE,
};
struct CEntity
{
CPoint m_Position;
int m_Type;
};
class CEnvelope
{
public:

View file

@ -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

View file

@ -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);

View file

@ -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();
}

View file

@ -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")