Merge pull request #112 from trml/prediction_weapons

Prediction weapons
This commit is contained in:
Dennis Felsing 2014-12-02 11:24:52 +01:00
commit b525de33b5
14 changed files with 753 additions and 40 deletions

View file

@ -111,6 +111,7 @@ public:
// input
virtual int *GetInput(int Tick) = 0;
virtual bool InputExists(int Tick) = 0;
// remote console
virtual void RconAuth(const char *pUsername, const char *pPassword) = 0;

View file

@ -595,6 +595,14 @@ int *CClient::GetInput(int Tick)
return 0;
}
bool CClient::InputExists(int Tick)
{
for(int i = 0; i < 200; i++)
if(m_aInputs[g_Config.m_ClDummy][i].m_Tick == Tick)
return true;
return false;
}
// ------ state handling -----
void CClient::SetState(int s)
{
@ -1818,13 +1826,22 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
// add new
m_SnapshotStorage[g_Config.m_ClDummy].Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1);
// for antiping: if the projectile netobjects from the server contains extra data, this is removed and the original content restored before recording demo
unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE];
mem_copy(aExtraInfoRemoved, pTmpBuffer3, SnapSize);
CServerInfo Info;
GetServerInfo(&Info);
bool IsDDNet = str_find_nocase(Info.m_aGameType, "ddracenetw") || str_find_nocase(Info.m_aGameType, "ddnet");
if(IsDDNet)
SnapshotRemoveExtraInfo(aExtraInfoRemoved);
// add snapshot to demo
for(int i = 0; i < RECORDER_MAX; i++)
{
if(m_DemoRecorder[i].IsRecording())
{
// write snapshot
m_DemoRecorder[i].RecordSnapshot(GameTick, pTmpBuffer3, SnapSize);
m_DemoRecorder[i].RecordSnapshot(GameTick, aExtraInfoRemoved, SnapSize);
}
}

View file

@ -236,6 +236,7 @@ public:
// TODO: OPT: do this alot smarter!
virtual int *GetInput(int Tick);
virtual bool InputExists(int Tick);
const char *LatestVersion();
void VersionUpdate();

View file

@ -600,8 +600,12 @@ void CServer::DoSnapshot()
GameServer()->OnSnap(-1);
SnapshotSize = m_SnapshotBuilder.Finish(aData);
// for antiping: if the projectile netobjects contains extra data, this is removed and the original content restored before recording demo
unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE];
mem_copy(aExtraInfoRemoved, aData, SnapshotSize);
SnapshotRemoveExtraInfo(aExtraInfoRemoved);
// write snapshot
m_aDemoRecorder[MAX_CLIENTS].RecordSnapshot(Tick(), aData, SnapshotSize);
m_aDemoRecorder[MAX_CLIENTS].RecordSnapshot(Tick(), aExtraInfoRemoved, SnapshotSize);
}
// create snapshots for all clients
@ -640,7 +644,14 @@ void CServer::DoSnapshot()
SnapshotSize = m_SnapshotBuilder.Finish(pData);
if(m_aDemoRecorder[i].IsRecording())
m_aDemoRecorder[i].RecordSnapshot(Tick(), aData, SnapshotSize);
{
// for antiping: if the projectile netobjects contains extra data, this is removed and the original content restored before recording demo
unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE];
mem_copy(aExtraInfoRemoved, aData, SnapshotSize);
SnapshotRemoveExtraInfo(aExtraInfoRemoved);
// write snapshot
m_aDemoRecorder[i].RecordSnapshot(Tick(), aExtraInfoRemoved, SnapshotSize);
}
Crc = pData->Crc();

View file

@ -104,6 +104,7 @@ enum
VERSION_DDNET_GOODHOOK = 221,
VERSION_DDNET_EXTRATUNES = 302,
VERSION_DDNET_RCONPROTECT = 408,
VERSION_DDNET_ANTIPING_PROJECTILE = 999,
};
#endif

View file

