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 | ////////////////////////////////////////////////// |
18 | CGameWorld::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 | |
30 | CGameWorld::~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 | |
38 | void CGameWorld::SetGameServer(CGameContext *pGameServer) |
39 | { |
40 | m_pGameServer = pGameServer; |
41 | m_pConfig = m_pGameServer->Config(); |
42 | m_pServer = m_pGameServer->Server(); |
43 | } |
44 | |
45 | CEntity *CGameWorld::FindFirst(int Type) |
46 | { |
47 | return Type < 0 || Type >= NUM_ENTTYPES ? 0 : m_apFirstEntityTypes[Type]; |
48 | } |
49 | |
50 | int 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 | |
71 | void 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 | |
86 | void 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 | // |
109 | void 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 | |
132 | void 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 | |
145 | void 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 | |
165 | void CGameWorld::RemoveEntitiesFromPlayer(int PlayerId) |
166 | { |
167 | RemoveEntitiesFromPlayers(PlayerIds: &PlayerId, NumPlayers: 1); |
168 | } |
169 | |
170 | void 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 | |
191 | void 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 | |
207 | void 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 | |
273 | void 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 |
286 | CCharacter *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 | |
324 | CCharacter *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 | |
350 | std::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 | |
372 | void 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 | |
384 | CTuningParams *CGameWorld::Tuning() |
385 | { |
386 | return &m_Core.m_aTuning[0]; |
387 | } |
388 | |