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