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#include "character.h"
4#include "laser.h"
5#include "pickup.h"
6#include "projectile.h"
7
8#include <antibot/antibot_data.h>
9
10#include <engine/antibot.h>
11#include <engine/shared/config.h>
12
13#include <game/generated/protocol.h>
14#include <game/generated/server_data.h>
15#include <game/mapitems.h>
16
17#include <game/server/gamecontext.h>
18#include <game/server/gamecontroller.h>
19#include <game/server/player.h>
20#include <game/server/score.h>
21#include <game/server/teams.h>
22
23MACRO_ALLOC_POOL_ID_IMPL(CCharacter, MAX_CLIENTS)
24
25// Character, "physical" player's part
26CCharacter::CCharacter(CGameWorld *pWorld, CNetObj_PlayerInput LastInput) :
27 CEntity(pWorld, CGameWorld::ENTTYPE_CHARACTER, vec2(0, 0), CCharacterCore::PhysicalSize())
28{
29 m_Health = 0;
30 m_Armor = 0;
31 m_TriggeredEvents7 = 0;
32 m_StrongWeakId = 0;
33
34 m_Input = LastInput;
35 // never initialize both to zero
36 m_Input.m_TargetX = 0;
37 m_Input.m_TargetY = -1;
38
39 m_LatestPrevPrevInput = m_LatestPrevInput = m_LatestInput = m_PrevInput = m_SavedInput = m_Input;
40
41 m_LastTimeCp = -1;
42 m_LastTimeCpBroadcasted = -1;
43 for(float &CurrentTimeCp : m_aCurrentTimeCp)
44 {
45 CurrentTimeCp = 0.0f;
46 }
47}
48
49void CCharacter::Reset()
50{
51 StopRecording();
52 Destroy();
53}
54
55bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
56{
57 m_EmoteStop = -1;
58 m_LastAction = -1;
59 m_LastNoAmmoSound = -1;
60 m_LastWeapon = WEAPON_HAMMER;
61 m_QueuedWeapon = -1;
62 m_LastRefillJumps = false;
63 m_LastPenalty = false;
64 m_LastBonus = false;
65
66 m_TeleGunTeleport = false;
67 m_IsBlueTeleGunTeleport = false;
68
69 m_pPlayer = pPlayer;
70 m_Pos = Pos;
71
72 mem_zero(block: &m_LatestPrevPrevInput, size: sizeof(m_LatestPrevPrevInput));
73 m_LatestPrevPrevInput.m_TargetY = -1;
74 m_NumInputs = 0;
75 m_SpawnTick = Server()->Tick();
76 m_WeaponChangeTick = Server()->Tick();
77 Antibot()->OnSpawn(ClientId: m_pPlayer->GetCid());
78
79 m_Core.Reset();
80 m_Core.Init(pWorld: &GameServer()->m_World.m_Core, pCollision: Collision());
81 m_Core.m_ActiveWeapon = WEAPON_GUN;
82 m_Core.m_Pos = m_Pos;
83 m_Core.m_Id = m_pPlayer->GetCid();
84 GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCid()] = &m_Core;
85
86 m_ReckoningTick = 0;
87 m_SendCore = CCharacterCore();
88 m_ReckoningCore = CCharacterCore();
89
90 GameServer()->m_World.InsertEntity(pEntity: this);
91 m_Alive = true;
92
93 GameServer()->m_pController->OnCharacterSpawn(pChr: this);
94
95 DDRaceInit();
96
97 m_TuneZone = Collision()->IsTune(Index: Collision()->GetMapIndex(Pos));
98 m_TuneZoneOld = -1; // no zone leave msg on spawn
99 m_NeededFaketuning = 0; // reset fake tunings on respawn and send the client
100 SendZoneMsgs(); // we want a entermessage also on spawn
101 GameServer()->SendTuningParams(ClientId: m_pPlayer->GetCid(), Zone: m_TuneZone);
102
103 TrySetRescue(RescueMode: RESCUEMODE_MANUAL);
104 Server()->StartRecord(ClientId: m_pPlayer->GetCid());
105
106 return true;
107}
108
109void CCharacter::Destroy()
110{
111 GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCid()] = 0;
112 m_Alive = false;
113 SetSolo(false);
114}
115
116void CCharacter::SetWeapon(int W)
117{
118 if(W == m_Core.m_ActiveWeapon)
119 return;
120
121 m_LastWeapon = m_Core.m_ActiveWeapon;
122 m_QueuedWeapon = -1;
123 m_Core.m_ActiveWeapon = W;
124 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_WEAPON_SWITCH, Mask: TeamMask());
125
126 if(m_Core.m_ActiveWeapon < 0 || m_Core.m_ActiveWeapon >= NUM_WEAPONS)
127 m_Core.m_ActiveWeapon = 0;
128}
129
130void CCharacter::SetJetpack(bool Active)
131{
132 m_Core.m_Jetpack = Active;
133}
134
135void CCharacter::SetSolo(bool Solo)
136{
137 m_Core.m_Solo = Solo;
138 Teams()->m_Core.SetSolo(ClientId: m_pPlayer->GetCid(), Value: Solo);
139}
140
141void CCharacter::SetSuper(bool Super)
142{
143 m_Core.m_Super = Super;
144 if(Super)
145 {
146 m_TeamBeforeSuper = Team();
147 Teams()->SetCharacterTeam(ClientId: GetPlayer()->GetCid(), Team: TEAM_SUPER);
148 m_DDRaceState = DDRACE_CHEAT;
149 }
150 else
151 {
152 Teams()->SetForceCharacterTeam(ClientId: GetPlayer()->GetCid(), Team: m_TeamBeforeSuper);
153 }
154}
155
156void CCharacter::SetLiveFrozen(bool Active)
157{
158 m_Core.m_LiveFrozen = Active;
159}
160
161void CCharacter::SetDeepFrozen(bool Active)
162{
163 m_Core.m_DeepFrozen = Active;
164}
165
166bool CCharacter::IsGrounded()
167{
168 if(Collision()->CheckPoint(x: m_Pos.x + GetProximityRadius() / 2, y: m_Pos.y + GetProximityRadius() / 2 + 5))
169 return true;
170 if(Collision()->CheckPoint(x: m_Pos.x - GetProximityRadius() / 2, y: m_Pos.y + GetProximityRadius() / 2 + 5))
171 return true;
172
173 int MoveRestrictionsBelow = Collision()->GetMoveRestrictions(Pos: m_Pos + vec2(0, GetProximityRadius() / 2 + 4), Distance: 0.0f);
174 return (MoveRestrictionsBelow & CANTMOVE_DOWN) != 0;
175}
176
177void CCharacter::HandleJetpack()
178{
179 vec2 Direction = normalize(v: vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
180
181 bool FullAuto = false;
182 if(m_Core.m_ActiveWeapon == WEAPON_GRENADE || m_Core.m_ActiveWeapon == WEAPON_SHOTGUN || m_Core.m_ActiveWeapon == WEAPON_LASER)
183 FullAuto = true;
184 if(m_Core.m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN)
185 FullAuto = true;
186
187 // check if we gonna fire
188 bool WillFire = false;
189 if(CountInput(Prev: m_LatestPrevInput.m_Fire, Cur: m_LatestInput.m_Fire).m_Presses)
190 WillFire = true;
191
192 if(FullAuto && (m_LatestInput.m_Fire & 1) && m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
193 WillFire = true;
194
195 if(!WillFire)
196 return;
197
198 // check for ammo
199 if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime)
200 {
201 return;
202 }
203
204 switch(m_Core.m_ActiveWeapon)
205 {
206 case WEAPON_GUN:
207 {
208 if(m_Core.m_Jetpack)
209 {
210 float Strength;
211 if(!m_TuneZone)
212 Strength = Tuning()->m_JetpackStrength;
213 else
214 Strength = TuningList()[m_TuneZone].m_JetpackStrength;
215 TakeDamage(Force: Direction * -1.0f * (Strength / 100.0f / 6.11f), Dmg: 0, From: m_pPlayer->GetCid(), Weapon: m_Core.m_ActiveWeapon);
216 }
217 }
218 }
219}
220
221void CCharacter::HandleNinja()
222{
223 if(m_Core.m_ActiveWeapon != WEAPON_NINJA)
224 return;
225
226 if((Server()->Tick() - m_Core.m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000))
227 {
228 // time's up, return
229 RemoveNinja();
230 return;
231 }
232
233 int NinjaTime = m_Core.m_Ninja.m_ActivationTick + (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000) - Server()->Tick();
234
235 if(NinjaTime % Server()->TickSpeed() == 0 && NinjaTime / Server()->TickSpeed() <= 5)
236 {
237 GameServer()->CreateDamageInd(Pos: m_Pos, AngleMod: 0, Amount: NinjaTime / Server()->TickSpeed(), Mask: TeamMask() & GameServer()->ClientsMaskExcludeClientVersionAndHigher(Version: VERSION_DDNET_NEW_HUD));
238 }
239
240 m_Armor = clamp(val: 10 - (NinjaTime / 15), lo: 0, hi: 10);
241
242 // force ninja Weapon
243 SetWeapon(WEAPON_NINJA);
244
245 m_Core.m_Ninja.m_CurrentMoveTime--;
246
247 if(m_Core.m_Ninja.m_CurrentMoveTime == 0)
248 {
249 // reset velocity
250 m_Core.m_Vel = m_Core.m_Ninja.m_ActivationDir * m_Core.m_Ninja.m_OldVelAmount;
251 }
252
253 if(m_Core.m_Ninja.m_CurrentMoveTime > 0)
254 {
255 // Set velocity
256 m_Core.m_Vel = m_Core.m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity;
257 vec2 OldPos = m_Pos;
258 vec2 GroundElasticity;
259
260 if(!m_TuneZone)
261 GroundElasticity = vec2(Tuning()->m_GroundElasticityX, Tuning()->m_GroundElasticityY);
262 else
263 GroundElasticity = vec2(TuningList()[m_TuneZone].m_GroundElasticityX, TuningList()[m_TuneZone].m_GroundElasticityY);
264
265 Collision()->MoveBox(pInoutPos: &m_Core.m_Pos, pInoutVel: &m_Core.m_Vel, Size: vec2(GetProximityRadius(), GetProximityRadius()), Elasticity: GroundElasticity);
266
267 // reset velocity so the client doesn't predict stuff
268 ResetVelocity();
269
270 // check if we Hit anything along the way
271 {
272 CEntity *apEnts[MAX_CLIENTS];
273 float Radius = GetProximityRadius() * 2.0f;
274 int Num = GameServer()->m_World.FindEntities(Pos: OldPos, Radius, ppEnts: apEnts, Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER);
275
276 // check that we're not in solo part
277 if(Teams()->m_Core.GetSolo(ClientId: m_pPlayer->GetCid()))
278 return;
279
280 for(int i = 0; i < Num; ++i)
281 {
282 auto *pChr = static_cast<CCharacter *>(apEnts[i]);
283 if(pChr == this)
284 continue;
285
286 // Don't hit players in other teams
287 if(Team() != pChr->Team())
288 continue;
289
290 // Don't hit players in solo parts
291 if(Teams()->m_Core.GetSolo(ClientId: pChr->m_pPlayer->GetCid()))
292 return;
293
294 // make sure we haven't Hit this object before
295 bool AlreadyHit = false;
296 for(int j = 0; j < m_NumObjectsHit; j++)
297 {
298 if(m_apHitObjects[j] == pChr)
299 AlreadyHit = true;
300 }
301 if(AlreadyHit)
302 continue;
303
304 // check so we are sufficiently close
305 if(distance(a: pChr->m_Pos, b: m_Pos) > (GetProximityRadius() * 2.0f))
306 continue;
307
308 // Hit a player, give them damage and stuffs...
309 GameServer()->CreateSound(Pos: pChr->m_Pos, Sound: SOUND_NINJA_HIT, Mask: TeamMask());
310 // set his velocity to fast upward (for now)
311 if(m_NumObjectsHit < 10)
312 m_apHitObjects[m_NumObjectsHit++] = pChr;
313
314 pChr->TakeDamage(Force: vec2(0, -10.0f), Dmg: g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, From: m_pPlayer->GetCid(), Weapon: WEAPON_NINJA);
315 }
316 }
317
318 return;
319 }
320}
321
322void CCharacter::DoWeaponSwitch()
323{
324 // make sure we can switch
325 if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_Core.m_aWeapons[WEAPON_NINJA].m_Got || !m_Core.m_aWeapons[m_QueuedWeapon].m_Got)
326 return;
327
328 // switch Weapon
329 SetWeapon(m_QueuedWeapon);
330}
331
332void CCharacter::HandleWeaponSwitch()
333{
334 int WantedWeapon = m_Core.m_ActiveWeapon;
335 if(m_QueuedWeapon != -1)
336 WantedWeapon = m_QueuedWeapon;
337
338 bool Anything = false;
339 for(int i = 0; i < NUM_WEAPONS - 1; ++i)
340 if(m_Core.m_aWeapons[i].m_Got)
341 Anything = true;
342 if(!Anything)
343 return;
344 // select Weapon
345 int Next = CountInput(Prev: m_LatestPrevInput.m_NextWeapon, Cur: m_LatestInput.m_NextWeapon).m_Presses;
346 int Prev = CountInput(Prev: m_LatestPrevInput.m_PrevWeapon, Cur: m_LatestInput.m_PrevWeapon).m_Presses;
347
348 if(Next < 128) // make sure we only try sane stuff
349 {
350 while(Next) // Next Weapon selection
351 {
352 WantedWeapon = (WantedWeapon + 1) % NUM_WEAPONS;
353 if(m_Core.m_aWeapons[WantedWeapon].m_Got)
354 Next--;
355 }
356 }
357
358 if(Prev < 128) // make sure we only try sane stuff
359 {
360 while(Prev) // Prev Weapon selection
361 {
362 WantedWeapon = (WantedWeapon - 1) < 0 ? NUM_WEAPONS - 1 : WantedWeapon - 1;
363 if(m_Core.m_aWeapons[WantedWeapon].m_Got)
364 Prev--;
365 }
366 }
367
368 // Direct Weapon selection
369 if(m_LatestInput.m_WantedWeapon)
370 WantedWeapon = m_Input.m_WantedWeapon - 1;
371
372 // check for insane values
373 if(WantedWeapon >= 0 && WantedWeapon < NUM_WEAPONS && WantedWeapon != m_Core.m_ActiveWeapon && m_Core.m_aWeapons[WantedWeapon].m_Got)
374 m_QueuedWeapon = WantedWeapon;
375
376 DoWeaponSwitch();
377}
378
379void CCharacter::FireWeapon()
380{
381 if(m_ReloadTimer != 0)
382 {
383 if(m_LatestInput.m_Fire & 1)
384 {
385 Antibot()->OnHammerFireReloading(ClientId: m_pPlayer->GetCid());
386 }
387 return;
388 }
389
390 DoWeaponSwitch();
391 vec2 MouseTarget = vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY);
392 vec2 Direction = normalize(v: MouseTarget);
393
394 bool FullAuto = false;
395 if(m_Core.m_ActiveWeapon == WEAPON_GRENADE || m_Core.m_ActiveWeapon == WEAPON_SHOTGUN || m_Core.m_ActiveWeapon == WEAPON_LASER)
396 FullAuto = true;
397 if(m_Core.m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN)
398 FullAuto = true;
399 // allow firing directly after coming out of freeze or being unfrozen
400 // by something
401 if(m_FrozenLastTick)
402 FullAuto = true;
403
404 // don't fire hammer when player is deep and sv_deepfly is disabled
405 if(!g_Config.m_SvDeepfly && m_Core.m_ActiveWeapon == WEAPON_HAMMER && m_Core.m_DeepFrozen)
406 return;
407
408 // check if we gonna fire
409 bool WillFire = false;
410 if(CountInput(Prev: m_LatestPrevInput.m_Fire, Cur: m_LatestInput.m_Fire).m_Presses)
411 WillFire = true;
412
413 if(FullAuto && (m_LatestInput.m_Fire & 1) && m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
414 WillFire = true;
415
416 if(!WillFire)
417 return;
418
419 if(m_FreezeTime)
420 {
421 // Timer stuff to avoid shrieking orchestra caused by unfreeze-plasma
422 if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1))
423 {
424 m_PainSoundTimer = 1 * Server()->TickSpeed();
425 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_PLAYER_PAIN_LONG, Mask: TeamMask());
426 }
427 return;
428 }
429
430 // check for ammo
431 if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
432 return;
433
434 vec2 ProjStartPos = m_Pos + Direction * GetProximityRadius() * 0.75f;
435
436 switch(m_Core.m_ActiveWeapon)
437 {
438 case WEAPON_HAMMER:
439 {
440 // reset objects Hit
441 m_NumObjectsHit = 0;
442 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_HAMMER_FIRE, Mask: TeamMask());
443
444 Antibot()->OnHammerFire(ClientId: m_pPlayer->GetCid());
445
446 if(m_Core.m_HammerHitDisabled)
447 break;
448
449 CEntity *apEnts[MAX_CLIENTS];
450 int Hits = 0;
451 int Num = GameServer()->m_World.FindEntities(Pos: ProjStartPos, Radius: GetProximityRadius() * 0.5f, ppEnts: apEnts,
452 Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER);
453
454 for(int i = 0; i < Num; ++i)
455 {
456 auto *pTarget = static_cast<CCharacter *>(apEnts[i]);
457
458 //if ((pTarget == this) || Collision()->IntersectLine(ProjStartPos, pTarget->m_Pos, NULL, NULL))
459 if((pTarget == this || (pTarget->IsAlive() && !CanCollide(ClientId: pTarget->GetPlayer()->GetCid()))))
460 continue;
461
462 // set his velocity to fast upward (for now)
463 if(length(a: pTarget->m_Pos - ProjStartPos) > 0.0f)
464 GameServer()->CreateHammerHit(Pos: pTarget->m_Pos - normalize(v: pTarget->m_Pos - ProjStartPos) * GetProximityRadius() * 0.5f, Mask: TeamMask());
465 else
466 GameServer()->CreateHammerHit(Pos: ProjStartPos, Mask: TeamMask());
467
468 vec2 Dir;
469 if(length(a: pTarget->m_Pos - m_Pos) > 0.0f)
470 Dir = normalize(v: pTarget->m_Pos - m_Pos);
471 else
472 Dir = vec2(0.f, -1.f);
473
474 float Strength;
475 if(!m_TuneZone)
476 Strength = Tuning()->m_HammerStrength;
477 else
478 Strength = TuningList()[m_TuneZone].m_HammerStrength;
479
480 vec2 Temp = pTarget->m_Core.m_Vel + normalize(v: Dir + vec2(0.f, -1.1f)) * 10.0f;
481 Temp = ClampVel(MoveRestriction: pTarget->m_MoveRestrictions, Vel: Temp);
482 Temp -= pTarget->m_Core.m_Vel;
483 pTarget->TakeDamage(Force: (vec2(0.f, -1.0f) + Temp) * Strength, Dmg: g_pData->m_Weapons.m_Hammer.m_pBase->m_Damage,
484 From: m_pPlayer->GetCid(), Weapon: m_Core.m_ActiveWeapon);
485 pTarget->UnFreeze();
486
487 if(m_FreezeHammer)
488 pTarget->Freeze();
489
490 Antibot()->OnHammerHit(ClientId: m_pPlayer->GetCid(), TargetId: pTarget->GetPlayer()->GetCid());
491
492 Hits++;
493 }
494
495 // if we Hit anything, we have to wait for the reload
496 if(Hits)
497 {
498 float FireDelay;
499 if(!m_TuneZone)
500 FireDelay = Tuning()->m_HammerHitFireDelay;
501 else
502 FireDelay = TuningList()[m_TuneZone].m_HammerHitFireDelay;
503 m_ReloadTimer = FireDelay * Server()->TickSpeed() / 1000;
504 }
505 }
506 break;
507
508 case WEAPON_GUN:
509 {
510 if(!m_Core.m_Jetpack || !m_pPlayer->m_NinjaJetpack || m_Core.m_HasTelegunGun)
511 {
512 int Lifetime;
513 if(!m_TuneZone)
514 Lifetime = (int)(Server()->TickSpeed() * Tuning()->m_GunLifetime);
515 else
516 Lifetime = (int)(Server()->TickSpeed() * TuningList()[m_TuneZone].m_GunLifetime);
517
518 new CProjectile(
519 GameWorld(),
520 WEAPON_GUN, //Type
521 m_pPlayer->GetCid(), //Owner
522 ProjStartPos, //Pos
523 Direction, //Dir
524 Lifetime, //Span
525 false, //Freeze
526 false, //Explosive
527 -1, //SoundImpact
528 MouseTarget //InitDir
529 );
530
531 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_GUN_FIRE, Mask: TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
532 }
533 }
534 break;
535
536 case WEAPON_SHOTGUN:
537 {
538 float LaserReach;
539 if(!m_TuneZone)
540 LaserReach = Tuning()->m_LaserReach;
541 else
542 LaserReach = TuningList()[m_TuneZone].m_LaserReach;
543
544 new CLaser(&GameServer()->m_World, m_Pos, Direction, LaserReach, m_pPlayer->GetCid(), WEAPON_SHOTGUN);
545 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_SHOTGUN_FIRE, Mask: TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
546 }
547 break;
548
549 case WEAPON_GRENADE:
550 {
551 int Lifetime;
552 if(!m_TuneZone)
553 Lifetime = (int)(Server()->TickSpeed() * Tuning()->m_GrenadeLifetime);
554 else
555 Lifetime = (int)(Server()->TickSpeed() * TuningList()[m_TuneZone].m_GrenadeLifetime);
556
557 new CProjectile(
558 GameWorld(),
559 WEAPON_GRENADE, //Type
560 m_pPlayer->GetCid(), //Owner
561 ProjStartPos, //Pos
562 Direction, //Dir
563 Lifetime, //Span
564 false, //Freeze
565 true, //Explosive
566 SOUND_GRENADE_EXPLODE, //SoundImpact
567 MouseTarget // MouseTarget
568 );
569
570 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_GRENADE_FIRE, Mask: TeamMask());
571 }
572 break;
573
574 case WEAPON_LASER:
575 {
576 float LaserReach;
577 if(!m_TuneZone)
578 LaserReach = Tuning()->m_LaserReach;
579 else
580 LaserReach = TuningList()[m_TuneZone].m_LaserReach;
581
582 new CLaser(GameWorld(), m_Pos, Direction, LaserReach, m_pPlayer->GetCid(), WEAPON_LASER);
583 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_LASER_FIRE, Mask: TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
584 }
585 break;
586
587 case WEAPON_NINJA:
588 {
589 // reset Hit objects
590 m_NumObjectsHit = 0;
591
592 m_Core.m_Ninja.m_ActivationDir = Direction;
593 m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000;
594 m_Core.m_Ninja.m_OldVelAmount = length(a: m_Core.m_Vel);
595
596 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_NINJA_FIRE, Mask: TeamMask());
597 }
598 break;
599 }
600
601 m_AttackTick = Server()->Tick();
602
603 if(!m_ReloadTimer)
604 {
605 float FireDelay;
606 if(!m_TuneZone)
607 Tuning()->Get(Index: 38 + m_Core.m_ActiveWeapon, pValue: &FireDelay);
608 else
609 TuningList()[m_TuneZone].Get(Index: 38 + m_Core.m_ActiveWeapon, pValue: &FireDelay);
610 m_ReloadTimer = FireDelay * Server()->TickSpeed() / 1000;
611 }
612}
613
614void CCharacter::HandleWeapons()
615{
616 //ninja
617 HandleNinja();
618 HandleJetpack();
619
620 if(m_PainSoundTimer > 0)
621 m_PainSoundTimer--;
622
623 // check reload timer
624 if(m_ReloadTimer)
625 {
626 m_ReloadTimer--;
627 return;
628 }
629
630 // fire Weapon, if wanted
631 FireWeapon();
632}
633
634void CCharacter::GiveNinja()
635{
636 m_Core.m_Ninja.m_ActivationTick = Server()->Tick();
637 m_Core.m_aWeapons[WEAPON_NINJA].m_Got = true;
638 m_Core.m_aWeapons[WEAPON_NINJA].m_Ammo = -1;
639 if(m_Core.m_ActiveWeapon != WEAPON_NINJA)
640 m_LastWeapon = m_Core.m_ActiveWeapon;
641 m_Core.m_ActiveWeapon = WEAPON_NINJA;
642
643 if(!m_Core.m_aWeapons[WEAPON_NINJA].m_Got)
644 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_PICKUP_NINJA, Mask: TeamMask());
645}
646
647void CCharacter::RemoveNinja()
648{
649 m_Core.m_Ninja.m_ActivationDir = vec2(0, 0);
650 m_Core.m_Ninja.m_ActivationTick = 0;
651 m_Core.m_Ninja.m_CurrentMoveTime = 0;
652 m_Core.m_Ninja.m_OldVelAmount = 0;
653 m_Core.m_aWeapons[WEAPON_NINJA].m_Got = false;
654 m_Core.m_aWeapons[WEAPON_NINJA].m_Ammo = 0;
655 m_Core.m_ActiveWeapon = m_LastWeapon;
656
657 SetWeapon(m_Core.m_ActiveWeapon);
658}
659
660void CCharacter::SetEmote(int Emote, int Tick)
661{
662 m_EmoteType = Emote;
663 m_EmoteStop = Tick;
664}
665
666void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
667{
668 // check for changes
669 if(mem_comp(a: &m_SavedInput, b: pNewInput, size: sizeof(CNetObj_PlayerInput)) != 0)
670 m_LastAction = Server()->Tick();
671
672 // copy new input
673 mem_copy(dest: &m_Input, source: pNewInput, size: sizeof(m_Input));
674
675 // it is not allowed to aim in the center
676 if(m_Input.m_TargetX == 0 && m_Input.m_TargetY == 0)
677 m_Input.m_TargetY = -1;
678
679 mem_copy(dest: &m_SavedInput, source: &m_Input, size: sizeof(m_SavedInput));
680}
681
682void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
683{
684 mem_copy(dest: &m_LatestPrevInput, source: &m_LatestInput, size: sizeof(m_LatestInput));
685 mem_copy(dest: &m_LatestInput, source: pNewInput, size: sizeof(m_LatestInput));
686 m_NumInputs++;
687
688 // it is not allowed to aim in the center
689 if(m_LatestInput.m_TargetX == 0 && m_LatestInput.m_TargetY == 0)
690 m_LatestInput.m_TargetY = -1;
691
692 Antibot()->OnDirectInput(ClientId: m_pPlayer->GetCid());
693
694 if(m_NumInputs > 1 && m_pPlayer->GetTeam() != TEAM_SPECTATORS)
695 {
696 HandleWeaponSwitch();
697 FireWeapon();
698 }
699
700 mem_copy(dest: &m_LatestPrevPrevInput, source: &m_LatestPrevInput, size: sizeof(m_LatestInput));
701 mem_copy(dest: &m_LatestPrevInput, source: &m_LatestInput, size: sizeof(m_LatestInput));
702}
703
704void CCharacter::ReleaseHook()
705{
706 m_Core.SetHookedPlayer(-1);
707 m_Core.m_HookState = HOOK_RETRACTED;
708 m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT;
709}
710
711void CCharacter::ResetHook()
712{
713 ReleaseHook();
714 m_Core.m_HookPos = m_Core.m_Pos;
715}
716
717void CCharacter::ResetInput()
718{
719 m_Input.m_Direction = 0;
720 // simulate releasing the fire button
721 if((m_Input.m_Fire & 1) != 0)
722 m_Input.m_Fire++;
723 m_Input.m_Fire &= INPUT_STATE_MASK;
724 m_Input.m_Jump = 0;
725 m_LatestPrevInput = m_LatestInput = m_Input;
726}
727
728void CCharacter::PreTick()
729{
730 if(m_StartTime > Server()->Tick())
731 {
732 // Prevent the player from getting a negative time
733 // The main reason why this can happen is because of time penalty tiles
734 // However, other reasons are hereby also excluded
735 GameServer()->SendChatTarget(To: m_pPlayer->GetCid(), pText: "You died of old age");
736 Die(Killer: m_pPlayer->GetCid(), Weapon: WEAPON_WORLD);
737 }
738
739 if(m_Paused)
740 return;
741
742 // set emote
743 if(m_EmoteStop < Server()->Tick())
744 {
745 SetEmote(Emote: m_pPlayer->GetDefaultEmote(), Tick: -1);
746 }
747
748 DDRaceTick();
749
750 Antibot()->OnCharacterTick(ClientId: m_pPlayer->GetCid());
751
752 m_Core.m_Input = m_Input;
753 m_Core.Tick(UseInput: true, DoDeferredTick: !g_Config.m_SvNoWeakHook);
754}
755
756void CCharacter::Tick()
757{
758 if(g_Config.m_SvNoWeakHook)
759 {
760 if(m_Paused)
761 return;
762
763 m_Core.TickDeferred();
764 }
765 else
766 {
767 PreTick();
768 }
769
770 if(!m_PrevInput.m_Hook && m_Input.m_Hook && !(m_Core.m_TriggeredEvents & COREEVENT_HOOK_ATTACH_PLAYER))
771 {
772 Antibot()->OnHookAttach(ClientId: m_pPlayer->GetCid(), Player: false);
773 }
774
775 // handle Weapons
776 HandleWeapons();
777
778 DDRacePostCoreTick();
779
780 if(m_Core.m_TriggeredEvents & COREEVENT_HOOK_ATTACH_PLAYER)
781 {
782 const int HookedPlayer = m_Core.HookedPlayer();
783 if(HookedPlayer != -1 && GameServer()->m_apPlayers[HookedPlayer]->GetTeam() != TEAM_SPECTATORS)
784 {
785 Antibot()->OnHookAttach(ClientId: m_pPlayer->GetCid(), Player: true);
786 }
787 }
788
789 // Previnput
790 m_PrevInput = m_Input;
791
792 m_PrevPos = m_Core.m_Pos;
793}
794
795void CCharacter::TickDeferred()
796{
797 // advance the dummy
798 {
799 CWorldCore TempWorld;
800 m_ReckoningCore.Init(pWorld: &TempWorld, pCollision: Collision(), pTeams: &Teams()->m_Core);
801 m_ReckoningCore.m_Id = m_pPlayer->GetCid();
802 m_ReckoningCore.Tick(UseInput: false);
803 m_ReckoningCore.Move();
804 m_ReckoningCore.Quantize();
805 }
806
807 //lastsentcore
808 vec2 StartPos = m_Core.m_Pos;
809 vec2 StartVel = m_Core.m_Vel;
810 bool StuckBefore = Collision()->TestBox(Pos: m_Core.m_Pos, Size: CCharacterCore::PhysicalSizeVec2());
811
812 m_Core.m_Id = m_pPlayer->GetCid();
813 m_Core.Move();
814 bool StuckAfterMove = Collision()->TestBox(Pos: m_Core.m_Pos, Size: CCharacterCore::PhysicalSizeVec2());
815 m_Core.Quantize();
816 bool StuckAfterQuant = Collision()->TestBox(Pos: m_Core.m_Pos, Size: CCharacterCore::PhysicalSizeVec2());
817 m_Pos = m_Core.m_Pos;
818
819 if(!StuckBefore && (StuckAfterMove || StuckAfterQuant))
820 {
821 // Hackish solution to get rid of strict-aliasing warning
822 union
823 {
824 float f;
825 unsigned u;
826 } StartPosX, StartPosY, StartVelX, StartVelY;
827
828 StartPosX.f = StartPos.x;
829 StartPosY.f = StartPos.y;
830 StartVelX.f = StartVel.x;
831 StartVelY.f = StartVel.y;
832
833 char aBuf[256];
834 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "STUCK!!! %d %d %d %f %f %f %f %x %x %x %x",
835 StuckBefore,
836 StuckAfterMove,
837 StuckAfterQuant,
838 StartPos.x, StartPos.y,
839 StartVel.x, StartVel.y,
840 StartPosX.u, StartPosY.u,
841 StartVelX.u, StartVelY.u);
842 GameServer()->Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "game", pStr: aBuf);
843 }
844
845 {
846 int Events = m_Core.m_TriggeredEvents;
847 int CID = m_pPlayer->GetCid();
848
849 CClientMask TeamMask = Teams()->TeamMask(Team: Team(), ExceptId: -1, Asker: CID);
850 // Some sounds are triggered client-side for the acting player
851 // so we need to avoid duplicating them
852 CClientMask TeamMaskExceptSelf = Teams()->TeamMask(Team: Team(), ExceptId: CID, Asker: CID);
853 // Some are triggered client-side but only on Sixup
854 CClientMask TeamMaskExceptSelfIfSixup = Server()->IsSixup(ClientId: CID) ? TeamMaskExceptSelf : TeamMask;
855
856 if(Events & COREEVENT_GROUND_JUMP)
857 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_PLAYER_JUMP, Mask: TeamMaskExceptSelf);
858
859 if(Events & COREEVENT_HOOK_ATTACH_PLAYER)
860 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_HOOK_ATTACH_PLAYER, Mask: TeamMaskExceptSelfIfSixup);
861
862 if(Events & COREEVENT_HOOK_ATTACH_GROUND)
863 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_HOOK_ATTACH_GROUND, Mask: TeamMaskExceptSelf);
864
865 if(Events & COREEVENT_HOOK_HIT_NOHOOK)
866 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_HOOK_NOATTACH, Mask: TeamMaskExceptSelf);
867
868 if(Events & COREEVENT_GROUND_JUMP)
869 m_TriggeredEvents7 |= protocol7::COREEVENTFLAG_GROUND_JUMP;
870 if(Events & COREEVENT_AIR_JUMP)
871 m_TriggeredEvents7 |= protocol7::COREEVENTFLAG_AIR_JUMP;
872 if(Events & COREEVENT_HOOK_ATTACH_PLAYER)
873 m_TriggeredEvents7 |= protocol7::COREEVENTFLAG_HOOK_ATTACH_PLAYER;
874 if(Events & COREEVENT_HOOK_ATTACH_GROUND)
875 m_TriggeredEvents7 |= protocol7::COREEVENTFLAG_HOOK_ATTACH_GROUND;
876 if(Events & COREEVENT_HOOK_HIT_NOHOOK)
877 m_TriggeredEvents7 |= protocol7::COREEVENTFLAG_HOOK_HIT_NOHOOK;
878 }
879
880 if(m_pPlayer->GetTeam() == TEAM_SPECTATORS)
881 {
882 m_Pos.x = m_Input.m_TargetX;
883 m_Pos.y = m_Input.m_TargetY;
884 }
885
886 // update the m_SendCore if needed
887 {
888 CNetObj_Character Predicted;
889 CNetObj_Character Current;
890 mem_zero(block: &Predicted, size: sizeof(Predicted));
891 mem_zero(block: &Current, size: sizeof(Current));
892 m_ReckoningCore.Write(pObjCore: &Predicted);
893 m_Core.Write(pObjCore: &Current);
894
895 // only allow dead reckoning for a top of 3 seconds
896 if(m_Core.m_Reset || m_ReckoningTick + Server()->TickSpeed() * 3 < Server()->Tick() || mem_comp(a: &Predicted, b: &Current, size: sizeof(CNetObj_Character)) != 0)
897 {
898 m_ReckoningTick = Server()->Tick();
899 m_SendCore = m_Core;
900 m_ReckoningCore = m_Core;
901 m_Core.m_Reset = false;
902 }
903 }
904}
905
906void CCharacter::TickPaused()
907{
908 ++m_AttackTick;
909 ++m_DamageTakenTick;
910 ++m_Core.m_Ninja.m_ActivationTick;
911 ++m_ReckoningTick;
912 if(m_LastAction != -1)
913 ++m_LastAction;
914 if(m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart > -1)
915 ++m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart;
916 if(m_EmoteStop > -1)
917 ++m_EmoteStop;
918}
919
920bool CCharacter::IncreaseHealth(int Amount)
921{
922 if(m_Health >= 10)
923 return false;
924 m_Health = clamp(val: m_Health + Amount, lo: 0, hi: 10);
925 return true;
926}
927
928bool CCharacter::IncreaseArmor(int Amount)
929{
930 if(m_Armor >= 10)
931 return false;
932 m_Armor = clamp(val: m_Armor + Amount, lo: 0, hi: 10);
933 return true;
934}
935
936void CCharacter::StopRecording()
937{
938 if(Server()->IsRecording(ClientId: m_pPlayer->GetCid()))
939 {
940 CPlayerData *pData = GameServer()->Score()->PlayerData(Id: m_pPlayer->GetCid());
941
942 if(pData->m_RecordStopTick - Server()->Tick() <= Server()->TickSpeed() && pData->m_RecordStopTick != -1)
943 Server()->SaveDemo(ClientId: m_pPlayer->GetCid(), Time: pData->m_RecordFinishTime);
944 else
945 Server()->StopRecord(ClientId: m_pPlayer->GetCid());
946
947 pData->m_RecordStopTick = -1;
948 }
949}
950
951void CCharacter::Die(int Killer, int Weapon, bool SendKillMsg)
952{
953 StopRecording();
954 int ModeSpecial = GameServer()->m_pController->OnCharacterDeath(pVictim: this, pKiller: GameServer()->m_apPlayers[Killer], Weapon);
955
956 char aBuf[256];
957 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d",
958 Killer, Server()->ClientName(ClientId: Killer),
959 m_pPlayer->GetCid(), Server()->ClientName(ClientId: m_pPlayer->GetCid()), Weapon, ModeSpecial);
960 GameServer()->Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "game", pStr: aBuf);
961
962 // send the kill message
963 if(SendKillMsg && (Team() == TEAM_FLOCK || Teams()->TeamFlock(Team: Team()) || Teams()->Count(Team: Team()) == 1 || Teams()->GetTeamState(Team: Team()) == CGameTeams::TEAMSTATE_OPEN || !Teams()->TeamLocked(Team: Team())))
964 {
965 CNetMsg_Sv_KillMsg Msg;
966 Msg.m_Killer = Killer;
967 Msg.m_Victim = m_pPlayer->GetCid();
968 Msg.m_Weapon = Weapon;
969 Msg.m_ModeSpecial = ModeSpecial;
970 Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId: -1);
971 }
972
973 // a nice sound
974 GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_PLAYER_DIE, Mask: TeamMask());
975
976 // this is to rate limit respawning to 3 secs
977 m_pPlayer->m_PreviousDieTick = m_pPlayer->m_DieTick;
978 m_pPlayer->m_DieTick = Server()->Tick();
979
980 m_Alive = false;
981 SetSolo(false);
982
983 GameServer()->m_World.RemoveEntity(pEntity: this);
984 GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCid()] = 0;
985 GameServer()->CreateDeath(Pos: m_Pos, ClientId: m_pPlayer->GetCid(), Mask: TeamMask());
986 Teams()->OnCharacterDeath(ClientId: GetPlayer()->GetCid(), Weapon);
987}
988
989bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
990{
991 if(Dmg)
992 {
993 SetEmote(Emote: EMOTE_PAIN, Tick: Server()->Tick() + 500 * Server()->TickSpeed() / 1000);
994 }
995
996 vec2 Temp = m_Core.m_Vel + Force;
997 m_Core.m_Vel = ClampVel(MoveRestriction: m_MoveRestrictions, Vel: Temp);
998
999 return true;
1000}
1001
1002//TODO: Move the emote stuff to a function
1003void CCharacter::SnapCharacter(int SnappingClient, int Id)
1004{
1005 int SnappingClientVersion = GameServer()->GetClientVersion(ClientId: SnappingClient);
1006 CCharacterCore *pCore;
1007 int Tick, Emote = m_EmoteType, Weapon = m_Core.m_ActiveWeapon, AmmoCount = 0,
1008 Health = 0, Armor = 0;
1009 if(!m_ReckoningTick || GameServer()->m_World.m_Paused)
1010 {
1011 Tick = 0;
1012 pCore = &m_Core;
1013 }
1014 else
1015 {
1016 Tick = m_ReckoningTick;
1017 pCore = &m_SendCore;
1018 }
1019
1020 // change eyes and use ninja graphic if player is frozen
1021 if(m_Core.m_DeepFrozen || m_FreezeTime > 0 || m_Core.m_LiveFrozen)
1022 {
1023 if(Emote == EMOTE_NORMAL)
1024 Emote = (m_Core.m_DeepFrozen || m_Core.m_LiveFrozen) ? EMOTE_PAIN : EMOTE_BLINK;
1025
1026 if((m_Core.m_DeepFrozen || m_FreezeTime > 0) && SnappingClientVersion < VERSION_DDNET_NEW_HUD)
1027 Weapon = WEAPON_NINJA;
1028 }
1029
1030 // solo, collision, jetpack and ninjajetpack prediction
1031 if(m_pPlayer->GetCid() == SnappingClient)
1032 {
1033 int Faketuning = 0;
1034 if(m_pPlayer->GetClientVersion() < VERSION_DDNET_NEW_HUD)
1035 {
1036 if(m_Core.m_Jetpack && Weapon != WEAPON_NINJA)
1037 Faketuning |= FAKETUNE_JETPACK;
1038 if(m_Core.m_Solo)
1039 Faketuning |= FAKETUNE_SOLO;
1040 if(m_Core.m_HammerHitDisabled)
1041 Faketuning |= FAKETUNE_NOHAMMER;
1042 if(m_Core.m_CollisionDisabled)
1043 Faketuning |= FAKETUNE_NOCOLL;
1044 if(m_Core.m_HookHitDisabled)
1045 Faketuning |= FAKETUNE_NOHOOK;
1046 if(!m_Core.m_EndlessJump && m_Core.m_Jumps == 0)
1047 Faketuning |= FAKETUNE_NOJUMP;
1048 }
1049 if(Faketuning != m_NeededFaketuning)
1050 {
1051 m_NeededFaketuning = Faketuning;
1052 GameServer()->SendTuningParams(ClientId: m_pPlayer->GetCid(), Zone: m_TuneZone); // update tunings
1053 }
1054 }
1055
1056 // change eyes, use ninja graphic and set ammo count if player has ninjajetpack
1057 if(m_pPlayer->m_NinjaJetpack && m_Core.m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN && !m_Core.m_DeepFrozen && m_FreezeTime == 0 && !m_Core.m_HasTelegunGun)
1058 {
1059 if(Emote == EMOTE_NORMAL)
1060 Emote = EMOTE_HAPPY;
1061 Weapon = WEAPON_NINJA;
1062 AmmoCount = 10;
1063 }
1064
1065 if(m_pPlayer->GetCid() == SnappingClient || SnappingClient == SERVER_DEMO_CLIENT ||
1066 (!g_Config.m_SvStrictSpectateMode && m_pPlayer->GetCid() == GameServer()->m_apPlayers[SnappingClient]->m_SpectatorId))
1067 {
1068 Health = m_Health;
1069 Armor = m_Armor;
1070 if(m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo > 0)
1071 AmmoCount = (m_FreezeTime == 0) ? m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo : 0;
1072 }
1073
1074 if(GetPlayer()->IsAfk() || GetPlayer()->IsPaused())
1075 {
1076 if(m_FreezeTime > 0 || m_Core.m_DeepFrozen || m_Core.m_LiveFrozen)
1077 Emote = EMOTE_NORMAL;
1078 else
1079 Emote = EMOTE_BLINK;
1080 }
1081
1082 if(Emote == EMOTE_NORMAL)
1083 {
1084 if(5 * Server()->TickSpeed() - ((Server()->Tick() - m_LastAction) % (5 * Server()->TickSpeed())) < 5)
1085 Emote = EMOTE_BLINK;
1086 }
1087
1088 if(!Server()->IsSixup(ClientId: SnappingClient))
1089 {
1090 CNetObj_Character *pCharacter = Server()->SnapNewItem<CNetObj_Character>(Id);
1091 if(!pCharacter)
1092 return;
1093
1094 pCore->Write(pObjCore: pCharacter);
1095
1096 pCharacter->m_Tick = Tick;
1097 pCharacter->m_Emote = Emote;
1098
1099 if(pCharacter->m_HookedPlayer != -1)
1100 {
1101 if(!Server()->Translate(Target&: pCharacter->m_HookedPlayer, Client: SnappingClient))
1102 pCharacter->m_HookedPlayer = -1;
1103 }
1104
1105 pCharacter->m_AttackTick = m_AttackTick;
1106 pCharacter->m_Direction = m_Input.m_Direction;
1107 pCharacter->m_Weapon = Weapon;
1108 pCharacter->m_AmmoCount = AmmoCount;
1109 pCharacter->m_Health = Health;
1110 pCharacter->m_Armor = Armor;
1111 pCharacter->m_PlayerFlags = GetPlayer()->m_PlayerFlags;
1112 }
1113 else
1114 {
1115 protocol7::CNetObj_Character *pCharacter = Server()->SnapNewItem<protocol7::CNetObj_Character>(Id);
1116 if(!pCharacter)
1117 return;
1118
1119 pCore->Write(pObjCore: reinterpret_cast<CNetObj_CharacterCore *>(static_cast<protocol7::CNetObj_CharacterCore *>(pCharacter)));
1120 if(pCharacter->m_Angle > (int)(pi * 256.0f))
1121 {
1122 pCharacter->m_Angle -= (int)(2.0f * pi * 256.0f);
1123 }
1124
1125 // m_HookTick can be negative when using the hook_duration tune, which 0.7 clients
1126 // will consider invalid. https://github.com/ddnet/ddnet/issues/3915
1127 pCharacter->m_HookTick = maximum(a: 0, b: pCharacter->m_HookTick);
1128
1129 pCharacter->m_Tick = Tick;
1130 pCharacter->m_Emote = Emote;
1131 pCharacter->m_AttackTick = m_AttackTick;
1132 pCharacter->m_Direction = m_Input.m_Direction;
1133 pCharacter->m_Weapon = Weapon;
1134 pCharacter->m_AmmoCount = AmmoCount;
1135
1136 if(m_FreezeTime > 0 || m_Core.m_DeepFrozen)
1137 pCharacter->m_AmmoCount = m_Core.m_FreezeStart + g_Config.m_SvFreezeDelay * Server()->TickSpeed();
1138 else if(Weapon == WEAPON_NINJA)
1139 pCharacter->m_AmmoCount = m_Core.m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000;
1140
1141 pCharacter->m_Health = Health;
1142 pCharacter->m_Armor = Armor;
1143 pCharacter->m_TriggeredEvents = m_TriggeredEvents7;
1144 }
1145}
1146
1147bool CCharacter::CanSnapCharacter(int SnappingClient)
1148{
1149 if(SnappingClient == SERVER_DEMO_CLIENT)
1150 return true;
1151
1152 CCharacter *pSnapChar = GameServer()->GetPlayerChar(ClientId: SnappingClient);
1153 CPlayer *pSnapPlayer = GameServer()->m_apPlayers[SnappingClient];
1154
1155 if(pSnapPlayer->GetTeam() == TEAM_SPECTATORS || pSnapPlayer->IsPaused())
1156 {
1157 if(pSnapPlayer->m_SpectatorId != SPEC_FREEVIEW && !CanCollide(ClientId: pSnapPlayer->m_SpectatorId) && (pSnapPlayer->m_ShowOthers == SHOW_OTHERS_OFF || (pSnapPlayer->m_ShowOthers == SHOW_OTHERS_ONLY_TEAM && !SameTeam(ClientId: pSnapPlayer->m_SpectatorId))))
1158 return false;
1159 else if(pSnapPlayer->m_SpectatorId == SPEC_FREEVIEW && !CanCollide(ClientId: SnappingClient) && pSnapPlayer->m_SpecTeam && !SameTeam(ClientId: SnappingClient))
1160 return false;
1161 }
1162 else if(pSnapChar && !pSnapChar->m_Core.m_Super && !CanCollide(ClientId: SnappingClient) && (pSnapPlayer->m_ShowOthers == SHOW_OTHERS_OFF || (pSnapPlayer->m_ShowOthers == SHOW_OTHERS_ONLY_TEAM && !SameTeam(ClientId: SnappingClient))))
1163 return false;
1164
1165 return true;
1166}
1167
1168bool CCharacter::IsSnappingCharacterInView(int SnappingClientId)
1169{
1170 int Id = m_pPlayer->GetCid();
1171
1172 // A player may not be clipped away if his hook or a hook attached to him is in the field of view
1173 bool PlayerAndHookNotInView = NetworkClippedLine(SnappingClient: SnappingClientId, StartPos: m_Pos, EndPos: m_Core.m_HookPos);
1174 bool AttachedHookInView = false;
1175 if(PlayerAndHookNotInView)
1176 {
1177 for(const auto &AttachedPlayerId : m_Core.m_AttachedPlayers)
1178 {
1179 const CCharacter *pOtherPlayer = GameServer()->GetPlayerChar(ClientId: AttachedPlayerId);
1180 if(pOtherPlayer && pOtherPlayer->m_Core.HookedPlayer() == Id)
1181 {
1182 if(!NetworkClippedLine(SnappingClient: SnappingClientId, StartPos: m_Pos, EndPos: pOtherPlayer->m_Pos))
1183 {
1184 AttachedHookInView = true;
1185 break;
1186 }
1187 }
1188 }
1189 }
1190 if(PlayerAndHookNotInView && !AttachedHookInView)
1191 {
1192 return false;
1193 }
1194 return true;
1195}
1196
1197void CCharacter::Snap(int SnappingClient)
1198{
1199 int Id = m_pPlayer->GetCid();
1200
1201 if(!Server()->Translate(Target&: Id, Client: SnappingClient))
1202 return;
1203
1204 if(!CanSnapCharacter(SnappingClient))
1205 {
1206 return;
1207 }
1208
1209 if(!IsSnappingCharacterInView(SnappingClientId: SnappingClient))
1210 return;
1211
1212 SnapCharacter(SnappingClient, Id);
1213
1214 CNetObj_DDNetCharacter *pDDNetCharacter = Server()->SnapNewItem<CNetObj_DDNetCharacter>(Id);
1215 if(!pDDNetCharacter)
1216 return;
1217
1218 pDDNetCharacter->m_Flags = 0;
1219 if(m_Core.m_Solo)
1220 pDDNetCharacter->m_Flags |= CHARACTERFLAG_SOLO;
1221 if(m_Core.m_Super)
1222 pDDNetCharacter->m_Flags |= CHARACTERFLAG_SUPER;
1223 if(m_Core.m_EndlessHook)
1224 pDDNetCharacter->m_Flags |= CHARACTERFLAG_ENDLESS_HOOK;
1225 if(m_Core.m_CollisionDisabled || !Tuning()->m_PlayerCollision)
1226 pDDNetCharacter->m_Flags |= CHARACTERFLAG_COLLISION_DISABLED;
1227 if(m_Core.m_HookHitDisabled || !Tuning()->m_PlayerHooking)
1228 pDDNetCharacter->m_Flags |= CHARACTERFLAG_HOOK_HIT_DISABLED;
1229 if(m_Core.m_EndlessJump)
1230 pDDNetCharacter->m_Flags |= CHARACTERFLAG_ENDLESS_JUMP;
1231 if(m_Core.m_Jetpack)
1232 pDDNetCharacter->m_Flags |= CHARACTERFLAG_JETPACK;
1233 if(m_Core.m_HammerHitDisabled)
1234 pDDNetCharacter->m_Flags |= CHARACTERFLAG_HAMMER_HIT_DISABLED;
1235 if(m_Core.m_ShotgunHitDisabled)
1236 pDDNetCharacter->m_Flags |= CHARACTERFLAG_SHOTGUN_HIT_DISABLED;
1237 if(m_Core.m_GrenadeHitDisabled)
1238 pDDNetCharacter->m_Flags |= CHARACTERFLAG_GRENADE_HIT_DISABLED;
1239 if(m_Core.m_LaserHitDisabled)
1240 pDDNetCharacter->m_Flags |= CHARACTERFLAG_LASER_HIT_DISABLED;
1241 if(m_Core.m_HasTelegunGun)
1242 pDDNetCharacter->m_Flags |= CHARACTERFLAG_TELEGUN_GUN;
1243 if(m_Core.m_HasTelegunGrenade)
1244 pDDNetCharacter->m_Flags |= CHARACTERFLAG_TELEGUN_GRENADE;
1245 if(m_Core.m_HasTelegunLaser)
1246 pDDNetCharacter->m_Flags |= CHARACTERFLAG_TELEGUN_LASER;
1247 if(m_Core.m_aWeapons[WEAPON_HAMMER].m_Got)
1248 pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_HAMMER;
1249 if(m_Core.m_aWeapons[WEAPON_GUN].m_Got)
1250 pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_GUN;
1251 if(m_Core.m_aWeapons[WEAPON_SHOTGUN].m_Got)
1252 pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_SHOTGUN;
1253 if(m_Core.m_aWeapons[WEAPON_GRENADE].m_Got)
1254 pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_GRENADE;
1255 if(m_Core.m_aWeapons[WEAPON_LASER].m_Got)
1256 pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_LASER;
1257 if(m_Core.m_ActiveWeapon == WEAPON_NINJA)
1258 pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_NINJA;
1259 if(m_Core.m_LiveFrozen)
1260 pDDNetCharacter->m_Flags |= CHARACTERFLAG_MOVEMENTS_DISABLED;
1261
1262 pDDNetCharacter->m_FreezeEnd = m_Core.m_DeepFrozen ? -1 : m_FreezeTime == 0 ? 0 : Server()->Tick() + m_FreezeTime;
1263 pDDNetCharacter->m_Jumps = m_Core.m_Jumps;
1264 pDDNetCharacter->m_TeleCheckpoint = m_TeleCheckpoint;
1265 pDDNetCharacter->m_StrongWeakId = m_StrongWeakId;
1266
1267 // Display Information
1268 pDDNetCharacter->m_JumpedTotal = m_Core.m_JumpedTotal;
1269 pDDNetCharacter->m_NinjaActivationTick = m_Core.m_Ninja.m_ActivationTick;
1270 pDDNetCharacter->m_FreezeStart = m_Core.m_FreezeStart;
1271 if(m_Core.m_IsInFreeze)
1272 {
1273 pDDNetCharacter->m_Flags |= CHARACTERFLAG_IN_FREEZE;
1274 }
1275 if(Teams()->IsPractice(Team: Team()))
1276 {
1277 pDDNetCharacter->m_Flags |= CHARACTERFLAG_PRACTICE_MODE;
1278 }
1279 if(Teams()->TeamLocked(Team: Team()))
1280 {
1281 pDDNetCharacter->m_Flags |= CHARACTERFLAG_LOCK_MODE;
1282 }
1283 if(Teams()->TeamFlock(Team: Team()))
1284 {
1285 pDDNetCharacter->m_Flags |= CHARACTERFLAG_TEAM0_MODE;
1286 }
1287 pDDNetCharacter->m_TargetX = m_Core.m_Input.m_TargetX;
1288 pDDNetCharacter->m_TargetY = m_Core.m_Input.m_TargetY;
1289}
1290
1291void CCharacter::PostSnap()
1292{
1293 m_TriggeredEvents7 = 0;
1294}
1295
1296// DDRace
1297
1298bool CCharacter::CanCollide(int ClientId)
1299{
1300 return Teams()->m_Core.CanCollide(ClientId1: GetPlayer()->GetCid(), ClientId2: ClientId);
1301}
1302bool CCharacter::SameTeam(int ClientId)
1303{
1304 return Teams()->m_Core.SameTeam(ClientId1: GetPlayer()->GetCid(), ClientId2: ClientId);
1305}
1306
1307int CCharacter::Team()
1308{
1309 return Teams()->m_Core.Team(ClientId: m_pPlayer->GetCid());
1310}
1311
1312void CCharacter::FillAntibot(CAntibotCharacterData *pData)
1313{
1314 pData->m_Pos = m_Pos;
1315 pData->m_Vel = m_Core.m_Vel;
1316 pData->m_Angle = m_Core.m_Angle;
1317 pData->m_HookedPlayer = m_Core.HookedPlayer();
1318 pData->m_SpawnTick = m_SpawnTick;
1319 pData->m_WeaponChangeTick = m_WeaponChangeTick;
1320 pData->m_aLatestInputs[0].m_TargetX = m_LatestInput.m_TargetX;
1321 pData->m_aLatestInputs[0].m_TargetY = m_LatestInput.m_TargetY;
1322 pData->m_aLatestInputs[1].m_TargetX = m_LatestPrevInput.m_TargetX;
1323 pData->m_aLatestInputs[1].m_TargetY = m_LatestPrevInput.m_TargetY;
1324 pData->m_aLatestInputs[2].m_TargetX = m_LatestPrevPrevInput.m_TargetX;
1325 pData->m_aLatestInputs[2].m_TargetY = m_LatestPrevPrevInput.m_TargetY;
1326}
1327
1328void CCharacter::HandleBroadcast()
1329{
1330 CPlayerData *pData = GameServer()->Score()->PlayerData(Id: m_pPlayer->GetCid());
1331
1332 if(m_DDRaceState == DDRACE_STARTED && m_pPlayer->GetClientVersion() == VERSION_VANILLA &&
1333 m_LastTimeCpBroadcasted != m_LastTimeCp && m_LastTimeCp > -1 &&
1334 m_TimeCpBroadcastEndTick > Server()->Tick() && pData->m_BestTime && pData->m_aBestTimeCp[m_LastTimeCp] != 0)
1335 {
1336 char aBroadcast[128];
1337 float Diff = m_aCurrentTimeCp[m_LastTimeCp] - pData->m_aBestTimeCp[m_LastTimeCp];
1338 str_format(buffer: aBroadcast, buffer_size: sizeof(aBroadcast), format: "Checkpoint | Diff : %+5.2f", Diff);
1339 GameServer()->SendBroadcast(pText: aBroadcast, ClientId: m_pPlayer->GetCid());
1340 m_LastTimeCpBroadcasted = m_LastTimeCp;
1341 m_LastBroadcast = Server()->Tick();
1342 }
1343 else if((m_pPlayer->m_TimerType == CPlayer::TIMERTYPE_BROADCAST || m_pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && m_DDRaceState == DDRACE_STARTED && m_LastBroadcast + Server()->TickSpeed() * g_Config.m_SvTimeInBroadcastInterval <= Server()->Tick())
1344 {
1345 char aBuf[32];
1346 int Time = (int64_t)100 * ((float)(Server()->Tick() - m_StartTime) / ((float)Server()->TickSpeed()));
1347 str_time(centisecs: Time, format: TIME_HOURS, buffer: aBuf, buffer_size: sizeof(aBuf));
1348 GameServer()->SendBroadcast(pText: aBuf, ClientId: m_pPlayer->GetCid(), IsImportant: false);
1349 m_LastTimeCpBroadcasted = m_LastTimeCp;
1350 m_LastBroadcast = Server()->Tick();
1351 }
1352}
1353
1354void CCharacter::HandleSkippableTiles(int Index)
1355{
1356 // handle death-tiles and leaving gamelayer
1357 if((Collision()->GetCollisionAt(x: m_Pos.x + GetProximityRadius() / 3.f, y: m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH ||
1358 Collision()->GetCollisionAt(x: m_Pos.x + GetProximityRadius() / 3.f, y: m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH ||
1359 Collision()->GetCollisionAt(x: m_Pos.x - GetProximityRadius() / 3.f, y: m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH ||
1360 Collision()->GetCollisionAt(x: m_Pos.x - GetProximityRadius() / 3.f, y: m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH ||
1361 Collision()->GetFCollisionAt(x: m_Pos.x + GetProximityRadius() / 3.f, y: m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH ||
1362 Collision()->GetFCollisionAt(x: m_Pos.x + GetProximityRadius() / 3.f, y: m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH ||
1363 Collision()->GetFCollisionAt(x: m_Pos.x - GetProximityRadius() / 3.f, y: m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH ||
1364 Collision()->GetFCollisionAt(x: m_Pos.x - GetProximityRadius() / 3.f, y: m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH) &&
1365 !m_Core.m_Super && !(Team() && Teams()->TeeFinished(ClientId: m_pPlayer->GetCid())))
1366 {
1367 Die(Killer: m_pPlayer->GetCid(), Weapon: WEAPON_WORLD);
1368 return;
1369 }
1370
1371 if(GameLayerClipped(CheckPos: m_Pos))
1372 {
1373 Die(Killer: m_pPlayer->GetCid(), Weapon: WEAPON_WORLD);
1374 return;
1375 }
1376
1377 if(Index < 0)
1378 return;
1379
1380 // handle speedup tiles
1381 if(Collision()->IsSpeedup(Index))
1382 {
1383 vec2 Direction, TempVel = m_Core.m_Vel;
1384 int Force, MaxSpeed = 0;
1385 float TeeAngle, SpeederAngle, DiffAngle, SpeedLeft, TeeSpeed;
1386 Collision()->GetSpeedup(Index, pDir: &Direction, pForce: &Force, pMaxSpeed: &MaxSpeed);
1387 if(Force == 255 && MaxSpeed)
1388 {
1389 m_Core.m_Vel = Direction * (MaxSpeed / 5);
1390 }
1391 else
1392 {
1393 if(MaxSpeed > 0 && MaxSpeed < 5)
1394 MaxSpeed = 5;
1395 if(MaxSpeed > 0)
1396 {
1397 if(Direction.x > 0.0000001f)
1398 SpeederAngle = -std::atan(x: Direction.y / Direction.x);
1399 else if(Direction.x < 0.0000001f)
1400 SpeederAngle = std::atan(x: Direction.y / Direction.x) + 2.0f * std::asin(x: 1.0f);
1401 else if(Direction.y > 0.0000001f)
1402 SpeederAngle = std::asin(x: 1.0f);
1403 else
1404 SpeederAngle = std::asin(x: -1.0f);
1405
1406 if(SpeederAngle < 0)
1407 SpeederAngle = 4.0f * std::asin(x: 1.0f) + SpeederAngle;
1408
1409 if(TempVel.x > 0.0000001f)
1410 TeeAngle = -std::atan(x: TempVel.y / TempVel.x);
1411 else if(TempVel.x < 0.0000001f)
1412 TeeAngle = std::atan(x: TempVel.y / TempVel.x) + 2.0f * std::asin(x: 1.0f);
1413 else if(TempVel.y > 0.0000001f)
1414 TeeAngle = std::asin(x: 1.0f);
1415 else
1416 TeeAngle = std::asin(x: -1.0f);
1417
1418 if(TeeAngle < 0)
1419 TeeAngle = 4.0f * std::asin(x: 1.0f) + TeeAngle;
1420
1421 TeeSpeed = std::sqrt(x: std::pow(x: TempVel.x, y: 2) + std::pow(x: TempVel.y, y: 2));
1422
1423 DiffAngle = SpeederAngle - TeeAngle;
1424 SpeedLeft = MaxSpeed / 5.0f - std::cos(x: DiffAngle) * TeeSpeed;
1425 if(absolute(a: (int)SpeedLeft) > Force && SpeedLeft > 0.0000001f)
1426 TempVel += Direction * Force;
1427 else if(absolute(a: (int)SpeedLeft) > Force)
1428 TempVel += Direction * -Force;
1429 else
1430 TempVel += Direction * SpeedLeft;
1431 }
1432 else
1433 TempVel += Direction * Force;
1434
1435 m_Core.m_Vel = ClampVel(MoveRestriction: m_MoveRestrictions, Vel: TempVel);
1436 }
1437 }
1438}
1439
1440bool CCharacter::IsSwitchActiveCb(int Number, void *pUser)
1441{
1442 CCharacter *pThis = (CCharacter *)pUser;
1443 auto &aSwitchers = pThis->Switchers();
1444 return !aSwitchers.empty() && pThis->Team() != TEAM_SUPER && aSwitchers[Number].m_aStatus[pThis->Team()];
1445}
1446
1447void CCharacter::SetTimeCheckpoint(int TimeCheckpoint)
1448{
1449 if(TimeCheckpoint > -1 && m_DDRaceState == DDRACE_STARTED && m_aCurrentTimeCp[TimeCheckpoint] == 0.0f && m_Time != 0.0f)
1450 {
1451 m_LastTimeCp = TimeCheckpoint;
1452 m_aCurrentTimeCp[m_LastTimeCp] = m_Time;
1453 m_TimeCpBroadcastEndTick = Server()->Tick() + Server()->TickSpeed() * 2;
1454 if(m_pPlayer->GetClientVersion() >= VERSION_DDRACE)
1455 {
1456 CPlayerData *pData = GameServer()->Score()->PlayerData(Id: m_pPlayer->GetCid());
1457 if(pData->m_aBestTimeCp[m_LastTimeCp] != 0.0f)
1458 {
1459 CNetMsg_Sv_DDRaceTime Msg;
1460 Msg.m_Time = (int)(m_Time * 100.0f);
1461 Msg.m_Finish = 0;
1462 float Diff = (m_aCurrentTimeCp[m_LastTimeCp] - pData->m_aBestTimeCp[m_LastTimeCp]) * 100;
1463 Msg.m_Check = (int)Diff;
1464 Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId: m_pPlayer->GetCid());
1465 }
1466 }
1467 }
1468}
1469
1470void CCharacter::HandleTiles(int Index)
1471{
1472 int MapIndex = Index;
1473 //int PureMapIndex = Collision()->GetPureMapIndex(m_Pos);
1474 m_TileIndex = Collision()->GetTileIndex(Index: MapIndex);
1475 m_TileFIndex = Collision()->GetFTileIndex(Index: MapIndex);
1476 m_MoveRestrictions = Collision()->GetMoveRestrictions(pfnSwitchActive: IsSwitchActiveCb, pUser: this, Pos: m_Pos, Distance: 18.0f, OverrideCenterTileIndex: MapIndex);
1477 if(Index < 0)
1478 {
1479 m_LastRefillJumps = false;
1480 m_LastPenalty = false;
1481 m_LastBonus = false;
1482 return;
1483 }
1484 SetTimeCheckpoint(Collision()->IsTimeCheckpoint(Index: MapIndex));
1485 SetTimeCheckpoint(Collision()->IsFTimeCheckpoint(Index: MapIndex));
1486 int TeleCheckpoint = Collision()->IsTeleCheckpoint(Index: MapIndex);
1487 if(TeleCheckpoint)
1488 m_TeleCheckpoint = TeleCheckpoint;
1489
1490 GameServer()->m_pController->HandleCharacterTiles(pChr: this, MapIndex: Index);
1491 if(!m_Alive)
1492 return;
1493
1494 // freeze
1495 if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Core.m_Super && !m_Core.m_DeepFrozen)
1496 {
1497 Freeze();
1498 }
1499 else if(((m_TileIndex == TILE_UNFREEZE) || (m_TileFIndex == TILE_UNFREEZE)) && !m_Core.m_DeepFrozen)
1500 UnFreeze();
1501
1502 // deep freeze
1503 if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Core.m_Super && !m_Core.m_DeepFrozen)
1504 m_Core.m_DeepFrozen = true;
1505 else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Core.m_Super && m_Core.m_DeepFrozen)
1506 m_Core.m_DeepFrozen = false;
1507
1508 // live freeze
1509 if(((m_TileIndex == TILE_LFREEZE) || (m_TileFIndex == TILE_LFREEZE)) && !m_Core.m_Super)
1510 {
1511 m_Core.m_LiveFrozen = true;
1512 }
1513 else if(((m_TileIndex == TILE_LUNFREEZE) || (m_TileFIndex == TILE_LUNFREEZE)) && !m_Core.m_Super)
1514 {
1515 m_Core.m_LiveFrozen = false;
1516 }
1517
1518 // endless hook
1519 if(((m_TileIndex == TILE_EHOOK_ENABLE) || (m_TileFIndex == TILE_EHOOK_ENABLE)))
1520 {
1521 SetEndlessHook(true);
1522 }
1523 else if(((m_TileIndex == TILE_EHOOK_DISABLE) || (m_TileFIndex == TILE_EHOOK_DISABLE)))
1524 {
1525 SetEndlessHook(false);
1526 }
1527
1528 // hit others
1529 if(((m_TileIndex == TILE_HIT_DISABLE) || (m_TileFIndex == TILE_HIT_DISABLE)) && (!m_Core.m_HammerHitDisabled || !m_Core.m_ShotgunHitDisabled || !m_Core.m_GrenadeHitDisabled || !m_Core.m_LaserHitDisabled))
1530 {
1531 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can't hit others");
1532 m_Core.m_HammerHitDisabled = true;
1533 m_Core.m_ShotgunHitDisabled = true;
1534 m_Core.m_GrenadeHitDisabled = true;
1535 m_Core.m_LaserHitDisabled = true;
1536 }
1537 else if(((m_TileIndex == TILE_HIT_ENABLE) || (m_TileFIndex == TILE_HIT_ENABLE)) && (m_Core.m_HammerHitDisabled || m_Core.m_ShotgunHitDisabled || m_Core.m_GrenadeHitDisabled || m_Core.m_LaserHitDisabled))
1538 {
1539 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can hit others");
1540 m_Core.m_ShotgunHitDisabled = false;
1541 m_Core.m_GrenadeHitDisabled = false;
1542 m_Core.m_HammerHitDisabled = false;
1543 m_Core.m_LaserHitDisabled = false;
1544 }
1545
1546 // collide with others
1547 if(((m_TileIndex == TILE_NPC_DISABLE) || (m_TileFIndex == TILE_NPC_DISABLE)) && !m_Core.m_CollisionDisabled)
1548 {
1549 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can't collide with others");
1550 m_Core.m_CollisionDisabled = true;
1551 }
1552 else if(((m_TileIndex == TILE_NPC_ENABLE) || (m_TileFIndex == TILE_NPC_ENABLE)) && m_Core.m_CollisionDisabled)
1553 {
1554 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can collide with others");
1555 m_Core.m_CollisionDisabled = false;
1556 }
1557
1558 // hook others
1559 if(((m_TileIndex == TILE_NPH_DISABLE) || (m_TileFIndex == TILE_NPH_DISABLE)) && !m_Core.m_HookHitDisabled)
1560 {
1561 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can't hook others");
1562 m_Core.m_HookHitDisabled = true;
1563 }
1564 else if(((m_TileIndex == TILE_NPH_ENABLE) || (m_TileFIndex == TILE_NPH_ENABLE)) && m_Core.m_HookHitDisabled)
1565 {
1566 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can hook others");
1567 m_Core.m_HookHitDisabled = false;
1568 }
1569
1570 // unlimited air jumps
1571 if(((m_TileIndex == TILE_UNLIMITED_JUMPS_ENABLE) || (m_TileFIndex == TILE_UNLIMITED_JUMPS_ENABLE)) && !m_Core.m_EndlessJump)
1572 {
1573 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You have unlimited air jumps");
1574 m_Core.m_EndlessJump = true;
1575 }
1576 else if(((m_TileIndex == TILE_UNLIMITED_JUMPS_DISABLE) || (m_TileFIndex == TILE_UNLIMITED_JUMPS_DISABLE)) && m_Core.m_EndlessJump)
1577 {
1578 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You don't have unlimited air jumps");
1579 m_Core.m_EndlessJump = false;
1580 }
1581
1582 // walljump
1583 if((m_TileIndex == TILE_WALLJUMP) || (m_TileFIndex == TILE_WALLJUMP))
1584 {
1585 if(m_Core.m_Vel.y > 0 && m_Core.m_Colliding && m_Core.m_LeftWall)
1586 {
1587 m_Core.m_LeftWall = false;
1588 m_Core.m_JumpedTotal = m_Core.m_Jumps >= 2 ? m_Core.m_Jumps - 2 : 0;
1589 m_Core.m_Jumped = 1;
1590 }
1591 }
1592
1593 // jetpack gun
1594 if(((m_TileIndex == TILE_JETPACK_ENABLE) || (m_TileFIndex == TILE_JETPACK_ENABLE)) && !m_Core.m_Jetpack)
1595 {
1596 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You have a jetpack gun");
1597 m_Core.m_Jetpack = true;
1598 }
1599 else if(((m_TileIndex == TILE_JETPACK_DISABLE) || (m_TileFIndex == TILE_JETPACK_DISABLE)) && m_Core.m_Jetpack)
1600 {
1601 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You lost your jetpack gun");
1602 m_Core.m_Jetpack = false;
1603 }
1604
1605 // refill jumps
1606 if(((m_TileIndex == TILE_REFILL_JUMPS) || (m_TileFIndex == TILE_REFILL_JUMPS)) && !m_LastRefillJumps)
1607 {
1608 m_Core.m_JumpedTotal = 0;
1609 m_Core.m_Jumped = 0;
1610 m_LastRefillJumps = true;
1611 }
1612 if((m_TileIndex != TILE_REFILL_JUMPS) && (m_TileFIndex != TILE_REFILL_JUMPS))
1613 {
1614 m_LastRefillJumps = false;
1615 }
1616
1617 // Teleport gun
1618 if(((m_TileIndex == TILE_TELE_GUN_ENABLE) || (m_TileFIndex == TILE_TELE_GUN_ENABLE)) && !m_Core.m_HasTelegunGun)
1619 {
1620 m_Core.m_HasTelegunGun = true;
1621
1622 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "Teleport gun enabled");
1623 }
1624 else if(((m_TileIndex == TILE_TELE_GUN_DISABLE) || (m_TileFIndex == TILE_TELE_GUN_DISABLE)) && m_Core.m_HasTelegunGun)
1625 {
1626 m_Core.m_HasTelegunGun = false;
1627
1628 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "Teleport gun disabled");
1629 }
1630
1631 if(((m_TileIndex == TILE_TELE_GRENADE_ENABLE) || (m_TileFIndex == TILE_TELE_GRENADE_ENABLE)) && !m_Core.m_HasTelegunGrenade)
1632 {
1633 m_Core.m_HasTelegunGrenade = true;
1634
1635 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "Teleport grenade enabled");
1636 }
1637 else if(((m_TileIndex == TILE_TELE_GRENADE_DISABLE) || (m_TileFIndex == TILE_TELE_GRENADE_DISABLE)) && m_Core.m_HasTelegunGrenade)
1638 {
1639 m_Core.m_HasTelegunGrenade = false;
1640
1641 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "Teleport grenade disabled");
1642 }
1643
1644 if(((m_TileIndex == TILE_TELE_LASER_ENABLE) || (m_TileFIndex == TILE_TELE_LASER_ENABLE)) && !m_Core.m_HasTelegunLaser)
1645 {
1646 m_Core.m_HasTelegunLaser = true;
1647
1648 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "Teleport laser enabled");
1649 }
1650 else if(((m_TileIndex == TILE_TELE_LASER_DISABLE) || (m_TileFIndex == TILE_TELE_LASER_DISABLE)) && m_Core.m_HasTelegunLaser)
1651 {
1652 m_Core.m_HasTelegunLaser = false;
1653
1654 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "Teleport laser disabled");
1655 }
1656
1657 // stopper
1658 if(m_Core.m_Vel.y > 0 && (m_MoveRestrictions & CANTMOVE_DOWN))
1659 {
1660 m_Core.m_Jumped = 0;
1661 m_Core.m_JumpedTotal = 0;
1662 }
1663 ApplyMoveRestrictions();
1664
1665 // handle switch tiles
1666 if(Collision()->GetSwitchType(Index: MapIndex) == TILE_SWITCHOPEN && Team() != TEAM_SUPER && Collision()->GetSwitchNumber(Index: MapIndex) > 0)
1667 {
1668 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()] = true;
1669 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aEndTick[Team()] = 0;
1670 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aType[Team()] = TILE_SWITCHOPEN;
1671 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aLastUpdateTick[Team()] = Server()->Tick();
1672 }
1673 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_SWITCHTIMEDOPEN && Team() != TEAM_SUPER && Collision()->GetSwitchNumber(Index: MapIndex) > 0)
1674 {
1675 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()] = true;
1676 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aEndTick[Team()] = Server()->Tick() + 1 + Collision()->GetSwitchDelay(Index: MapIndex) * Server()->TickSpeed();
1677 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aType[Team()] = TILE_SWITCHTIMEDOPEN;
1678 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aLastUpdateTick[Team()] = Server()->Tick();
1679 }
1680 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_SWITCHTIMEDCLOSE && Team() != TEAM_SUPER && Collision()->GetSwitchNumber(Index: MapIndex) > 0)
1681 {
1682 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()] = false;
1683 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aEndTick[Team()] = Server()->Tick() + 1 + Collision()->GetSwitchDelay(Index: MapIndex) * Server()->TickSpeed();
1684 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aType[Team()] = TILE_SWITCHTIMEDCLOSE;
1685 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aLastUpdateTick[Team()] = Server()->Tick();
1686 }
1687 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_SWITCHCLOSE && Team() != TEAM_SUPER && Collision()->GetSwitchNumber(Index: MapIndex) > 0)
1688 {
1689 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()] = false;
1690 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aEndTick[Team()] = 0;
1691 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aType[Team()] = TILE_SWITCHCLOSE;
1692 Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aLastUpdateTick[Team()] = Server()->Tick();
1693 }
1694 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER)
1695 {
1696 if(Collision()->GetSwitchNumber(Index: MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()])
1697 {
1698 Freeze(Seconds: Collision()->GetSwitchDelay(Index: MapIndex));
1699 }
1700 }
1701 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER)
1702 {
1703 if(Collision()->GetSwitchNumber(Index: MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()])
1704 m_Core.m_DeepFrozen = true;
1705 }
1706 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER)
1707 {
1708 if(Collision()->GetSwitchNumber(Index: MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()])
1709 m_Core.m_DeepFrozen = false;
1710 }
1711 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_LFREEZE && Team() != TEAM_SUPER)
1712 {
1713 if(Collision()->GetSwitchNumber(Index: MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()])
1714 {
1715 m_Core.m_LiveFrozen = true;
1716 }
1717 }
1718 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_LUNFREEZE && Team() != TEAM_SUPER)
1719 {
1720 if(Collision()->GetSwitchNumber(Index: MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(Index: MapIndex)].m_aStatus[Team()])
1721 {
1722 m_Core.m_LiveFrozen = false;
1723 }
1724 }
1725 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_HIT_ENABLE && m_Core.m_HammerHitDisabled && Collision()->GetSwitchDelay(Index: MapIndex) == WEAPON_HAMMER)
1726 {
1727 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can hammer hit others");
1728 m_Core.m_HammerHitDisabled = false;
1729 }
1730 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_HIT_DISABLE && !(m_Core.m_HammerHitDisabled) && Collision()->GetSwitchDelay(Index: MapIndex) == WEAPON_HAMMER)
1731 {
1732 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can't hammer hit others");
1733 m_Core.m_HammerHitDisabled = true;
1734 }
1735 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_HIT_ENABLE && m_Core.m_ShotgunHitDisabled && Collision()->GetSwitchDelay(Index: MapIndex) == WEAPON_SHOTGUN)
1736 {
1737 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can shoot others with shotgun");
1738 m_Core.m_ShotgunHitDisabled = false;
1739 }
1740 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_HIT_DISABLE && !(m_Core.m_ShotgunHitDisabled) && Collision()->GetSwitchDelay(Index: MapIndex) == WEAPON_SHOTGUN)
1741 {
1742 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can't shoot others with shotgun");
1743 m_Core.m_ShotgunHitDisabled = true;
1744 }
1745 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_HIT_ENABLE && m_Core.m_GrenadeHitDisabled && Collision()->GetSwitchDelay(Index: MapIndex) == WEAPON_GRENADE)
1746 {
1747 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can shoot others with grenade");
1748 m_Core.m_GrenadeHitDisabled = false;
1749 }
1750 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_HIT_DISABLE && !(m_Core.m_GrenadeHitDisabled) && Collision()->GetSwitchDelay(Index: MapIndex) == WEAPON_GRENADE)
1751 {
1752 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can't shoot others with grenade");
1753 m_Core.m_GrenadeHitDisabled = true;
1754 }
1755 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_HIT_ENABLE && m_Core.m_LaserHitDisabled && Collision()->GetSwitchDelay(Index: MapIndex) == WEAPON_LASER)
1756 {
1757 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can shoot others with laser");
1758 m_Core.m_LaserHitDisabled = false;
1759 }
1760 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_HIT_DISABLE && !(m_Core.m_LaserHitDisabled) && Collision()->GetSwitchDelay(Index: MapIndex) == WEAPON_LASER)
1761 {
1762 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: "You can't shoot others with laser");
1763 m_Core.m_LaserHitDisabled = true;
1764 }
1765 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_JUMP)
1766 {
1767 int NewJumps = Collision()->GetSwitchDelay(Index: MapIndex);
1768 if(NewJumps == 255)
1769 {
1770 NewJumps = -1;
1771 }
1772
1773 if(NewJumps != m_Core.m_Jumps)
1774 {
1775 char aBuf[256];
1776 if(NewJumps == -1)
1777 str_copy(dst&: aBuf, src: "You only have your ground jump now");
1778 else if(NewJumps == 1)
1779 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You can jump %d time", NewJumps);
1780 else
1781 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You can jump %d times", NewJumps);
1782 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: aBuf);
1783 m_Core.m_Jumps = NewJumps;
1784 }
1785 }
1786 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_ADD_TIME && !m_LastPenalty)
1787 {
1788 int min = Collision()->GetSwitchDelay(Index: MapIndex);
1789 int sec = Collision()->GetSwitchNumber(Index: MapIndex);
1790 int Team = Teams()->m_Core.Team(ClientId: m_Core.m_Id);
1791
1792 m_StartTime -= (min * 60 + sec) * Server()->TickSpeed();
1793
1794 if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || (Team != TEAM_FLOCK && !Teams()->TeamFlock(Team))) && Team != TEAM_SUPER)
1795 {
1796 for(int i = 0; i < MAX_CLIENTS; i++)
1797 {
1798 if(Teams()->m_Core.Team(ClientId: i) == Team && i != m_Core.m_Id && GameServer()->m_apPlayers[i])
1799 {
1800 CCharacter *pChar = GameServer()->m_apPlayers[i]->GetCharacter();
1801
1802 if(pChar)
1803 pChar->m_StartTime = m_StartTime;
1804 }
1805 }
1806 }
1807
1808 m_LastPenalty = true;
1809 }
1810 else if(Collision()->GetSwitchType(Index: MapIndex) == TILE_SUBTRACT_TIME && !m_LastBonus)
1811 {
1812 int min = Collision()->GetSwitchDelay(Index: MapIndex);
1813 int sec = Collision()->GetSwitchNumber(Index: MapIndex);
1814 int Team = Teams()->m_Core.Team(ClientId: m_Core.m_Id);
1815
1816 m_StartTime += (min * 60 + sec) * Server()->TickSpeed();
1817 if(m_StartTime > Server()->Tick())
1818 m_StartTime = Server()->Tick();
1819
1820 if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || (Team != TEAM_FLOCK && !Teams()->TeamFlock(Team))) && Team != TEAM_SUPER)
1821 {
1822 for(int i = 0; i < MAX_CLIENTS; i++)
1823 {
1824 if(Teams()->m_Core.Team(ClientId: i) == Team && i != m_Core.m_Id && GameServer()->m_apPlayers[i])
1825 {
1826 CCharacter *pChar = GameServer()->m_apPlayers[i]->GetCharacter();
1827
1828 if(pChar)
1829 pChar->m_StartTime = m_StartTime;
1830 }
1831 }
1832 }
1833
1834 m_LastBonus = true;
1835 }
1836
1837 if(Collision()->GetSwitchType(Index: MapIndex) != TILE_ADD_TIME)
1838 {
1839 m_LastPenalty = false;
1840 }
1841
1842 if(Collision()->GetSwitchType(Index: MapIndex) != TILE_SUBTRACT_TIME)
1843 {
1844 m_LastBonus = false;
1845 }
1846
1847 int z = Collision()->IsTeleport(Index: MapIndex);
1848 if(!g_Config.m_SvOldTeleportHook && !g_Config.m_SvOldTeleportWeapons && z && !Collision()->TeleOuts(Number: z - 1).empty())
1849 {
1850 if(m_Core.m_Super)
1851 return;
1852 int TeleOut = GameWorld()->m_Core.RandomOr0(BelowThis: Collision()->TeleOuts(Number: z - 1).size());
1853 m_Core.m_Pos = Collision()->TeleOuts(Number: z - 1)[TeleOut];
1854 if(!g_Config.m_SvTeleportHoldHook)
1855 {
1856 ResetHook();
1857 }
1858 if(g_Config.m_SvTeleportLoseWeapons)
1859 ResetPickups();
1860 return;
1861 }
1862 int evilz = Collision()->IsEvilTeleport(Index: MapIndex);
1863 if(evilz && !Collision()->TeleOuts(Number: evilz - 1).empty())
1864 {
1865 if(m_Core.m_Super)
1866 return;
1867 int TeleOut = GameWorld()->m_Core.RandomOr0(BelowThis: Collision()->TeleOuts(Number: evilz - 1).size());
1868 m_Core.m_Pos = Collision()->TeleOuts(Number: evilz - 1)[TeleOut];
1869 if(!g_Config.m_SvOldTeleportHook && !g_Config.m_SvOldTeleportWeapons)
1870 {
1871 m_Core.m_Vel = vec2(0, 0);
1872
1873 if(!g_Config.m_SvTeleportHoldHook)
1874 {
1875 ResetHook();
1876 GameWorld()->ReleaseHooked(ClientId: GetPlayer()->GetCid());
1877 }
1878 if(g_Config.m_SvTeleportLoseWeapons)
1879 {
1880 ResetPickups();
1881 }
1882 }
1883 return;
1884 }
1885 if(Collision()->IsCheckEvilTeleport(Index: MapIndex))
1886 {
1887 if(m_Core.m_Super)
1888 return;
1889 // first check if there is a TeleCheckOut for the current recorded checkpoint, if not check previous checkpoints
1890 for(int k = m_TeleCheckpoint - 1; k >= 0; k--)
1891 {
1892 if(!Collision()->TeleCheckOuts(Number: k).empty())
1893 {
1894 int TeleOut = GameWorld()->m_Core.RandomOr0(BelowThis: Collision()->TeleCheckOuts(Number: k).size());
1895 m_Core.m_Pos = Collision()->TeleCheckOuts(Number: k)[TeleOut];
1896 m_Core.m_Vel = vec2(0, 0);
1897
1898 if(!g_Config.m_SvTeleportHoldHook)
1899 {
1900 ResetHook();
1901 GameWorld()->ReleaseHooked(ClientId: GetPlayer()->GetCid());
1902 }
1903
1904 return;
1905 }
1906 }
1907 // if no checkpointout have been found (or if there no recorded checkpoint), teleport to start
1908 vec2 SpawnPos;
1909 if(GameServer()->m_pController->CanSpawn(Team: m_pPlayer->GetTeam(), pOutPos: &SpawnPos, DDTeam: GameServer()->GetDDRaceTeam(ClientId: GetPlayer()->GetCid())))
1910 {
1911 m_Core.m_Pos = SpawnPos;
1912 m_Core.m_Vel = vec2(0, 0);
1913
1914 if(!g_Config.m_SvTeleportHoldHook)
1915 {
1916 ResetHook();
1917 GameWorld()->ReleaseHooked(ClientId: GetPlayer()->GetCid());
1918 }
1919 }
1920 return;
1921 }
1922 if(Collision()->IsCheckTeleport(Index: MapIndex))
1923 {
1924 if(m_Core.m_Super)
1925 return;
1926 // first check if there is a TeleCheckOut for the current recorded checkpoint, if not check previous checkpoints
1927 for(int k = m_TeleCheckpoint - 1; k >= 0; k--)
1928 {
1929 if(!Collision()->TeleCheckOuts(Number: k).empty())
1930 {
1931 int TeleOut = GameWorld()->m_Core.RandomOr0(BelowThis: Collision()->TeleCheckOuts(Number: k).size());
1932 m_Core.m_Pos = Collision()->TeleCheckOuts(Number: k)[TeleOut];
1933
1934 if(!g_Config.m_SvTeleportHoldHook)
1935 {
1936 ResetHook();
1937 }
1938
1939 return;
1940 }
1941 }
1942 // if no checkpointout have been found (or if there no recorded checkpoint), teleport to start
1943 vec2 SpawnPos;
1944 if(GameServer()->m_pController->CanSpawn(Team: m_pPlayer->GetTeam(), pOutPos: &SpawnPos, DDTeam: GameServer()->GetDDRaceTeam(ClientId: GetPlayer()->GetCid())))
1945 {
1946 m_Core.m_Pos = SpawnPos;
1947
1948 if(!g_Config.m_SvTeleportHoldHook)
1949 {
1950 ResetHook();
1951 }
1952 }
1953 return;
1954 }
1955}
1956
1957void CCharacter::HandleTuneLayer()
1958{
1959 m_TuneZoneOld = m_TuneZone;
1960 int CurrentIndex = Collision()->GetMapIndex(Pos: m_Pos);
1961 m_TuneZone = Collision()->IsTune(Index: CurrentIndex);
1962
1963 if(m_TuneZone)
1964 m_Core.m_Tuning = TuningList()[m_TuneZone]; // throw tunings from specific zone into gamecore
1965 else
1966 m_Core.m_Tuning = *Tuning();
1967
1968 if(m_TuneZone != m_TuneZoneOld) // don't send tunigs all the time
1969 {
1970 // send zone msgs
1971 SendZoneMsgs();
1972 }
1973}
1974
1975void CCharacter::SendZoneMsgs()
1976{
1977 // send zone leave msg
1978 // (m_TuneZoneOld >= 0: avoid zone leave msgs on spawn)
1979 if(m_TuneZoneOld >= 0 && GameServer()->m_aaZoneLeaveMsg[m_TuneZoneOld][0])
1980 {
1981 const char *pCur = GameServer()->m_aaZoneLeaveMsg[m_TuneZoneOld];
1982 const char *pPos;
1983 while((pPos = str_find(haystack: pCur, needle: "\\n")))
1984 {
1985 char aBuf[256];
1986 str_copy(dst: aBuf, src: pCur, dst_size: pPos - pCur + 1);
1987 aBuf[pPos - pCur + 1] = '\0';
1988 pCur = pPos + 2;
1989 GameServer()->SendChatTarget(To: m_pPlayer->GetCid(), pText: aBuf);
1990 }
1991 GameServer()->SendChatTarget(To: m_pPlayer->GetCid(), pText: pCur);
1992 }
1993 // send zone enter msg
1994 if(GameServer()->m_aaZoneEnterMsg[m_TuneZone][0])
1995 {
1996 const char *pCur = GameServer()->m_aaZoneEnterMsg[m_TuneZone];
1997 const char *pPos;
1998 while((pPos = str_find(haystack: pCur, needle: "\\n")))
1999 {
2000 char aBuf[256];
2001 str_copy(dst: aBuf, src: pCur, dst_size: pPos - pCur + 1);
2002 aBuf[pPos - pCur + 1] = '\0';
2003 pCur = pPos + 2;
2004 GameServer()->SendChatTarget(To: m_pPlayer->GetCid(), pText: aBuf);
2005 }
2006 GameServer()->SendChatTarget(To: m_pPlayer->GetCid(), pText: pCur);
2007 }
2008}
2009
2010IAntibot *CCharacter::Antibot()
2011{
2012 return GameServer()->Antibot();
2013}
2014
2015void CCharacter::SetTeams(CGameTeams *pTeams)
2016{
2017 m_pTeams = pTeams;
2018 m_Core.SetTeamsCore(&m_pTeams->m_Core);
2019}
2020
2021bool CCharacter::TrySetRescue(int RescueMode)
2022{
2023 bool Set = false;
2024 if(g_Config.m_SvRescue || ((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || Team() > TEAM_FLOCK) && Team() >= TEAM_FLOCK && Team() < TEAM_SUPER))
2025 {
2026 // check for nearby health pickups (also freeze)
2027 bool InHealthPickup = false;
2028 if(!m_Core.m_IsInFreeze)
2029 {
2030 CEntity *apEnts[9];
2031 int Num = GameWorld()->FindEntities(Pos: m_Pos, Radius: GetProximityRadius() + CPickup::ms_CollisionExtraSize, ppEnts: apEnts, Max: std::size(apEnts), Type: CGameWorld::ENTTYPE_PICKUP);
2032 for(int i = 0; i < Num; ++i)
2033 {
2034 CPickup *pPickup = static_cast<CPickup *>(apEnts[i]);
2035 if(pPickup->Type() == POWERUP_HEALTH)
2036 {
2037 // This uses a separate variable InHealthPickup instead of setting m_Core.m_IsInFreeze
2038 // as the latter causes freezebars to flicker when standing in the freeze range of a
2039 // health pickup. When the same code for client prediction is added, the freezebars
2040 // still flicker, but only when standing at the edge of the health pickup's freeze range.
2041 InHealthPickup = true;
2042 break;
2043 }
2044 }
2045 }
2046
2047 if(!m_Core.m_IsInFreeze && IsGrounded() && !m_Core.m_DeepFrozen && !InHealthPickup)
2048 {
2049 ForceSetRescue(RescueMode);
2050 Set = true;
2051 }
2052 }
2053
2054 return Set;
2055}
2056
2057void CCharacter::ForceSetRescue(int RescueMode)
2058{
2059 m_RescueTee[RescueMode].Save(pchr: this);
2060 m_SetSavePos[RescueMode] = true;
2061}
2062
2063void CCharacter::DDRaceTick()
2064{
2065 mem_copy(dest: &m_Input, source: &m_SavedInput, size: sizeof(m_Input));
2066 m_Armor = clamp(val: 10 - (m_FreezeTime / 15), lo: 0, hi: 10);
2067 if(m_Input.m_Direction != 0 || m_Input.m_Jump != 0)
2068 m_LastMove = Server()->Tick();
2069
2070 if(m_Core.m_LiveFrozen && !m_Core.m_Super)
2071 {
2072 m_Input.m_Direction = 0;
2073 m_Input.m_Jump = 0;
2074 // Hook is possible in live freeze
2075 }
2076 if(m_FreezeTime > 0)
2077 {
2078 if(m_FreezeTime % Server()->TickSpeed() == Server()->TickSpeed() - 1)
2079 {
2080 GameServer()->CreateDamageInd(Pos: m_Pos, AngleMod: 0, Amount: (m_FreezeTime + 1) / Server()->TickSpeed(), Mask: TeamMask() & GameServer()->ClientsMaskExcludeClientVersionAndHigher(Version: VERSION_DDNET_NEW_HUD));
2081 }
2082 m_FreezeTime--;
2083 m_Input.m_Direction = 0;
2084 m_Input.m_Jump = 0;
2085 m_Input.m_Hook = 0;
2086 if(m_FreezeTime == 1)
2087 UnFreeze();
2088 }
2089
2090 HandleTuneLayer(); // need this before coretick
2091
2092 // check if the tee is in any type of freeze
2093 int Index = Collision()->GetPureMapIndex(Pos: m_Pos);
2094 const int aTiles[] = {
2095 Collision()->GetTileIndex(Index),
2096 Collision()->GetFTileIndex(Index),
2097 Collision()->GetSwitchType(Index)};
2098 m_Core.m_IsInFreeze = false;
2099 for(const int Tile : aTiles)
2100 {
2101 if(Tile == TILE_FREEZE || Tile == TILE_DFREEZE || Tile == TILE_LFREEZE)
2102 {
2103 m_Core.m_IsInFreeze = true;
2104 break;
2105 }
2106 }
2107
2108 // look for save position for rescue feature
2109 // always update auto rescue
2110 TrySetRescue(RescueMode: RESCUEMODE_AUTO);
2111
2112 m_Core.m_Id = GetPlayer()->GetCid();
2113}
2114
2115void CCharacter::DDRacePostCoreTick()
2116{
2117 m_Time = (float)(Server()->Tick() - m_StartTime) / ((float)Server()->TickSpeed());
2118
2119 if(m_Core.m_EndlessHook || (m_Core.m_Super && g_Config.m_SvEndlessSuperHook))
2120 m_Core.m_HookTick = 0;
2121
2122 m_FrozenLastTick = false;
2123
2124 if(m_Core.m_DeepFrozen && !m_Core.m_Super)
2125 Freeze();
2126
2127 // following jump rules can be overridden by tiles, like Refill Jumps, Stopper and Wall Jump
2128 if(m_Core.m_Jumps == -1)
2129 {
2130 // The player has only one ground jump, so his feet are always dark
2131 m_Core.m_Jumped |= 2;
2132 }
2133 else if(m_Core.m_Jumps == 0)
2134 {
2135 // The player has no jumps at all, so his feet are always dark
2136 m_Core.m_Jumped |= 2;
2137 }
2138 else if(m_Core.m_Jumps == 1 && m_Core.m_Jumped > 0)
2139 {
2140 // If the player has only one jump, each jump is the last one
2141 m_Core.m_Jumped |= 2;
2142 }
2143 else if(m_Core.m_JumpedTotal < m_Core.m_Jumps - 1 && m_Core.m_Jumped > 1)
2144 {
2145 // The player has not yet used up all his jumps, so his feet remain light
2146 m_Core.m_Jumped = 1;
2147 }
2148
2149 if((m_Core.m_Super || m_Core.m_EndlessJump) && m_Core.m_Jumped > 1)
2150 {
2151 // Super players and players with infinite jumps always have light feet
2152 m_Core.m_Jumped = 1;
2153 }
2154
2155 int CurrentIndex = Collision()->GetMapIndex(Pos: m_Pos);
2156 HandleSkippableTiles(Index: CurrentIndex);
2157 if(!m_Alive)
2158 return;
2159
2160 // handle Anti-Skip tiles
2161 std::vector<int> vIndices = Collision()->GetMapIndices(PrevPos: m_PrevPos, Pos: m_Pos);
2162 if(!vIndices.empty())
2163 {
2164 for(int &Index : vIndices)
2165 {
2166 HandleTiles(Index);
2167 if(!m_Alive)
2168 return;
2169 }
2170 }
2171 else
2172 {
2173 HandleTiles(Index: CurrentIndex);
2174 if(!m_Alive)
2175 return;
2176 }
2177
2178 // teleport gun
2179 if(m_TeleGunTeleport)
2180 {
2181 GameServer()->CreateDeath(Pos: m_Pos, ClientId: m_pPlayer->GetCid(), Mask: TeamMask());
2182 m_Core.m_Pos = m_TeleGunPos;
2183 if(!m_IsBlueTeleGunTeleport)
2184 m_Core.m_Vel = vec2(0, 0);
2185 GameServer()->CreateDeath(Pos: m_TeleGunPos, ClientId: m_pPlayer->GetCid(), Mask: TeamMask());
2186 GameServer()->CreateSound(Pos: m_TeleGunPos, Sound: SOUND_WEAPON_SPAWN, Mask: TeamMask());
2187 m_TeleGunTeleport = false;
2188 m_IsBlueTeleGunTeleport = false;
2189 }
2190
2191 HandleBroadcast();
2192}
2193
2194bool CCharacter::Freeze(int Seconds)
2195{
2196 if(Seconds <= 0 || m_Core.m_Super || m_FreezeTime > Seconds * Server()->TickSpeed())
2197 return false;
2198 if(m_FreezeTime == 0 || m_Core.m_FreezeStart < Server()->Tick() - Server()->TickSpeed())
2199 {
2200 m_Armor = 0;
2201 m_FreezeTime = Seconds * Server()->TickSpeed();
2202 m_Core.m_FreezeStart = Server()->Tick();
2203 return true;
2204 }
2205 return false;
2206}
2207
2208bool CCharacter::Freeze()
2209{
2210 return Freeze(Seconds: g_Config.m_SvFreezeDelay);
2211}
2212
2213bool CCharacter::UnFreeze()
2214{
2215 if(m_FreezeTime > 0)
2216 {
2217 m_Armor = 10;
2218 if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Got)
2219 m_Core.m_ActiveWeapon = WEAPON_GUN;
2220 m_FreezeTime = 0;
2221 m_Core.m_FreezeStart = 0;
2222 m_FrozenLastTick = true;
2223 return true;
2224 }
2225 return false;
2226}
2227
2228void CCharacter::ResetJumps()
2229{
2230 m_Core.m_JumpedTotal = 0;
2231 m_Core.m_Jumped = 0;
2232}
2233
2234void CCharacter::GiveWeapon(int Weapon, bool Remove)
2235{
2236 if(Weapon == WEAPON_NINJA)
2237 {
2238 if(Remove)
2239 RemoveNinja();
2240 else
2241 GiveNinja();
2242 return;
2243 }
2244
2245 if(Remove)
2246 {
2247 if(GetActiveWeapon() == Weapon)
2248 SetActiveWeapon(WEAPON_GUN);
2249 }
2250 else
2251 {
2252 m_Core.m_aWeapons[Weapon].m_Ammo = -1;
2253 }
2254
2255 m_Core.m_aWeapons[Weapon].m_Got = !Remove;
2256}
2257
2258void CCharacter::GiveAllWeapons()
2259{
2260 for(int i = WEAPON_GUN; i < NUM_WEAPONS - 1; i++)
2261 {
2262 GiveWeapon(Weapon: i);
2263 }
2264}
2265
2266void CCharacter::ResetPickups()
2267{
2268 for(int i = WEAPON_SHOTGUN; i < NUM_WEAPONS - 1; i++)
2269 {
2270 m_Core.m_aWeapons[i].m_Got = false;
2271 if(m_Core.m_ActiveWeapon == i)
2272 m_Core.m_ActiveWeapon = WEAPON_GUN;
2273 }
2274}
2275
2276void CCharacter::SetEndlessHook(bool Enable)
2277{
2278 if(m_Core.m_EndlessHook == Enable)
2279 {
2280 return;
2281 }
2282 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: Enable ? "Endless hook has been activated" : "Endless hook has been deactivated");
2283
2284 m_Core.m_EndlessHook = Enable;
2285}
2286
2287void CCharacter::Pause(bool Pause)
2288{
2289 m_Paused = Pause;
2290 if(Pause)
2291 {
2292 GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCid()] = 0;
2293 GameServer()->m_World.RemoveEntity(pEntity: this);
2294
2295 if(m_Core.HookedPlayer() != -1) // Keeping hook would allow cheats
2296 {
2297 ResetHook();
2298 GameWorld()->ReleaseHooked(ClientId: GetPlayer()->GetCid());
2299 }
2300 m_PausedTick = Server()->Tick();
2301 }
2302 else
2303 {
2304 m_Core.m_Vel = vec2(0, 0);
2305 GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCid()] = &m_Core;
2306 GameServer()->m_World.InsertEntity(pEntity: this);
2307 if(m_Core.m_FreezeStart > 0 && m_PausedTick >= 0)
2308 {
2309 m_Core.m_FreezeStart += Server()->Tick() - m_PausedTick;
2310 }
2311 }
2312}
2313
2314void CCharacter::DDRaceInit()
2315{
2316 m_Paused = false;
2317 m_DDRaceState = DDRACE_NONE;
2318 m_PrevPos = m_Pos;
2319 for(bool &Set : m_SetSavePos)
2320 Set = false;
2321 m_LastBroadcast = 0;
2322 m_TeamBeforeSuper = 0;
2323 m_Core.m_Id = GetPlayer()->GetCid();
2324 m_TeleCheckpoint = 0;
2325 m_Core.m_EndlessHook = g_Config.m_SvEndlessDrag;
2326 if(g_Config.m_SvHit)
2327 {
2328 m_Core.m_HammerHitDisabled = false;
2329 m_Core.m_ShotgunHitDisabled = false;
2330 m_Core.m_GrenadeHitDisabled = false;
2331 m_Core.m_LaserHitDisabled = false;
2332 }
2333 else
2334 {
2335 m_Core.m_HammerHitDisabled = true;
2336 m_Core.m_ShotgunHitDisabled = true;
2337 m_Core.m_GrenadeHitDisabled = true;
2338 m_Core.m_LaserHitDisabled = true;
2339 }
2340 m_Core.m_Jumps = 2;
2341 m_FreezeHammer = false;
2342
2343 int Team = Teams()->m_Core.Team(ClientId: m_Core.m_Id);
2344
2345 if(Teams()->TeamLocked(Team))
2346 {
2347 for(int i = 0; i < MAX_CLIENTS; i++)
2348 {
2349 if(Teams()->m_Core.Team(ClientId: i) == Team && i != m_Core.m_Id && GameServer()->m_apPlayers[i])
2350 {
2351 CCharacter *pChar = GameServer()->m_apPlayers[i]->GetCharacter();
2352
2353 if(pChar)
2354 {
2355 m_DDRaceState = pChar->m_DDRaceState;
2356 m_StartTime = pChar->m_StartTime;
2357 }
2358 }
2359 }
2360 }
2361
2362 if(g_Config.m_SvTeam == SV_TEAM_MANDATORY && Team == TEAM_FLOCK)
2363 {
2364 GameServer()->SendStartWarning(ClientId: GetPlayer()->GetCid(), pMessage: "Please join a team before you start");
2365 }
2366}
2367
2368void CCharacter::Rescue()
2369{
2370 if(m_SetSavePos[GetPlayer()->m_RescueMode] && !m_Core.m_Super)
2371 {
2372 if(m_LastRescue + (int64_t)g_Config.m_SvRescueDelay * Server()->TickSpeed() > Server()->Tick())
2373 {
2374 char aBuf[256];
2375 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You have to wait %d seconds until you can rescue yourself", (int)((m_LastRescue + (int64_t)g_Config.m_SvRescueDelay * Server()->TickSpeed() - Server()->Tick()) / Server()->TickSpeed()));
2376 GameServer()->SendChatTarget(To: GetPlayer()->GetCid(), pText: aBuf);
2377 return;
2378 }
2379
2380 float StartTime = m_StartTime;
2381 m_RescueTee[GetPlayer()->m_RescueMode].Load(pchr: this, Team: Team());
2382 // Don't load these from saved tee:
2383 m_Core.m_Vel = vec2(0, 0);
2384 m_Core.m_HookState = HOOK_IDLE;
2385 m_StartTime = StartTime;
2386 m_SavedInput.m_Direction = 0;
2387 m_SavedInput.m_Jump = 0;
2388 // simulate releasing the fire button
2389 if((m_SavedInput.m_Fire & 1) != 0)
2390 m_SavedInput.m_Fire++;
2391 m_SavedInput.m_Fire &= INPUT_STATE_MASK;
2392 m_SavedInput.m_Hook = 0;
2393 m_pPlayer->Pause(State: CPlayer::PAUSE_NONE, Force: true);
2394 }
2395}
2396
2397CClientMask CCharacter::TeamMask()
2398{
2399 return Teams()->TeamMask(Team: Team(), ExceptId: -1, Asker: GetPlayer()->GetCid());
2400}
2401
2402void CCharacter::SetPosition(const vec2 &Position)
2403{
2404 m_Core.m_Pos = Position;
2405}
2406
2407void CCharacter::Move(vec2 RelPos)
2408{
2409 m_Core.m_Pos += RelPos;
2410}
2411
2412void CCharacter::ResetVelocity()
2413{
2414 m_Core.m_Vel = vec2(0, 0);
2415}
2416
2417void CCharacter::SetVelocity(vec2 NewVelocity)
2418{
2419 m_Core.m_Vel = ClampVel(MoveRestriction: m_MoveRestrictions, Vel: NewVelocity);
2420}
2421
2422// The method is needed only to reproduce 'shotgun bug' ddnet#5258
2423// Use SetVelocity() instead.
2424void CCharacter::SetRawVelocity(vec2 NewVelocity)
2425{
2426 m_Core.m_Vel = NewVelocity;
2427}
2428
2429void CCharacter::AddVelocity(vec2 Addition)
2430{
2431 SetVelocity(m_Core.m_Vel + Addition);
2432}
2433
2434void CCharacter::ApplyMoveRestrictions()
2435{
2436 m_Core.m_Vel = ClampVel(MoveRestriction: m_MoveRestrictions, Vel: m_Core.m_Vel);
2437}
2438
2439void CCharacter::SwapClients(int Client1, int Client2)
2440{
2441 const int HookedPlayer = m_Core.HookedPlayer();
2442 m_Core.SetHookedPlayer(HookedPlayer == Client1 ? Client2 : HookedPlayer == Client2 ? Client1 : HookedPlayer);
2443}
2444