Add dragger prediction

This commit is contained in:
trml 2023-06-03 18:51:38 +02:00
parent 2d3221492d
commit 8121abf877
5 changed files with 253 additions and 25 deletions

View file

@ -2260,6 +2260,8 @@ if(CLIENT)
pickup_data.h
prediction/entities/character.cpp
prediction/entities/character.h
prediction/entities/dragger.cpp
prediction/entities/dragger.h
prediction/entities/laser.cpp
prediction/entities/laser.h
prediction/entities/pickup.cpp

View file

@ -0,0 +1,161 @@
/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
#include "dragger.h"
#include "character.h"
#include <engine/shared/config.h>
#include <game/client/laser_data.h>
#include <game/collision.h>
#include <game/generated/protocol.h>
#include <game/mapitems.h>
void CDragger::Tick()
{
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;
LookForPlayersToDrag();
}
DraggerBeamTick();
}
void CDragger::LookForPlayersToDrag()
{
// Create a list of players who are in the range of the dragger
CEntity *apPlayersInRange[MAX_CLIENTS];
mem_zero(apPlayersInRange, sizeof(apPlayersInRange));
int NumPlayersInRange = GameWorld()->FindEntities(m_Pos,
g_Config.m_SvDraggerRange - CCharacterCore::PhysicalSize(),
apPlayersInRange, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
// The closest player (within range) in a team is selected as the target
int ClosestTargetId = -1;
bool CanStillBeTeamTarget = false;
int MinDistInTeam = 0;
for(int i = 0; i < NumPlayersInRange; i++)
{
CCharacter *pTarget = static_cast<CCharacter *>(apPlayersInRange[i]);
const int &TargetTeam = pTarget->Team();
// Do not create a dragger beam for super player
if(TargetTeam == TEAM_SUPER)
{
continue;
}
// If the dragger is disabled for the target's team, no dragger beam will be generated
if(m_Layer == LAYER_SWITCH && m_Number > 0 &&
!Switchers()[m_Number].m_aStatus[TargetTeam])
{
continue;
}
// Dragger beams can be created only for reachable, alive players
int IsReachable =
m_IgnoreWalls ?
!Collision()->IntersectNoLaserNW(m_Pos, pTarget->m_Pos, 0, 0) :
!Collision()->IntersectNoLaser(m_Pos, pTarget->m_Pos, 0, 0);
if(IsReachable)
{
const int &TargetClientId = pTarget->GetCID();
int Distance = distance(pTarget->m_Pos, m_Pos);
if(MinDistInTeam == 0 || MinDistInTeam > Distance)
{
MinDistInTeam = Distance;
ClosestTargetId = TargetClientId;
}
if(TargetClientId == m_TargetId)
{
CanStillBeTeamTarget = true;
}
}
}
// Set the closest player for each team as a target if the team does not have a target player yet
if((m_TargetId != -1 && !CanStillBeTeamTarget) || m_TargetId == -1)
{
m_TargetId = ClosestTargetId;
}
}
void CDragger::DraggerBeamReset()
{
m_TargetId = -1;
}
void CDragger::DraggerBeamTick()
{
CCharacter *pTarget = GameWorld()->GetCharacterByID(m_TargetId);
if(!pTarget)
{
DraggerBeamReset();
return;
}
if(GameWorld()->GameTick() % (int)(GameWorld()->GameTickSpeed() * 0.15f) == 0)
{
if(m_Layer == LAYER_SWITCH && m_Number > 0 &&
!Switchers()[m_Number].m_aStatus[pTarget->Team()])
{
DraggerBeamReset();
return;
}
}
// When the dragger can no longer reach the target player, the dragger beam dissolves
int IsReachable =
m_IgnoreWalls ?
!Collision()->IntersectNoLaserNW(m_Pos, pTarget->m_Pos, 0, 0) :
!Collision()->IntersectNoLaser(m_Pos, pTarget->m_Pos, 0, 0);
if(!IsReachable || distance(pTarget->m_Pos, m_Pos) >= g_Config.m_SvDraggerRange)
{
DraggerBeamReset();
return;
}
// In the center of the dragger a tee does not experience speed-up
else if(distance(pTarget->m_Pos, m_Pos) > 28)
{
vec2 Temp = pTarget->Core()->m_Vel + (normalize(m_Pos - pTarget->m_Pos) * m_Strength);
pTarget->Core()->m_Vel = ClampVel(pTarget->m_MoveRestrictions, Temp);
}
}
CDragger::CDragger(CGameWorld *pGameWorld, int ID, const CLaserData *pData) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_DRAGGER)
{
m_Core = vec2(0.f, 0.f);
m_ID = ID;
m_TargetId = -1;
m_Strength = 0;
m_IgnoreWalls = false;
if(0 <= pData->m_Subtype && pData->m_Subtype < NUM_LASERDRAGGERTYPES)
{
m_IgnoreWalls = (pData->m_Subtype & 1);
m_Strength = (pData->m_Subtype >> 1) + 1;
}
m_Number = pData->m_SwitchNumber;
m_Layer = m_Number > 0 ? LAYER_SWITCH : LAYER_GAME;
Read(pData);
}
void CDragger::Read(const CLaserData *pData)
{
m_Pos = pData->m_From;
m_TargetId = pData->m_Owner;
}
bool CDragger::Match(CDragger *pDragger)
{
return pDragger->m_Strength == m_Strength && pDragger->m_Number == m_Number && pDragger->m_IgnoreWalls == m_IgnoreWalls;
}

