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