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