2019-04-11 22:46:54 +00:00
|
|
|
/* (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 "entities/character.h"
|
|
|
|
#include "entities/laser.h"
|
|
|
|
#include "entities/pickup.h"
|
2020-09-26 19:41:58 +00:00
|
|
|
#include "entities/projectile.h"
|
|
|
|
#include "entity.h"
|
2019-04-11 22:46:54 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <engine/shared/config.h>
|
2021-01-17 16:18:08 +00:00
|
|
|
#include <game/client/projectile_data.h>
|
2022-05-24 21:53:40 +00:00
|
|
|
#include <game/mapitems.h>
|
2020-09-26 19:41:58 +00:00
|
|
|
#include <utility>
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
// game world
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
CGameWorld::CGameWorld()
|
|
|
|
{
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &pFirstEntityType : m_apFirstEntityTypes)
|
|
|
|
pFirstEntityType = 0;
|
|
|
|
for(auto &pCharacter : m_apCharacters)
|
|
|
|
pCharacter = 0;
|
2019-04-11 22:46:54 +00:00
|
|
|
m_pCollision = 0;
|
|
|
|
m_GameTick = 0;
|
|
|
|
m_pParent = 0;
|
|
|
|
m_pChild = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
CGameWorld::~CGameWorld()
|
|
|
|
{
|
|
|
|
// delete all entities
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &pFirstEntityType : m_apFirstEntityTypes)
|
|
|
|
while(pFirstEntityType)
|
|
|
|
delete pFirstEntityType;
|
2019-04-11 22:46:54 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(distance(pEnt->m_Pos, Pos) < Radius + pEnt->m_ProximityRadius)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-04-24 16:33:31 +00:00
|
|
|
if(ppEnts)
|
|
|
|
ppEnts[Num] = pEnt;
|
2019-04-11 22:46:54 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
auto *pChar = (CCharacter *)pEnt;
|
2019-04-11 22:46:54 +00:00
|
|
|
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::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;
|
|
|
|
|
2020-05-01 14:29:06 +00:00
|
|
|
if(m_IsValidCopy && m_pParent && m_pParent->m_pChild == this && pEnt->m_pParent)
|
|
|
|
pEnt->m_pParent->m_DestroyTick = GameTick();
|
|
|
|
pEnt->m_pParent = 0;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
|
2022-05-26 17:04:13 +00:00
|
|
|
void CGameWorld::RemoveCharacter(CCharacter *pChar)
|
|
|
|
{
|
|
|
|
int ID = pChar->GetCID();
|
|
|
|
if(ID >= 0 && ID < MAX_CLIENTS)
|
|
|
|
{
|
|
|
|
m_apCharacters[ID] = 0;
|
|
|
|
m_Core.m_apCharacters[ID] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
void CGameWorld::RemoveEntities()
|
|
|
|
{
|
|
|
|
// destroy objects marked for destruction
|
2020-10-26 13:11:11 +00:00
|
|
|
for(auto *pEnt : m_apFirstEntityTypes)
|
|
|
|
for(; pEnt;)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
|
|
|
|
if(pEnt->m_MarkedForDestroy)
|
|
|
|
{
|
|
|
|
pEnt->Destroy();
|
|
|
|
}
|
|
|
|
pEnt = m_pNextTraverseEntity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
bool distCompare(std::pair<float, int> a, std::pair<float, int> b)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
return (a.first < b.first);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGameWorld::Tick()
|
|
|
|
{
|
|
|
|
// update all objects
|
2020-10-26 13:11:11 +00:00
|
|
|
for(auto *pEnt : m_apFirstEntityTypes)
|
|
|
|
for(; pEnt;)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
|
|
|
|
pEnt->Tick();
|
|
|
|
pEnt = m_pNextTraverseEntity;
|
|
|
|
}
|
|
|
|
|
2020-10-26 13:11:11 +00:00
|
|
|
for(auto *pEnt : m_apFirstEntityTypes)
|
|
|
|
for(; pEnt;)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
|
|
|
|
pEnt->TickDefered();
|
|
|
|
pEnt->m_SnapTicks++;
|
|
|
|
pEnt = m_pNextTraverseEntity;
|
|
|
|
}
|
|
|
|
|
|
|
|
RemoveEntities();
|
|
|
|
|
2022-02-13 20:29:36 +00:00
|
|
|
// update switch state
|
2022-05-01 23:54:29 +00:00
|
|
|
for(auto &Switcher : Switchers())
|
2022-02-13 20:29:36 +00:00
|
|
|
{
|
2022-05-01 23:54:29 +00:00
|
|
|
for(int j = 0; j < MAX_CLIENTS; ++j)
|
2022-02-13 20:29:36 +00:00
|
|
|
{
|
2022-05-01 23:54:29 +00:00
|
|
|
if(Switcher.m_EndTick[j] <= GameTick() && Switcher.m_Type[j] == TILE_SWITCHTIMEDOPEN)
|
2022-02-13 20:29:36 +00:00
|
|
|
{
|
2022-05-01 23:54:29 +00:00
|
|
|
Switcher.m_Status[j] = false;
|
|
|
|
Switcher.m_EndTick[j] = 0;
|
|
|
|
Switcher.m_Type[j] = TILE_SWITCHCLOSE;
|
|
|
|
}
|
|
|
|
else if(Switcher.m_EndTick[j] <= GameTick() && Switcher.m_Type[j] == TILE_SWITCHTIMEDCLOSE)
|
|
|
|
{
|
|
|
|
Switcher.m_Status[j] = true;
|
|
|
|
Switcher.m_EndTick[j] = 0;
|
|
|
|
Switcher.m_Type[j] = TILE_SWITCHOPEN;
|
2022-02-13 20:29:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
OnModified();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: should be more general
|
2020-09-26 19:41:58 +00:00
|
|
|
CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, CCharacter *pNotThis, int CollideWith, class CCharacter *pThisOnly)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
// Find other players
|
2019-12-09 13:58:22 +00:00
|
|
|
float ClosestLen = distance(Pos0, Pos1) * 100.0f;
|
2019-04-11 22:46:54 +00:00
|
|
|
CCharacter *pClosest = 0;
|
|
|
|
|
|
|
|
CCharacter *p = (CCharacter *)FindFirst(ENTTYPE_CHARACTER);
|
|
|
|
for(; p; p = (CCharacter *)p->TypeNext())
|
|
|
|
{
|
|
|
|
if(p == pNotThis)
|
|
|
|
continue;
|
|
|
|
|
2019-10-14 14:03:28 +00:00
|
|
|
if(pThisOnly && p != pThisOnly)
|
|
|
|
continue;
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
if(CollideWith != -1 && !p->CanCollide(CollideWith))
|
|
|
|
continue;
|
|
|
|
|
2020-10-17 18:22:13 +00:00
|
|
|
vec2 IntersectPos;
|
|
|
|
if(closest_point_on_line(Pos0, Pos1, p->m_Pos, IntersectPos))
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2020-10-17 18:22:13 +00:00
|
|
|
float Len = distance(p->m_Pos, IntersectPos);
|
|
|
|
if(Len < p->m_ProximityRadius + Radius)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2020-10-17 18:22:13 +00:00
|
|
|
Len = distance(Pos0, IntersectPos);
|
|
|
|
if(Len < ClosestLen)
|
|
|
|
{
|
|
|
|
NewPos = IntersectPos;
|
|
|
|
ClosestLen = Len;
|
|
|
|
pClosest = p;
|
|
|
|
}
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return pClosest;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::list<class CCharacter *> CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, class CEntity *pNotThis)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
std::list<CCharacter *> listOfChars;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
CCharacter *pChr = (CCharacter *)FindFirst(CGameWorld::ENTTYPE_CHARACTER);
|
|
|
|
for(; pChr; pChr = (CCharacter *)pChr->TypeNext())
|
|
|
|
{
|
|
|
|
if(pChr == pNotThis)
|
|
|
|
continue;
|
|
|
|
|
2020-10-17 18:22:13 +00:00
|
|
|
vec2 IntersectPos;
|
|
|
|
if(closest_point_on_line(Pos0, Pos1, pChr->m_Pos, IntersectPos))
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2020-10-17 18:22:13 +00:00
|
|
|
float Len = distance(pChr->m_Pos, IntersectPos);
|
|
|
|
if(Len < pChr->m_ProximityRadius + Radius)
|
|
|
|
{
|
|
|
|
listOfChars.push_back(pChr);
|
|
|
|
}
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return listOfChars;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGameWorld::ReleaseHooked(int ClientID)
|
|
|
|
{
|
|
|
|
CCharacter *pChr = (CCharacter *)CGameWorld::FindFirst(CGameWorld::ENTTYPE_CHARACTER);
|
|
|
|
for(; pChr; pChr = (CCharacter *)pChr->TypeNext())
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
CCharacterCore *Core = pChr->Core();
|
2019-04-11 22:46:54 +00:00
|
|
|
if(Core->m_HookedPlayer == ClientID)
|
|
|
|
{
|
2022-05-22 19:40:15 +00:00
|
|
|
Core->SetHookedPlayer(-1);
|
2019-04-11 22:46:54 +00:00
|
|
|
Core->m_HookState = HOOK_RETRACTED;
|
|
|
|
Core->m_TriggeredEvents |= COREEVENT_HOOK_RETRACT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CTuningParams *CGameWorld::Tuning()
|
|
|
|
{
|
2021-05-01 02:34:20 +00:00
|
|
|
return &m_Core.m_Tuning[g_Config.m_ClDummy];
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
|
2022-01-22 13:12:59 +00:00
|
|
|
CEntity *CGameWorld::GetEntity(int ID, int EntityType)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2022-01-22 13:12:59 +00:00
|
|
|
for(CEntity *pEnt = m_apFirstEntityTypes[EntityType]; pEnt; pEnt = pEnt->m_pNextTypeEntity)
|
2019-04-11 22:46:54 +00:00
|
|
|
if(pEnt->m_ID == ID)
|
|
|
|
return pEnt;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-06-23 05:05:49 +00:00
|
|
|
void CGameWorld::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage, int ActivatedTeam, int64_t Mask)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-05-11 19:13:09 +00:00
|
|
|
if(Owner < 0 && m_WorldConfig.m_IsSolo && !(Weapon == WEAPON_SHOTGUN && m_WorldConfig.m_IsDDRace))
|
|
|
|
return;
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
// deal damage
|
2020-06-01 19:23:18 +00:00
|
|
|
CEntity *apEnts[MAX_CLIENTS];
|
2019-04-11 22:46:54 +00:00
|
|
|
float Radius = 135.0f;
|
|
|
|
float InnerRadius = 48.0f;
|
2020-09-26 19:41:58 +00:00
|
|
|
int Num = FindEntities(Pos, Radius, (CEntity **)apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
|
2019-04-11 22:46:54 +00:00
|
|
|
for(int i = 0; i < Num; i++)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
CCharacter *pChar = (CCharacter *)apEnts[i];
|
2020-06-01 19:23:18 +00:00
|
|
|
vec2 Diff = pChar->m_Pos - Pos;
|
2020-09-26 19:41:58 +00:00
|
|
|
vec2 ForceDir(0, 1);
|
2019-04-11 22:46:54 +00:00
|
|
|
float l = length(Diff);
|
|
|
|
if(l)
|
|
|
|
ForceDir = normalize(Diff);
|
2020-09-26 19:41:58 +00:00
|
|
|
l = 1 - clamp((l - InnerRadius) / (Radius - InnerRadius), 0.0f, 1.0f);
|
2019-04-11 22:46:54 +00:00
|
|
|
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)
|
2020-09-26 19:41:58 +00:00
|
|
|
if((GetCharacterByID(Owner) ? !(GetCharacterByID(Owner)->m_Hit & CCharacter::DISABLE_HIT_GRENADE) : g_Config.m_SvHit || NoDamage) || Owner == pChar->GetCID())
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2022-05-26 21:06:17 +00:00
|
|
|
if(Owner != -1 && !pChar->CanCollide(Owner))
|
2019-04-11 22:46:54 +00:00
|
|
|
continue;
|
2022-05-26 21:06:17 +00:00
|
|
|
if(Owner == -1 && ActivatedTeam != -1 && pChar->Team() != ActivatedTeam)
|
2019-04-11 22:46:54 +00:00
|
|
|
continue;
|
2020-09-26 19:41:58 +00:00
|
|
|
pChar->TakeDamage(ForceDir * Dmg * 2, (int)Dmg, Owner, Weapon);
|
|
|
|
if(GetCharacterByID(Owner) ? GetCharacterByID(Owner)->m_Hit & CCharacter::DISABLE_HIT_GRENADE : !g_Config.m_SvHit || NoDamage)
|
2019-04-11 22:46:54 +00:00
|
|
|
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)
|
2020-09-26 19:41:58 +00:00
|
|
|
((CCharacter *)pEnt)->m_KeepHooked = false;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
OnModified();
|
|
|
|
}
|
|
|
|
|
2022-03-29 09:17:09 +00:00
|
|
|
void CGameWorld::NetCharAdd(int ObjID, CNetObj_Character *pCharObj, CNetObj_DDNetCharacter *pExtended, CNetObj_DDNetCharacterDisplayInfo *pExtendedDisplayInfo, int GameTeam, bool IsLocal)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
CCharacter *pChar;
|
2020-09-26 19:41:58 +00:00
|
|
|
if((pChar = (CCharacter *)GetEntity(ObjID, ENTTYPE_CHARACTER)))
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2022-03-29 09:17:09 +00:00
|
|
|
pChar->Read(pCharObj, pExtended, pExtendedDisplayInfo, IsLocal);
|
2019-04-11 22:46:54 +00:00
|
|
|
pChar->Keep();
|
|
|
|
}
|
|
|
|
else
|
2022-05-27 22:17:29 +00:00
|
|
|
{
|
2022-03-29 09:17:09 +00:00
|
|
|
pChar = new CCharacter(this, ObjID, pCharObj, pExtended, pExtendedDisplayInfo);
|
2022-05-27 22:17:29 +00:00
|
|
|
InsertEntity(pChar);
|
|
|
|
}
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
if(pChar)
|
|
|
|
pChar->m_GameTeam = GameTeam;
|
|
|
|
}
|
|
|
|
|
2021-09-19 12:14:00 +00:00
|
|
|
void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData, const CNetObj_EntityEx *pDataEx)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2021-01-17 16:18:08 +00:00
|
|
|
if((ObjType == NETOBJTYPE_PROJECTILE || ObjType == NETOBJTYPE_DDNETPROJECTILE) && m_WorldConfig.m_PredictWeapons)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2021-01-17 16:18:08 +00:00
|
|
|
CProjectileData Data;
|
|
|
|
if(ObjType == NETOBJTYPE_PROJECTILE)
|
|
|
|
{
|
2021-04-23 03:01:38 +00:00
|
|
|
Data = ExtractProjectileInfo((const CNetObj_Projectile *)pObjData, this);
|
2021-01-17 16:18:08 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-04-23 03:01:38 +00:00
|
|
|
Data = ExtractProjectileInfoDDNet((const CNetObj_DDNetProjectile *)pObjData, this);
|
2021-01-17 16:18:08 +00:00
|
|
|
}
|
2021-09-19 12:14:00 +00:00
|
|
|
CProjectile NetProj = CProjectile(this, ObjID, &Data, pDataEx);
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
if(NetProj.m_Type != WEAPON_SHOTGUN && fabs(length(NetProj.m_Direction) - 1.f) > 0.02f) // workaround to skip grenades on ball mod
|
|
|
|
return;
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(CProjectile *pProj = (CProjectile *)GetEntity(ObjID, ENTTYPE_PROJECTILE))
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2021-01-17 16:18:08 +00:00
|
|
|
if(!Data.m_ExtraInfo)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-05-11 19:13:09 +00:00
|
|
|
// try to match the newly received (unrecognized) projectile with a locally fired one
|
2020-09-26 19:41:58 +00:00
|
|
|
for(CProjectile *pProj = (CProjectile *)FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile *)pProj->TypeNext())
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-05-11 19:13:09 +00:00
|
|
|
if(pProj->m_ID == -1 && NetProj.Match(pProj))
|
|
|
|
{
|
|
|
|
pProj->m_ID = ObjID;
|
|
|
|
pProj->Keep();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// otherwise try to determine its owner by checking if there is only one player nearby
|
2020-09-26 19:41:58 +00:00
|
|
|
if(NetProj.m_StartTick >= GameTick() - 4)
|
2019-05-11 19:13:09 +00:00
|
|
|
{
|
2022-05-28 17:28:44 +00:00
|
|
|
const vec2 NetPos = NetProj.m_Pos - normalize(NetProj.m_Direction) * CCharacterCore::PhysicalSize() * 0.75;
|
2020-09-26 19:41:58 +00:00
|
|
|
const bool Prev = (GameTick() - NetProj.m_StartTick) > 1;
|
2019-07-08 21:08:42 +00:00
|
|
|
float First = 200.0f, Second = 200.0f;
|
2019-05-11 19:13:09 +00:00
|
|
|
CCharacter *pClosest = 0;
|
2020-09-26 19:41:58 +00:00
|
|
|
for(CCharacter *pChar = (CCharacter *)FindFirst(ENTTYPE_CHARACTER); pChar; pChar = (CCharacter *)pChar->TypeNext())
|
2019-05-11 19:13:09 +00:00
|
|
|
{
|
|
|
|
float Dist = distance(Prev ? pChar->m_PrevPrevPos : pChar->m_PrevPos, NetPos);
|
|
|
|
if(Dist < First)
|
|
|
|
{
|
|
|
|
pClosest = pChar;
|
|
|
|
First = Dist;
|
|
|
|
}
|
|
|
|
else if(Dist < Second)
|
|
|
|
Second = Dist;
|
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
if(pClosest && maximum(First, 2.f) * 1.2f < Second)
|
2019-05-11 19:13:09 +00:00
|
|
|
NetProj.m_Owner = pClosest->m_ID;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-11 19:13:09 +00:00
|
|
|
CProjectile *pProj = new CProjectile(NetProj);
|
2022-05-27 22:17:29 +00:00
|
|
|
InsertEntity(pProj);
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
else if(ObjType == NETOBJTYPE_PICKUP && m_WorldConfig.m_PredictWeapons)
|
|
|
|
{
|
2021-09-19 12:14:00 +00:00
|
|
|
CPickup NetPickup = CPickup(this, ObjID, (CNetObj_Pickup *)pObjData, pDataEx);
|
2020-09-26 19:41:58 +00:00
|
|
|
if(CPickup *pPickup = (CPickup *)GetEntity(ObjID, ENTTYPE_PICKUP))
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
CLaser NetLaser = CLaser(this, ObjID, (CNetObj_Laser *)pObjData);
|
2019-06-16 14:33:19 +00:00
|
|
|
CLaser *pMatching = 0;
|
2021-12-11 22:56:21 +00:00
|
|
|
if(CLaser *pLaser = dynamic_cast<CLaser *>(GetEntity(ObjID, ENTTYPE_LASER)))
|
2019-04-11 22:46:54 +00:00
|
|
|
if(NetLaser.Match(pLaser))
|
2019-06-16 14:33:19 +00:00
|
|
|
pMatching = pLaser;
|
|
|
|
if(!pMatching)
|
|
|
|
{
|
2021-12-11 22:56:21 +00:00
|
|
|
for(CEntity *pEnt = FindFirst(CGameWorld::ENTTYPE_LASER); pEnt; pEnt = pEnt->TypeNext())
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2021-12-11 22:56:21 +00:00
|
|
|
auto *const pLaser = dynamic_cast<CLaser *>(pEnt);
|
|
|
|
if(pLaser && pLaser->m_ID == -1 && NetLaser.Match(pLaser))
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-06-16 14:33:19 +00:00
|
|
|
pMatching = pLaser;
|
|
|
|
pMatching->m_ID = ObjID;
|
|
|
|
break;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-06-16 14:33:19 +00:00
|
|
|
if(pMatching)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-06-16 14:33:19 +00:00
|
|
|
pMatching->Keep();
|
|
|
|
if(distance(NetLaser.m_From, NetLaser.m_Pos) < distance(pMatching->m_From, pMatching->m_Pos) - 2.f)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-06-16 14:33:19 +00:00
|
|
|
// if the laser stopped earlier than predicted, set the energy to 0
|
|
|
|
pMatching->m_Energy = 0.f;
|
|
|
|
pMatching->m_Pos = NetLaser.m_Pos;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-09-26 19:41:58 +00:00
|
|
|
pHookedChar->m_Core.m_Vel = vec2(0, 0);
|
2019-04-11 22:46:54 +00:00
|
|
|
mem_zero(&pHookedChar->m_SavedInput, sizeof(pHookedChar->m_SavedInput));
|
2020-10-17 17:02:58 +00:00
|
|
|
pHookedChar->m_SavedInput.m_TargetY = -1;
|
2019-04-11 22:46:54 +00:00
|
|
|
pHookedChar->m_KeepHooked = true;
|
|
|
|
pHookedChar->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;
|
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
for(CCharacter *pChar = (CCharacter *)FindFirst(ENTTYPE_CHARACTER); pChar; pChar = (CCharacter *)pChar->TypeNext())
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
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++)
|
2019-09-08 22:53:07 +00:00
|
|
|
{
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Core.m_Tuning[i] = pFrom->m_Core.m_Tuning[i];
|
2019-09-08 22:53:07 +00:00
|
|
|
}
|
|
|
|
m_pTuningList = pFrom->m_pTuningList;
|
2019-06-16 14:31:06 +00:00
|
|
|
m_Teams = pFrom->m_Teams;
|
2022-02-13 19:57:27 +00:00
|
|
|
m_Core.m_aSwitchers = pFrom->m_Core.m_aSwitchers;
|
2019-04-11 22:46:54 +00:00
|
|
|
// delete the previous entities
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &pFirstEntityType : m_apFirstEntityTypes)
|
|
|
|
while(pFirstEntityType)
|
|
|
|
delete pFirstEntityType;
|
2019-04-11 22:46:54 +00:00
|
|
|
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)
|
2020-09-26 19:41:58 +00:00
|
|
|
pCopy = new CProjectile(*((CProjectile *)pEnt));
|
2019-04-11 22:46:54 +00:00
|
|
|
else if(Type == ENTTYPE_LASER)
|
2020-09-26 19:41:58 +00:00
|
|
|
pCopy = new CLaser(*((CLaser *)pEnt));
|
2019-04-11 22:46:54 +00:00
|
|
|
else if(Type == ENTTYPE_CHARACTER)
|
2020-09-26 19:41:58 +00:00
|
|
|
pCopy = new CCharacter(*((CCharacter *)pEnt));
|
2019-04-11 22:46:54 +00:00
|
|
|
else if(Type == ENTTYPE_PICKUP)
|
2020-09-26 19:41:58 +00:00
|
|
|
pCopy = new CPickup(*((CPickup *)pEnt));
|
2019-04-11 22:46:54 +00:00
|
|
|
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); \
|
2020-09-26 19:41:58 +00:00
|
|
|
if(pEnt && EntClass(this, ObjID, (ObjClass *)pObjData).Match((EntClass *)pEnt)) \
|
2019-04-11 22:46:54 +00:00
|
|
|
return pEnt; \
|
|
|
|
return 0; \
|
|
|
|
}
|
|
|
|
switch(ObjType)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
case NETOBJTYPE_CHARACTER: FindType(ENTTYPE_CHARACTER, CCharacter, CNetObj_Character);
|
2021-01-17 16:18:08 +00:00
|
|
|
case NETOBJTYPE_PROJECTILE:
|
|
|
|
case NETOBJTYPE_DDNETPROJECTILE:
|
|
|
|
{
|
|
|
|
CProjectileData Data;
|
|
|
|
if(ObjType == NETOBJTYPE_PROJECTILE)
|
|
|
|
{
|
2021-04-23 03:01:38 +00:00
|
|
|
Data = ExtractProjectileInfo((const CNetObj_Projectile *)pObjData, this);
|
2021-01-17 16:18:08 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-04-23 03:01:38 +00:00
|
|
|
Data = ExtractProjectileInfoDDNet((const CNetObj_DDNetProjectile *)pObjData, this);
|
2021-01-17 16:18:08 +00:00
|
|
|
}
|
|
|
|
CProjectile *pEnt = (CProjectile *)GetEntity(ObjID, ENTTYPE_PROJECTILE);
|
|
|
|
if(pEnt && CProjectile(this, ObjID, &Data).Match(pEnt))
|
|
|
|
{
|
|
|
|
return pEnt;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
case NETOBJTYPE_LASER: FindType(ENTTYPE_LASER, CLaser, CNetObj_Laser);
|
|
|
|
case NETOBJTYPE_PICKUP: FindType(ENTTYPE_PICKUP, CPickup, CNetObj_Pickup);
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGameWorld::OnModified()
|
|
|
|
{
|
|
|
|
if(m_pChild)
|
|
|
|
m_pChild->m_IsValidCopy = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGameWorld::Clear()
|
|
|
|
{
|
|
|
|
// delete all entities
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &pFirstEntityType : m_apFirstEntityTypes)
|
|
|
|
while(pFirstEntityType)
|
|
|
|
delete pFirstEntityType;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|