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#include <engine/shared/config.h>
11
12#include <limits>
13
14const char *CTuningParams::ms_apNames[] =
15 {
16#define MACRO_TUNING_PARAM(Name, ScriptName, Value, Description) #ScriptName,
17#include "tuning.h"
18#undef MACRO_TUNING_PARAM
19};
20
21bool CTuningParams::Set(int Index, float Value)
22{
23 if(Index < 0 || Index >= Num())
24 return false;
25 ((CTuneParam *)this)[Index] = Value;
26 return true;
27}
28
29bool CTuningParams::Get(int Index, float *pValue) const
30{
31 if(Index < 0 || Index >= Num())
32 return false;
33 *pValue = (float)((CTuneParam *)this)[Index];
34 return true;
35}
36
37bool CTuningParams::Set(const char *pName, float Value)
38{
39 for(int i = 0; i < Num(); i++)
40 if(str_comp_nocase(a: pName, b: Name(Index: i)) == 0)
41 return Set(Index: i, Value);
42 return false;
43}
44
45bool CTuningParams::Get(const char *pName, float *pValue) const
46{
47 for(int i = 0; i < Num(); i++)
48 if(str_comp_nocase(a: pName, b: Name(Index: i)) == 0)
49 return Get(Index: i, pValue);
50
51 return false;
52}
53
54float CTuningParams::GetWeaponFireDelay(int Weapon) const
55{
56 switch(Weapon)
57 {
58 case WEAPON_HAMMER: return (float)m_HammerHitFireDelay / 1000.0f;
59 case WEAPON_GUN: return (float)m_GunFireDelay / 1000.0f;
60 case WEAPON_SHOTGUN: return (float)m_ShotgunFireDelay / 1000.0f;
61 case WEAPON_GRENADE: return (float)m_GrenadeFireDelay / 1000.0f;
62 case WEAPON_LASER: return (float)m_LaserFireDelay / 1000.0f;
63 case WEAPON_NINJA: return (float)m_NinjaFireDelay / 1000.0f;
64 default: dbg_assert(false, "invalid weapon"); return 0.0f; // this value should not be reached
65 }
66}
67
68static_assert(std::numeric_limits<char>::is_signed, "char must be signed for StrToInts to work correctly");
69
70void StrToInts(int *pInts, size_t NumInts, const char *pStr)
71{
72 dbg_assert(NumInts > 0, "StrToInts: NumInts invalid");
73 const size_t StrSize = str_length(str: pStr) + 1;
74 dbg_assert(StrSize <= NumInts * sizeof(int), "StrToInts: string truncated");
75
76 for(size_t i = 0; i < NumInts; i++)
77 {
78 // Copy to temporary buffer to ensure we don't read past the end of the input string
79 char aBuf[sizeof(int)] = {0, 0, 0, 0};
80 for(size_t c = 0; c < sizeof(int) && i * sizeof(int) + c < StrSize; c++)
81 {
82 aBuf[c] = pStr[i * sizeof(int) + c];
83 }
84 pInts[i] = ((aBuf[0] + 128) << 24) | ((aBuf[1] + 128) << 16) | ((aBuf[2] + 128) << 8) | (aBuf[3] + 128);
85 }
86 // Last byte is always zero and unused in this format
87 pInts[NumInts - 1] &= 0xFFFFFF00;
88}
89
90bool IntsToStr(const int *pInts, size_t NumInts, char *pStr, size_t StrSize)
91{
92 dbg_assert(NumInts > 0, "IntsToStr: NumInts invalid");
93 dbg_assert(StrSize >= NumInts * sizeof(int), "IntsToStr: StrSize invalid");
94
95 // Unpack string without validation
96 size_t StrIndex = 0;
97 for(size_t IntIndex = 0; IntIndex < NumInts; IntIndex++)
98 {
99 const int CurrentInt = pInts[IntIndex];
100 pStr[StrIndex] = ((CurrentInt >> 24) & 0xff) - 128;
101 StrIndex++;
102 pStr[StrIndex] = ((CurrentInt >> 16) & 0xff) - 128;
103 StrIndex++;
104 pStr[StrIndex] = ((CurrentInt >> 8) & 0xff) - 128;
105 StrIndex++;
106 pStr[StrIndex] = (CurrentInt & 0xff) - 128;
107 StrIndex++;
108 }
109 // Ensure null-termination
110 pStr[StrIndex - 1] = '\0';
111
112 // Ensure valid UTF-8
113 if(str_utf8_check(str: pStr))
114 {
115 return true;
116 }
117 pStr[0] = '\0';
118 return false;
119}
120
121float VelocityRamp(float Value, float Start, float Range, float Curvature)
122{
123 if(Value < Start)
124 return 1.0f;
125 return 1.0f / std::pow(x: Curvature, y: (Value - Start) / Range);
126}
127
128void CCharacterCore::Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams)
129{
130 m_pWorld = pWorld;
131 m_pCollision = pCollision;
132
133 m_pTeams = pTeams;
134 m_Id = -1;
135
136 // fail safe, if core's tuning didn't get updated at all, just fallback to world tuning.
137 m_Tuning = m_pWorld->m_aTuning[g_Config.m_ClDummy];
138}
139
140void CCharacterCore::SetCoreWorld(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams)
141{
142 m_pWorld = pWorld;
143 m_pCollision = pCollision;
144 m_pTeams = pTeams;
145}
146
147void CCharacterCore::Reset()
148{
149 m_Pos = vec2(0, 0);
150 m_Vel = vec2(0, 0);
151 m_NewHook = false;
152 m_HookPos = vec2(0, 0);
153 m_HookDir = vec2(0, 0);
154 m_HookTeleBase = vec2(0, 0);
155 m_HookTick = 0;
156 m_HookState = HOOK_IDLE;
157 SetHookedPlayer(-1);
158 m_AttachedPlayers.clear();
159 m_Jumped = 0;
160 m_JumpedTotal = 0;
161 m_Jumps = 2;
162 m_TriggeredEvents = 0;
163
164 // DDNet Character
165 m_Solo = false;
166 m_Jetpack = false;
167 m_CollisionDisabled = false;
168 m_EndlessHook = false;
169 m_EndlessJump = false;
170 m_HammerHitDisabled = false;
171 m_GrenadeHitDisabled = false;
172 m_LaserHitDisabled = false;
173 m_ShotgunHitDisabled = false;
174 m_HookHitDisabled = false;
175 m_Super = 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 : 0, pUser: this, Pos: m_Pos);
193 m_TriggeredEvents = 0;
194
195 // get ground state
196 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);
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: 0, pTeleNr: &teleNr);
334
335 // m_NewHook = false;
336
337 if(Hit)
338 {
339 if(Hit == TILE_NOHOOK)
340 GoingToRetract = true;
341 else if(Hit == TILE_TELEINHOOK)
342 GoingThroughTele = true;
343 else
344 GoingToHitGround = true;
345 m_Reset = true;
346 }
347
348 // Check against other players first
349 if(!this->m_HookHitDisabled && m_pWorld && m_Tuning.m_PlayerHooking && (m_HookState == HOOK_FLYING || !m_NewHook))
350 {
351 float Distance = 0.0f;
352 for(int i = 0; i < MAX_CLIENTS; i++)
353 {
354 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[i];
355 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)))
356 continue;
357
358 vec2 ClosestPoint;
359 if(closest_point_on_line(line_pointA: m_HookPos, line_pointB: NewPos, target_point: pCharCore->m_Pos, out_pos&: ClosestPoint))
360 {
361 if(distance(a: pCharCore->m_Pos, b: ClosestPoint) < PhysicalSize() + 2.0f)
362 {
363 if(m_HookedPlayer == -1 || distance(a: m_HookPos, b: pCharCore->m_Pos) < Distance)
364 {
365 m_TriggeredEvents |= COREEVENT_HOOK_ATTACH_PLAYER;
366 m_HookState = HOOK_GRABBED;
367 SetHookedPlayer(i);
368 Distance = distance(a: m_HookPos, b: pCharCore->m_Pos);
369 }
370 }
371 }
372 }
373 }
374
375 if(m_HookState == HOOK_FLYING)
376 {
377 // check against ground
378 if(GoingToHitGround)
379 {
380 m_TriggeredEvents |= COREEVENT_HOOK_ATTACH_GROUND;
381 m_HookState = HOOK_GRABBED;
382 }
383 else if(GoingToRetract)
384 {
385 m_TriggeredEvents |= COREEVENT_HOOK_HIT_NOHOOK;
386 m_HookState = HOOK_RETRACT_START;
387 }
388
389 if(GoingThroughTele && m_pWorld && !m_pCollision->TeleOuts(Number: teleNr - 1).empty())
390 {
391 m_TriggeredEvents = 0;
392 SetHookedPlayer(-1);
393
394 m_NewHook = true;
395 int RandomOut = m_pWorld->RandomOr0(BelowThis: m_pCollision->TeleOuts(Number: teleNr - 1).size());
396 m_HookPos = m_pCollision->TeleOuts(Number: teleNr - 1)[RandomOut] + TargetDirection * PhysicalSize() * 1.5f;
397 m_HookDir = TargetDirection;
398 m_HookTeleBase = m_HookPos;
399 }
400 else
401 {
402 m_HookPos = NewPos;
403 }
404 }
405 }
406
407 if(m_HookState == HOOK_GRABBED)
408 {
409 if(m_HookedPlayer != -1 && m_pWorld)
410 {
411 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[m_HookedPlayer];
412 if(pCharCore && m_Id != -1 && m_pTeams->CanKeepHook(ClientId1: m_Id, ClientId2: pCharCore->m_Id))
413 m_HookPos = pCharCore->m_Pos;
414 else
415 {
416 // release hook
417 SetHookedPlayer(-1);
418 m_HookState = HOOK_RETRACTED;
419 m_HookPos = m_Pos;
420 }
421
422 // keep players hooked for a max of 1.5sec
423 // if(Server()->Tick() > hook_tick+(Server()->TickSpeed()*3)/2)
424 // release_hooked();
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 if(length(a: NewVel) < m_Tuning.m_HookDragSpeed || length(a: NewVel) < length(a: m_Vel))
447 m_Vel = NewVel; // no problem. apply
448 }
449
450 // release hook (max default hook time is 1.25 s)
451 m_HookTick++;
452 if(m_HookedPlayer != -1 && (m_HookTick > SERVER_TICK_SPEED + SERVER_TICK_SPEED / 5 || (m_pWorld && !m_pWorld->m_apCharacters[m_HookedPlayer])))
453 {
454 SetHookedPlayer(-1);
455 m_HookState = HOOK_RETRACTED;
456 m_HookPos = m_Pos;
457 }
458 }
459
460 if(DoDeferredTick)
461 TickDeferred();
462}
463
464void CCharacterCore::TickDeferred()
465{
466 if(m_pWorld)
467 {
468 for(int i = 0; i < MAX_CLIENTS; i++)
469 {
470 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[i];
471 if(!pCharCore)
472 continue;
473
474 // player *p = (player*)ent;
475 // if(pCharCore == this) // || !(p->flags&FLAG_ALIVE)
476
477 if(pCharCore == this || (m_Id != -1 && !m_pTeams->CanCollide(ClientId1: m_Id, ClientId2: i)))
478 continue; // make sure that we don't nudge our self
479
480 if(!(m_Super || pCharCore->m_Super) && (m_Solo || pCharCore->m_Solo))
481 continue;
482
483 // handle player <-> player collision
484 float Distance = distance(a: m_Pos, b: pCharCore->m_Pos);
485 if(Distance > 0)
486 {
487 vec2 Dir = normalize(v: m_Pos - pCharCore->m_Pos);
488
489 bool CanCollide = (m_Super || pCharCore->m_Super) || (!m_CollisionDisabled && !pCharCore->m_CollisionDisabled && m_Tuning.m_PlayerCollision);
490
491 if(CanCollide && Distance < PhysicalSize() * 1.25f && Distance > 0.0f)
492 {
493 float a = (PhysicalSize() * 1.45f - Distance);
494 float Velocity = 0.5f;
495
496 // make sure that we don't add excess force by checking the
497 // direction against the current velocity. if not zero.
498 if(length(a: m_Vel) > 0.0001f)
499 Velocity = 1 - (dot(a: normalize(v: m_Vel), b: Dir) + 1) / 2; // Wdouble-promotion don't fix this as this might change game physics
500
501 m_Vel += Dir * a * (Velocity * 0.75f);
502 m_Vel *= 0.85f;
503 }
504
505 // handle hook influence
506 if(!m_HookHitDisabled && m_HookedPlayer == i && m_Tuning.m_PlayerHooking)
507 {
508 if(Distance > PhysicalSize() * 1.50f) // TODO: fix tweakable variable
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() && D >= 0.0f)
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
659 // Endless
660 m_EndlessHook = pObjDDNet->m_Flags & CHARACTERFLAG_ENDLESS_HOOK;
661 m_EndlessJump = pObjDDNet->m_Flags & CHARACTERFLAG_ENDLESS_JUMP;
662
663 // Freeze
664 m_FreezeEnd = pObjDDNet->m_FreezeEnd;
665 m_DeepFrozen = pObjDDNet->m_FreezeEnd == -1;
666 m_LiveFrozen = (pObjDDNet->m_Flags & CHARACTERFLAG_MOVEMENTS_DISABLED) != 0;
667
668 // Telegun
669 m_HasTelegunGrenade = pObjDDNet->m_Flags & CHARACTERFLAG_TELEGUN_GRENADE;
670 m_HasTelegunGun = pObjDDNet->m_Flags & CHARACTERFLAG_TELEGUN_GUN;
671 m_HasTelegunLaser = pObjDDNet->m_Flags & CHARACTERFLAG_TELEGUN_LASER;
672
673 // Weapons
674 m_aWeapons[WEAPON_HAMMER].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_HAMMER) != 0;
675 m_aWeapons[WEAPON_GUN].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_GUN) != 0;
676 m_aWeapons[WEAPON_SHOTGUN].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_SHOTGUN) != 0;
677 m_aWeapons[WEAPON_GRENADE].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_GRENADE) != 0;
678 m_aWeapons[WEAPON_LASER].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_LASER) != 0;
679 m_aWeapons[WEAPON_NINJA].m_Got = (pObjDDNet->m_Flags & CHARACTERFLAG_WEAPON_NINJA) != 0;
680
681 // Available jumps
682 m_Jumps = pObjDDNet->m_Jumps;
683
684 // Display Information
685 // We only accept the display information when it is received, which means it is not -1 in each case.
686 if(pObjDDNet->m_JumpedTotal != -1)
687 {
688 m_JumpedTotal = pObjDDNet->m_JumpedTotal;
689 }
690 if(pObjDDNet->m_NinjaActivationTick != -1)
691 {
692 m_Ninja.m_ActivationTick = pObjDDNet->m_NinjaActivationTick;
693 }
694 if(pObjDDNet->m_FreezeStart != -1)
695 {
696 m_FreezeStart = pObjDDNet->m_FreezeStart;
697 m_IsInFreeze = pObjDDNet->m_Flags & CHARACTERFLAG_IN_FREEZE;
698 }
699}
700
701void CCharacterCore::Quantize()
702{
703 CNetObj_CharacterCore Core;
704 Write(pObjCore: &Core);
705 Read(pObjCore: &Core);
706}
707
708void CCharacterCore::SetHookedPlayer(int HookedPlayer)
709{
710 if(HookedPlayer != m_HookedPlayer)
711 {
712 if(m_HookedPlayer != -1 && m_Id != -1 && m_pWorld)
713 {
714 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[m_HookedPlayer];
715 if(pCharCore)
716 {
717 pCharCore->m_AttachedPlayers.erase(x: m_Id);
718 }
719 }
720 if(HookedPlayer != -1 && m_Id != -1 && m_pWorld)
721 {
722 CCharacterCore *pCharCore = m_pWorld->m_apCharacters[HookedPlayer];
723 if(pCharCore)
724 {
725 pCharCore->m_AttachedPlayers.insert(x: m_Id);
726 }
727 }
728 m_HookedPlayer = HookedPlayer;
729 }
730}
731
732// DDRace
733
734void CCharacterCore::SetTeamsCore(CTeamsCore *pTeams)
735{
736 m_pTeams = pTeams;
737}
738
739bool CCharacterCore::IsSwitchActiveCb(int Number, void *pUser)
740{
741 CCharacterCore *pThis = (CCharacterCore *)pUser;
742 if(pThis->m_pWorld && !pThis->m_pWorld->m_vSwitchers.empty())
743 if(pThis->m_Id != -1 && pThis->m_pTeams->Team(ClientId: pThis->m_Id) != (pThis->m_pTeams->m_IsDDRace16 ? VANILLA_TEAM_SUPER : TEAM_SUPER))
744 return pThis->m_pWorld->m_vSwitchers[Number].m_aStatus[pThis->m_pTeams->Team(ClientId: pThis->m_Id)];
745 return false;
746}
747
748void CWorldCore::InitSwitchers(int HighestSwitchNumber)
749{
750 if(HighestSwitchNumber > 0)
751 m_vSwitchers.resize(new_size: HighestSwitchNumber + 1);
752 else
753 m_vSwitchers.clear();
754
755 for(auto &Switcher : m_vSwitchers)
756 {
757 Switcher.m_Initial = true;
758 for(int j = 0; j < MAX_CLIENTS; j++)
759 {
760 Switcher.m_aStatus[j] = true;
761 Switcher.m_aEndTick[j] = 0;
762 Switcher.m_aType[j] = 0;
763 Switcher.m_aLastUpdateTick[j] = 0;
764 }
765 }
766}
767