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