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 "entity.h"
8#include "gamecontext.h"
9#include "gamecontroller.h"
10
11#include <engine/shared/config.h>
12
13#include <game/collision.h>
14
15#include <algorithm>
16#include <utility>
17
18//////////////////////////////////////////////////
19// game world
20//////////////////////////////////////////////////
21CGameWorld::CGameWorld()
22{
23 m_pGameServer = nullptr;
24 m_pConfig = nullptr;
25 m_pServer = nullptr;
26
27 m_Paused = false;
28 m_ResetRequested = false;
29 for(auto &pFirstEntityType : m_apFirstEntityTypes)
30 pFirstEntityType = nullptr;
31}
32
33CGameWorld::~CGameWorld()
34{
35 // delete all entities
36 for(auto &pFirstEntityType : m_apFirstEntityTypes)
37 while(pFirstEntityType)
38 delete pFirstEntityType; // NOLINT(clang-analyzer-cplusplus.NewDelete)
39}
40
41void CGameWorld::SetGameServer(CGameContext *pGameServer)
42{
43 m_pGameServer = pGameServer;
44 m_pConfig = m_pGameServer->Config();
45 m_pServer = m_pGameServer->Server();
46}
47
48void CGameWorld::Init(CCollision *pCollision, CTuningParams *pTuningList)
49{
50 m_Core.InitSwitchers(HighestSwitchNumber: pCollision->m_HighestSwitchNumber);
51 m_pTuningList = pTuningList;
52}
53
54CEntity *CGameWorld::FindFirst(int Type)
55{
56 return Type < 0 || Type >= NUM_ENTTYPES ? nullptr : m_apFirstEntityTypes[Type];
57}
58
59int CGameWorld::FindEntities(vec2 Pos, float Radius, CEntity **ppEnts, int Max, int Type)
60{
61 if(Type < 0 || Type >= NUM_ENTTYPES)
62 return 0;
63
64 int Num = 0;
65 for(CEntity *pEnt = m_apFirstEntityTypes[Type]; pEnt; pEnt = pEnt->m_pNextTypeEntity)
66 {
67 if(distance(a: pEnt->m_Pos, b: Pos) < Radius + pEnt->m_ProximityRadius)
68 {
69 if(ppEnts)
70 ppEnts[Num] = pEnt;
71 Num++;
72 if(Num == Max)
73 break;
74 }
75 }
76
77 return Num;
78}
79
80void CGameWorld::InsertEntity(CEntity *pEnt)
81{
82#ifdef CONF_DEBUG
83 for(CEntity *pCur = m_apFirstEntityTypes[pEnt->m_ObjType]; pCur; pCur = pCur->m_pNextTypeEntity)
84 dbg_assert(pCur != pEnt, "err");
85#endif
86
87 // insert it
88 if(m_apFirstEntityTypes[pEnt->m_ObjType])
89 m_apFirstEntityTypes[pEnt->m_ObjType]->m_pPrevTypeEntity = pEnt;
90 pEnt->m_pNextTypeEntity = m_apFirstEntityTypes[pEnt->m_ObjType];
91 pEnt->m_pPrevTypeEntity = nullptr;
92 m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt;
93}
94
95void CGameWorld::RemoveEntity(CEntity *pEnt)
96{
97 // not in the list
98 if(!pEnt->m_pNextTypeEntity && !pEnt->m_pPrevTypeEntity && m_apFirstEntityTypes[pEnt->m_ObjType] != pEnt)
99 return;
100
101 // remove
102 if(pEnt->m_pPrevTypeEntity)
103 pEnt->m_pPrevTypeEntity->m_pNextTypeEntity = pEnt->m_pNextTypeEntity;
104 else
105 m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt->m_pNextTypeEntity;
106 if(pEnt->m_pNextTypeEntity)
107 pEnt->m_pNextTypeEntity->m_pPrevTypeEntity = pEnt->m_pPrevTypeEntity;
108
109 // keep list traversing valid
110 if(m_pNextTraverseEntity == pEnt)
111 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
112
113 pEnt->m_pNextTypeEntity = nullptr;
114 pEnt->m_pPrevTypeEntity = nullptr;
115}
116
117//
118void CGameWorld::Snap(int SnappingClient)
119{
120 for(CEntity *pEnt = m_apFirstEntityTypes[ENTTYPE_CHARACTER]; pEnt;)
121 {
122 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
123 pEnt->Snap(SnappingClient);
124 pEnt = m_pNextTraverseEntity;
125 }
126
127 for(int i = 0; i < NUM_ENTTYPES; i++)
128 {
129 if(i == ENTTYPE_CHARACTER)
130 continue;
131
132 for(CEntity *pEnt = m_apFirstEntityTypes[i]; pEnt;)
133 {
134 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
135 pEnt->Snap(SnappingClient);
136 pEnt = m_pNextTraverseEntity;
137 }
138 }
139}
140
141void CGameWorld::Reset()
142{
143 // reset all entities
144 for(auto *pEnt : m_apFirstEntityTypes)
145 for(; pEnt;)
146 {
147 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
148 pEnt->Reset();
149 pEnt = m_pNextTraverseEntity;
150 }
151 RemoveEntities();
152
153 GameServer()->m_pController->OnReset();
154 RemoveEntities();
155
156 m_ResetRequested = false;
157
158 GameServer()->CreateAllEntities(Initial: false);
159}
160
161void CGameWorld::RemoveEntitiesFromPlayer(int PlayerId)
162{
163 RemoveEntitiesFromPlayers(PlayerIds: &PlayerId, NumPlayers: 1);
164}
165
166void CGameWorld::RemoveEntitiesFromPlayers(int PlayerIds[], int NumPlayers)
167{
168 for(auto *pEnt : m_apFirstEntityTypes)
169 {
170 for(; pEnt;)
171 {
172 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
173 for(int i = 0; i < NumPlayers; i++)
174 {
175 if(pEnt->GetOwnerId() == PlayerIds[i])
176 {
177 RemoveEntity(pEnt);
178 pEnt->Destroy();
179 break;
180 }
181 }
182 pEnt = m_pNextTraverseEntity;
183 }
184 }
185}
186
187void CGameWorld::RemoveEntities()
188{
189 // destroy objects marked for destruction
190 for(auto *pEnt : m_apFirstEntityTypes)
191 for(; pEnt;)
192 {
193 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
194 if(pEnt->m_MarkedForDestroy)
195 {
196 RemoveEntity(pEnt);
197 pEnt->Destroy();
198 }
199 pEnt = m_pNextTraverseEntity;
200 }
201}
202
203void CGameWorld::Tick()
204{
205 if(m_ResetRequested)
206 Reset();
207
208 if(!m_Paused)
209 {
210 // update all objects
211 for(int i = 0; i < NUM_ENTTYPES; i++)
212 {
213 // It's important to call PreTick() and Tick() after each other.
214 // If we call PreTick() before, and Tick() after other entities have been processed, it causes physics changes such as a stronger shotgun or grenade.
215 if(g_Config.m_SvNoWeakHook && i == ENTTYPE_CHARACTER)
216 {
217 auto *pEnt = m_apFirstEntityTypes[i];
218 for(; pEnt;)
219 {
220 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
221 ((CCharacter *)pEnt)->PreTick();
222 pEnt = m_pNextTraverseEntity;
223 }
224 }
225
226 auto *pEnt = m_apFirstEntityTypes[i];
227 for(; pEnt;)
228 {
229 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
230 pEnt->Tick();
231 pEnt = m_pNextTraverseEntity;
232 }
233 }
234
235 for(auto *pEnt : m_apFirstEntityTypes)
236 for(; pEnt;)
237 {
238 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
239 pEnt->TickDeferred();
240 pEnt = m_pNextTraverseEntity;
241 }
242 }
243 else
244 {
245 // update all objects
246 for(auto *pEnt : m_apFirstEntityTypes)
247 for(; pEnt;)
248 {
249 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
250 pEnt->TickPaused();
251 pEnt = m_pNextTraverseEntity;
252 }
253 }
254
255 RemoveEntities();
256
257 // find the characters' strong/weak id
258 int StrongWeakId = 0;
259 for(CCharacter *pChar = (CCharacter *)FindFirst(Type: ENTTYPE_CHARACTER); pChar; pChar = (CCharacter *)pChar->TypeNext())
260 {
261 pChar->m_StrongWeakId = StrongWeakId;
262 StrongWeakId++;
263 }
264}
265
266ESaveResult CGameWorld::BlocksSave(int ClientId)
267{
268 // check all objects
269 for(auto *pEnt : m_apFirstEntityTypes)
270 for(; pEnt;)
271 {
272 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
273 ESaveResult Result = pEnt->BlocksSave(ClientId);
274 if(Result != ESaveResult::SUCCESS)
275 return Result;
276 pEnt = m_pNextTraverseEntity;
277 }
278 return ESaveResult::SUCCESS;
279}
280
281void CGameWorld::SwapClients(int Client1, int Client2)
282{
283 // update all objects
284 for(auto *pEnt : m_apFirstEntityTypes)
285 for(; pEnt;)
286 {
287 m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
288 pEnt->SwapClients(Client1, Client2);
289 pEnt = m_pNextTraverseEntity;
290 }
291}
292
293CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, const CCharacter *pNotThis, int CollideWith, const CCharacter *pThisOnly)
294{
295 return (CCharacter *)IntersectEntity(Pos0, Pos1, Radius, Type: ENTTYPE_CHARACTER, NewPos, pNotThis, CollideWith, pThisOnly);
296}
297
298CEntity *CGameWorld::IntersectEntity(vec2 Pos0, vec2 Pos1, float Radius, int Type, vec2 &NewPos, const CEntity *pNotThis, int CollideWith, const CEntity *pThisOnly)
299{
300 float ClosestLen = distance(a: Pos0, b: Pos1) * 100.0f;
301 CEntity *pClosest = nullptr;
302
303 CEntity *pEntity = FindFirst(Type);
304 for(; pEntity; pEntity = pEntity->TypeNext())
305 {
306 if(pEntity == pNotThis)
307 continue;
308
309 if(pThisOnly && pEntity != pThisOnly)
310 continue;
311
312 if(CollideWith != -1 && !pEntity->CanCollide(ClientId: CollideWith))
313 continue;
314
315 vec2 IntersectPos;
316 if(closest_point_on_line(line_pointA: Pos0, line_pointB: Pos1, target_point: pEntity->m_Pos, out_pos&: IntersectPos))
317 {
318 float Len = distance(a: pEntity->m_Pos, b: IntersectPos);
319 if(Len < pEntity->m_ProximityRadius + Radius)
320 {
321 Len = distance(a: Pos0, b: IntersectPos);
322 if(Len < ClosestLen)
323 {
324 NewPos = IntersectPos;
325 ClosestLen = Len;
326 pClosest = pEntity;
327 }
328 }
329 }
330 }
331
332 return pClosest;
333}
334
335CCharacter *CGameWorld::ClosestCharacter(vec2 Pos, float Radius, const CEntity *pNotThis)
336{
337 // Find other players
338 float ClosestRange = Radius * 2;
339 CCharacter *pClosest = nullptr;
340
341 CCharacter *p = (CCharacter *)FindFirst(Type: ENTTYPE_CHARACTER);
342 for(; p; p = (CCharacter *)p->TypeNext())
343 {
344 if(p == pNotThis)
345 continue;
346
347 float Len = distance(a: Pos, b: p->m_Pos);
348 if(Len < p->m_ProximityRadius + Radius)
349 {
350 if(Len < ClosestRange)
351 {
352 ClosestRange = Len;
353 pClosest = p;
354 }
355 }
356 }
357
358 return pClosest;
359}
360
361std::vector<CCharacter *> CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis)
362{
363 std::vector<CCharacter *> vpCharacters;
364 CCharacter *pChr = (CCharacter *)FindFirst(Type: CGameWorld::ENTTYPE_CHARACTER);
365 for(; pChr; pChr = (CCharacter *)pChr->TypeNext())
366 {
367 if(pChr == pNotThis)
368 continue;
369
370 vec2 IntersectPos;
371 if(closest_point_on_line(line_pointA: Pos0, line_pointB: Pos1, target_point: pChr->m_Pos, out_pos&: IntersectPos))
372 {
373 float Len = distance(a: pChr->m_Pos, b: IntersectPos);
374 if(Len < pChr->m_ProximityRadius + Radius)
375 {
376 vpCharacters.push_back(x: pChr);
377 }
378 }
379 }
380 return vpCharacters;
381}
382
383void CGameWorld::ReleaseHooked(int ClientId)
384{
385 CCharacter *pChr = (CCharacter *)FindFirst(Type: CGameWorld::ENTTYPE_CHARACTER);
386 for(; pChr; pChr = (CCharacter *)pChr->TypeNext())
387 {
388 if(pChr->Core()->HookedPlayer() == ClientId && !pChr->IsSuper())
389 {
390 pChr->ReleaseHook();
391 }
392 }
393}
394