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 "gamecore.h"
4
5#include "collision.h"
6#include "mapitems.h"
7#include "teamscore.h"
8
9#include <base/dbg.h>
10#include <base/str.h>
11
12#include <engine/shared/config.h>
13
14#include <limits>
15
16const char *CTuningParams::ms_apNames[] =
17 {
18#define MACRO_TUNING_PARAM(Name, ScriptName, Value, Description) #ScriptName,
19#include "tuning.h"
20#undef MACRO_TUNING_PARAM
21};
22
23bool CTuningParams::Set(int Index, float Value)
24{
25 if(Index < 0 || Index >= Num())
26 return false;
27 ((CTuneParam *)this)[Index] = Value;
28 return true;
29}
30
31bool CTuningParams::Get(int Index, float *pValue) const
32{
33 if(Index < 0 || Index >= Num())
34 return false;
35 *pValue = (float)((CTuneParam *)this)[Index];
36 return true;
37}
38
39bool CTuningParams::Set(const char *pName, float Value)
40{
41 for(int i = 0; i < Num(); i++)
42 if(str_comp_nocase(a: pName, b: Name(Index: i)) == 0)
43 return Set(Index: i, Value);
44 return false;
45}
46
47bool CTuningParams::Get(const char *pName, float *pValue) const
48{
49 for(int i = 0; i < Num(); i++)
50 if(str_comp_nocase(a: pName, b: Name(Index: i)) == 0)
51 return Get(Index: i, pValue);
52
53 return false;
54}
55
56float CTuningParams::GetWeaponFireDelay(int Weapon) const
57{
58 switch(Weapon)
59 {
60 case WEAPON_HAMMER: return (float)m_HammerFireDelay / 1000.0f;
61 case WEAPON_GUN: return (float)m_GunFireDelay / 1000.0f;
62 case WEAPON_SHOTGUN: return (float)m_ShotgunFireDelay / 1000.0f;
63 case WEAPON_GRENADE: return (float)m_GrenadeFireDelay / 1000.0f;
64 case WEAPON_LASER: return (float)m_LaserFireDelay / 1000.0f;
65 case WEAPON_NINJA: return (float)m_NinjaFireDelay / 1000.0f;
66 default: dbg_assert_failed("invalid weapon");
67 }
68}
69
70static_assert(std::numeric_limits<char>::is_signed, "char must be signed for StrToInts to work correctly");
71
72void StrToInts(int *pInts, size_t NumInts, const char *pStr)
73{
74 dbg_assert(NumInts > 0, "StrToInts: NumInts invalid");
75 const size_t StrSize = str_length(str: pStr) + 1;
76 dbg_assert(StrSize <= NumInts * sizeof(int), "StrToInts: string truncated");
77
78 for(size_t i = 0; i < NumInts; i++)
79 {
80 // Copy to temporary buffer to ensure we don't read past the end of the input string
81 char aBuf[sizeof(int)] = {0, 0, 0, 0};
82 for(size_t c = 0; c < sizeof(int) && i * sizeof(int) + c < StrSize; c++)
83 {
84 aBuf[c] = pStr[i * sizeof(int) + c];
85 }
86 pInts[i] = ((aBuf[0] + 128) << 24) | ((aBuf[1] + 128) << 16) | ((aBuf[2] + 128) << 8) | (aBuf[3] + 128);
87 }
88 // Last byte is always zero and unused in this format
89 pInts[NumInts - 1] &= 0xFFFFFF00;
90}
91
92bool IntsToStr(const int *pInts, size_t NumInts, char *pStr, size_t StrSize)
93{
94 dbg_assert(NumInts > 0, "IntsToStr: NumInts invalid");
95 dbg_assert(StrSize >= NumInts * sizeof(int), "IntsToStr: StrSize invalid");
96
97 // Unpack string without validation
98 size_t StrIndex = 0;
99 for(size_t IntIndex = 0; IntIndex < NumInts; IntIndex++)
100 {
101 const int CurrentInt = pInts[IntIndex];
102 pStr[StrIndex] = ((CurrentInt >> 24) & 0xff) - 128;
103 StrIndex++;
104 pStr[StrIndex] = ((CurrentInt >> 16) & 0xff) - 128;
105 StrIndex++;
106 pStr[StrIndex] = ((CurrentInt >> 8) & 0xff) - 128;
107 StrIndex++;
108 pStr[StrIndex] = (CurrentInt & 0xff) - 128;
109 StrIndex++;
110 }
111 // Ensure null-termination
112 pStr[StrIndex - 1] = '\0';
113
114 // Ensure valid UTF-8
115 if(str_utf8_check(str: pStr))
116 {
117 return true;
118 }
119 pStr[0] = '\0';
120 return false;
121}
122
123float VelocityRamp(float Value, float Start, float Range, float Curvature)
124{
125 if(Value < Start)
126 return 1.0f;
127 return 1.0f / std::pow(x: Curvature, y: (Value - Start) / Range);
128}
129
130void CCharacterCore::Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams)
131{
132 m_pWorld = pWorld;
133 m_pCollision = pCollision;
134
135 m_pTeams = pTeams;
136 m_Id = -1;
137}
138
139void CCharacterCore::SetCoreWorld(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams)
140{
141 m_pWorld = pWorld;
142 m_pCollision = pCollision;
143 m_pTeams = pTeams;
144}
145
146void CCharacterCore::SetAntiPingInterfereCallback(FAntiPingInterfereCallback Callback)
147{
148 m_AntiPingInterfereCallback = std::move(Callback);
149}
150
151void CCharacterCore::Reset()
152{
153 m_Pos = vec2(0, 0);
154 m_Vel = vec2(0, 0);
155 m_NewHook = false;
156 m_HookPos = vec2(0, 0);
157 m_HookDir = vec2(0, 0);
158 m_HookTeleBase = vec2(0, 0);
159 m_HookTick = 0;
160 m_HookState = HOOK_IDLE;
161 SetHookedPlayer(-1);
162 m_AttachedPlayers.clear();
163 m_Jumped = 0;
164 m_JumpedTotal = 0;
165 m_Jumps = 2;
166 m_TriggeredEvents = 0;
167
168 // DDNet Character
169 m_Solo = false;
170 m_Jetpack = false;
171 m_CollisionDisabled = false;
172 m_EndlessHook = false;
173 m_EndlessJump = false;
174 m_HammerHitDisabled = false;
175 m_GrenadeHitDisabled = false;
176 m_LaserHitDisabled = false;
177 m_ShotgunHitDisabled = false;
178 m_HookHitDisabled = false;
179 m_Super = false;
180 m_Invincible = false;
181 m_HasTelegunGun = false;
182 m_HasTelegunGrenade = false;
183 m_HasTelegunLaser = false;
184 m_FreezeStart = 0;
185 m_FreezeEnd = 0;
186 m_IsInFreeze = false;
187 m_DeepFrozen = false;
188 m_LiveFrozen = false;
189
190 // never initialize both to 0
191 m_Input.m_TargetX = 0;
192 m_Input.m_TargetY = -1;
193}
194
195void CCharacterCore::Tick(bool UseInput, bool DoDeferredTick)
196{
197 m_MoveRestrictions = m_pCollision->GetMoveRestrictions(pfnSwitchActive: UseInput ? IsSwitchActiveCb : nullptr, pUser: this, Pos: m_Pos);
198 m_TriggeredEvents = 0;
199
200 // get ground state
201 const bool Grounded = m_pCollision->IsOnGround(Pos: m_Pos, Size: PhysicalSize());
202 vec2 TargetDirection = normalize(v: vec2(m_Input.m_TargetX, m_Input.m_TargetY));
203
204 m_Vel.y += m_Tuning.m_Gravity;
205
206 float MaxSpeed = Grounded ? m_Tuning.m_GroundControlSpeed : m_Tuning.m_AirControlSpeed;
207 float Accel = Grounded ? m_Tuning.m_GroundControlAccel : m_Tuning.m_AirControlAccel;
208 float Friction = Grounded ? m_Tuning.m_GroundFriction : m_Tuning.m_AirFriction;
209
210 // handle input
211 if(UseInput)
212 {
213 m_Direction = m_Input.m_Direction;
214
215 // setup angle
216 float TmpAngle = std::atan2(y: m_Input.m_TargetY, x: m_Input.m_TargetX);
217 if(TmpAngle < -(pi / 2.0f))
218 {
219 m_Angle = (int)((TmpAngle + (2.0f * pi)) * 256.0f);
220 }
221 else
222 {
223 m_Angle = (int)(TmpAngle * 256.0f);
224 }
225
226 // Special jump cases:
227 // m_Jumps == -1: A tee may only make one ground jump. Second jumped bit is always set
228 // m_Jumps == 0: A tee may not make a jump. Second jumped bit is always set
229 // m_Jumps == 1: A tee may do either a ground jump or an air jump. Second jumped bit is set after the first jump
230 // The second jumped bit can be overridden by special tiles so that the tee can nevertheless jump.
231
232 // handle jump
233 if(m_Input.m_Jump)
234 {
235 if(!(m_Jumped & 1))
236 {
237 if(Grounded && (!(m_Jumped & 2) || m_Jumps != 0))
238 {
239 m_TriggeredEvents |= COREEVENT_GROUND_JUMP;
240 m_Vel.y = -m_Tuning.m_GroundJumpImpulse;
241 if(m_Jumps > 1)
242 {
243 m_Jumped |= 1;
244 }
245 else
246 {
247 m_Jumped |= 3;
248 }
249 m_JumpedTotal = 0;
250 }
251 else if(!(m_Jumped & 2))
252 {
253 m_TriggeredEvents |= COREEVENT_AIR_JUMP;
254 m_Vel.y = -m_Tuning.m_AirJumpImpulse;
255 m_Jumped |= 3;
256 m_JumpedTotal++;
257 }
258 }
259 }
260 else
261 {
262 m_Jumped &= ~1;
263 }
264
265 // handle hook
266 if(m_Input.m_Hook)
267 {
268 if(m_HookState == HOOK_IDLE)
269 {
270 m_HookState = HOOK_FLYING;
271 m_HookPos = m_Pos + TargetDirection * PhysicalSize() * 1.5f;
272 m_HookDir = TargetDirection;
273 SetHookedPlayer(-1);
274 m_HookTick = (float)SERVER_TICK_SPEED * (1.25f - m_Tuning.m_HookDuration);
275 m_TriggeredEvents |= COREEVENT_HOOK_LAUNCH;
276 }
277 }
278 else
279 {
280 SetHookedPlayer(-1);
281 m_HookState = HOOK_IDLE;
282 m_HookPos = m_Pos;
283 }
284 }
285
286 // handle jumping
287 // 1 bit = to keep track if a jump has been made on this input (player is holding space bar)
288 // 2 bit = to track if all air-jumps have been used up (tee gets dark feet)
289 if(Grounded)
290 {
291 m_Jumped &= ~2;
292 m_JumpedTotal = 0;
293 }
294
295 // add the speed modification according to players wanted direction
296 if(m_Direction < 0)
297 m_Vel.x = SaturatedAdd(Min: -MaxSpeed, Max: MaxSpeed, Current: m_Vel.x, Modifier: -Accel);
298 if(m_Direction > 0)
299 m_Vel.x = SaturatedAdd(Min: -MaxSpeed, Max: MaxSpeed, Current: m_Vel.x, Modifier: Accel);
300 if(m_Direction == 0)
301 m_Vel.x *= Friction;
302
303 // do hook
304 if(m_HookState == HOOK_IDLE)
305 {
306 SetHookedPlayer(-1);
307 m_HookPos = m_Pos;
308 }
309 else if(m_HookState >= HOOK_RETRACT_START && m_HookState < HOOK_RETRACT_END)
310 {
311 m_HookState++;
312 }
313 else if(m_HookState == HOOK_RETRACT_END)
314 {
315 m_TriggeredEvents |= COREEVENT_HOOK_RETRACT;
316 m_HookState = HOOK_RETRACTED;
317 }
318 else if(m_HookState == HOOK_FLYING)
319 {
320 vec2 HookBase = m_Pos;
321 if(m_NewHook)
322 {
323 HookBase = m_HookTeleBase;
324 }
325 vec2 NewPos = m_HookPos + m_HookDir * m_Tuning.m_HookFireSpeed;
326 if(distance(a: HookBase, b: NewPos) > m_Tuning.m_HookLength)
327 {
328 m_HookState = HOOK_RETRACT_START;
329 NewPos = HookBase + normalize(v: NewPos - HookBase) * m_Tuning.m_HookLength;
330 m_Reset = true;
331 }
332
333 // make sure that the hook doesn't go though the ground
334 bool GoingToHitGround = false;
335 bool GoingToRetract = false;
336 bool GoingThroughTele = false;
337 int TeleNr = 0;
338 int Hit = m_pCollision->IntersectLineTeleHook(Pos0: m_HookPos, Pos1: NewPos, pOutCollision: &NewPos, pOutBeforeCollision: nullptr, pTeleNr: &TeleNr);
339
340 if(Hit)
341 {
342 if(Hit == TILE_NOHOOK)
343 GoingToRetract = true;
344 else if(Hit == TILE_TELEINHOOK)
345 GoingThroughTele = true;
346 else
347 GoingToHitGround = true;
348 m_Reset = true;
349 }
350
351 // Check against other players first
352 if(!m_HookHitDisabled && m_pWorld && m_Tuning.m_PlayerHooking && (m_HookState == HOOK_FLYING || !m_NewHook))
353 {
354 float Distance = 0.0f;
355 for(int i = 0; i < MAX_CLIENTS; i++)
356 {
357 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[i];
358 if(!pCharCore || pCharCore == this || (!(m_Super || pCharCore->m_Super) && ((m_Id != -1 && !m_pTeams->CanCollide(ClientId1: i, ClientId2: m_Id)) || pCharCore->m_Solo || m_Solo)))
359 continue;
360
361 vec2 ClosestPoint;
362 if(closest_point_on_line(line_pointA: m_HookPos, line_pointB: NewPos, target_point: pCharCore->m_Pos, out_pos&: ClosestPoint))
363 {
364 if(distance(a: pCharCore->m_Pos, b: ClosestPoint) < PhysicalSize() + 2.0f)
365 {
366 if(m_HookedPlayer == -1 || distance(a: m_HookPos, b: pCharCore->m_Pos) < Distance)
367 {
368 m_TriggeredEvents |= COREEVENT_HOOK_ATTACH_PLAYER;
369 m_HookState = HOOK_GRABBED;
370 SetHookedPlayer(i);
371 Distance = distance(a: m_HookPos, b: pCharCore->m_Pos);
372 m_AntiPingInterfereCallback(i, false);
373 }
374 }
375 }
376 }
377 }
378
379 if(m_HookState == HOOK_FLYING)
380 {
381 // check against ground
382 if(GoingToHitGround)
383 {
384 m_TriggeredEvents |= COREEVENT_HOOK_ATTACH_GROUND;
385 m_HookState = HOOK_GRABBED;
386 }
387 else if(GoingToRetract)
388 {
389 m_TriggeredEvents |= COREEVENT_HOOK_HIT_NOHOOK;
390 m_HookState = HOOK_RETRACT_START;
391 }
392
393 if(GoingThroughTele && m_pWorld && !m_pCollision->TeleOuts(Number: TeleNr - 1).empty())
394 {
395 m_TriggeredEvents = 0;
396 SetHookedPlayer(-1);
397
398 m_NewHook = true;
399 int RandomOut = m_pWorld->RandomOr0(BelowThis: m_pCollision->TeleOuts(Number: TeleNr - 1).size());
400 m_HookPos = m_pCollision->TeleOuts(Number: TeleNr - 1)[RandomOut] + TargetDirection * PhysicalSize() * 1.5f;
401 m_HookDir = TargetDirection;
402 m_HookTeleBase = m_HookPos;
403 }
404 else
405 {
406 m_HookPos = NewPos;
407 }
408 }
409 }
410
411 if(m_HookState == HOOK_GRABBED)
412 {
413 if(m_HookedPlayer != -1 && m_pWorld)
414 {
415 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[m_HookedPlayer];
416 if(pCharCore && m_Id != -1 && m_pTeams->CanKeepHook(ClientId1: m_Id, ClientId2: pCharCore->m_Id))
417 m_HookPos = pCharCore->m_Pos;
418 else
419 {
420 // release hook
421 SetHookedPlayer(-1);
422 m_HookState = HOOK_RETRACTED;
423 m_HookPos = m_Pos;
424 }
425 }
426
427 // don't do this hook routine when we are already hooked to a player
428 if(m_HookedPlayer == -1 && distance(a: m_HookPos, b: m_Pos) > 46.0f)
429 {
430 vec2 HookVel = normalize(v: m_HookPos - m_Pos) * m_Tuning.m_HookDragAccel;
431 // the hook as more power to drag you up then down.
432 // this makes it easier to get on top of an platform
433 if(HookVel.y > 0)
434 HookVel.y *= 0.3f;
435
436 // the hook will boost it's power if the player wants to move
437 // in that direction. otherwise it will dampen everything abit
438 if((HookVel.x < 0 && m_Direction < 0) || (HookVel.x > 0 && m_Direction > 0))
439 HookVel.x *= 0.95f;
440 else
441 HookVel.x *= 0.75f;
442
443 vec2 NewVel = m_Vel + HookVel;
444
445 // check if we are under the legal limit for the hook
446 const float NewVelLength = length(a: NewVel);
447 if(NewVelLength < m_Tuning.m_HookDragSpeed || NewVelLength < length(a: m_Vel))
448 m_Vel = NewVel; // no problem. apply
449 }
450
451 // release hook (max default hook time is 1.25 s)
452 m_HookTick++;
453 if(m_HookedPlayer != -1 && (m_HookTick > SERVER_TICK_SPEED + SERVER_TICK_SPEED / 5 || (m_pWorld && !m_pWorld->m_apCharacters[m_HookedPlayer])))
454 {
455 SetHookedPlayer(-1);
456 m_HookState = HOOK_RETRACTED;
457 m_HookPos = m_Pos;
458 }
459 }
460
461 if(DoDeferredTick)
462 TickDeferred();
463}
464
465void CCharacterCore::TickDeferred()
466{
467 if(m_pWorld)
468 {
469 for(int i = 0; i < MAX_CLIENTS; i++)
470 {
471 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[i];
472 if(!pCharCore)
473 continue;
474
475 if(pCharCore == this || (m_Id != -1 && !m_pTeams->CanCollide(ClientId1: m_Id, ClientId2: i)))
476 continue; // make sure that we don't nudge our self
477
478 if(!(m_Super || pCharCore->m_Super) && (m_Solo || pCharCore->m_Solo))
479 continue;
480
481 // handle player <-> player collision
482 float Distance = distance(a: m_Pos, b: pCharCore->m_Pos);
483 if(Distance > 0)
484 {
485 vec2 Dir = normalize(v: m_Pos - pCharCore->m_Pos);
486
487 bool CanCollide = (m_Super || pCharCore->m_Super) || (!m_CollisionDisabled && !pCharCore->m_CollisionDisabled && m_Tuning.m_PlayerCollision);
488
489 if(CanCollide && Distance < PhysicalSize() * 1.25f)
490 {
491 float a = (PhysicalSize() * 1.45f - Distance);
492 float Velocity = 0.5f;
493
494 // make sure that we don't add excess force by checking the
495 // direction against the current velocity. if not zero.
496 if(length(a: m_Vel) > 0.0001f)
497 Velocity = 1 - (dot(a: normalize(v: m_Vel), b: Dir) + 1) / 2; // Wdouble-promotion don't fix this as this might change game physics
498
499 m_Vel += Dir * a * (Velocity * 0.75f);
500 m_Vel *= 0.85f;
501
502 m_AntiPingInterfereCallback(i, true);
503 }
504
505 // handle hook influence
506 if(!m_HookHitDisabled && m_HookedPlayer == i && m_Tuning.m_PlayerHooking)
507 {
508 if(Distance > PhysicalSize() * 1.50f)
509 {
510 float HookAccel = m_Tuning.m_HookDragAccel * (Distance / m_Tuning.m_HookLength);
511 float DragSpeed = m_Tuning.m_HookDragSpeed;
512
513 vec2 Temp;
514 // add force to the hooked player
515 Temp.x = SaturatedAdd(Min: -DragSpeed, Max: DragSpeed, Current: pCharCore->m_Vel.x, Modifier: HookAccel * Dir.x * 1.5f);
516 Temp.y = SaturatedAdd(Min: -DragSpeed, Max: DragSpeed, Current: pCharCore->m_Vel.y, Modifier: HookAccel * Dir.y * 1.5f);
517 pCharCore->m_Vel = ClampVel(MoveRestriction: pCharCore->m_MoveRestrictions, Vel: Temp);
518 // add a little bit force to the guy who has the grip
519 Temp.x = SaturatedAdd(Min: -DragSpeed, Max: DragSpeed, Current: m_Vel.x, Modifier: -HookAccel * Dir.x * 0.25f);
520 Temp.y = SaturatedAdd(Min: -DragSpeed, Max: DragSpeed, Current: m_Vel.y, Modifier: -HookAccel * Dir.y * 0.25f);
521 m_Vel = ClampVel(MoveRestriction: m_MoveRestrictions, Vel: Temp);
522 }
523 }
524 }
525 }
526
527 if(m_HookState != HOOK_FLYING)
528 {
529 m_NewHook = false;
530 }
531 }
532
533 // clamp the velocity to something sane
534 if(length(a: m_Vel) > 6000)
535 m_Vel = normalize(v: m_Vel) * 6000;
536}
537
538void CCharacterCore::Move()
539{
540 float RampValue = VelocityRamp(Value: length(a: m_Vel) * 50, Start: m_Tuning.m_VelrampStart, Range: m_Tuning.m_VelrampRange, Curvature: m_Tuning.m_VelrampCurvature);
541
542 m_Vel.x = m_Vel.x * RampValue;
543
544 vec2 NewPos = m_Pos;
545
546 vec2 OldVel = m_Vel;
547 bool Grounded = false;
548 m_pCollision->MoveBox(pInoutPos: &NewPos, pInoutVel: &m_Vel, Size: PhysicalSizeVec2(),
549 Elasticity: vec2(m_Tuning.m_GroundElasticityX,
550 m_Tuning.m_GroundElasticityY),
551 pGrounded: &Grounded);
552
553 if(Grounded)
554 {
555 m_Jumped &= ~2;
556 m_JumpedTotal = 0;
557 }
558
559 m_Colliding = 0;
560 if(m_Vel.x < 0.001f && m_Vel.x > -0.001f)
561 {
562 if(OldVel.x > 0)
563 m_Colliding = 1;
564 else if(OldVel.x < 0)
565 m_Colliding = 2;
566 }
567 else
568 m_LeftWall = true;
569
570 m_Vel.x = m_Vel.x * (1.0f / RampValue);
571
572 if(m_pWorld && (m_Super || (m_Tuning.m_PlayerCollision && !m_CollisionDisabled && !m_Solo)))
573 {
574 // check player collision
575 float Distance = distance(a: m_Pos, b: NewPos);
576 if(Distance > 0)
577 {
578 int End = Distance + 1;
579 vec2 LastPos = m_Pos;
580 for(int i = 0; i < End; i++)
581 {
582 float a = i / Distance;
583 vec2 Pos = mix(a: m_Pos, b: NewPos, amount: a);
584 for(int p = 0; p < MAX_CLIENTS; p++)
585 {
586 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[p];
587 if(!pCharCore || pCharCore == this)
588 continue;
589 if((!(pCharCore->m_Super || m_Super) && (m_Solo || pCharCore->m_Solo || pCharCore->m_CollisionDisabled || (m_Id != -1 && !m_pTeams->CanCollide(ClientId1: m_Id, ClientId2: p)))))
590 continue;
591 float D = distance(a: Pos, b: pCharCore->m_Pos);
592 if(D < PhysicalSize())
593 {
594 if(a > 0.0f)
595 m_Pos = LastPos;
596 else if(distance(a: NewPos, b: pCharCore->m_Pos) > D)
597 m_Pos = NewPos;
598 return;
599 }
600 }
601 LastPos = Pos;
602 }
603 }
604 }
605
606 m_Pos = NewPos;
607}
608
609void CCharacterCore::Write(CNetObj_CharacterCore *pObjCore) const
610{
611 pObjCore->m_X = round_to_int(f: m_Pos.x);
612 pObjCore->m_Y = round_to_int(f: m_Pos.y);
613
614 pObjCore->m_VelX = round_to_int(f: m_Vel.x * 256.0f);
615 pObjCore->m_VelY = round_to_int(f: m_Vel.y * 256.0f);
616 pObjCore->m_HookState = m_HookState;
617 pObjCore->m_HookTick = m_HookTick;
618 pObjCore->m_HookX = round_to_int(f: m_HookPos.x);
619 pObjCore->m_HookY = round_to_int(f: m_HookPos.y);
620 pObjCore->m_HookDx = round_to_int(f: m_HookDir.x * 256.0f);
621 pObjCore->m_HookDy = round_to_int(f: m_HookDir.y * 256.0f);
622 pObjCore->m_HookedPlayer = m_HookedPlayer;
623 pObjCore->m_Jumped = m_Jumped;
624 pObjCore->m_Direction = m_Direction;
625 pObjCore->m_Angle = m_Angle;
626}
627
628void CCharacterCore::Read(const CNetObj_CharacterCore *pObjCore)
629{
630 m_Pos.x = pObjCore->m_X;
631 m_Pos.y = pObjCore->m_Y;
632 m_Vel.x = pObjCore->m_VelX / 256.0f;
633 m_Vel.y = pObjCore->m_VelY / 256.0f;
634 m_HookState = pObjCore->m_HookState;
635 m_HookTick = pObjCore->m_HookTick;
636 m_HookPos.x = pObjCore->m_HookX;
637 m_HookPos.y = pObjCore->m_HookY;
638 m_HookDir.x = pObjCore->m_HookDx / 256.0f;
639 m_HookDir.y = pObjCore->m_HookDy / 256.0f;
640 SetHookedPlayer(pObjCore->m_HookedPlayer);
641 m_Jumped = pObjCore->m_Jumped;
642 m_Direction = pObjCore->m_Direction;
643 m_Angle = pObjCore->m_Angle;
644}
645
646void CCharacterCore::ReadDDNet(const CNetObj_DDNetCharacter *pObjDDNet)
647{
648 // Collision
649 m_Solo = pObjDDNet->m_Flags & CHARACTERFLAG_SOLO;
650 m_Jetpack = pObjDDNet->m_Flags & CHARACTERFLAG_JETPACK;
651 m_CollisionDisabled = pObjDDNet->m_Flags & CHARACTERFLAG_COLLISION_DISABLED;
652 m_HammerHitDisabled = pObjDDNet->m_Flags & CHARACTERFLAG_HAMMER_HIT_DISABLED;
653 m_ShotgunHitDisabled = pObjDDNet->m_Flags & CHARACTERFLAG_SHOTGUN_HIT_DISABLED;
654 m_GrenadeHitDisabled = pObjDDNet->m_Flags & CHARACTERFLAG_GRENADE_HIT_DISABLED;
655 m_LaserHitDisabled = pObjDDNet->m_Flags & CHARACTERFLAG_LASER_HIT_DISABLED;
656 m_HookHitDisabled = pObjDDNet->m_Flags & CHARACTERFLAG_HOOK_HIT_DISABLED;
657 m_Super = pObjDDNet->m_Flags & CHARACTERFLAG_SUPER;
658 m_Invincible = pObjDDNet->m_Flags & CHARACTERFLAG_INVINCIBLE;
659
660 // Endless
661 m_EndlessHook = pObjDDNet->m_Flags & CHARACTERFLAG_ENDLESS_HOOK;
662 m_EndlessJump = pObjDDNet->m_Flags & CHARACTERFLAG_ENDLESS_JUMP;
663
664 // Freeze
665 m_FreezeEnd = pObjDDNet->m_FreezeEnd;
666 m_DeepFrozen = pObjDDNet->m_FreezeEnd == -1;
667 m_LiveFrozen = (pObjDDNet->m_Flags & CHARACTERFLAG_MOVEMENTS_DISABLED) != 0;
668
669 // Telegun
670 m_HasTelegunGrenade = pObjDDNet->m_Flags & CHARACTERFLAG_TELEGUN_GRENADE;
671 m_HasTelegunGun = pObjDDNet->m_Flags & CHARACTERFLAG_TELEGUN_GUN;
672 m_HasTelegunLaser = pObjDDNet->m_Flags & CHARACTERFLAG_TELEGUN_LASER;
673
674 // Weapons
675 m_aWeapons[WEAPON_HAMMER].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_HAMMER) != 0;
676 m_aWeapons[WEAPON_GUN].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_GUN) != 0;
677 m_aWeapons[WEAPON_SHOTGUN].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_SHOTGUN) != 0;
678 m_aWeapons[WEAPON_GRENADE].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_GRENADE) != 0;
679 m_aWeapons[WEAPON_LASER].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_LASER) != 0;
680 m_aWeapons[WEAPON_NINJA].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_NINJA) != 0;
681
682 // Available jumps
683 m_Jumps = pObjDDNet->m_Jumps;
684
685 // Display Information
686 // We only accept the display information when it is received, which means it is not -1 in each case.
687 if(pObjDDNet->m_JumpedTotal != -1)
688 {
689 m_JumpedTotal = pObjDDNet->m_JumpedTotal;
690 }
691 if(pObjDDNet->m_NinjaActivationTick != -1)
692 {
693 m_Ninja.m_ActivationTick = pObjDDNet->m_NinjaActivationTick;
694 }
695 if(pObjDDNet->m_FreezeStart != -1)
696 {
697 m_FreezeStart = pObjDDNet->m_FreezeStart;
698 m_IsInFreeze = pObjDDNet->m_Flags & CHARACTERFLAG_IN_FREEZE;
699 }
700}
701
702void CCharacterCore::Quantize()
703{
704 CNetObj_CharacterCore Core;
705 Write(pObjCore: &Core);
706 Read(pObjCore: &Core);
707}
708
709void CCharacterCore::SetHookedPlayer(int HookedPlayer)
710{
711 if(HookedPlayer != m_HookedPlayer)
712 {
713 if(m_HookedPlayer != -1 && m_Id != -1 && m_pWorld)
714 {
715 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[m_HookedPlayer];
716 if(pCharCore)
717 {
718 pCharCore->m_AttachedPlayers.erase(x: m_Id);
719 }
720 }
721 if(HookedPlayer != -1 && m_Id != -1 && m_pWorld)
722 {
723 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[HookedPlayer];
724 if(pCharCore)
725 {
726 pCharCore->m_AttachedPlayers.insert(x: m_Id);
727 }
728 }
729 m_HookedPlayer = HookedPlayer;
730 }
731}
732
733// DDRace
734
735void CCharacterCore::SetTeamsCore(CTeamsCore *pTeams)
736{
737 m_pTeams = pTeams;
738}
739
740bool CCharacterCore::IsSwitchActiveCb(unsigned char Number, void *pUser)
741{
742 CCharacterCore *pThis = (CCharacterCore *)pUser;
743 if(pThis->m_pWorld && !pThis->m_pWorld->m_vSwitchers.empty())
744 if(pThis->m_Id != -1 && pThis->m_pTeams->Team(ClientId: pThis->m_Id) != pThis->m_pTeams->TeamSuper())
745 return pThis->m_pWorld->m_vSwitchers[Number].m_aStatus[pThis->m_pTeams->Team(ClientId: pThis->m_Id)];
746 return false;
747}
748
749void CWorldCore::InitSwitchers(int HighestSwitchNumber)
750{
751 if(HighestSwitchNumber > 0)
752 m_vSwitchers.resize(sz: HighestSwitchNumber + 1);
753 else
754 m_vSwitchers.clear();
755
756 for(auto &Switcher : m_vSwitchers)
757 {
758 Switcher.m_Initial = true;
759 for(int j = 0; j < NUM_DDRACE_TEAMS; j++)
760 {
761 Switcher.m_aStatus[j] = true;
762 Switcher.m_aEndTick[j] = 0;
763 Switcher.m_aType[j] = 0;
764 Switcher.m_aLastUpdateTick[j] = 0;
765 }
766 }
767}
768
769const CTuningParams CTuningParams::DEFAULT;
770