View file

@ -0,0 +1,29 @@
/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
#ifndef GAME_CLIENT_PREDICTION_ENTITIES_DRAGGER_H
#define GAME_CLIENT_PREDICTION_ENTITIES_DRAGGER_H
#include <game/client/prediction/entity.h>
class CLaserData;
class CDragger : public CEntity
{
vec2 m_Core;
float m_Strength;
bool m_IgnoreWalls;
int m_TargetId;
void LookForPlayersToDrag();
void DraggerBeamTick();
void DraggerBeamReset();
public:
CDragger(CGameWorld *pGameWorld, int ID, const CLaserData *pData);
bool Match(CDragger *pDragger);
void Read(const CLaserData *pData);
float GetStrength() { return m_Strength; }
void Tick() override;
};
#endif // GAME_CLIENT_PREDICTION_ENTITIES_DRAGGER_H

View file

@ -3,6 +3,7 @@
#include "gameworld.h"
#include "entities/character.h"
#include "entities/dragger.h"
#include "entities/laser.h"
#include "entities/pickup.h"
#include "entities/projectile.h"
@ -497,37 +498,54 @@ void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData, const C
else if((ObjType == NETOBJTYPE_LASER || ObjType == NETOBJTYPE_DDNETLASER) && m_WorldConfig.m_PredictWeapons)
{
CLaserData Data = ExtractLaserInfo(ObjType, pObjData, this, pDataEx);
if((Data.m_Type >= 0 && Data.m_Type != LASERTYPE_RIFLE && Data.m_Type != LASERTYPE_SHOTGUN) || !IsLocalTeam(Data.m_Owner))
{
if(!IsLocalTeam(Data.m_Owner))
return;
}
CLaser NetLaser = CLaser(this, ObjID, &Data);
CLaser *pMatching = 0;
if(CLaser *pLaser = dynamic_cast<CLaser *>(GetEntity(ObjID, ENTTYPE_LASER)))
if(NetLaser.Match(pLaser))
pMatching = pLaser;
if(!pMatching)
if(Data.m_Type == LASERTYPE_RIFLE || Data.m_Type == LASERTYPE_SHOTGUN || Data.m_Type < 0)
{
for(CEntity *pEnt = FindFirst(CGameWorld::ENTTYPE_LASER); pEnt; pEnt = pEnt->TypeNext())
{
auto *const pLaser = dynamic_cast<CLaser *>(pEnt);
if(pLaser && pLaser->m_ID == -1 && NetLaser.Match(pLaser))
{
CLaser NetLaser = CLaser(this, ObjID, &Data);
CLaser *pMatching = 0;
if(CLaser *pLaser = dynamic_cast<CLaser *>(GetEntity(ObjID, ENTTYPE_LASER)))
if(NetLaser.Match(pLaser))
pMatching = pLaser;
pMatching->m_ID = ObjID;
break;
if(!pMatching)
{
for(CEntity *pEnt = FindFirst(CGameWorld::ENTTYPE_LASER); pEnt; pEnt = pEnt->TypeNext())
{
auto *const pLaser = dynamic_cast<CLaser *>(pEnt);
if(pLaser && pLaser->m_ID == -1 && NetLaser.Match(pLaser))
{
pMatching = pLaser;
pMatching->m_ID = ObjID;
break;
}
}
}
if(pMatching)
{
pMatching->Keep();
if(distance(NetLaser.m_From, NetLaser.m_Pos) < distance(pMatching->m_From, pMatching->m_Pos) - 2.f)
{
// if the laser stopped earlier than predicted, set the energy to 0
pMatching->m_Energy = 0.f;
pMatching->m_Pos = NetLaser.m_Pos;
}
}
}
if(pMatching)
else if(Data.m_Type == LASERTYPE_DRAGGER)
{
pMatching->Keep();
if(distance(NetLaser.m_From, NetLaser.m_Pos) < distance(pMatching->m_From, pMatching->m_Pos) - 2.f)
CDragger NetDragger = CDragger(this, ObjID, &Data);
if(NetDragger.GetStrength() > 0)
{
// if the laser stopped earlier than predicted, set the energy to 0
pMatching->m_Energy = 0.f;
pMatching->m_Pos = NetLaser.m_Pos;
auto *pDragger = dynamic_cast<CDragger *>(GetEntity(ObjID, ENTTYPE_DRAGGER));
if(pDragger && NetDragger.Match(pDragger))
{
pDragger->Keep();
pDragger->Read(&Data);
return;
}
CEntity *pEnt = new CDragger(NetDragger);
InsertEntity(pEnt);
}
}
}
@ -606,6 +624,8 @@ void CGameWorld::CopyWorld(CGameWorld *pFrom)
pCopy = new CProjectile(*((CProjectile *)pEnt));
else if(Type == ENTTYPE_LASER)
pCopy = new CLaser(*((CLaser *)pEnt));
else if(Type == ENTTYPE_DRAGGER)
pCopy = new CDragger(*((CDragger *)pEnt));
else if(Type == ENTTYPE_CHARACTER)
pCopy = new CCharacter(*((CCharacter *)pEnt));
else if(Type == ENTTYPE_PICKUP)
@ -650,10 +670,21 @@ CEntity *CGameWorld::FindMatch(int ObjID, int ObjType, const void *pObjData)
case NETOBJTYPE_DDNETLASER:
{
CLaserData Data = ExtractLaserInfo(ObjType, pObjData, this, nullptr);
CLaser *pEnt = (CLaser *)GetEntity(ObjID, ENTTYPE_LASER);
if(pEnt && CLaser(this, ObjID, &Data).Match(pEnt))
if(Data.m_Type == LASERTYPE_RIFLE || Data.m_Type == LASERTYPE_SHOTGUN)
{
return pEnt;
CLaser *pEnt = (CLaser *)GetEntity(ObjID, ENTTYPE_LASER);
if(pEnt && CLaser(this, ObjID, &Data).Match(pEnt))
{
return pEnt;
}
}
else if(Data.m_Type == LASERTYPE_DRAGGER)
{
CDragger *pEnt = (CDragger *)GetEntity(ObjID, ENTTYPE_DRAGGER);
if(pEnt && CDragger(this, ObjID, &Data).Match(pEnt))
{
return pEnt;
}
}
return 0;
}

View file

@ -21,6 +21,11 @@ public:
{
ENTTYPE_PROJECTILE = 0,
ENTTYPE_LASER,
ENTTYPE_DOOR,
ENTTYPE_DRAGGER,
ENTTYPE_LIGHT,
ENTTYPE_GUN,
ENTTYPE_PLASMA,
ENTTYPE_PICKUP,
ENTTYPE_FLAG,
ENTTYPE_CHARACTER,