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 | ////////////////////////////////////////////////// |
22 | CGameWorld::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 | |
34 | CGameWorld::~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 | |
46 | CEntity *CGameWorld::FindFirst(int Type) |
47 | { |
48 | return Type < 0 || Type >= NUM_ENTTYPES ? 0 : m_apFirstEntityTypes[Type]; |
49 | } |
50 | |
51 | CEntity *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 | |
60 | int 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 | |
81 | void 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 | |
125 | void 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 | |
160 | void 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 | |
170 | void 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 | |
185 | void 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 |
247 | CCharacter *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 | |
285 | std::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 | |
307 | void 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 | |
319 | CTuningParams *CGameWorld::Tuning() |
320 | { |
321 | return &m_Core.m_aTuning[g_Config.m_ClDummy]; |
322 | } |
323 | |
324 | CEntity *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 | |
332 | void 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 | |
372 | bool CGameWorld::IsLocalTeam(int OwnerId) const |
373 | { |
374 | return OwnerId < 0 || m_Teams.CanCollide(ClientId1: m_LocalClientId, ClientId2: OwnerId); |
375 | } |
376 | |
377 | void 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 | |
392 | void 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 | |
413 | void 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 | |
547 | void 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 | |
582 | void 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 | |
636 | CEntity *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 | |
698 | void CGameWorld::OnModified() const |
699 | { |
700 | if(m_pChild) |
701 | m_pChild->m_IsValidCopy = false; |
702 | } |
703 | |
704 | void 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 | |