ddnet/src/game/client/prediction/entities/character.cpp

1395 lines
38 KiB
C++
Raw Normal View History

/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <engine/shared/config.h>
2022-06-16 16:06:35 +00:00
#include <game/collision.h>
2022-05-29 16:33:38 +00:00
#include <game/generated/client_data.h>
#include <game/mapitems.h>
#include "character.h"
#include "laser.h"
#include "projectile.h"
// Character, "physical" player's part
void CCharacter::SetWeapon(int W)
{
if(W == m_Core.m_ActiveWeapon)
return;
m_LastWeapon = m_Core.m_ActiveWeapon;
m_QueuedWeapon = -1;
2020-07-15 00:47:51 +00:00
SetActiveWeapon(W);
if(m_Core.m_ActiveWeapon < 0 || m_Core.m_ActiveWeapon >= NUM_WEAPONS)
2020-07-15 00:47:51 +00:00
SetActiveWeapon(0);
}
void CCharacter::SetSolo(bool Solo)
{
m_Core.m_Solo = Solo;
TeamsCore()->SetSolo(GetCID(), Solo);
}
void CCharacter::SetSuper(bool Super)
{
m_Core.m_Super = Super;
if(m_Core.m_Super)
TeamsCore()->Team(GetCID(), TeamsCore()->m_IsDDRace16 ? VANILLA_TEAM_SUPER : TEAM_SUPER);
}
bool CCharacter::IsGrounded()
{
if(Collision()->CheckPoint(m_Pos.x + GetProximityRadius() / 2, m_Pos.y + GetProximityRadius() / 2 + 5))
return true;
if(Collision()->CheckPoint(m_Pos.x - GetProximityRadius() / 2, m_Pos.y + GetProximityRadius() / 2 + 5))
return true;
int MoveRestrictionsBelow = Collision()->GetMoveRestrictions(m_Pos + vec2(0, GetProximityRadius() / 2 + 4), 0.0f);
2022-01-22 16:34:23 +00:00
return (MoveRestrictionsBelow & CANTMOVE_DOWN) != 0;
}
void CCharacter::HandleJetpack()
{
if(m_NumInputs < 2)
return;
vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
bool FullAuto = false;
if(m_Core.m_ActiveWeapon == WEAPON_GRENADE || m_Core.m_ActiveWeapon == WEAPON_SHOTGUN || m_Core.m_ActiveWeapon == WEAPON_LASER)
FullAuto = true;
if(m_Core.m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN)
FullAuto = true;
// check if we gonna fire
bool WillFire = false;
if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses)
WillFire = true;
if(FullAuto && (m_LatestInput.m_Fire & 1) && m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
WillFire = true;
if(!WillFire)
return;
// check for ammo
if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime)
{
return;
}
switch(m_Core.m_ActiveWeapon)
{
case WEAPON_GUN:
{
if(m_Core.m_Jetpack)
{
float Strength = GetTuning(m_TuneZone)->m_JetpackStrength;
if(!m_TuneZone)
Strength = m_LastJetpackStrength;
TakeDamage(Direction * -1.0f * (Strength / 100.0f / 6.11f), 0, GetCID(), m_Core.m_ActiveWeapon);
}
}
}
}
void CCharacter::RemoveNinja()
{
2022-03-24 00:05:41 +00:00
m_Core.m_Ninja.m_CurrentMoveTime = 0;
m_Core.m_aWeapons[WEAPON_NINJA].m_Got = false;
m_Core.m_ActiveWeapon = m_LastWeapon;
SetWeapon(m_Core.m_ActiveWeapon);
}
void CCharacter::HandleNinja()
{
if(m_Core.m_ActiveWeapon != WEAPON_NINJA)
return;
2022-03-24 00:05:41 +00:00
if((GameWorld()->GameTick() - m_Core.m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * GameWorld()->GameTickSpeed() / 1000))
{
// time's up, return
RemoveNinja();
return;
}
// force ninja Weapon
SetWeapon(WEAPON_NINJA);
2022-03-24 00:05:41 +00:00
m_Core.m_Ninja.m_CurrentMoveTime--;
2022-03-24 00:05:41 +00:00
if(m_Core.m_Ninja.m_CurrentMoveTime == 0)
{
// reset velocity
2022-03-24 00:05:41 +00:00
m_Core.m_Vel = m_Core.m_Ninja.m_ActivationDir * m_Core.m_Ninja.m_OldVelAmount;
}
2022-03-24 00:05:41 +00:00
if(m_Core.m_Ninja.m_CurrentMoveTime > 0)
{
// Set velocity
2022-03-24 00:05:41 +00:00
m_Core.m_Vel = m_Core.m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity;
vec2 OldPos = m_Pos;
Collision()->MoveBox(&m_Core.m_Pos, &m_Core.m_Vel, vec2(m_ProximityRadius, m_ProximityRadius), 0.f);
// reset velocity so the client doesn't predict stuff
m_Core.m_Vel = vec2(0.f, 0.f);
// check if we Hit anything along the way
{
CEntity *apEnts[MAX_CLIENTS];
vec2 Dir = m_Pos - OldPos;
float Radius = m_ProximityRadius * 2.0f;
vec2 Center = OldPos + Dir * 0.5f;
int Num = GameWorld()->FindEntities(Center, Radius, apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
// check that we're not in solo part
if(TeamsCore()->GetSolo(GetCID()))
return;
for(int i = 0; i < Num; ++i)
{
auto *pChr = static_cast<CCharacter *>(apEnts[i]);
if(pChr == this)
continue;
// Don't hit players in other teams
if(Team() != pChr->Team())
continue;
// Don't hit players in solo parts
if(TeamsCore()->GetSolo(pChr->GetCID()))
return;
// make sure we haven't Hit this object before
bool bAlreadyHit = false;
for(int j = 0; j < m_NumObjectsHit; j++)
{
if(m_aHitObjects[j] == pChr->GetCID())
bAlreadyHit = true;
}
if(bAlreadyHit)
continue;
// check so we are sufficiently close
if(distance(pChr->m_Pos, m_Pos) > (m_ProximityRadius * 2.0f))
continue;
2022-06-16 22:12:25 +00:00
// Hit a player, give them damage and stuffs...
// set his velocity to fast upward (for now)
if(m_NumObjectsHit < 10)
m_aHitObjects[m_NumObjectsHit++] = pChr->GetCID();
CCharacter *pChar = GameWorld()->GetCharacterByID(pChr->GetCID());
if(pChar)
pChar->TakeDamage(vec2(0, -10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, GetCID(), WEAPON_NINJA);
}
}
return;
}
}
void CCharacter::DoWeaponSwitch()
{
// make sure we can switch
if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_Core.m_aWeapons[WEAPON_NINJA].m_Got || !m_Core.m_aWeapons[m_QueuedWeapon].m_Got)
return;
// switch Weapon
SetWeapon(m_QueuedWeapon);
}
void CCharacter::HandleWeaponSwitch()
{
if(m_NumInputs < 2)
return;
int WantedWeapon = m_Core.m_ActiveWeapon;
if(m_QueuedWeapon != -1)
WantedWeapon = m_QueuedWeapon;
bool Anything = false;
for(int i = 0; i < NUM_WEAPONS - 1; ++i)
if(m_Core.m_aWeapons[i].m_Got)
Anything = true;
if(!Anything)
return;
// select Weapon
int Next = CountInput(m_LatestPrevInput.m_NextWeapon, m_LatestInput.m_NextWeapon).m_Presses;
int Prev = CountInput(m_LatestPrevInput.m_PrevWeapon, m_LatestInput.m_PrevWeapon).m_Presses;
if(Next < 128) // make sure we only try sane stuff
{
while(Next) // Next Weapon selection
{
WantedWeapon = (WantedWeapon + 1) % NUM_WEAPONS;
if(m_Core.m_aWeapons[WantedWeapon].m_Got)
Next--;
}
}
if(Prev < 128) // make sure we only try sane stuff
{
while(Prev) // Prev Weapon selection
{
WantedWeapon = (WantedWeapon - 1) < 0 ? NUM_WEAPONS - 1 : WantedWeapon - 1;
if(m_Core.m_aWeapons[WantedWeapon].m_Got)
Prev--;
}
}
// Direct Weapon selection
if(m_LatestInput.m_WantedWeapon)
WantedWeapon = m_Input.m_WantedWeapon - 1;
// check for insane values
if(WantedWeapon >= 0 && WantedWeapon < NUM_WEAPONS && WantedWeapon != m_Core.m_ActiveWeapon && m_Core.m_aWeapons[WantedWeapon].m_Got)
m_QueuedWeapon = WantedWeapon;
DoWeaponSwitch();
}
void CCharacter::FireWeapon()
{
if(m_NumInputs < 2)
return;
if(!GameWorld()->m_WorldConfig.m_PredictWeapons)
return;
if(m_ReloadTimer != 0)
return;
DoWeaponSwitch();
vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
bool FullAuto = false;
if(m_Core.m_ActiveWeapon == WEAPON_GRENADE || m_Core.m_ActiveWeapon == WEAPON_SHOTGUN || m_Core.m_ActiveWeapon == WEAPON_LASER)
FullAuto = true;
if(m_Core.m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN)
FullAuto = true;
if(m_FrozenLastTick)
FullAuto = true;
// don't fire hammer when player is deep and sv_deepfly is disabled
if(!g_Config.m_SvDeepfly && m_Core.m_ActiveWeapon == WEAPON_HAMMER && m_Core.m_DeepFrozen)
return;
// check if we gonna fire
bool WillFire = false;
if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses)
WillFire = true;
if(FullAuto && (m_LatestInput.m_Fire & 1) && m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
WillFire = true;
if(!WillFire)
return;
// check for ammo
if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime)
{
return;
}
vec2 ProjStartPos = m_Pos + Direction * m_ProximityRadius * 0.75f;
switch(m_Core.m_ActiveWeapon)
{
case WEAPON_HAMMER:
{
// reset objects Hit
m_NumObjectsHit = 0;
if(m_Core.m_HammerHitDisabled)
break;
CEntity *apEnts[MAX_CLIENTS];
int Hits = 0;
int Num = GameWorld()->FindEntities(ProjStartPos, m_ProximityRadius * 0.5f, apEnts,
MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
for(int i = 0; i < Num; ++i)
{
auto *pTarget = static_cast<CCharacter *>(apEnts[i]);
if((pTarget == this || !CanCollide(pTarget->GetCID())))
continue;
// set his velocity to fast upward (for now)
vec2 Dir;
if(length(pTarget->m_Pos - m_Pos) > 0.0f)
Dir = normalize(pTarget->m_Pos - m_Pos);
else
Dir = vec2(0.f, -1.f);
float Strength = GetTuning(m_TuneZone)->m_HammerStrength;
vec2 Temp = pTarget->m_Core.m_Vel + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f;
Temp = ClampVel(pTarget->m_MoveRestrictions, Temp);
Temp -= pTarget->m_Core.m_Vel;
vec2 Force = vec2(0.f, -1.0f) + Temp;
if(GameWorld()->m_WorldConfig.m_IsFNG)
{
if(m_GameTeam == pTarget->m_GameTeam && pTarget->m_LastSnapWeapon == WEAPON_NINJA) // melt hammer
{
Force.x *= 50 * 0.01f;
Force.y *= 50 * 0.01f;
}
else
{
Force.x *= 320 * 0.01f;
Force.y *= 120 * 0.01f;
}
}
else
Force *= Strength;
pTarget->TakeDamage(Force, g_pData->m_Weapons.m_Hammer.m_pBase->m_Damage,
GetCID(), m_Core.m_ActiveWeapon);
pTarget->UnFreeze();
Hits++;
}
// if we Hit anything, we have to wait for the reload
if(Hits)
{
float FireDelay = GetTuning(m_TuneZone)->m_HammerHitFireDelay;
m_ReloadTimer = FireDelay * GameWorld()->GameTickSpeed() / 1000;
}
}
break;
case WEAPON_GUN:
{
if(!m_Core.m_Jetpack)
{
int Lifetime = (int)(GameWorld()->GameTickSpeed() * GetTuning(m_TuneZone)->m_GunLifetime);
new CProjectile(
GameWorld(),
WEAPON_GUN, //Type
GetCID(), //Owner
ProjStartPos, //Pos
Direction, //Dir
Lifetime, //Span
2022-02-14 23:12:52 +00:00
false, //Freeze
false, //Explosive
0, //Force
-1 //SoundImpact
);
}
}
break;
case WEAPON_SHOTGUN:
{
if(GameWorld()->m_WorldConfig.m_IsVanilla)
{
int ShotSpread = 2;
for(int i = -ShotSpread; i <= ShotSpread; ++i)
{
float aSpreading[] = {-0.185f, -0.070f, 0, 0.070f, 0.185f};
float a = angle(Direction);
a += aSpreading[i + 2];
float v = 1 - (absolute(i) / (float)ShotSpread);
float Speed = mix((float)Tuning()->m_ShotgunSpeeddiff, 1.0f, v);
new CProjectile(
GameWorld(),
WEAPON_SHOTGUN, //Type
GetCID(), //Owner
ProjStartPos, //Pos
vec2(cosf(a), sinf(a)) * Speed, //Dir
(int)(GameWorld()->GameTickSpeed() * Tuning()->m_ShotgunLifetime), //Span
2022-02-14 23:12:52 +00:00
false, //Freeze
false, //Explosive
0, //Force
-1 //SoundImpact
);
}
}
else if(GameWorld()->m_WorldConfig.m_IsDDRace)
{
float LaserReach = GetTuning(m_TuneZone)->m_LaserReach;
2019-09-08 22:53:07 +00:00
new CLaser(GameWorld(), m_Pos, Direction, LaserReach, GetCID(), WEAPON_SHOTGUN);
}
}
break;
case WEAPON_GRENADE:
{
int Lifetime = (int)(GameWorld()->GameTickSpeed() * GetTuning(m_TuneZone)->m_GrenadeLifetime);
new CProjectile(
GameWorld(),
WEAPON_GRENADE, //Type
GetCID(), //Owner
ProjStartPos, //Pos
Direction, //Dir
Lifetime, //Span
2022-02-14 23:12:52 +00:00
false, //Freeze
true, //Explosive
0, //Force
SOUND_GRENADE_EXPLODE //SoundImpact
); //SoundImpact
}
break;
case WEAPON_LASER:
{
float LaserReach = GetTuning(m_TuneZone)->m_LaserReach;
new CLaser(GameWorld(), m_Pos, Direction, LaserReach, GetCID(), WEAPON_LASER);
}
break;
case WEAPON_NINJA:
{
// reset Hit objects
m_NumObjectsHit = 0;
2022-03-24 00:05:41 +00:00
m_Core.m_Ninja.m_ActivationDir = Direction;
m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * GameWorld()->GameTickSpeed() / 1000;
m_Core.m_Ninja.m_OldVelAmount = length(m_Core.m_Vel);
}
break;
}
m_AttackTick = GameWorld()->GameTick();
if(!m_ReloadTimer)
{
float FireDelay;
GetTuning(m_TuneZone)->Get(38 + m_Core.m_ActiveWeapon, &FireDelay);
2019-09-08 22:53:07 +00:00
m_ReloadTimer = FireDelay * GameWorld()->GameTickSpeed() / 1000;
}
}
void CCharacter::HandleWeapons()
{
//ninja
HandleNinja();
HandleJetpack();
// check reload timer
if(m_ReloadTimer)
{
m_ReloadTimer--;
return;
}
// fire Weapon, if wanted
FireWeapon();
}
void CCharacter::GiveNinja()
{
2022-03-24 00:05:41 +00:00
m_Core.m_Ninja.m_ActivationTick = GameWorld()->GameTick();
m_Core.m_aWeapons[WEAPON_NINJA].m_Got = true;
if(!m_FreezeTime)
m_Core.m_aWeapons[WEAPON_NINJA].m_Ammo = -1;
if(m_Core.m_ActiveWeapon != WEAPON_NINJA)
m_LastWeapon = m_Core.m_ActiveWeapon;
m_Core.m_ActiveWeapon = WEAPON_NINJA;
}
void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
{
2020-07-15 01:15:35 +00:00
// skip the input if chat is active
if(!GameWorld()->m_WorldConfig.m_BugDDRaceInput && pNewInput->m_PlayerFlags & PLAYERFLAG_CHATTING)
{
2022-10-25 16:51:56 +00:00
// save the reset input
mem_copy(&m_SavedInput, &m_Input, sizeof(m_SavedInput));
2020-07-15 01:15:35 +00:00
return;
}
2020-07-15 01:15:35 +00:00
// copy new input
mem_copy(&m_Input, pNewInput, sizeof(m_Input));
//m_NumInputs++;
// it is not allowed to aim in the center
if(m_Input.m_TargetX == 0 && m_Input.m_TargetY == 0)
m_Input.m_TargetY = -1;
mem_copy(&m_SavedInput, &m_Input, sizeof(m_SavedInput));
}
void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
{
2020-07-15 01:15:35 +00:00
// skip the input if chat is active
if(!GameWorld()->m_WorldConfig.m_BugDDRaceInput && pNewInput->m_PlayerFlags & PLAYERFLAG_CHATTING)
2020-07-15 01:15:35 +00:00
{
// reset input
ResetInput();
// mods that do not allow inputs to be held while chatting also do not allow to hold hook
m_Input.m_Hook = 0;
2020-07-15 01:15:35 +00:00
return;
}
m_NumInputs++;
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
mem_copy(&m_LatestInput, pNewInput, sizeof(m_LatestInput));
// it is not allowed to aim in the center
if(m_LatestInput.m_TargetX == 0 && m_LatestInput.m_TargetY == 0)
m_LatestInput.m_TargetY = -1;
2022-04-28 13:34:25 +00:00
if(m_NumInputs > 1 && Team() != TEAM_SPECTATORS)
{
HandleWeaponSwitch();
FireWeapon();
}
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
}
2020-07-15 01:15:35 +00:00
void CCharacter::ResetInput()
{
m_Input.m_Direction = 0;
// m_Input.m_Hook = 0;
// simulate releasing the fire button
if((m_Input.m_Fire & 1) != 0)
m_Input.m_Fire++;
m_Input.m_Fire &= INPUT_STATE_MASK;
m_Input.m_Jump = 0;
m_LatestPrevInput = m_LatestInput = m_Input;
2020-07-15 01:15:35 +00:00
}
void CCharacter::PreTick()
{
DDRaceTick();
m_Core.m_Input = m_Input;
m_Core.Tick(true, !m_pGameWorld->m_WorldConfig.m_NoWeakHookAndBounce);
}
void CCharacter::Tick()
{
if(m_pGameWorld->m_WorldConfig.m_NoWeakHookAndBounce)
{
m_Core.TickDeferred();
}
else
{
PreTick();
}
// handle Weapons
HandleWeapons();
DDRacePostCoreTick();
// Previnput
m_PrevInput = m_Input;
m_PrevPrevPos = m_PrevPos;
m_PrevPos = m_Core.m_Pos;
}
2022-07-19 16:22:44 +00:00
void CCharacter::TickDeferred()
{
m_Core.Move();
m_Core.Quantize();
m_Pos = m_Core.m_Pos;
}
bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
{
vec2 Temp = m_Core.m_Vel + Force;
m_Core.m_Vel = ClampVel(m_MoveRestrictions, Temp);
return true;
}
// DDRace
bool CCharacter::CanCollide(int ClientID)
{
return TeamsCore()->CanCollide(GetCID(), ClientID);
}
bool CCharacter::SameTeam(int ClientID)
{
return TeamsCore()->SameTeam(GetCID(), ClientID);
}
int CCharacter::Team()
{
return TeamsCore()->Team(GetCID());
}
void CCharacter::HandleSkippableTiles(int Index)
{
if(Index < 0)
return;
// handle speedup tiles
if(Collision()->IsSpeedup(Index))
{
vec2 Direction, TempVel = m_Core.m_Vel;
int Force, MaxSpeed = 0;
float TeeAngle, SpeederAngle, DiffAngle, SpeedLeft, TeeSpeed;
Collision()->GetSpeedup(Index, &Direction, &Force, &MaxSpeed);
if(Force == 255 && MaxSpeed)
{
m_Core.m_Vel = Direction * (MaxSpeed / 5);
}
else
{
if(MaxSpeed > 0 && MaxSpeed < 5)
MaxSpeed = 5;
if(MaxSpeed > 0)
{
if(Direction.x > 0.0000001f)
SpeederAngle = -atan(Direction.y / Direction.x);
else if(Direction.x < 0.0000001f)
SpeederAngle = atan(Direction.y / Direction.x) + 2.0f * asin(1.0f);
else if(Direction.y > 0.0000001f)
SpeederAngle = asin(1.0f);
else
SpeederAngle = asin(-1.0f);
if(SpeederAngle < 0)
SpeederAngle = 4.0f * asin(1.0f) + SpeederAngle;
if(TempVel.x > 0.0000001f)
TeeAngle = -atan(TempVel.y / TempVel.x);
else if(TempVel.x < 0.0000001f)
TeeAngle = atan(TempVel.y / TempVel.x) + 2.0f * asin(1.0f);
else if(TempVel.y > 0.0000001f)
TeeAngle = asin(1.0f);
else
TeeAngle = asin(-1.0f);
if(TeeAngle < 0)
TeeAngle = 4.0f * asin(1.0f) + TeeAngle;
TeeSpeed = sqrt(pow(TempVel.x, 2) + pow(TempVel.y, 2));
DiffAngle = SpeederAngle - TeeAngle;
SpeedLeft = MaxSpeed / 5.0f - cos(DiffAngle) * TeeSpeed;
if(abs((int)SpeedLeft) > Force && SpeedLeft > 0.0000001f)
TempVel += Direction * Force;
else if(abs((int)SpeedLeft) > Force)
TempVel += Direction * -Force;
else
TempVel += Direction * SpeedLeft;
}
else
TempVel += Direction * Force;
m_Core.m_Vel = ClampVel(m_MoveRestrictions, TempVel);
}
}
}
bool CCharacter::IsSwitchActiveCb(int Number, void *pUser)
{
2021-08-18 23:11:38 +00:00
CCharacter *pThis = (CCharacter *)pUser;
2022-02-13 19:57:27 +00:00
auto &aSwitchers = pThis->Switchers();
return !aSwitchers.empty() && pThis->Team() != TEAM_SUPER && aSwitchers[Number].m_aStatus[pThis->Team()];
}
void CCharacter::HandleTiles(int Index)
{
int MapIndex = Index;
m_TileIndex = Collision()->GetTileIndex(MapIndex);
m_TileFIndex = Collision()->GetFTileIndex(MapIndex);
m_MoveRestrictions = Collision()->GetMoveRestrictions(IsSwitchActiveCb, this, m_Pos);
// stopper
if(m_Core.m_Vel.y > 0 && (m_MoveRestrictions & CANTMOVE_DOWN))
{
m_Core.m_Jumped = 0;
m_Core.m_JumpedTotal = 0;
}
m_Core.m_Vel = ClampVel(m_MoveRestrictions, m_Core.m_Vel);
if(!GameWorld()->m_WorldConfig.m_PredictTiles)
return;
if(Index < 0)
{
m_LastRefillJumps = false;
return;
}
2021-08-18 23:11:38 +00:00
// handle switch tiles
if(Collision()->GetSwitchType(MapIndex) == TILE_SWITCHOPEN && Team() != TEAM_SUPER && Collision()->GetSwitchNumber(MapIndex) > 0)
{
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()] = true;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aEndTick[Team()] = 0;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aType[Team()] = TILE_SWITCHOPEN;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aLastUpdateTick[Team()] = GameWorld()->GameTick();
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_SWITCHTIMEDOPEN && Team() != TEAM_SUPER && Collision()->GetSwitchNumber(MapIndex) > 0)
{
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()] = true;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aEndTick[Team()] = GameWorld()->GameTick() + 1 + Collision()->GetSwitchDelay(MapIndex) * GameWorld()->GameTickSpeed();
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aType[Team()] = TILE_SWITCHTIMEDOPEN;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aLastUpdateTick[Team()] = GameWorld()->GameTick();
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_SWITCHTIMEDCLOSE && Team() != TEAM_SUPER && Collision()->GetSwitchNumber(MapIndex) > 0)
{
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()] = false;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aEndTick[Team()] = GameWorld()->GameTick() + 1 + Collision()->GetSwitchDelay(MapIndex) * GameWorld()->GameTickSpeed();
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aType[Team()] = TILE_SWITCHTIMEDCLOSE;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aLastUpdateTick[Team()] = GameWorld()->GameTick();
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_SWITCHCLOSE && Team() != TEAM_SUPER && Collision()->GetSwitchNumber(MapIndex) > 0)
{
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()] = false;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aEndTick[Team()] = 0;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aType[Team()] = TILE_SWITCHCLOSE;
Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aLastUpdateTick[Team()] = GameWorld()->GameTick();
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER)
2021-08-18 23:11:38 +00:00
{
if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()])
{
2021-08-18 23:11:38 +00:00
Freeze(Collision()->GetSwitchDelay(MapIndex));
}
2021-08-18 23:11:38 +00:00
}
2021-12-19 11:05:51 +00:00
else if(Collision()->GetSwitchType(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER)
2021-08-18 23:11:38 +00:00
{
if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()])
m_Core.m_DeepFrozen = true;
2021-08-18 23:11:38 +00:00
}
2021-12-19 11:05:51 +00:00
else if(Collision()->GetSwitchType(MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER)
2021-08-18 23:11:38 +00:00
{
if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()])
m_Core.m_DeepFrozen = false;
2021-08-18 23:11:38 +00:00
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_HIT_ENABLE && m_Core.m_HammerHitDisabled && Collision()->GetSwitchDelay(MapIndex) == WEAPON_HAMMER)
2021-08-18 23:11:38 +00:00
{
m_Core.m_HammerHitDisabled = false;
2021-08-18 23:11:38 +00:00
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_HIT_DISABLE && !m_Core.m_HammerHitDisabled && Collision()->GetSwitchDelay(MapIndex) == WEAPON_HAMMER)
2021-08-18 23:11:38 +00:00
{
m_Core.m_HammerHitDisabled = true;
2021-08-18 23:11:38 +00:00
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_HIT_ENABLE && m_Core.m_ShotgunHitDisabled && Collision()->GetSwitchDelay(MapIndex) == WEAPON_SHOTGUN)
2021-08-18 23:11:38 +00:00
{
m_Core.m_ShotgunHitDisabled = false;
2021-08-18 23:11:38 +00:00
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_HIT_DISABLE && !m_Core.m_ShotgunHitDisabled && Collision()->GetSwitchDelay(MapIndex) == WEAPON_SHOTGUN)
2021-08-18 23:11:38 +00:00
{
m_Core.m_ShotgunHitDisabled = true;
2021-08-18 23:11:38 +00:00
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_HIT_ENABLE && m_Core.m_GrenadeHitDisabled && Collision()->GetSwitchDelay(MapIndex) == WEAPON_GRENADE)
2021-08-18 23:11:38 +00:00
{
m_Core.m_GrenadeHitDisabled = false;
2021-08-18 23:11:38 +00:00
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_HIT_DISABLE && !m_Core.m_GrenadeHitDisabled && Collision()->GetSwitchDelay(MapIndex) == WEAPON_GRENADE)
2021-08-18 23:11:38 +00:00
{
m_Core.m_GrenadeHitDisabled = true;
2021-08-18 23:11:38 +00:00
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_HIT_ENABLE && m_Core.m_LaserHitDisabled && Collision()->GetSwitchDelay(MapIndex) == WEAPON_LASER)
2021-08-18 23:11:38 +00:00
{
m_Core.m_LaserHitDisabled = false;
2021-08-18 23:11:38 +00:00
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_HIT_DISABLE && !m_Core.m_LaserHitDisabled && Collision()->GetSwitchDelay(MapIndex) == WEAPON_LASER)
2021-08-18 23:11:38 +00:00
{
m_Core.m_LaserHitDisabled = true;
2021-08-18 23:11:38 +00:00
}
2021-12-19 11:05:51 +00:00
else if(Collision()->GetSwitchType(MapIndex) == TILE_JUMP)
2021-08-18 23:11:38 +00:00
{
2022-01-31 14:43:03 +00:00
int NewJumps = Collision()->GetSwitchDelay(MapIndex);
if(NewJumps == 255)
{
NewJumps = -1;
}
2022-01-31 14:43:03 +00:00
if(NewJumps != m_Core.m_Jumps)
m_Core.m_Jumps = NewJumps;
2021-08-18 23:11:38 +00:00
}
2022-01-08 11:30:51 +00:00
else if(Collision()->GetSwitchType(MapIndex) == TILE_LFREEZE && Team() != TEAM_SUPER)
{
if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()])
{
m_Core.m_LiveFrozen = true;
}
}
else if(Collision()->GetSwitchType(MapIndex) == TILE_LUNFREEZE && Team() != TEAM_SUPER)
{
if(Collision()->GetSwitchNumber(MapIndex) == 0 || Switchers()[Collision()->GetSwitchNumber(MapIndex)].m_aStatus[Team()])
{
m_Core.m_LiveFrozen = false;
}
}
2021-08-18 23:11:38 +00:00
// freeze
if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Core.m_Super && !m_Core.m_DeepFrozen)
{
Freeze();
}
else if(((m_TileIndex == TILE_UNFREEZE) || (m_TileFIndex == TILE_UNFREEZE)) && !m_Core.m_DeepFrozen)
{
UnFreeze();
}
// deep freeze
if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Core.m_Super && !m_Core.m_DeepFrozen)
{
m_Core.m_DeepFrozen = true;
}
else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Core.m_Super && m_Core.m_DeepFrozen)
{
m_Core.m_DeepFrozen = false;
}
// live freeze
if(((m_TileIndex == TILE_LFREEZE) || (m_TileFIndex == TILE_LFREEZE)) && !m_Core.m_Super)
{
m_Core.m_LiveFrozen = true;
}
else if(((m_TileIndex == TILE_LUNFREEZE) || (m_TileFIndex == TILE_LUNFREEZE)) && !m_Core.m_Super)
{
m_Core.m_LiveFrozen = false;
}
// endless hook
if(((m_TileIndex == TILE_EHOOK_ENABLE) || (m_TileFIndex == TILE_EHOOK_ENABLE)) && !m_Core.m_EndlessHook)
{
m_Core.m_EndlessHook = true;
}
else if(((m_TileIndex == TILE_EHOOK_DISABLE) || (m_TileFIndex == TILE_EHOOK_DISABLE)) && m_Core.m_EndlessHook)
{
m_Core.m_EndlessHook = false;
}
// collide with others
if(((m_TileIndex == TILE_NPC_DISABLE) || (m_TileFIndex == TILE_NPC_DISABLE)) && !m_Core.m_CollisionDisabled)
{
m_Core.m_CollisionDisabled = true;
}
else if(((m_TileIndex == TILE_NPC_ENABLE) || (m_TileFIndex == TILE_NPC_ENABLE)) && m_Core.m_CollisionDisabled)
{
m_Core.m_CollisionDisabled = false;
}
// hook others
if(((m_TileIndex == TILE_NPH_DISABLE) || (m_TileFIndex == TILE_NPH_DISABLE)) && !m_Core.m_HookHitDisabled)
{
m_Core.m_HookHitDisabled = true;
}
else if(((m_TileIndex == TILE_NPH_ENABLE) || (m_TileFIndex == TILE_NPH_ENABLE)) && m_Core.m_HookHitDisabled)
{
m_Core.m_HookHitDisabled = false;
}
// unlimited air jumps
if(((m_TileIndex == TILE_UNLIMITED_JUMPS_ENABLE) || (m_TileFIndex == TILE_UNLIMITED_JUMPS_ENABLE)) && !m_Core.m_EndlessJump)
{
m_Core.m_EndlessJump = true;
}
else if(((m_TileIndex == TILE_UNLIMITED_JUMPS_DISABLE) || (m_TileFIndex == TILE_UNLIMITED_JUMPS_DISABLE)) && m_Core.m_EndlessJump)
{
m_Core.m_EndlessJump = false;
}
// walljump
if((m_TileIndex == TILE_WALLJUMP) || (m_TileFIndex == TILE_WALLJUMP))
{
if(m_Core.m_Vel.y > 0 && m_Core.m_Colliding && m_Core.m_LeftWall)
{
m_Core.m_LeftWall = false;
2022-05-16 21:17:19 +00:00
m_Core.m_JumpedTotal = m_Core.m_Jumps >= 2 ? m_Core.m_Jumps - 2 : 0;
m_Core.m_Jumped = 1;
}
}
// jetpack gun
if(((m_TileIndex == TILE_JETPACK_ENABLE) || (m_TileFIndex == TILE_JETPACK_ENABLE)) && !m_Core.m_Jetpack)
{
m_Core.m_Jetpack = true;
}
else if(((m_TileIndex == TILE_JETPACK_DISABLE) || (m_TileFIndex == TILE_JETPACK_DISABLE)) && m_Core.m_Jetpack)
{
m_Core.m_Jetpack = false;
}
// solo part
2020-09-13 20:00:49 +00:00
if(((m_TileIndex == TILE_SOLO_ENABLE) || (m_TileFIndex == TILE_SOLO_ENABLE)) && !TeamsCore()->GetSolo(GetCID()))
{
SetSolo(true);
}
2020-09-13 20:00:49 +00:00
else if(((m_TileIndex == TILE_SOLO_DISABLE) || (m_TileFIndex == TILE_SOLO_DISABLE)) && TeamsCore()->GetSolo(GetCID()))
{
SetSolo(false);
}
// refill jumps
if(((m_TileIndex == TILE_REFILL_JUMPS) || (m_TileFIndex == TILE_REFILL_JUMPS)) && !m_LastRefillJumps)
{
m_Core.m_JumpedTotal = 0;
m_Core.m_Jumped = 0;
m_LastRefillJumps = true;
}
if((m_TileIndex != TILE_REFILL_JUMPS) && (m_TileFIndex != TILE_REFILL_JUMPS))
{
m_LastRefillJumps = false;
}
}
2019-09-08 22:53:07 +00:00
void CCharacter::HandleTuneLayer()
{
int CurrentIndex = Collision()->GetMapIndex(m_Pos);
2021-05-12 16:57:50 +00:00
SetTuneZone(GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(CurrentIndex) : 0);
2019-09-08 22:53:07 +00:00
2021-05-12 16:57:50 +00:00
if(m_IsLocal)
m_Core.m_pWorld->m_aTuning[g_Config.m_ClDummy] = *GetTuning(m_TuneZone); // throw tunings (from specific zone if in a tunezone) into gamecore if the character is local
2021-05-12 16:57:50 +00:00
m_Core.m_Tuning = *GetTuning(m_TuneZone);
2019-09-08 22:53:07 +00:00
}
void CCharacter::DDRaceTick()
{
mem_copy(&m_Input, &m_SavedInput, sizeof(m_Input));
if(m_Core.m_LiveFrozen && !m_CanMoveInFreeze && !m_Core.m_Super)
{
m_Input.m_Direction = 0;
m_Input.m_Jump = 0;
//Hook and weapons are possible in live freeze
}
if(m_FreezeTime > 0 || m_FreezeTime == -1)
{
if(m_FreezeTime > 0)
m_FreezeTime--;
else
2022-03-24 00:05:41 +00:00
m_Core.m_Ninja.m_ActivationTick = GameWorld()->GameTick();
if(!m_CanMoveInFreeze)
{
m_Input.m_Direction = 0;
m_Input.m_Jump = 0;
m_Input.m_Hook = 0;
}
if(m_FreezeTime == 1)
UnFreeze();
}
2019-09-08 22:53:07 +00:00
HandleTuneLayer();
2022-03-29 21:24:39 +00:00
// check if the tee is in any type of freeze
int Index = Collision()->GetPureMapIndex(m_Pos);
const int aTiles[] = {
Collision()->GetTileIndex(Index),
Collision()->GetFTileIndex(Index),
Collision()->GetSwitchType(Index)};
m_Core.m_IsInFreeze = false;
for(const int Tile : aTiles)
{
if(Tile == TILE_FREEZE || Tile == TILE_DFREEZE || Tile == TILE_LFREEZE)
{
m_Core.m_IsInFreeze = true;
break;
}
}
}
void CCharacter::DDRacePostCoreTick()
{
if(!GameWorld()->m_WorldConfig.m_PredictDDRace)
return;
if(m_Core.m_EndlessHook)
m_Core.m_HookTick = 0;
m_FrozenLastTick = false;
if(m_Core.m_DeepFrozen && !m_Core.m_Super)
Freeze();
// following jump rules can be overridden by tiles, like Refill Jumps, Stopper and Wall Jump
2022-05-16 21:17:19 +00:00
if(m_Core.m_Jumps == -1)
{
// The player has only one ground jump, so his feet are always dark
m_Core.m_Jumped |= 2;
}
else if(m_Core.m_Jumps == 0)
{
// The player has no jumps at all, so his feet are always dark
m_Core.m_Jumped |= 2;
2022-05-16 21:17:19 +00:00
}
else if(m_Core.m_Jumps == 1 && m_Core.m_Jumped > 0)
2022-05-16 21:17:19 +00:00
{
// If the player has only one jump, each jump is the last one
m_Core.m_Jumped |= 2;
}
else if(m_Core.m_JumpedTotal < m_Core.m_Jumps - 1 && m_Core.m_Jumped > 1)
2022-05-16 21:17:19 +00:00
{
// The player has not yet used up all his jumps, so his feet remain light
m_Core.m_Jumped = 1;
2022-05-16 21:17:19 +00:00
}
if((m_Core.m_Super || m_Core.m_EndlessJump) && m_Core.m_Jumped > 1)
2022-05-16 21:17:19 +00:00
{
// Super players and players with infinite jumps always have light feet
m_Core.m_Jumped = 1;
2022-05-16 21:17:19 +00:00
}
int CurrentIndex = Collision()->GetMapIndex(m_Pos);
HandleSkippableTiles(CurrentIndex);
// handle Anti-Skip tiles
std::list<int> Indices = Collision()->GetMapIndices(m_PrevPos, m_Pos);
if(!Indices.empty())
2020-10-26 14:14:07 +00:00
for(int Index : Indices)
HandleTiles(Index);
else
{
HandleTiles(CurrentIndex);
}
}
bool CCharacter::Freeze(int Seconds)
{
if(!GameWorld()->m_WorldConfig.m_PredictFreeze)
return false;
if((Seconds <= 0 || m_Core.m_Super || m_FreezeTime == -1 || m_FreezeTime > Seconds * GameWorld()->GameTickSpeed()) && Seconds != -1)
return false;
if(m_Core.m_FreezeStart < GameWorld()->GameTick() - GameWorld()->GameTickSpeed() || Seconds == -1)
{
m_FreezeTime = Seconds == -1 ? Seconds : Seconds * GameWorld()->GameTickSpeed();
m_Core.m_FreezeStart = GameWorld()->GameTick();
return true;
}
return false;
}
bool CCharacter::Freeze()
{
return Freeze(g_Config.m_SvFreezeDelay);
}
bool CCharacter::UnFreeze()
{
if(m_FreezeTime > 0)
{
if(!m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Got)
m_Core.m_ActiveWeapon = WEAPON_GUN;
m_FreezeTime = 0;
m_Core.m_FreezeStart = 0;
m_FrozenLastTick = true;
return true;
}
return false;
}
void CCharacter::GiveWeapon(int Weapon, bool Remove)
{
if(Weapon == WEAPON_NINJA)
{
if(Remove)
RemoveNinja();
else
GiveNinja();
return;
}
if(Remove)
{
if(GetActiveWeapon() == Weapon)
SetActiveWeapon(WEAPON_GUN);
}
else
{
m_Core.m_aWeapons[Weapon].m_Ammo = -1;
}
m_Core.m_aWeapons[Weapon].m_Got = !Remove;
}
void CCharacter::GiveAllWeapons()
{
for(int i = WEAPON_GUN; i < NUM_WEAPONS - 1; i++)
{
GiveWeapon(i);
}
}
CTeamsCore *CCharacter::TeamsCore()
{
return m_Core.m_pTeams;
}
CCharacter::CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_CHARACTER, vec2(0, 0), CCharacterCore::PhysicalSize())
{
m_ID = ID;
2021-05-12 16:57:50 +00:00
m_IsLocal = false;
m_LastWeapon = WEAPON_HAMMER;
m_QueuedWeapon = -1;
m_LastRefillJumps = false;
m_PrevPrevPos = m_PrevPos = m_Pos = vec2(pChar->m_X, pChar->m_Y);
m_Core.Reset();
m_Core.Init(&GameWorld()->m_Core, GameWorld()->Collision(), GameWorld()->Teams());
m_Core.m_Id = ID;
2022-03-24 00:05:41 +00:00
mem_zero(&m_Core.m_Ninja, sizeof(m_Core.m_Ninja));
mem_zero(&m_SavedInput, sizeof(m_SavedInput));
m_LatestInput = m_LatestPrevInput = m_PrevInput = m_Input = m_SavedInput;
2022-02-14 23:12:52 +00:00
m_Core.m_LeftWall = true;
m_ReloadTimer = 0;
m_NumObjectsHit = 0;
m_LastRefillJumps = false;
2019-07-08 21:08:42 +00:00
m_LastJetpackStrength = 400.0f;
m_CanMoveInFreeze = false;
m_TeleCheckpoint = 0;
m_StrongWeakID = 0;
2022-05-22 08:39:18 +00:00
// never initialize both to zero
2020-10-17 16:17:13 +00:00
m_Input.m_TargetX = 0;
m_Input.m_TargetY = -1;
m_LatestPrevInput = m_LatestInput = m_PrevInput = m_SavedInput = m_Input;
2019-05-04 01:48:17 +00:00
ResetPrediction();
Read(pChar, pExtended, false);
}
2019-05-04 01:48:17 +00:00
void CCharacter::ResetPrediction()
{
SetSolo(false);
SetSuper(false);
m_Core.m_EndlessHook = false;
m_Core.m_HammerHitDisabled = false;
m_Core.m_ShotgunHitDisabled = false;
m_Core.m_GrenadeHitDisabled = false;
m_Core.m_LaserHitDisabled = false;
m_Core.m_EndlessJump = false;
m_Core.m_Jetpack = false;
2019-05-12 23:45:49 +00:00
m_NinjaJetpack = false;
2019-05-04 01:48:17 +00:00
m_Core.m_Jumps = 2;
m_Core.m_HookHitDisabled = false;
m_Core.m_CollisionDisabled = false;
2019-05-04 01:48:17 +00:00
m_NumInputs = 0;
m_FreezeTime = 0;
m_Core.m_FreezeStart = 0;
m_Core.m_IsInFreeze = false;
m_Core.m_DeepFrozen = false;
m_Core.m_LiveFrozen = false;
m_FrozenLastTick = false;
2019-05-04 01:48:17 +00:00
for(int w = 0; w < NUM_WEAPONS; w++)
{
2019-05-04 01:48:17 +00:00
SetWeaponGot(w, false);
SetWeaponAmmo(w, -1);
}
2019-05-05 12:28:39 +00:00
if(m_Core.m_HookedPlayer >= 0)
{
m_Core.SetHookedPlayer(-1);
2019-05-05 12:28:39 +00:00
m_Core.m_HookState = HOOK_IDLE;
}
2020-07-15 00:47:51 +00:00
m_LastWeaponSwitchTick = 0;
m_LastTuneZoneTick = 0;
2019-05-04 01:48:17 +00:00
}
void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal)
{
m_Core.Read((const CNetObj_CharacterCore *)pChar);
2021-05-12 16:57:50 +00:00
m_IsLocal = IsLocal;
if(pExtended)
{
SetSolo(pExtended->m_Flags & CHARACTERFLAG_SOLO);
SetSuper(pExtended->m_Flags & CHARACTERFLAG_SUPER);
m_TeleCheckpoint = pExtended->m_TeleCheckpoint;
m_StrongWeakID = pExtended->m_StrongWeakID;
const bool Ninja = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_NINJA) != 0;
if(Ninja && m_Core.m_ActiveWeapon != WEAPON_NINJA)
GiveNinja();
else if(!Ninja && m_Core.m_ActiveWeapon == WEAPON_NINJA)
RemoveNinja();
2020-04-19 10:50:06 +00:00
if(GameWorld()->m_WorldConfig.m_PredictFreeze && pExtended->m_FreezeEnd != 0)
{
if(pExtended->m_FreezeEnd > 0)
{
if(m_FreezeTime == 0)
Freeze();
m_FreezeTime = pExtended->m_FreezeEnd - GameWorld()->GameTick();
}
else if(pExtended->m_FreezeEnd == -1)
m_Core.m_DeepFrozen = true;
2020-04-19 10:50:06 +00:00
}
else
UnFreeze();
m_Core.ReadDDNet(pExtended);
if(!GameWorld()->m_WorldConfig.m_PredictFreeze)
{
UnFreeze();
}
}
else
{
// ddnetcharacter is not available, try to get some info from the tunings and the character netobject instead.
// remove weapons that are unavailable. if the current weapon is ninja just set ammo to zero in case the player is frozen
if(pChar->m_Weapon != m_Core.m_ActiveWeapon)
{
if(pChar->m_Weapon == WEAPON_NINJA)
m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo = 0;
else
{
if(m_Core.m_ActiveWeapon == WEAPON_NINJA)
{
SetNinjaActivationDir(vec2(0, 0));
SetNinjaActivationTick(-500);
SetNinjaCurrentMoveTime(0);
}
if(pChar->m_Weapon == m_LastSnapWeapon)
m_Core.m_aWeapons[m_Core.m_ActiveWeapon].m_Got = false;
}
}
// add weapon
if(pChar->m_Weapon != WEAPON_NINJA)
m_Core.m_aWeapons[pChar->m_Weapon].m_Got = true;
// jetpack
if(GameWorld()->m_WorldConfig.m_PredictWeapons && Tuning()->m_JetpackStrength > 0)
{
m_LastJetpackStrength = Tuning()->m_JetpackStrength;
2021-08-08 10:36:25 +00:00
m_Core.m_Jetpack = true;
m_Core.m_aWeapons[WEAPON_GUN].m_Got = true;
m_Core.m_aWeapons[WEAPON_GUN].m_Ammo = -1;
m_NinjaJetpack = pChar->m_Weapon == WEAPON_NINJA;
}
else if(pChar->m_Weapon != WEAPON_NINJA)
2021-08-08 10:36:25 +00:00
{
m_Core.m_Jetpack = false;
}
// number of jumps
if(GameWorld()->m_WorldConfig.m_PredictTiles)
{
if(pChar->m_Jumped & 2)
{
m_Core.m_EndlessJump = false;
if(m_Core.m_Jumps > m_Core.m_JumpedTotal && m_Core.m_JumpedTotal > 0 && m_Core.m_Jumps > 2)
m_Core.m_Jumps = m_Core.m_JumpedTotal + 1;
}
else if(m_Core.m_Jumps < 2)
m_Core.m_Jumps = m_Core.m_JumpedTotal + 2;
if(Tuning()->m_AirJumpImpulse == 0)
{
m_Core.m_Jumps = 0;
m_Core.m_Jumped = 3;
}
}
// set player collision
SetSolo(!Tuning()->m_PlayerCollision && !Tuning()->m_PlayerHooking);
m_Core.m_CollisionDisabled = !Tuning()->m_PlayerCollision;
m_Core.m_HookHitDisabled = !Tuning()->m_PlayerHooking;
if(m_Core.m_HookTick != 0)
m_Core.m_EndlessHook = false;
2020-04-19 10:50:06 +00:00
// detect unfreeze (in case the player was frozen in the tile prediction and not correctly unfrozen)
if(pChar->m_Emote != EMOTE_PAIN && pChar->m_Emote != EMOTE_NORMAL)
m_Core.m_DeepFrozen = false;
if(pChar->m_Weapon != WEAPON_NINJA || pChar->m_AttackTick > m_Core.m_FreezeStart || absolute(pChar->m_VelX) == 256 * 10 || !GameWorld()->m_WorldConfig.m_PredictFreeze)
2020-04-19 10:50:06 +00:00
{
m_Core.m_DeepFrozen = false;
2020-04-19 10:50:06 +00:00
UnFreeze();
}
}
vec2 PosBefore = m_Pos;
m_Pos = m_Core.m_Pos;
if(distance(PosBefore, m_Pos) > 2.f) // misprediction, don't use prevpos
m_PrevPos = m_Pos;
if(distance(m_PrevPos, m_Pos) > 10.f * 32.f) // reset prevpos if the distance is high
m_PrevPos = m_Pos;
if(pChar->m_Jumped & 2)
m_Core.m_JumpedTotal = m_Core.m_Jumps;
m_AttackTick = pChar->m_AttackTick;
m_LastSnapWeapon = pChar->m_Weapon;
2021-05-12 16:57:50 +00:00
SetTuneZone(GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0);
2019-09-08 22:53:07 +00:00
// set the current weapon
if(pChar->m_Weapon != WEAPON_NINJA)
{
m_Core.m_aWeapons[pChar->m_Weapon].m_Ammo = (GameWorld()->m_WorldConfig.m_InfiniteAmmo || GameWorld()->m_WorldConfig.m_IsDDRace || pChar->m_Weapon == WEAPON_HAMMER) ? -1 : pChar->m_AmmoCount;
2020-07-15 00:47:51 +00:00
if(pChar->m_Weapon != m_Core.m_ActiveWeapon)
SetActiveWeapon(pChar->m_Weapon);
}
// reset all input except direction and hook for non-local players (as in vanilla prediction)
if(!IsLocal)
{
mem_zero(&m_Input, sizeof(m_Input));
mem_zero(&m_SavedInput, sizeof(m_SavedInput));
m_Input.m_Direction = m_SavedInput.m_Direction = m_Core.m_Direction;
2019-06-05 00:01:31 +00:00
m_Input.m_Hook = m_SavedInput.m_Hook = (m_Core.m_HookState != HOOK_IDLE);
if(pExtended && pExtended->m_TargetX != 0 && pExtended->m_TargetY != 0)
{
m_Input.m_TargetX = m_SavedInput.m_TargetX = pExtended->m_TargetX;
m_Input.m_TargetY = m_SavedInput.m_TargetY = pExtended->m_TargetY;
}
else
{
m_Input.m_TargetX = m_SavedInput.m_TargetX = cosf(pChar->m_Angle / 256.0f) * 256.0f;
m_Input.m_TargetY = m_SavedInput.m_TargetY = sinf(pChar->m_Angle / 256.0f) * 256.0f;
}
}
2020-07-15 00:47:51 +00:00
// in most cases the reload timer can be determined from the last attack tick
// (this is only needed for autofire weapons to prevent the predicted reload timer from desyncing)
if(IsLocal && m_Core.m_ActiveWeapon != WEAPON_HAMMER)
{
if(maximum(m_LastTuneZoneTick, m_LastWeaponSwitchTick) + GameWorld()->GameTickSpeed() < GameWorld()->GameTick())
{
float FireDelay;
GetTuning(m_TuneZone)->Get(38 + m_Core.m_ActiveWeapon, &FireDelay);
const int FireDelayTicks = FireDelay * GameWorld()->GameTickSpeed() / 1000;
m_ReloadTimer = maximum(0, m_AttackTick + FireDelayTicks - GameWorld()->GameTick());
}
}
}
void CCharacter::SetCoreWorld(CGameWorld *pGameWorld)
{
m_Core.m_pWorld = &pGameWorld->m_Core;
m_Core.m_pCollision = pGameWorld->Collision();
m_Core.m_pTeams = pGameWorld->Teams();
}
bool CCharacter::Match(CCharacter *pChar)
{
2022-01-22 16:34:23 +00:00
return distance(pChar->m_Core.m_Pos, m_Core.m_Pos) <= 32.f;
}
2020-07-15 00:47:51 +00:00
void CCharacter::SetActiveWeapon(int ActiveWeap)
{
m_Core.m_ActiveWeapon = ActiveWeap;
m_LastWeaponSwitchTick = GameWorld()->GameTick();
}
void CCharacter::SetTuneZone(int Zone)
{
if(Zone == m_TuneZone)
return;
m_TuneZone = Zone;
m_LastTuneZoneTick = GameWorld()->GameTick();
}
CCharacter::~CCharacter()
{
if(GameWorld())
GameWorld()->RemoveCharacter(this);
}