1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include "gameworld.h"
5
6#include "entities/character.h"
7#include "entities/door.h"
8#include "entities/dragger.h"
9#include "entities/laser.h"
10#include "entities/pickup.h"
11#include "entities/plasma.h"
12#include "entities/projectile.h"
13#include "entity.h"
14
15#include <engine/shared/config.h>
16
17#include <game/client/laser_data.h>
18#include <game/client/pickup_data.h>
19#include <game/client/projectile_data.h>
20#include <game/mapbugs.h>
21#include <game/mapitems.h>
22
23#include <algorithm>
24#include <utility>
25
26//////////////////////////////////////////////////
27// game world
28//////////////////////////////////////////////////
29CGameWorld::CGameWorld()
30{
31 for(auto &pFirstEntityType : m_apFirstEntityTypes)
32 pFirstEntityType = nullptr;
33 for(auto &pCharacter : m_apCharacters)
34 pCharacter = nullptr;
35 m_pCollision = nullptr;
36 m_GameTick = 0;
37 m_pParent = nullptr;
38 m_pChild = nullptr;
39}
40
41CGameWorld::~CGameWorld()
42{
43 Clear();
44 if(m_pChild && m_pChild->m_pParent == this)
45 {
46 OnModified();
47 m_pChild->m_pParent = nullptr;
48 }
49 if(m_pParent && m_pParent->m_pChild == this)
50 m_pParent->m_pChild = nullptr;
51}
52
53void CGameWorld::Init(CCollision *pCollision, CTuningParams *pTuningList, const CMapBugs *pMapBugs)
54{
55 m_pCollision = pCollision;
56 m_pTuningList = pTuningList;
57 m_pMapBugs = pMapBugs;
58}
59
60CEntity *CGameWorld::FindFirst(int Type)
61{
62 return Type < 0 || Type >= NUM_ENTTYPES ? nullptr : m_apFirstEntityTypes[Type];
63}
64
65CEntity *CGameWorld::FindLast(int Type)
66{
67 CEntity *pLast = FindFirst(Type);
68 if(pLast)
69 while(pLast->TypeNext())
70 pLast = pLast->TypeNext();
71 return pLast;
72}
73
74int CGameWorld::FindEntities(vec2 Pos, float Radius, CEntity **ppEnts, int Max, int Type)
75{
76 if(Type < 0 || Type >= NUM_ENTTYPES)
77 return 0;
78
79 int Num = 0;
80 for(CEntity *pEnt = m_apFirstEntityTypes[Type]; pEnt; pEnt = pEnt->m_pNextTypeEntity)
81 {
82 if(distance(a: pEnt->m_Pos, b: Pos) < Radius + pEnt->m_ProximityRadius)
83 {
84 if(ppEnts)
85 ppEnts[Num] = pEnt;
86 Num++;
87 if(Num == Max)
88 break;
89 }
90 }
91
92 return Num;
93}
94
95void CGameWorld::InsertEntity(CEntity *pEnt, bool Last)
96{
97 pEnt->m_pGameWorld = this;
98 pEnt->m_pNextTypeEntity = nullptr;
99 pEnt->m_pPrevTypeEntity = nullptr;
100
101 // insert it
102 if(!Last)
103 {
104 if(m_apFirstEntityTypes[pEnt->m_ObjType])
105 m_apFirstEntityTypes[pEnt->m_ObjType]->m_pPrevTypeEntity = pEnt;
106 pEnt->m_pNextTypeEntity = m_apFirstEntityTypes[pEnt->m_ObjType];
107 pEnt->m_pPrevTypeEntity = nullptr;
108 m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt;
109 }
110 else
111 {
112 // insert it at the end of the list
113 CEntity *pLast = m_apFirstEntityTypes[pEnt->m_ObjType];
114 if(pLast)
115 {
116 while(pLast->m_pNextTypeEntity)
117 pLast = pLast->m_pNextTypeEntity;
118 pLast->m_pNextTypeEntity = pEnt;
119 }
120 else
121 m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt;
122 pEnt->m_pPrevTypeEntity = pLast;
123 pEnt->m_pNextTypeEntity = nullptr;
124 }
125
126 if(pEnt->m_ObjType == ENTTYPE_CHARACTER)
127 {
128 auto *pChar = (CCharacter *)pEnt;
129 int Id = pChar->GetCid();
130 if(Id >= 0 && Id < MAX_CLIENTS)
131 {
132 m_apCharacters[Id] = pChar;
133 m_Core.m_apCharacters[Id] = &pChar->m_Core;
134 }
135 pChar->SetCoreWorld(this);
136 }
137}
138
139void CGameWorld::RemoveEntity(CEntity *pEnt)
140{
141 // not in the list
142 if(!pEnt->m_pNextTypeEntity && !pEnt->m_pPrevTypeEntity && m_apFirstEntityTypes[pEnt->m_ObjType] != pEnt)
143 return;
144
145 // remove
146 if(pEnt->m_pPrevTypeEntity)
147 pEnt->m_pPrevTypeEntity->m_pNextTypeEntity = pEnt->m_pNextTypeEntity;
148 else
149 m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt->m_pNextTypeEntity;
150 if(pEnt->m_pNextTypeEntity)
151 pEnt->m_pNextTypeEntity->m_pPrevTypeEntity = pEnt->m_pPrevTypeEntity;
152
153 // keep list traversing valid
154 if(m_pNextTraverseEntity == pEnt)
155 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
156
157 pEnt->m_pNextTypeEntity = nullptr;
158 pEnt->m_pPrevTypeEntity = nullptr;
159
160 if(pEnt->m_pParent)
161 {
162 if(m_IsValidCopy && m_pParent && m_pParent->m_pChild == this)
163 pEnt->m_pParent->m_DestroyTick = GameTick();
164 pEnt->m_pParent->m_pChild = nullptr;
165 pEnt->m_pParent = nullptr;
166 }
167 if(pEnt->m_pChild)
168 {
169 pEnt->m_pChild->m_pParent = nullptr;
170 pEnt->m_pChild = nullptr;
171 }
172}
173
174void CGameWorld::RemoveCharacter(CCharacter *pChar)
175{
176 int Id = pChar->GetCid();
177 if(Id >= 0 && Id < MAX_CLIENTS)
178 {
179 m_apCharacters[Id] = nullptr;
180 m_Core.m_apCharacters[Id] = nullptr;
181 }
182}
183
184void CGameWorld::RemoveEntities()
185{
186 // destroy objects marked for destruction
187 for(auto *pEnt : m_apFirstEntityTypes)
188 for(; pEnt;)
189 {
190 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
191 if(pEnt->m_MarkedForDestroy)
192 {
193 pEnt->Destroy();
194 }
195 pEnt = m_pNextTraverseEntity;
196 }
197}
198
199void CGameWorld::Tick()
200{
201 // update all objects
202 for(int i = 0; i < NUM_ENTTYPES; i++)
203 {
204 // It's important to call PreTick() and Tick() after each other.
205 // If we call PreTick() before, and Tick() after other entities have been processed, it causes physics changes such as a stronger shotgun or grenade.
206 if(m_WorldConfig.m_NoWeakHookAndBounce && i == ENTTYPE_CHARACTER)
207 {
208 auto *pEnt = m_apFirstEntityTypes[i];
209 for(; pEnt;)
210 {
211 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
212 ((CCharacter *)pEnt)->PreTick();
213 pEnt = m_pNextTraverseEntity;
214 }
215 }
216
217 auto *pEnt = m_apFirstEntityTypes[i];
218 for(; pEnt;)
219 {
220 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
221 pEnt->Tick();
222 pEnt = m_pNextTraverseEntity;
223 }
224 }
225
226 for(auto *pEnt : m_apFirstEntityTypes)
227 for(; pEnt;)
228 {
229 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
230 pEnt->TickDeferred();
231 pEnt->m_SnapTicks++;
232 pEnt = m_pNextTraverseEntity;
233 }
234
235 RemoveEntities();
236
237 // update switch state
238 for(auto &Switcher : Switchers())
239 {
240 for(int j = 0; j < NUM_DDRACE_TEAMS; ++j)
241 {
242 if(Switcher.m_aEndTick[j] <= GameTick() && Switcher.m_aType[j] == TILE_SWITCHTIMEDOPEN)
243 {
244 Switcher.m_aStatus[j] = false;
245 Switcher.m_aEndTick[j] = 0;
246 Switcher.m_aType[j] = TILE_SWITCHCLOSE;
247 }
248 else if(Switcher.m_aEndTick[j] <= GameTick() && Switcher.m_aType[j] == TILE_SWITCHTIMEDCLOSE)
249 {
250 Switcher.m_aStatus[j] = true;
251 Switcher.m_aEndTick[j] = 0;
252 Switcher.m_aType[j] = TILE_SWITCHOPEN;
253 }
254 }
255 }
256
257 OnModified();
258}
259
260CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, const CCharacter *pNotThis, int CollideWith, const CCharacter *pThisOnly)
261{
262 return (CCharacter *)IntersectEntity(Pos0, Pos1, Radius, Type: ENTTYPE_CHARACTER, NewPos, pNotThis, CollideWith, pThisOnly);
263}
264
265CEntity *CGameWorld::IntersectEntity(vec2 Pos0, vec2 Pos1, float Radius, int Type, vec2 &NewPos, const CEntity *pNotThis, int CollideWith, const CEntity *pThisOnly)
266{
267 float ClosestLen = distance(a: Pos0, b: Pos1) * 100.0f;
268 CEntity *pClosest = nullptr;
269
270 CEntity *pEntity = FindFirst(Type);
271 for(; pEntity; pEntity = pEntity->TypeNext())
272 {
273 if(pEntity == pNotThis)
274 continue;
275
276 if(pThisOnly && pEntity != pThisOnly)
277 continue;
278
279 if(CollideWith != -1 && !pEntity->CanCollide(ClientId: CollideWith))
280 continue;
281
282 vec2 IntersectPos;
283 if(closest_point_on_line(line_pointA: Pos0, line_pointB: Pos1, target_point: pEntity->m_Pos, out_pos&: IntersectPos))
284 {
285 float Len = distance(a: pEntity->m_Pos, b: IntersectPos);
286 if(Len < pEntity->m_ProximityRadius + Radius)
287 {
288 Len = distance(a: Pos0, b: IntersectPos);
289 if(Len < ClosestLen)
290 {
291 NewPos = IntersectPos;
292 ClosestLen = Len;
293 pClosest = pEntity;
294 }
295 }
296 }
297 }
298
299 return pClosest;
300}
301
302std::vector<CCharacter *> CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis)
303{
304 std::vector<CCharacter *> vpCharacters;
305 CCharacter *pChr = (CCharacter *)FindFirst(Type: CGameWorld::ENTTYPE_CHARACTER);
306 for(; pChr; pChr = (CCharacter *)pChr->TypeNext())
307 {
308 if(pChr == pNotThis)
309 continue;
310
311 vec2 IntersectPos;
312 if(closest_point_on_line(line_pointA: Pos0, line_pointB: Pos1, target_point: pChr->m_Pos, out_pos&: IntersectPos))
313 {
314 float Len = distance(a: pChr->m_Pos, b: IntersectPos);
315 if(Len < pChr->m_ProximityRadius + Radius)
316 {
317 vpCharacters.push_back(x: pChr);
318 }
319 }
320 }
321 return vpCharacters;
322}
323
324void CGameWorld::ReleaseHooked(int ClientId)
325{
326 CCharacter *pChr = (CCharacter *)CGameWorld::FindFirst(Type: CGameWorld::ENTTYPE_CHARACTER);
327 for(; pChr; pChr = (CCharacter *)pChr->TypeNext())
328 {
329 if(pChr->Core()->HookedPlayer() == ClientId && !pChr->IsSuper())
330 {
331 pChr->ReleaseHook();
332 }
333 }
334}
335
336CEntity *CGameWorld::GetEntity(int Id, int EntityType)
337{
338 for(CEntity *pEnt = m_apFirstEntityTypes[EntityType]; pEnt; pEnt = pEnt->m_pNextTypeEntity)
339 if(pEnt->m_Id == Id)
340 return pEnt;
341 return nullptr;
342}
343
344void CGameWorld::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage, int ActivatedTeam, CClientMask Mask)
345{
346 if(Owner < 0 && m_WorldConfig.m_IsSolo && !(Weapon == WEAPON_SHOTGUN && m_WorldConfig.m_IsDDRace))
347 return;
348
349 // deal damage
350 CEntity *apEnts[MAX_CLIENTS];
351 float Radius = 135.0f;
352 float InnerRadius = 48.0f;
353 int Num = FindEntities(Pos, Radius, ppEnts: apEnts, Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER);
354 for(int i = 0; i < Num; i++)
355 {
356 auto *pChar = static_cast<CCharacter *>(apEnts[i]);
357 vec2 Diff = pChar->m_Pos - Pos;
358 vec2 ForceDir(0, 1);
359 float l = length(a: Diff);
360 if(l)
361 ForceDir = normalize(v: Diff);
362 l = 1 - std::clamp(val: (l - InnerRadius) / (Radius - InnerRadius), lo: 0.0f, hi: 1.0f);
363 float Strength;
364 if(Owner == -1 || !GetCharacterById(Id: Owner))
365 Strength = GlobalTuning()->m_ExplosionStrength;
366 else
367 Strength = GetCharacterById(Id: Owner)->GetTuning(i: GetCharacterById(Id: Owner)->GetOverriddenTuneZone())->m_ExplosionStrength;
368
369 float Dmg = Strength * l;
370 if((int)Dmg)
371 if((GetCharacterById(Id: Owner) ? !GetCharacterById(Id: Owner)->GrenadeHitDisabled() : g_Config.m_SvHit || NoDamage) || Owner == pChar->GetCid())
372 {
373 if(Owner != -1 && !pChar->CanCollide(ClientId: Owner))
374 continue;
375 if(Owner == -1 && ActivatedTeam != -1 && pChar->Team() != ActivatedTeam)
376 continue;
377 pChar->TakeDamage(Force: ForceDir * Dmg * 2, Dmg: (int)Dmg, From: Owner, Weapon);
378 if(GetCharacterById(Id: Owner) ? GetCharacterById(Id: Owner)->GrenadeHitDisabled() : !g_Config.m_SvHit || NoDamage)
379 break;
380 }
381 }
382}
383
384bool CGameWorld::IsLocalTeam(int OwnerId) const
385{
386 return OwnerId < 0 || m_Teams.CanCollide(ClientId1: m_LocalClientId, ClientId2: OwnerId);
387}
388
389void CGameWorld::NetObjBegin(CTeamsCore Teams, int LocalClientId)
390{
391 m_Teams = Teams;
392 m_LocalClientId = LocalClientId;
393
394 for(int i = 0; i < NUM_ENTTYPES; i++)
395 for(CEntity *pEnt = FindFirst(Type: i); pEnt; pEnt = pEnt->TypeNext())
396 {
397 pEnt->m_MarkedForDestroy = true;
398 if(i == ENTTYPE_CHARACTER)
399 ((CCharacter *)pEnt)->m_KeepHooked = false;
400 }
401 OnModified();
402}
403
404void CGameWorld::NetCharAdd(int ObjId, CNetObj_Character *pCharObj, CNetObj_DDNetCharacter *pExtended, int GameTeam, bool IsLocal)
405{
406 if(IsLocalTeam(OwnerId: ObjId))
407 {
408 CCharacter *pChar;
409 if((pChar = (CCharacter *)GetEntity(Id: ObjId, EntityType: ENTTYPE_CHARACTER)))
410 {
411 pChar->Read(pChar: pCharObj, pExtended, IsLocal);
412 pChar->Keep();
413 }
414 else
415 {
416 pChar = new CCharacter(this, ObjId, pCharObj, pExtended);
417 InsertEntity(pEnt: pChar);
418 }
419
420 if(pChar)
421 pChar->m_GameTeam = GameTeam;
422 }
423}
424
425void CGameWorld::NetObjAdd(int ObjId, int ObjType, const void *pObjData, const CNetObj_EntityEx *pDataEx)
426{
427 if((ObjType == NETOBJTYPE_PROJECTILE || ObjType == NETOBJTYPE_DDRACEPROJECTILE || ObjType == NETOBJTYPE_DDNETPROJECTILE) && m_WorldConfig.m_PredictWeapons)
428 {
429 CProjectileData Data = ExtractProjectileInfo(NetObjType: ObjType, pData: pObjData, pGameWorld: this, pEntEx: pDataEx);
430 if(!IsLocalTeam(OwnerId: Data.m_Owner))
431 return;
432
433 CProjectile NetProj = CProjectile(this, ObjId, &Data);
434
435 if(NetProj.m_Type != WEAPON_SHOTGUN && absolute(a: length(a: NetProj.m_Direction) - 1.f) > 0.02f) // workaround to skip grenades on ball mod
436 return;
437
438 if(CProjectile *pProj = (CProjectile *)GetEntity(Id: ObjId, EntityType: ENTTYPE_PROJECTILE))
439 {
440 if(NetProj.Match(pProj))
441 {
442 pProj->Keep();
443 if(pProj->m_Type == WEAPON_SHOTGUN && m_WorldConfig.m_IsDDRace)
444 pProj->m_LifeSpan = 20 * GameTickSpeed() - (GameTick() - pProj->m_StartTick);
445 return;
446 }
447 }
448 if(!Data.m_ExtraInfo)
449 {
450 // try to match the newly received (unrecognized) projectile with a locally fired one
451 for(CProjectile *pProj = (CProjectile *)FindFirst(Type: CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile *)pProj->TypeNext())
452 {
453 if(pProj->m_Id == -1 && NetProj.Match(pProj))
454 {
455 pProj->m_Id = ObjId;
456 pProj->Keep();
457 return;
458 }
459 }
460 // otherwise try to determine its owner by checking if there is only one player nearby
461 if(NetProj.m_StartTick >= GameTick() - 4)
462 {
463 const vec2 NetPos = NetProj.m_Pos - normalize(v: NetProj.m_Direction) * CCharacterCore::PhysicalSize() * 0.75;
464 const bool Prev = (GameTick() - NetProj.m_StartTick) > 1;
465 float First = 200.0f, Second = 200.0f;
466 CCharacter *pClosest = nullptr;
467 for(CCharacter *pChar = (CCharacter *)FindFirst(Type: ENTTYPE_CHARACTER); pChar; pChar = (CCharacter *)pChar->TypeNext())
468 {
469 float Dist = distance(a: Prev ? pChar->m_PrevPrevPos : pChar->m_PrevPos, b: NetPos);
470 if(Dist < First)
471 {
472 pClosest = pChar;
473 First = Dist;
474 }
475 else if(Dist < Second)
476 Second = Dist;
477 }
478 if(pClosest && maximum(a: First, b: 2.f) * 1.2f < Second)
479 NetProj.m_Owner = pClosest->m_Id;
480 }
481 }
482 CProjectile *pProj = new CProjectile(NetProj);
483 InsertEntity(pEnt: pProj);
484 }
485 else if((ObjType == NETOBJTYPE_PICKUP || ObjType == NETOBJTYPE_DDNETPICKUP) && m_WorldConfig.m_PredictWeapons)
486 {
487 CPickupData Data = ExtractPickupInfo(NetObjType: ObjType, pData: pObjData, pEntEx: pDataEx);
488 if(Data.m_Flags & PICKUPFLAG_NO_PREDICT)
489 return;
490 CPickup NetPickup = CPickup(this, ObjId, &Data);
491 if(CPickup *pPickup = (CPickup *)GetEntity(Id: ObjId, EntityType: ENTTYPE_PICKUP))
492 {
493 if(NetPickup.Match(pPickup))
494 {
495 pPickup->m_Pos = NetPickup.m_Pos;
496 pPickup->Keep();
497 return;
498 }
499 }
500 CEntity *pEnt = new CPickup(NetPickup);
501 InsertEntity(pEnt, Last: true);
502 }
503 else if((ObjType == NETOBJTYPE_LASER || ObjType == NETOBJTYPE_DDNETLASER) && m_WorldConfig.m_PredictWeapons)
504 {
505 CLaserData Data = ExtractLaserInfo(NetObjType: ObjType, pData: pObjData, pGameWorld: this, pEntEx: pDataEx);
506 if(!IsLocalTeam(OwnerId: Data.m_Owner) || !Data.m_Predict)
507 {
508 return;
509 }
510
511 if(Data.m_Type == LASERTYPE_RIFLE || Data.m_Type == LASERTYPE_SHOTGUN || Data.m_Type < 0)
512 {
513 CLaser NetLaser = CLaser(this, ObjId, &Data);
514 CLaser *pMatching = nullptr;
515 if(CLaser *pLaser = dynamic_cast<CLaser *>(GetEntity(Id: ObjId, EntityType: ENTTYPE_LASER)))
516 if(NetLaser.Match(pLaser))
517 pMatching = pLaser;
518 if(!pMatching)
519 {
520 for(CEntity *pEnt = FindFirst(Type: CGameWorld::ENTTYPE_LASER); pEnt; pEnt = pEnt->TypeNext())
521 {
522 auto *const pLaser = dynamic_cast<CLaser *>(pEnt);
523 if(pLaser && pLaser->m_Id == -1 && NetLaser.Match(pLaser))
524 {
525 pMatching = pLaser;
526 pMatching->m_Id = ObjId;
527 break;
528 }
529 }
530 }
531 if(pMatching)
532 {
533 pMatching->Keep();
534 if(distance(a: NetLaser.m_From, b: NetLaser.m_Pos) < distance(a: pMatching->m_From, b: pMatching->m_Pos) - 2.f)
535 {
536 // if the laser stopped earlier than predicted, set the energy to 0
537 pMatching->m_Energy = 0.f;
538 pMatching->m_Pos = NetLaser.m_Pos;
539 }
540 }
541 }
542 else if(Data.m_Type == LASERTYPE_DRAGGER)
543 {
544 CDragger NetDragger = CDragger(this, ObjId, &Data);
545 if(NetDragger.GetStrength() > 0)
546 {
547 auto *pDragger = dynamic_cast<CDragger *>(GetEntity(Id: ObjId, EntityType: ENTTYPE_DRAGGER));
548 if(pDragger && NetDragger.Match(pDragger))
549 {
550 pDragger->Keep();
551 pDragger->Read(pData: &Data);
552 return;
553 }
554 CEntity *pEnt = new CDragger(NetDragger);
555 InsertEntity(pEnt);
556 }
557 }
558 else if(Data.m_Type == LASERTYPE_DOOR)
559 {
560 CDoor NetDoor = CDoor(this, ObjId, &Data);
561 auto *pDoor = dynamic_cast<CDoor *>(GetEntity(Id: ObjId, EntityType: ENTTYPE_DOOR));
562 if(pDoor && NetDoor.Match(pDoor))
563 {
564 pDoor->Keep();
565 pDoor->Read(pData: &Data);
566 return;
567 }
568 CDoor *pEnt = new CDoor(NetDoor);
569 pEnt->ResetCollision();
570 InsertEntity(pEnt);
571 }
572 else if(Data.m_Type == LASERTYPE_PLASMA)
573 {
574 CPlasma NetPlasma = CPlasma(this, ObjId, &Data);
575 auto *pPlasma = dynamic_cast<CPlasma *>(GetEntity(Id: ObjId, EntityType: ENTTYPE_PLASMA));
576 if(pPlasma && NetPlasma.Match(pPlasma))
577 {
578 pPlasma->Keep();
579 pPlasma->Read(pData: &Data);
580 return;
581 }
582 CPlasma *pEnt = new CPlasma(NetPlasma);
583 InsertEntity(pEnt);
584 }
585 }
586}
587
588void CGameWorld::NetObjEnd()
589{
590 // keep predicting hooked characters, based on hook position
591 for(int i = 0; i < MAX_CLIENTS; i++)
592 if(CCharacter *pChar = GetCharacterById(Id: i))
593 if(!pChar->m_MarkedForDestroy)
594 if(CCharacter *pHookedChar = GetCharacterById(Id: pChar->m_Core.HookedPlayer()))
595 if(pHookedChar->m_MarkedForDestroy)
596 {
597 pHookedChar->m_Pos = pHookedChar->m_Core.m_Pos = pChar->m_Core.m_HookPos;
598 pHookedChar->ResetVelocity();
599 mem_zero(block: &pHookedChar->m_SavedInput, size: sizeof(pHookedChar->m_SavedInput));
600 pHookedChar->m_SavedInput.m_TargetY = -1;
601 pHookedChar->m_KeepHooked = true;
602 pHookedChar->m_MarkedForDestroy = false;
603 }
604 RemoveEntities();
605
606 // Update character IDs and pointers
607 for(int i = 0; i < MAX_CLIENTS; i++)
608 {
609 m_apCharacters[i] = nullptr;
610 m_Core.m_apCharacters[i] = nullptr;
611 }
612 for(CCharacter *pChar = (CCharacter *)FindFirst(Type: ENTTYPE_CHARACTER); pChar; pChar = (CCharacter *)pChar->TypeNext())
613 {
614 int Id = pChar->GetCid();
615 if(Id >= 0 && Id < MAX_CLIENTS)
616 {
617 m_apCharacters[Id] = pChar;
618 m_Core.m_apCharacters[Id] = &pChar->m_Core;
619 }
620 }
621}
622
623void CGameWorld::CopyWorld(CGameWorld *pFrom)
624{
625 if(pFrom == this || !pFrom)
626 return;
627 m_IsValidCopy = false;
628 m_pParent = pFrom;
629 if(m_pParent->m_pChild && m_pParent->m_pChild != this)
630 m_pParent->m_pChild->m_IsValidCopy = false;
631 pFrom->m_pChild = this;
632
633 m_GameTick = pFrom->m_GameTick;
634 m_pCollision = pFrom->m_pCollision;
635 m_WorldConfig = pFrom->m_WorldConfig;
636 m_pTuningList = pFrom->m_pTuningList;
637 m_pMapBugs = pFrom->m_pMapBugs;
638 m_Teams = pFrom->m_Teams;
639 m_Core.m_vSwitchers = pFrom->m_Core.m_vSwitchers;
640 // delete the previous entities
641 Clear();
642 for(int i = 0; i < MAX_CLIENTS; i++)
643 {
644 m_apCharacters[i] = nullptr;
645 m_Core.m_apCharacters[i] = nullptr;
646 }
647 // copy and add the new entities
648 for(int Type = 0; Type < NUM_ENTTYPES; Type++)
649 {
650 for(CEntity *pEnt = pFrom->FindLast(Type); pEnt; pEnt = pEnt->TypePrev())
651 {
652 CEntity *pCopy = nullptr;
653 if(Type == ENTTYPE_PROJECTILE)
654 pCopy = new CProjectile(*((CProjectile *)pEnt));
655 else if(Type == ENTTYPE_LASER)
656 pCopy = new CLaser(*((CLaser *)pEnt));
657 else if(Type == ENTTYPE_DRAGGER)
658 pCopy = new CDragger(*((CDragger *)pEnt));
659 else if(Type == ENTTYPE_CHARACTER)
660 pCopy = new CCharacter(*((CCharacter *)pEnt));
661 else if(Type == ENTTYPE_PICKUP)
662 pCopy = new CPickup(*((CPickup *)pEnt));
663 else if(Type == ENTTYPE_PLASMA)
664 pCopy = new CPlasma(*((CPlasma *)pEnt));
665 if(pCopy)
666 {
667 pCopy->m_pParent = pEnt;
668 pEnt->m_pChild = pCopy;
669 this->InsertEntity(pEnt: pCopy);
670 }
671 }
672 }
673 m_IsValidCopy = true;
674}
675
676CEntity *CGameWorld::FindMatch(int ObjId, int ObjType, const void *pObjData)
677{
678 switch(ObjType)
679 {
680 case NETOBJTYPE_CHARACTER:
681 {
682 CCharacter *pEnt = (CCharacter *)GetEntity(Id: ObjId, EntityType: ENTTYPE_CHARACTER);
683 if(pEnt && CCharacter(this, ObjId, (CNetObj_Character *)pObjData).Match(pChar: pEnt))
684 {
685 return pEnt;
686 }
687 return nullptr;
688 }
689 case NETOBJTYPE_PROJECTILE:
690 case NETOBJTYPE_DDRACEPROJECTILE:
691 case NETOBJTYPE_DDNETPROJECTILE:
692 {
693 CProjectileData Data = ExtractProjectileInfo(NetObjType: ObjType, pData: pObjData, pGameWorld: this, pEntEx: nullptr);
694 CProjectile *pEnt = (CProjectile *)GetEntity(Id: ObjId, EntityType: ENTTYPE_PROJECTILE);
695 if(pEnt && CProjectile(this, ObjId, &Data).Match(pProj: pEnt))
696 {
697 return pEnt;
698 }
699 return nullptr;
700 }
701 case NETOBJTYPE_LASER:
702 case NETOBJTYPE_DDNETLASER:
703 {
704 CLaserData Data = ExtractLaserInfo(NetObjType: ObjType, pData: pObjData, pGameWorld: this, pEntEx: nullptr);
705 if(Data.m_Type == LASERTYPE_RIFLE || Data.m_Type == LASERTYPE_SHOTGUN)
706 {
707 CLaser *pEnt = (CLaser *)GetEntity(Id: ObjId, EntityType: ENTTYPE_LASER);
708 if(pEnt && CLaser(this, ObjId, &Data).Match(pLaser: pEnt))
709 {
710 return pEnt;
711 }
712 }
713 else if(Data.m_Type == LASERTYPE_DRAGGER)
714 {
715 CDragger *pEnt = (CDragger *)GetEntity(Id: ObjId, EntityType: ENTTYPE_DRAGGER);
716 if(pEnt && CDragger(this, ObjId, &Data).Match(pDragger: pEnt))
717 {
718 return pEnt;
719 }
720 }
721 else if(Data.m_Type == LASERTYPE_DOOR)
722 {
723 CDoor *pEnt = (CDoor *)GetEntity(Id: ObjId, EntityType: ENTTYPE_DOOR);
724 if(pEnt && CDoor(this, ObjId, &Data).Match(pDoor: pEnt))
725 {
726 return pEnt;
727 }
728 }
729 else if(Data.m_Type == LASERTYPE_PLASMA)
730 {
731 CPlasma *pEnt = (CPlasma *)GetEntity(Id: ObjId, EntityType: ENTTYPE_PLASMA);
732 if(pEnt && CPlasma(this, ObjId, &Data).Match(pPlasma: pEnt))
733 {
734 return pEnt;
735 }
736 }
737 return nullptr;
738 }
739 case NETOBJTYPE_PICKUP:
740 case NETOBJTYPE_DDNETPICKUP:
741 {
742 CPickupData Data = ExtractPickupInfo(NetObjType: ObjType, pData: pObjData, pEntEx: nullptr);
743 CPickup *pEnt = (CPickup *)GetEntity(Id: ObjId, EntityType: ENTTYPE_PICKUP);
744 if(pEnt && CPickup(this, ObjId, &Data).Match(pPickup: pEnt))
745 {
746 return pEnt;
747 }
748 return nullptr;
749 }
750 }
751 return nullptr;
752}
753
754void CGameWorld::OnModified() const
755{
756 if(m_pChild)
757 m_pChild->m_IsValidCopy = false;
758}
759
760void CGameWorld::Clear()
761{
762 // delete all entities
763 for(auto &pFirstEntityType : m_apFirstEntityTypes)
764 while(pFirstEntityType)
765 delete pFirstEntityType; // NOLINT(clang-analyzer-cplusplus.NewDelete)
766}
767
768bool CGameWorld::EmulateBug(int Bug) const
769{
770 return m_pMapBugs->Contains(Bug);
771}
772