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