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