@ -16,6 +16,8 @@
#include "items.h"
#include <stdio.h>
#include <engine/serverbrowser.h>
void CItems::OnReset()
{
m_NumExtraProjectiles = 0;
@ -49,8 +51,14 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
if(Ct < 0)
return; // projectile havn't been shot yet
vec2 StartPos(pCurrent->m_X, pCurrent->m_Y);
vec2 StartVel(pCurrent->m_VelX/100.0f, pCurrent->m_VelY/100.0f);
vec2 StartPos;
vec2 StartVel;
CServerInfo Info;
Client()->GetServerInfo(&Info);
bool IsDDNet = str_find_nocase(Info.m_aGameType, "ddracenetw") || str_find_nocase(Info.m_aGameType, "ddnet");
ExtractInfo(pCurrent, &StartPos, &StartVel, IsDDNet);
vec2 Pos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct);
vec2 PrevPos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct-0.001f);

View file

@ -267,7 +267,7 @@ void CGameClient::OnInit()
m_UI.SetGraphics(Graphics(), TextRender());
m_RenderTools.m_pGraphics = Graphics();
m_RenderTools.m_pUI = UI();
int64 Start = time_get();
// set the language
@ -440,6 +440,9 @@ void CGameClient::OnReset()
m_DDRaceMsgSent[1] = false;
m_ShowOthers[0] = -1;
m_ShowOthers[1] = -1;
for(int i = 0; i < 150; i++)
m_aWeaponData[i].m_Tick = -1;
}
@ -474,7 +477,7 @@ void CGameClient::UpdatePositions()
vec2(m_Snap.m_pLocalPrevCharacter->m_X, m_Snap.m_pLocalPrevCharacter->m_Y),
vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y), Client()->IntraGameTick());
}
if (g_Config.m_ClAntiPing)
{
for (int i = 0; i < MAX_CLIENTS; i++)
@ -536,7 +539,6 @@ static void Evolve(CNetObj_Character *pCharacter, int Tick)
TempCore.Write(pCharacter);
}
void CGameClient::OnRender()
{
/*Graphics()->Clear(1,0,0);
@ -622,6 +624,33 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, bool IsDummy)
return;
g_GameClient.m_pItems->AddExtraProjectile(&Proj);
if(g_Config.m_ClAntiPingWeapons && Proj.m_Type == WEAPON_GRENADE && !UseExtraInfo(&Proj))
{
vec2 StartPos;
vec2 Direction;
ExtractInfo(&Proj, &StartPos, &Direction, 1);
if(CWeaponData *pCurrentData = GetWeaponData(Proj.m_StartTick))
{
if(CWeaponData *pMatchingData = FindWeaponData(Proj.m_StartTick))
{
if(distance(pMatchingData->m_Direction, Direction) < 0.015)
Direction = pMatchingData->m_Direction;
else if(int *pData = Client()->GetInput(Proj.m_StartTick+2))
{
CNetObj_PlayerInput *pNextInput = (CNetObj_PlayerInput*) pData;
vec2 NextDirection = normalize(vec2(pNextInput->m_TargetX, pNextInput->m_TargetY));
if(distance(NextDirection, Direction) < 0.015)
Direction = NextDirection;
}
if(distance(pMatchingData->StartPos(), StartPos) < 1)
StartPos = pMatchingData->StartPos();
}
pCurrentData->m_Tick = Proj.m_StartTick;
pCurrentData->m_Direction = Direction;
pCurrentData->m_Pos = StartPos - Direction * 28.0f * 0.75f;
}
}
}
return;
@ -646,7 +675,7 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, bool IsDummy)
m_Tuning[IsDummy ? !g_Config.m_ClDummy : g_Config.m_ClDummy] = NewTuning;
return;
}
void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgId, pUnpacker);
if(!pRawMsg)
{
@ -1168,6 +1197,7 @@ void CGameClient::OnNewSnapshot()
void CGameClient::OnPredict()
{
// store the previous values so we can detect prediction errors
CCharacterCore BeforePrevChar = m_PredictedPrevChar;
CCharacterCore BeforeChar = m_PredictedChar;
@ -1209,6 +1239,64 @@ void CGameClient::OnPredict()
g_GameClient.m_aClients[i].m_Predicted.m_ActiveWeapon = m_Snap.m_aCharacters[i].m_Cur.m_Weapon;
}
CServerInfo Info;
Client()->GetServerInfo(&Info);
bool IsRace = str_find_nocase(Info.m_aGameType, "race") || str_find_nocase(Info.m_aGameType, "fastcap");
const int MaxProjectiles = 128;
class CLocalProjectile PredictedProjectiles[MaxProjectiles];
int NumProjectiles = 0;
int ReloadTimer = 0;
vec2 PrevPos;
if(g_Config.m_ClAntiPingWeapons)
{
for(int Index = 0; Index < MaxProjectiles; Index++)
PredictedProjectiles[Index].Deactivate();
int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT);
for(int Index = 0; Index < Num && NumProjectiles < MaxProjectiles; Index++)
{
IClient::CSnapItem Item;
const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item);
if(Item.m_Type == NETOBJTYPE_PROJECTILE)
{
CNetObj_Projectile* pProj = (CNetObj_Projectile*) pData;
if(pProj->m_Type == WEAPON_GRENADE || (pProj->m_Type == WEAPON_SHOTGUN && UseExtraInfo(pProj)))
{
CLocalProjectile NewProj;
NewProj.Init(this, &World, Collision(), pProj);
if(fabs(1.0f - length(NewProj.m_Direction)) < 0.015)
{
if(!NewProj.m_ExtraInfo)
{
if(CWeaponData *pData = FindWeaponData(NewProj.m_StartTick))
{
NewProj.m_Pos = pData->StartPos();
NewProj.m_Direction = pData->m_Direction;
NewProj.m_Owner = m_Snap.m_LocalClientID;
}
}
PredictedProjectiles[NumProjectiles] = NewProj;
NumProjectiles++;
}
}
}
}
int AttackTick = m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Cur.m_AttackTick;
if(World.m_apCharacters[m_Snap.m_LocalClientID]->m_ActiveWeapon == WEAPON_HAMMER)
{
CWeaponData *pWeaponData = GetWeaponData(AttackTick);
if(pWeaponData && pWeaponData->m_Tick == AttackTick)
ReloadTimer = SERVER_TICK_SPEED / 3 - (Client()->GameTick() - AttackTick);
else
ReloadTimer = 0;
}
else
ReloadTimer = g_pData->m_Weapons.m_aId[World.m_apCharacters[m_Snap.m_LocalClientID]->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000 - (Client()->GameTick() - AttackTick);
ReloadTimer = max(ReloadTimer, 0);
}
// predict
for(int Tick = Client()->GameTick()+1; Tick <= Client()->PredGameTick(); Tick++)
{
@ -1216,14 +1304,20 @@ void CGameClient::OnPredict()
if(Tick == Client()->PredGameTick() && World.m_apCharacters[m_Snap.m_LocalClientID])
m_PredictedPrevChar = *World.m_apCharacters[m_Snap.m_LocalClientID];
// first calculate where everyone should move
for(int c = 0; c < MAX_CLIENTS; c++)
{
if(!World.m_apCharacters[c])
continue;
if(g_Config.m_ClAntiPing && Tick == Client()->PredGameTick())
g_GameClient.m_aClients[c].m_PrevPredicted = *World.m_apCharacters[c];
}
// input
for(int c = 0; c < MAX_CLIENTS; c++)
{
if(!World.m_apCharacters[c])
continue;
mem_zero(&World.m_apCharacters[c]->m_Input, sizeof(World.m_apCharacters[c]->m_Input));
if(m_Snap.m_LocalClientID == c)
@ -1232,11 +1326,194 @@ void CGameClient::OnPredict()
int *pInput = Client()->GetInput(Tick);
if(pInput)
World.m_apCharacters[c]->m_Input = *((CNetObj_PlayerInput*)pInput);
}
}
if(g_Config.m_ClAntiPingWeapons)
{
const float ProximityRadius = 28.0f;
CNetObj_PlayerInput Input;
CNetObj_PlayerInput PrevInput;
mem_zero(&Input, sizeof(Input));
mem_zero(&PrevInput, sizeof(PrevInput));
int *pInput = Client()->GetInput(Tick);
if(pInput)
Input = *((CNetObj_PlayerInput*)pInput);
int *pPrevInput = Client()->GetInput(Tick-1);
if(pPrevInput)
PrevInput = *((CNetObj_PlayerInput*)pPrevInput);
CCharacterCore *Local = World.m_apCharacters[m_Snap.m_LocalClientID];
vec2 Direction = normalize(vec2(Input.m_TargetX, Input.m_TargetY));
vec2 Pos = Local->m_Pos;
vec2 ProjStartPos = Pos + Direction * ProximityRadius * 0.75f;
bool WeaponFired = false;
bool NewPresses = false;
// handle weapons
do
{
if(ReloadTimer)
break;
if(!World.m_apCharacters[m_Snap.m_LocalClientID])
break;
if(!pInput || !pPrevInput)
break;
bool FullAuto = false;
if(Local->m_ActiveWeapon == WEAPON_GRENADE || Local->m_ActiveWeapon == WEAPON_SHOTGUN || Local->m_ActiveWeapon == WEAPON_RIFLE)
FullAuto = true;
bool WillFire = false;
if(CountInput(PrevInput.m_Fire, Input.m_Fire).m_Presses)
{
WillFire = true;
NewPresses = true;
}
if(FullAuto && (Input.m_Fire&1))
WillFire = true;
if(!WillFire)
break;
if(!IsRace && !m_Snap.m_pLocalCharacter->m_AmmoCount && Local->m_ActiveWeapon != WEAPON_HAMMER)
break;
int ExpectedStartTick = Tick-1;
ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000;
bool DirectInput = Client()->InputExists(Tick);
if(!DirectInput)
{
ReloadTimer++;
ExpectedStartTick++;
}
switch(Local->m_ActiveWeapon)
{
case WEAPON_RIFLE:
case WEAPON_SHOTGUN:
case WEAPON_GUN:
{
WeaponFired = true;
} break;
case WEAPON_GRENADE:
{
if(NumProjectiles >= MaxProjectiles)
break;
PredictedProjectiles[NumProjectiles].Init(
this, &World, Collision(),
Direction, //StartDir
ProjStartPos, //StartPos
ExpectedStartTick, //StartTick
WEAPON_GRENADE, //Type
m_Snap.m_LocalClientID, //Owner
WEAPON_GRENADE, //Weapon
1, 0, 0, 1); //Explosive, Bouncing, Freeze, ExtraInfo
NumProjectiles++;
WeaponFired = true;
} break;
case WEAPON_HAMMER:
{
vec2 ProjPos = ProjStartPos;
float Radius = ProximityRadius*0.5f;
int Hits = 0;
bool OwnerCanProbablyHitOthers = (m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_Tuning[g_Config.m_ClDummy].m_PlayerHooking);
if(!OwnerCanProbablyHitOthers)
break;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!World.m_apCharacters[i])
continue;
if(i == m_Snap.m_LocalClientID)
continue;
if(!(distance(World.m_apCharacters[i]->m_Pos, ProjPos) < Radius+ProximityRadius))
continue;;
CCharacterCore *pTarget = World.m_apCharacters[i];
if(m_aClients[i].m_Active && !m_Teams.CanCollide(i, m_Snap.m_LocalClientID))
continue;
vec2 Dir;
if (length(pTarget->m_Pos - Pos) > 0.0f)
Dir = normalize(pTarget->m_Pos - Pos);
else
Dir = vec2(0.f, -1.f);
float Strength;
Strength = World.m_Tuning[g_Config.m_ClDummy].m_HammerStrength;
vec2 Temp = pTarget->m_Vel + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f;
pTarget->LimitForce(&Temp);
Temp -= pTarget->m_Vel;
pTarget->ApplyForce((vec2(0.f, -1.0f) + Temp) * Strength);
Hits++;
}
// if we Hit anything, we have to wait for the reload
if(Hits)
{
ReloadTimer = SERVER_TICK_SPEED/3;
WeaponFired = true;
}
} break;
}
if(!ReloadTimer)
{
ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000;
if(!DirectInput)
ReloadTimer++;
}
} while(false);
if(ReloadTimer)
ReloadTimer--;
if(Tick > Client()->GameTick()+1)
if(CWeaponData *pWeaponData = GetWeaponData(Tick-1))
pWeaponData->m_Pos = Pos;
if(WeaponFired)
{
if(CWeaponData *pWeaponData = GetWeaponData(Tick-1))
{
pWeaponData->m_Direction = Direction;
pWeaponData->m_Tick = Tick-1;
}
if(NewPresses)
{
if(CWeaponData *pWeaponData = GetWeaponData(Tick-2))
{
pWeaponData->m_Direction = Direction;
pWeaponData->m_Tick = Tick-2;
}
if(CWeaponData *pWeaponData = GetWeaponData(Tick))
{
pWeaponData->m_Direction = Direction;
pWeaponData->m_Tick = Tick;
}
}
}
// projectiles
for(int g = 0; g < MaxProjectiles; g++)
if(PredictedProjectiles[g].m_Active)
PredictedProjectiles[g].Tick(Tick, Client()->GameTickSpeed(), m_Snap.m_LocalClientID);
}
// first calculate where everyone should move
for(int c = 0; c < MAX_CLIENTS; c++)
{
if(!World.m_apCharacters[c])
continue;
if(m_Snap.m_LocalClientID == c)
{
World.m_apCharacters[c]->Tick(true, true);
}
else
World.m_apCharacters[c]->Tick(false, true);
}
// move all players and quantize their data
@ -1281,6 +1558,7 @@ void CGameClient::OnPredict()
}
}
if(Tick == Client()->PredGameTick() && World.m_apCharacters[m_Snap.m_LocalClientID])
{
m_PredictedChar = *World.m_apCharacters[m_Snap.m_LocalClientID];
@ -1526,3 +1804,222 @@ int CGameClient::IntersectCharacter(vec2 HookPos, vec2 NewPos, vec2& NewPos2, in
return ClosestID;
}
int CGameClient::IntersectCharacter(vec2 OldPos, vec2 NewPos, float Radius, vec2 *NewPos2, int ownID, CWorldCore *World)
{
float PhysSize = 28.0f;
float Distance = 0.0f;
int ClosestID = -1;
if(!World)
return ClosestID;
for (int i=0; i<MAX_CLIENTS; i++)
{
if(!World->m_apCharacters[i])
continue;
CClientData cData = m_aClients[i];
if(!cData.m_Active || i == ownID || !m_Teams.CanCollide(i, ownID))
continue;
vec2 Position = World->m_apCharacters[i]->m_Pos;
vec2 ClosestPoint = closest_point_on_line(OldPos, NewPos, Position);
if(distance(Position, ClosestPoint) < PhysSize+Radius)
{
if(ClosestID == -1 || distance(OldPos, Position) < Distance)
{
*NewPos2 = ClosestPoint;
ClosestID = i;
Distance = distance(OldPos, Position);
}
}
}
return ClosestID;
}
void CLocalProjectile::Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, const CNetObj_Projectile *pProj)
{
m_Active = 1;
m_pGameClient = pGameClient;
m_pWorld = pWorld;
m_pCollision = pCollision;
m_StartTick = pProj->m_StartTick;
m_Type = pProj->m_Type;
m_Weapon = m_Type;
ExtractInfo(pProj, &m_Pos, &m_Direction, 1);
if(UseExtraInfo(pProj))
{
ExtractExtraInfo(pProj, &m_Owner, &m_Explosive, &m_Bouncing, &m_Freeze);
m_ExtraInfo = true;
}
else
{
bool StandardVel = (fabs(1.0f - length(m_Direction)) < 0.015);
m_Owner = -1;
m_Explosive = ((m_Type == WEAPON_GRENADE && StandardVel) ? true : false);
m_Bouncing = 0;
m_Freeze = 0;
m_ExtraInfo = false;
}
}
void CLocalProjectile::Init(CGameClient *pGameClient, CWorldCore *pWorld, CCollision *pCollision, vec2 Direction, vec2 Pos, int StartTick, int Type, int Owner, int Weapon, bool Explosive, int Bouncing, bool Freeze, bool ExtraInfo)
{
m_Active = 1;
m_pGameClient = pGameClient;
m_pWorld = pWorld;
m_pCollision = pCollision;
m_Direction = Direction;
m_Pos = Pos;
m_StartTick = StartTick;
m_Type = Type;
m_Weapon = Weapon;
m_Owner = Owner;
m_Explosive = Explosive;
m_Bouncing = Bouncing;
m_Freeze = Freeze;
m_ExtraInfo = ExtraInfo;
}
vec2 CLocalProjectile::GetPos(float Time)
{
float Curvature = 0;
float Speed = 0;
switch(m_Type)
{
case WEAPON_GRENADE:
Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GrenadeCurvature;
Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GrenadeSpeed;
break;
case WEAPON_SHOTGUN:
Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ShotgunCurvature;
Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ShotgunSpeed;
break;
case WEAPON_GUN:
Curvature = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GunCurvature;
Speed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GunSpeed;
break;
}
return CalcPos(m_Pos, m_Direction, Curvature, Speed, Time);
}
bool CLocalProjectile::GameLayerClipped(vec2 CheckPos)
{
return round_to_int(CheckPos.x)/32 < -200 || round_to_int(CheckPos.x)/32 > m_pCollision->GetWidth()+200 ||
round_to_int(CheckPos.y)/32 < -200 || round_to_int(CheckPos.y)/32 > m_pCollision->GetHeight()+200 ? true : false;
}
void CLocalProjectile::Tick(int CurrentTick, int GameTickSpeed, int LocalClientID)
{
if(!m_pWorld)
return;
if(CurrentTick <= m_StartTick)
return;
float Pt = (CurrentTick-m_StartTick-1)/(float)GameTickSpeed;
float Ct = (CurrentTick-m_StartTick)/(float)GameTickSpeed;
vec2 PrevPos = GetPos(Pt);
vec2 CurPos = GetPos(Ct);
vec2 ColPos;
vec2 NewPos;
int Collide = 0;
if(m_pCollision)
Collide = m_pCollision->IntersectLine(PrevPos, CurPos, &ColPos, &NewPos, false);
int Target = m_pGameClient->IntersectCharacter(PrevPos, ColPos, m_Freeze ? 1.0f : 6.0f, &ColPos, m_Owner, m_pWorld);
bool isWeaponCollide = false;
if(m_Owner >= 0 && Target >= 0 && m_pGameClient->m_aClients[m_Owner].m_Active && m_pGameClient->m_aClients[Target].m_Active && !m_pGameClient->m_Teams.CanCollide(m_Owner, Target))
isWeaponCollide = true;
bool OwnerCanProbablyHitOthers = (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking);
if(((Target >= 0 && (m_Owner >= 0 ? OwnerCanProbablyHitOthers : 1 || Target == m_Owner)) || Collide || GameLayerClipped(CurPos)) && !isWeaponCollide)
{
if(m_Explosive && (Target < 0 || (Target >= 0 && (!m_Freeze || (m_Weapon == WEAPON_SHOTGUN && Collide)))))
CreateExplosion(ColPos, m_Owner);
if(Collide && m_Bouncing != 0)
{
m_StartTick = CurrentTick;
m_Pos = NewPos+(-(m_Direction*4));
if (m_Bouncing == 1)
m_Direction.x = -m_Direction.x;
else if(m_Bouncing == 2)
m_Direction.y =- m_Direction.y;
if (fabs(m_Direction.x) < 1e-6)
m_Direction.x = 0;
if (fabs(m_Direction.y) < 1e-6)
m_Direction.y = 0;
m_Pos += m_Direction;
}
else if(!m_Bouncing)
Deactivate();
}
if(!m_Bouncing)
{
int Lifetime = 0;
if(m_Weapon == WEAPON_GRENADE)
Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED;
else if(m_Weapon == WEAPON_GUN)
Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED;
else if(m_Weapon == WEAPON_SHOTGUN)
Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunLifetime * SERVER_TICK_SPEED;
int LifeSpan = Lifetime - (CurrentTick - m_StartTick);
if(LifeSpan == -1)
{
if(m_Explosive)
CreateExplosion(ColPos, LocalClientID);
Deactivate();
}
}
}
void CLocalProjectile::CreateExplosion(vec2 Pos, int LocalClientID)
{
if(!m_pWorld)
return;
float Radius = 135.0f;
float InnerRadius = 48.0f;
bool OwnerCanProbablyHitOthers = (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking);
for(int c = 0; c < MAX_CLIENTS; c++)
{
if(!m_pWorld->m_apCharacters[c])
continue;
if(m_Owner >= 0 && c >= 0)
if(m_pGameClient->m_aClients[c].m_Active && !m_pGameClient->m_Teams.CanCollide(c, m_Owner))
continue;
if(c != LocalClientID && !OwnerCanProbablyHitOthers)
continue;
vec2 Diff = m_pWorld->m_apCharacters[c]->m_Pos - Pos;
vec2 ForceDir(0,1);
float l = length(Diff);
if(l)
ForceDir = normalize(Diff);
l = 1-clamp((l-InnerRadius)/(Radius-InnerRadius), 0.0f, 1.0f);
float Strength = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_ExplosionStrength;
float Dmg = Strength * l;
if((int)Dmg)
m_pWorld->m_apCharacters[c]->ApplyForce(ForceDir*Dmg*2);
}
}
CWeaponData *CGameClient::FindWeaponData(int TargetTick)
{
CWeaponData *pData;
int TickDiff[3] = {0, -1, 1};
for(unsigned int i = 0; i < sizeof(TickDiff)/sizeof(int); i++)
if((pData = GetWeaponData(TargetTick + TickDiff[i])))
if(pData->m_Tick == TargetTick + TickDiff[i])
return GetWeaponData(TargetTick + TickDiff[i]);
return NULL;
}

View file

@ -22,6 +22,46 @@
: \
((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
@ -283,6 +323,11 @@ 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);
private:
@ -350,4 +395,5 @@ inline vec3 RgbToHsl(vec3 RGB)
extern const char *Localize(const char *Str);
#endif

View file

@ -704,3 +704,89 @@ bool CCharacterCore::IsRightTeam(int MapIndex)
return Collision()->m_pSwitchers[Collision()->GetDTileNumber(MapIndex)].m_Status[m_pTeams->Team(m_Id)];
return false;
}
void CCharacterCore::LimitForce(vec2 *Force)
{
vec2 Temp = *Force;
if(Temp.x > 0 && ((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)))
Temp.x = 0;
if(Temp.x < 0 && ((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)))
Temp.x = 0;
if(Temp.y < 0 && ((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)))
Temp.y = 0;
if(Temp.y > 0 && ((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)))
Temp.y = 0;
*Force = Temp;
}
void CCharacterCore::ApplyForce(vec2 Force)
{
vec2 Temp = m_Vel + Force;
LimitForce(&Temp);
m_Vel = Temp;
}
bool UseExtraInfo(const CNetObj_Projectile *pProj)
{
bool ExtraInfoFlag = ((abs(pProj->m_VelY) & (1<<9)) != 0);
return ExtraInfoFlag;
}
void ExtractInfo(const CNetObj_Projectile *pProj, vec2 *StartPos, vec2 *StartVel, bool IsDDNet)
{
if(!UseExtraInfo(pProj) || !IsDDNet)
{
StartPos->x = pProj->m_X;
StartPos->y = pProj->m_Y;
StartVel->x = pProj->m_VelX/100.0f;
StartVel->y = pProj->m_VelY/100.0f;
}
else
{
StartPos->x = pProj->m_X/100.0f;
StartPos->y = pProj->m_Y/100.0f;
float Angle = pProj->m_VelX/1000000.0f;
StartVel->x = sin(-Angle);
StartVel->y = cos(-Angle);
}
}
void ExtractExtraInfo(const CNetObj_Projectile *pProj, int *Owner, bool *Explosive, int *Bouncing, bool *Freeze)
{
int Data = pProj->m_VelY;
if(Owner)
{
*Owner = Data & 255;
if((Data>>8) & 1)
*Owner = -(*Owner);
}
if(Bouncing)
*Bouncing = (Data>>10) & 3;
if(Explosive)
*Explosive = (Data>>12) & 1;
if(Freeze)
*Freeze = (Data>>13) & 1;
}
void SnapshotRemoveExtraInfo(unsigned char *pData)
{
CSnapshot *pSnap = (CSnapshot*) pData;
for(int Index = 0; Index < pSnap->NumItems(); Index++)
{
CSnapshotItem *pItem = pSnap->GetItem(Index);
if(pItem->Type() == NETOBJTYPE_PROJECTILE)
{
CNetObj_Projectile* pProj = (CNetObj_Projectile*) ((void*)pItem->Data());
if(UseExtraInfo(pProj))
{
vec2 Pos;
vec2 Vel;
ExtractInfo(pProj, &Pos, &Vel, 1);
pProj->m_X = Pos.x;
pProj->m_Y = Pos.y;
pProj->m_VelX = (int)(Vel.x*100.0f);
pProj->m_VelY = (int)(Vel.y*100.0f);
}
}
}
}

View file

@ -233,6 +233,9 @@ public:
int m_Colliding;
bool m_LeftWall;
void LimitForce(vec2 *Force);
void ApplyForce(vec2 Force);
private:
CTeamsCore* m_pTeams;
@ -269,4 +272,35 @@ private:
bool IsRightTeam(int MapIndex);
};
//input count
struct CInputCount
{
int m_Presses;
int m_Releases;
};
inline CInputCount CountInput(int Prev, int Cur)
{
CInputCount c = {0, 0};
Prev &= INPUT_STATE_MASK;
Cur &= INPUT_STATE_MASK;
int i = Prev;
while(i != Cur)
{
i = (i+1)&INPUT_STATE_MASK;
if(i&1)
c.m_Presses++;
else
c.m_Releases++;
}
return c;
}
bool UseExtraInfo(const CNetObj_Projectile *pProj);
void ExtractInfo(const CNetObj_Projectile *pProj, vec2 *StartPos, vec2 *StartVel, bool IsDDNet);
void ExtractExtraInfo(const CNetObj_Projectile *pProj, int *Owner, bool *Explosive, int *Bouncing, bool *Freeze);
void SnapshotRemoveExtraInfo(unsigned char *pData);
#endif

View file

@ -16,33 +16,6 @@
#include <game/server/score.h>
#include "light.h"
//input count
struct CInputCount
{
int m_Presses;
int m_Releases;
};
CInputCount CountInput(int Prev, int Cur)
{
CInputCount c = {0, 0};
Prev &= INPUT_STATE_MASK;
Cur &= INPUT_STATE_MASK;
int i = Prev;
while(i != Cur)
{
i = (i+1)&INPUT_STATE_MASK;
if(i&1)
c.m_Presses++;
else
c.m_Releases++;
}
return c;
}
MACRO_ALLOC_POOL_ID_IMPL(CCharacter, MAX_CLIENTS)
// Character, "physical" player's part

View file

@ -258,7 +258,12 @@ void CProjectile::Snap(int SnappingClient)
CNetObj_Projectile *pProj = static_cast<CNetObj_Projectile *>(Server()->SnapNewItem(NETOBJTYPE_PROJECTILE, m_ID, sizeof(CNetObj_Projectile)));
if(pProj)
FillInfo(pProj);
{
if(SnappingClient > -1 && GameServer()->m_apPlayers[SnappingClient] && GameServer()->m_apPlayers[SnappingClient]->m_ClientVersion >= VERSION_DDNET_ANTIPING_PROJECTILE)
FillExtraInfo(pProj);
else
FillInfo(pProj);
}
}
// DDRace
@ -267,3 +272,34 @@ void CProjectile::SetBouncing(int Value)
{
m_Bouncing = Value;
}
void CProjectile::FillExtraInfo(CNetObj_Projectile *pProj)
{
const int MaxPos = 0x7fffffff/100;
if(abs(m_Pos.y)+1 >= MaxPos || abs(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;
}

View file

@ -53,6 +53,7 @@ private:
public:
void SetBouncing(int Value);
void FillExtraInfo(CNetObj_Projectile *pProj);
};
#endif

View file

@ -9,6 +9,7 @@
MACRO_CONFIG_INT(ClPredict, cl_predict, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Predict client movements")
MACRO_CONFIG_INT(ClAntiPing, cl_antiping, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Antiping (predict other players' movements)")
MACRO_CONFIG_INT(ClAntiPingGrenade, cl_antiping_grenade, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Antiping (predict grenades)")
MACRO_CONFIG_INT(ClAntiPingWeapons, cl_antiping_weapons, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Antiping (predict weapons)")
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")