2019-04-11 22:46:54 +00:00
|
|
|
/* (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 <new>
|
|
|
|
#include <engine/shared/config.h>
|
|
|
|
#include <game/mapitems.h>
|
|
|
|
|
|
|
|
#include "character.h"
|
|
|
|
#include "projectile.h"
|
|
|
|
#include "laser.h"
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.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;
|
|
|
|
m_Core.m_ActiveWeapon = W;
|
|
|
|
|
|
|
|
if(m_Core.m_ActiveWeapon < 0 || m_Core.m_ActiveWeapon >= NUM_WEAPONS)
|
|
|
|
m_Core.m_ActiveWeapon = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::SetSolo(bool Solo)
|
|
|
|
{
|
|
|
|
TeamsCore()->SetSolo(GetCID(), Solo);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCharacter::IsGrounded()
|
|
|
|
{
|
|
|
|
if(Collision()->CheckPoint(m_Pos.x+m_ProximityRadius/2, m_Pos.y+m_ProximityRadius/2+5))
|
|
|
|
return true;
|
|
|
|
if(Collision()->CheckPoint(m_Pos.x-m_ProximityRadius/2, m_Pos.y+m_ProximityRadius/2+5))
|
|
|
|
return true;
|
|
|
|
|
2018-08-15 15:47:07 +00:00
|
|
|
int MoveRestrictionsBelow = Collision()->GetMoveRestrictions(m_Pos + vec2(0, m_ProximityRadius / 2 + 4), 0.0f);
|
|
|
|
if(MoveRestrictionsBelow&CANTMOVE_DOWN)
|
|
|
|
{
|
2019-04-11 22:46:54 +00:00
|
|
|
return true;
|
2018-08-15 15:47:07 +00:00
|
|
|
}
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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_RIFLE)
|
|
|
|
FullAuto = true;
|
|
|
|
if (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_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
|
|
|
|
WillFire = true;
|
|
|
|
|
|
|
|
if(!WillFire)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// check for ammo
|
|
|
|
if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(m_Core.m_ActiveWeapon)
|
|
|
|
{
|
|
|
|
case WEAPON_GUN:
|
|
|
|
{
|
|
|
|
if (m_Jetpack)
|
|
|
|
{
|
2019-09-15 22:07:42 +00:00
|
|
|
float Strength = GetTuning(m_TuneZone)->m_JetpackStrength;
|
2019-09-08 22:53:07 +00:00
|
|
|
if(!m_TuneZone)
|
|
|
|
Strength = m_LastJetpackStrength;
|
2019-04-11 22:46:54 +00:00
|
|
|
TakeDamage(Direction * -1.0f * (Strength / 100.0f / 6.11f), g_pData->m_Weapons.m_Hammer.m_pBase->m_Damage, GetCID(), m_Core.m_ActiveWeapon);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::RemoveNinja()
|
|
|
|
{
|
|
|
|
m_Ninja.m_CurrentMoveTime = 0;
|
|
|
|
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;
|
|
|
|
|
|
|
|
if ((GameWorld()->GameTick() - 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);
|
|
|
|
|
|
|
|
m_Ninja.m_CurrentMoveTime--;
|
|
|
|
|
|
|
|
if (m_Ninja.m_CurrentMoveTime == 0)
|
|
|
|
{
|
|
|
|
// reset velocity
|
|
|
|
m_Core.m_Vel = m_Ninja.m_ActivationDir*m_Ninja.m_OldVelAmount;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_Ninja.m_CurrentMoveTime > 0)
|
|
|
|
{
|
|
|
|
// Set velocity
|
|
|
|
m_Core.m_Vel = 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
|
|
|
|
{
|
|
|
|
CCharacter *aEnts[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, (CEntity**)aEnts, 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)
|
|
|
|
{
|
|
|
|
if (aEnts[i] == this)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Don't hit players in other teams
|
|
|
|
if (Team() != aEnts[i]->Team())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Don't hit players in solo parts
|
|
|
|
if (TeamsCore()->GetSolo(aEnts[i]->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] == aEnts[i]->GetCID())
|
|
|
|
bAlreadyHit = true;
|
|
|
|
}
|
|
|
|
if (bAlreadyHit)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// check so we are sufficiently close
|
|
|
|
if (distance(aEnts[i]->m_Pos, m_Pos) > (m_ProximityRadius * 2.0f))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Hit a player, give him damage and stuffs...
|
|
|
|
// set his velocity to fast upward (for now)
|
|
|
|
if(m_NumObjectsHit < 10)
|
|
|
|
m_aHitObjects[m_NumObjectsHit++] = aEnts[i]->GetCID();
|
|
|
|
|
|
|
|
CCharacter *pChar = GameWorld()->GetCharacterByID(aEnts[i]->GetCID());
|
|
|
|
if(pChar)
|
|
|
|
pChar->TakeDamage(vec2(0, -10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, GetCID(), WEAPON_NINJA);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::DoWeaponSwitch()
|
|
|
|
{
|
|
|
|
// make sure we can switch
|
|
|
|
if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_aWeapons[WEAPON_NINJA].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_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_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_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_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_RIFLE)
|
|
|
|
FullAuto = true;
|
|
|
|
if (m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN)
|
|
|
|
FullAuto = true;
|
|
|
|
|
|
|
|
// don't fire non auto weapons when player is deep and sv_deepfly is disabled
|
|
|
|
if(!g_Config.m_SvDeepfly && !FullAuto && m_DeepFreeze)
|
|
|
|
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_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
|
|
|
|
WillFire = true;
|
|
|
|
|
|
|
|
if(!WillFire)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// check for ammo
|
|
|
|
if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
|
|
|
|
{
|
|
|
|
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_Hit&DISABLE_HIT_HAMMER) break;
|
|
|
|
|
|
|
|
CCharacter *apEnts[MAX_CLIENTS];
|
|
|
|
int Hits = 0;
|
|
|
|
int Num = GameWorld()->FindEntities(ProjStartPos, m_ProximityRadius*0.5f, (CEntity**)apEnts,
|
|
|
|
MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
|
|
|
|
|
|
|
|
for (int i = 0; i < Num; ++i)
|
|
|
|
{
|
|
|
|
CCharacter *pTarget = apEnts[i];
|
|
|
|
|
|
|
|
if((pTarget == this || (pTarget->IsAlive() && !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);
|
|
|
|
|
2019-09-15 22:07:42 +00:00
|
|
|
float Strength = GetTuning(m_TuneZone)->m_HammerStrength;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
vec2 Temp = pTarget->m_Core.m_Vel + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f;
|
2018-08-15 15:47:07 +00:00
|
|
|
pTarget->Core()->LimitVel(&Temp);
|
2019-04-11 22:46:54 +00:00
|
|
|
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)
|
|
|
|
m_ReloadTimer = GameWorld()->GameTickSpeed()/3;
|
|
|
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case WEAPON_GUN:
|
|
|
|
{
|
|
|
|
if (!m_Jetpack)
|
|
|
|
{
|
2019-09-15 22:07:42 +00:00
|
|
|
int Lifetime = (int)(GameWorld()->GameTickSpeed()*GetTuning(m_TuneZone)->m_GunLifetime);
|
2019-09-08 22:53:07 +00:00
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
new CProjectile
|
|
|
|
(
|
|
|
|
GameWorld(),
|
|
|
|
WEAPON_GUN,//Type
|
|
|
|
GetCID(),//Owner
|
|
|
|
ProjStartPos,//Pos
|
|
|
|
Direction,//Dir
|
|
|
|
Lifetime,//Span
|
|
|
|
0,//Freeze
|
|
|
|
0,//Explosive
|
|
|
|
0,//Force
|
|
|
|
-1,//SoundImpact
|
|
|
|
WEAPON_GUN//Weapon
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case WEAPON_SHOTGUN:
|
|
|
|
{
|
|
|
|
if(GameWorld()->m_WorldConfig.m_IsVanilla)
|
|
|
|
{
|
|
|
|
int ShotSpread = 2;
|
|
|
|
for(int i = -ShotSpread; i <= ShotSpread; ++i)
|
|
|
|
{
|
|
|
|
float Spreading[] = {-0.185f, -0.070f, 0, 0.070f, 0.185f};
|
|
|
|
float a = GetAngle(Direction);
|
|
|
|
a += Spreading[i+2];
|
|
|
|
float v = 1-(absolute(i)/(float)ShotSpread);
|
2019-09-15 22:07:42 +00:00
|
|
|
float Speed = mix((float)Tuning()->m_ShotgunSpeeddiff, 1.0f, v);
|
2019-04-11 22:46:54 +00:00
|
|
|
new CProjectile
|
|
|
|
(
|
|
|
|
GameWorld(),
|
|
|
|
WEAPON_SHOTGUN,//Type
|
|
|
|
GetCID(),//Owner
|
|
|
|
ProjStartPos,//Pos
|
|
|
|
vec2(cosf(a), sinf(a))*Speed,//Dir
|
2019-09-15 22:07:42 +00:00
|
|
|
(int)(GameWorld()->GameTickSpeed()*Tuning()->m_ShotgunLifetime),//Span
|
2019-04-11 22:46:54 +00:00
|
|
|
0,//Freeze
|
|
|
|
0,//Explosive
|
|
|
|
0,//Force
|
|
|
|
-1,//SoundImpact
|
|
|
|
WEAPON_SHOTGUN//Weapon
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(GameWorld()->m_WorldConfig.m_IsDDRace)
|
|
|
|
{
|
2019-09-15 22:07:42 +00:00
|
|
|
float LaserReach = GetTuning(m_TuneZone)->m_LaserReach;
|
2019-09-08 22:53:07 +00:00
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
new CLaser(GameWorld(), m_Pos, Direction, LaserReach, GetCID(), WEAPON_SHOTGUN);
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case WEAPON_GRENADE:
|
|
|
|
{
|
2019-09-15 22:07:42 +00:00
|
|
|
int Lifetime = (int)(GameWorld()->GameTickSpeed()*GetTuning(m_TuneZone)->m_GrenadeLifetime);
|
2019-09-08 22:53:07 +00:00
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
new CProjectile
|
|
|
|
(
|
|
|
|
GameWorld(),
|
|
|
|
WEAPON_GRENADE,//Type
|
|
|
|
GetCID(),//Owner
|
|
|
|
ProjStartPos,//Pos
|
|
|
|
Direction,//Dir
|
|
|
|
Lifetime,//Span
|
|
|
|
0,//Freeze
|
|
|
|
true,//Explosive
|
|
|
|
0,//Force
|
|
|
|
SOUND_GRENADE_EXPLODE,//SoundImpact
|
|
|
|
WEAPON_GRENADE//Weapon
|
|
|
|
);//SoundImpact
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case WEAPON_RIFLE:
|
|
|
|
{
|
2019-09-15 22:07:42 +00:00
|
|
|
float LaserReach = GetTuning(m_TuneZone)->m_LaserReach;
|
2019-09-08 22:53:07 +00:00
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
new CLaser(GameWorld(), m_Pos, Direction, LaserReach, GetCID(), WEAPON_RIFLE);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case WEAPON_NINJA:
|
|
|
|
{
|
|
|
|
// reset Hit objects
|
|
|
|
m_NumObjectsHit = 0;
|
|
|
|
|
|
|
|
m_Ninja.m_ActivationDir = Direction;
|
|
|
|
m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * GameWorld()->GameTickSpeed() / 1000;
|
|
|
|
m_Ninja.m_OldVelAmount = length(m_Core.m_Vel);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:24:48 +00:00
|
|
|
m_AttackTick = GameWorld()->GameTick();
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
if(!m_ReloadTimer)
|
|
|
|
{
|
|
|
|
float FireDelay;
|
2019-09-15 22:07:42 +00:00
|
|
|
GetTuning(m_TuneZone)->Get(38 + m_Core.m_ActiveWeapon, &FireDelay);
|
2019-09-08 22:53:07 +00:00
|
|
|
|
2019-04-11 22:46:54 +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();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCharacter::GiveWeapon(int Weapon, int Ammo)
|
|
|
|
{
|
|
|
|
if(m_aWeapons[Weapon].m_Ammo < g_pData->m_Weapons.m_aId[Weapon].m_Maxammo || !m_aWeapons[Weapon].m_Got)
|
|
|
|
{
|
|
|
|
m_aWeapons[Weapon].m_Got = true;
|
|
|
|
if(!m_FreezeTime)
|
2019-04-26 19:36:49 +00:00
|
|
|
m_aWeapons[Weapon].m_Ammo = minimum(g_pData->m_Weapons.m_aId[Weapon].m_Maxammo, Ammo);
|
2019-04-11 22:46:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::GiveNinja()
|
|
|
|
{
|
|
|
|
m_Ninja.m_ActivationTick = GameWorld()->GameTick();
|
|
|
|
m_aWeapons[WEAPON_NINJA].m_Got = true;
|
|
|
|
if (!m_FreezeTime)
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
// copy new input
|
|
|
|
mem_copy(&m_SavedInput, pNewInput, sizeof(m_SavedInput));
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
|
|
|
if(m_NumInputs > 2 && Team() != TEAM_SPECTATORS)
|
|
|
|
{
|
|
|
|
HandleWeaponSwitch();
|
|
|
|
FireWeapon();
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::Tick()
|
|
|
|
{
|
|
|
|
DDRaceTick();
|
|
|
|
|
|
|
|
m_Core.m_Input = m_Input;
|
|
|
|
m_Core.Tick(true);
|
|
|
|
|
|
|
|
// handle Weapons
|
|
|
|
HandleWeapons();
|
|
|
|
|
|
|
|
DDRacePostCoreTick();
|
|
|
|
|
|
|
|
// Previnput
|
|
|
|
m_PrevInput = m_Input;
|
|
|
|
|
2019-05-11 19:13:09 +00:00
|
|
|
m_PrevPrevPos = m_PrevPos;
|
2019-04-11 22:46:54 +00:00
|
|
|
m_PrevPos = m_Core.m_Pos;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::TickDefered()
|
|
|
|
{
|
|
|
|
m_Core.Move();
|
|
|
|
m_Core.Quantize();
|
|
|
|
m_Pos = m_Core.m_Pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
|
|
|
|
{
|
|
|
|
m_Core.ApplyForce(Force);
|
|
|
|
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, MaxVel, 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;
|
2018-08-15 15:47:07 +00:00
|
|
|
m_Core.LimitVel(&TempVel);
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Core.m_Vel = TempVel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-15 15:47:07 +00:00
|
|
|
bool CCharacter::IsSwitchActiveCb(int Number, void *pUser)
|
|
|
|
{
|
|
|
|
CCharacter *pThis = (CCharacter *)pUser;
|
|
|
|
CCollision *pCollision = pThis->Collision();
|
|
|
|
return pCollision->m_pSwitchers && pCollision->m_pSwitchers[Number].m_Status[pThis->Team()] && pThis->Team() != TEAM_SUPER;
|
|
|
|
}
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
void CCharacter::HandleTiles(int Index)
|
|
|
|
{
|
|
|
|
int MapIndex = Index;
|
|
|
|
m_TileIndex = Collision()->GetTileIndex(MapIndex);
|
|
|
|
m_TileFIndex = Collision()->GetFTileIndex(MapIndex);
|
2018-08-15 15:47:07 +00:00
|
|
|
m_MoveRestrictions = Collision()->GetMoveRestrictions(IsSwitchActiveCb, this, m_Pos);
|
2019-05-03 18:57:01 +00:00
|
|
|
|
|
|
|
// stopper
|
2018-08-15 15:47:07 +00:00
|
|
|
m_Core.m_Vel = ClampVel(m_MoveRestrictions, m_Core.m_Vel);
|
|
|
|
if(m_MoveRestrictions&CANTMOVE_DOWN)
|
2019-05-03 18:57:01 +00:00
|
|
|
{
|
|
|
|
m_Core.m_Jumped = 0;
|
|
|
|
m_Core.m_JumpedTotal = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!GameWorld()->m_WorldConfig.m_PredictTiles)
|
|
|
|
return;
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
if(Index < 0)
|
|
|
|
{
|
|
|
|
m_LastRefillJumps = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// freeze
|
2019-04-21 22:38:24 +00:00
|
|
|
if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Super && !m_DeepFreeze)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
Freeze();
|
|
|
|
}
|
|
|
|
else if(((m_TileIndex == TILE_UNFREEZE) || (m_TileFIndex == TILE_UNFREEZE)) && !m_DeepFreeze)
|
|
|
|
{
|
|
|
|
UnFreeze();
|
|
|
|
}
|
|
|
|
|
|
|
|
// deep freeze
|
2019-04-21 22:38:24 +00:00
|
|
|
if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Super && !m_DeepFreeze)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_DeepFreeze = true;
|
|
|
|
}
|
2019-04-21 22:38:24 +00:00
|
|
|
else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Super && m_DeepFreeze)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_DeepFreeze = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// endless hook
|
|
|
|
if(((m_TileIndex == TILE_EHOOK_START) || (m_TileFIndex == TILE_EHOOK_START)) && !m_EndlessHook)
|
|
|
|
{
|
|
|
|
m_EndlessHook = true;
|
|
|
|
}
|
|
|
|
else if(((m_TileIndex == TILE_EHOOK_END) || (m_TileFIndex == TILE_EHOOK_END)) && m_EndlessHook)
|
|
|
|
{
|
|
|
|
m_EndlessHook = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// collide with others
|
|
|
|
if(((m_TileIndex == TILE_NPC_END) || (m_TileFIndex == TILE_NPC_END)) && m_Core.m_Collision)
|
|
|
|
{
|
|
|
|
m_Core.m_Collision = false;
|
|
|
|
}
|
|
|
|
else if(((m_TileIndex == TILE_NPC_START) || (m_TileFIndex == TILE_NPC_START)) && !m_Core.m_Collision)
|
|
|
|
{
|
|
|
|
m_Core.m_Collision = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// hook others
|
|
|
|
if(((m_TileIndex == TILE_NPH_END) || (m_TileFIndex == TILE_NPH_END)) && m_Core.m_Hook)
|
|
|
|
{
|
|
|
|
m_Core.m_Hook = false;
|
|
|
|
}
|
|
|
|
else if(((m_TileIndex == TILE_NPH_START) || (m_TileFIndex == TILE_NPH_START)) && !m_Core.m_Hook)
|
|
|
|
{
|
|
|
|
m_Core.m_Hook = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// unlimited air jumps
|
|
|
|
if(((m_TileIndex == TILE_SUPER_START) || (m_TileFIndex == TILE_SUPER_START)) && !m_SuperJump)
|
|
|
|
{
|
|
|
|
m_SuperJump = true;
|
|
|
|
}
|
|
|
|
else if(((m_TileIndex == TILE_SUPER_END) || (m_TileFIndex == TILE_SUPER_END)) && m_SuperJump)
|
|
|
|
{
|
|
|
|
m_SuperJump = 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;
|
|
|
|
m_Core.m_JumpedTotal = m_Core.m_Jumps - 1;
|
|
|
|
m_Core.m_Jumped = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// jetpack gun
|
|
|
|
if(((m_TileIndex == TILE_JETPACK_START) || (m_TileFIndex == TILE_JETPACK_START)) && !m_Jetpack)
|
|
|
|
{
|
|
|
|
m_Jetpack = true;
|
|
|
|
}
|
|
|
|
else if(((m_TileIndex == TILE_JETPACK_END) || (m_TileFIndex == TILE_JETPACK_END)) && m_Jetpack)
|
|
|
|
{
|
|
|
|
m_Jetpack = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// solo part
|
|
|
|
if(((m_TileIndex == TILE_SOLO_START) || (m_TileFIndex == TILE_SOLO_START)) && !TeamsCore()->GetSolo(GetCID()))
|
|
|
|
{
|
|
|
|
SetSolo(true);
|
|
|
|
}
|
|
|
|
else if(((m_TileIndex == TILE_SOLO_END) || (m_TileFIndex == TILE_SOLO_END)) && 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle switch tiles
|
|
|
|
if(Collision()->IsSwitch(MapIndex) == TILE_SWITCHOPEN && Team() != TEAM_SUPER)
|
|
|
|
{
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = true;
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = 0;
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHOPEN;
|
|
|
|
}
|
|
|
|
else if(Collision()->IsSwitch(MapIndex) == TILE_SWITCHTIMEDOPEN && Team() != TEAM_SUPER)
|
|
|
|
{
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = true;
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = GameWorld()->GameTick() + 1 + Collision()->GetSwitchDelay(MapIndex)*GameWorld()->GameTickSpeed() ;
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHTIMEDOPEN;
|
|
|
|
}
|
|
|
|
else if(Collision()->IsSwitch(MapIndex) == TILE_SWITCHTIMEDCLOSE && Team() != TEAM_SUPER)
|
|
|
|
{
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = false;
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = GameWorld()->GameTick() + 1 + Collision()->GetSwitchDelay(MapIndex)*GameWorld()->GameTickSpeed();
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHTIMEDCLOSE;
|
|
|
|
}
|
|
|
|
else if(Collision()->IsSwitch(MapIndex) == TILE_SWITCHCLOSE && Team() != TEAM_SUPER)
|
|
|
|
{
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = false;
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = 0;
|
|
|
|
Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHCLOSE;
|
|
|
|
}
|
|
|
|
else if(Collision()->IsSwitch(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER)
|
|
|
|
{
|
|
|
|
if(Collision()->GetSwitchNumber(MapIndex) == 0 || Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()])
|
|
|
|
Freeze(Collision()->GetSwitchDelay(MapIndex));
|
|
|
|
}
|
|
|
|
else if(Collision()->IsSwitch(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER && Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()])
|
|
|
|
{
|
|
|
|
m_DeepFreeze = true;
|
|
|
|
}
|
|
|
|
else if(Collision()->IsSwitch(MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER && Collision()->m_pSwitchers[Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()])
|
|
|
|
{
|
|
|
|
m_DeepFreeze = false;
|
|
|
|
}
|
|
|
|
else if(Collision()->IsSwitch(MapIndex) == TILE_JUMP)
|
|
|
|
{
|
|
|
|
int newJumps = Collision()->GetSwitchDelay(MapIndex);
|
|
|
|
|
|
|
|
if (newJumps != m_Core.m_Jumps)
|
|
|
|
{
|
|
|
|
m_Core.m_Jumps = newJumps;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-08 22:53:07 +00:00
|
|
|
void CCharacter::HandleTuneLayer()
|
|
|
|
{
|
|
|
|
int CurrentIndex = Collision()->GetMapIndex(m_Pos);
|
|
|
|
m_TuneZone = GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(CurrentIndex) : 0;
|
|
|
|
|
2019-09-15 22:07:42 +00:00
|
|
|
m_Core.m_pWorld->m_Tuning[g_Config.m_ClDummy] = *GetTuning(m_TuneZone); // throw tunings (from specific zone if in a tunezone) into gamecore
|
2019-09-08 22:53:07 +00:00
|
|
|
}
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
void CCharacter::DDRaceTick()
|
|
|
|
{
|
|
|
|
mem_copy(&m_Input, &m_SavedInput, sizeof(m_Input));
|
|
|
|
if(m_FreezeTime > 0 || m_FreezeTime == -1)
|
|
|
|
{
|
|
|
|
if(m_FreezeTime > 0)
|
|
|
|
m_FreezeTime--;
|
|
|
|
else
|
|
|
|
m_Ninja.m_ActivationTick = GameWorld()->GameTick();
|
2019-05-02 20:35:08 +00:00
|
|
|
if(!m_CanMoveInFreeze)
|
|
|
|
{
|
|
|
|
m_Input.m_Direction = 0;
|
|
|
|
m_Input.m_Jump = 0;
|
|
|
|
m_Input.m_Hook = 0;
|
|
|
|
}
|
2019-04-11 22:46:54 +00:00
|
|
|
if (m_FreezeTime == 1)
|
|
|
|
UnFreeze();
|
|
|
|
}
|
2019-09-08 22:53:07 +00:00
|
|
|
|
|
|
|
HandleTuneLayer();
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::DDRacePostCoreTick()
|
|
|
|
{
|
2019-05-03 18:57:01 +00:00
|
|
|
if(!GameWorld()->m_WorldConfig.m_PredictDDRace)
|
|
|
|
return;
|
|
|
|
|
2019-04-11 22:46:54 +00:00
|
|
|
if (m_EndlessHook)
|
|
|
|
m_Core.m_HookTick = 0;
|
|
|
|
|
2019-04-21 22:38:24 +00:00
|
|
|
if (m_DeepFreeze && !m_Super)
|
2019-04-11 22:46:54 +00:00
|
|
|
Freeze();
|
|
|
|
|
2019-04-21 22:38:24 +00:00
|
|
|
if (m_Core.m_Jumps == 0 && !m_Super)
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Core.m_Jumped = 3;
|
|
|
|
else if (m_Core.m_Jumps == 1 && m_Core.m_Jumped > 0)
|
|
|
|
m_Core.m_Jumped = 3;
|
|
|
|
else if (m_Core.m_JumpedTotal < m_Core.m_Jumps - 1 && m_Core.m_Jumped > 1)
|
|
|
|
m_Core.m_Jumped = 1;
|
|
|
|
|
2019-04-21 22:38:24 +00:00
|
|
|
if ((m_Super || m_SuperJump) && m_Core.m_Jumped > 1)
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Core.m_Jumped = 1;
|
|
|
|
|
2019-05-03 18:57:01 +00:00
|
|
|
int CurrentIndex = Collision()->GetMapIndex(m_Pos);
|
|
|
|
HandleSkippableTiles(CurrentIndex);
|
2019-05-02 01:33:40 +00:00
|
|
|
|
2019-05-03 18:57:01 +00:00
|
|
|
// handle Anti-Skip tiles
|
|
|
|
std::list < int > Indices = Collision()->GetMapIndices(m_PrevPos, m_Pos);
|
|
|
|
if(!Indices.empty())
|
|
|
|
for(std::list < int >::iterator i = Indices.begin(); i != Indices.end(); i++)
|
|
|
|
HandleTiles(*i);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
HandleTiles(CurrentIndex);
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCharacter::Freeze(int Seconds)
|
|
|
|
{
|
|
|
|
if(!GameWorld()->m_WorldConfig.m_PredictFreeze)
|
|
|
|
return false;
|
2019-04-21 22:38:24 +00:00
|
|
|
if ((Seconds <= 0 || m_Super || m_FreezeTime == -1 || m_FreezeTime > Seconds * GameWorld()->GameTickSpeed()) && Seconds != -1)
|
2019-04-11 22:46:54 +00:00
|
|
|
return false;
|
|
|
|
if (m_FreezeTick < GameWorld()->GameTick() - GameWorld()->GameTickSpeed() || Seconds == -1)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < NUM_WEAPONS; i++)
|
|
|
|
if(m_aWeapons[i].m_Got)
|
|
|
|
{
|
|
|
|
m_aWeapons[i].m_Ammo = 0;
|
|
|
|
}
|
|
|
|
m_FreezeTime = Seconds == -1 ? Seconds : Seconds * GameWorld()->GameTickSpeed();
|
|
|
|
m_FreezeTick = GameWorld()->GameTick();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCharacter::Freeze()
|
|
|
|
{
|
|
|
|
return Freeze(g_Config.m_SvFreezeDelay);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCharacter::UnFreeze()
|
|
|
|
{
|
|
|
|
if (m_FreezeTime > 0)
|
|
|
|
{
|
|
|
|
for(int i=0;i<NUM_WEAPONS;i++)
|
|
|
|
m_aWeapons[i].m_Ammo = -1;
|
|
|
|
if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Got)
|
|
|
|
m_Core.m_ActiveWeapon = WEAPON_GUN;
|
|
|
|
m_FreezeTime = 0;
|
|
|
|
m_FreezeTick = 0;
|
|
|
|
if (m_Core.m_ActiveWeapon==WEAPON_HAMMER) m_ReloadTimer = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCharacter::GiveAllWeapons()
|
|
|
|
{
|
|
|
|
for(int i=WEAPON_GUN;i<NUM_WEAPONS-1;i++)
|
|
|
|
{
|
|
|
|
m_aWeapons[i].m_Got = true;
|
|
|
|
if(!m_FreezeTime) m_aWeapons[i].m_Ammo = -1;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CTeamsCore* CCharacter::TeamsCore()
|
|
|
|
{
|
|
|
|
return m_Core.m_pTeams;
|
|
|
|
}
|
|
|
|
|
2019-04-21 22:38:24 +00:00
|
|
|
CCharacter::CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended)
|
2019-04-11 22:46:54 +00:00
|
|
|
: CEntity(pGameWorld, CGameWorld::ENTTYPE_CHARACTER)
|
|
|
|
{
|
|
|
|
m_ID = ID;
|
|
|
|
|
|
|
|
m_LastWeapon = WEAPON_HAMMER;
|
|
|
|
m_QueuedWeapon = -1;
|
|
|
|
m_LastRefillJumps = false;
|
2019-05-11 19:13:09 +00:00
|
|
|
m_PrevPrevPos = m_PrevPos = m_Pos = vec2(pChar->m_X, pChar->m_Y);
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Core.Reset();
|
|
|
|
m_Core.Init(&GameWorld()->m_Core, GameWorld()->Collision(), GameWorld()->Teams());
|
|
|
|
m_Core.m_Id = ID;
|
|
|
|
mem_zero(&m_Ninja, sizeof(m_Ninja));
|
|
|
|
mem_zero(&m_SavedInput, sizeof(m_SavedInput));
|
|
|
|
m_LatestInput = m_LatestPrevInput = m_PrevInput = m_Input = m_SavedInput;
|
|
|
|
m_ProximityRadius = ms_PhysSize;
|
|
|
|
m_Core.m_LeftWall = 1;
|
|
|
|
m_ReloadTimer = 0;
|
|
|
|
m_NumObjectsHit = 0;
|
|
|
|
m_LastRefillJumps = false;
|
2019-07-08 21:08:42 +00:00
|
|
|
m_LastJetpackStrength = 400.0f;
|
2019-04-21 22:38:24 +00:00
|
|
|
m_Super = false;
|
2019-05-02 20:35:08 +00:00
|
|
|
m_CanMoveInFreeze = false;
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Alive = true;
|
2019-06-30 21:47:33 +00:00
|
|
|
m_TeleCheckpoint = 0;
|
|
|
|
m_StrongWeakID = 0;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2019-05-04 01:48:17 +00:00
|
|
|
ResetPrediction();
|
2019-04-21 22:38:24 +00:00
|
|
|
Read(pChar, pExtended, false);
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
GameWorld()->InsertEntity(this);
|
|
|
|
}
|
|
|
|
|
2019-05-04 01:48:17 +00:00
|
|
|
void CCharacter::ResetPrediction()
|
|
|
|
{
|
|
|
|
SetSolo(false);
|
|
|
|
m_EndlessHook = false;
|
|
|
|
m_Hit = HIT_ALL;
|
|
|
|
m_SuperJump = false;
|
|
|
|
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_NumInputs = 0;
|
|
|
|
m_FreezeTime = 0;
|
|
|
|
m_FreezeTick = 0;
|
|
|
|
m_DeepFreeze = 0;
|
|
|
|
m_Super = false;
|
|
|
|
for(int w = 0; w < NUM_WEAPONS; w++)
|
2019-06-30 21:47:33 +00:00
|
|
|
{
|
2019-05-04 01:48:17 +00:00
|
|
|
SetWeaponGot(w, false);
|
2019-06-30 21:47:33 +00:00
|
|
|
SetWeaponAmmo(w, -1);
|
|
|
|
}
|
2019-05-05 12:28:39 +00:00
|
|
|
if(m_Core.m_HookedPlayer >= 0)
|
|
|
|
{
|
|
|
|
m_Core.m_HookedPlayer = -1;
|
|
|
|
m_Core.m_HookState = HOOK_IDLE;
|
|
|
|
}
|
2019-05-04 01:48:17 +00:00
|
|
|
}
|
|
|
|
|
2019-04-21 22:38:24 +00:00
|
|
|
void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_Core.Read((CNetObj_CharacterCore*) pChar);
|
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
if(pExtended)
|
|
|
|
{
|
|
|
|
m_Core.ReadDDNet(pExtended);
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
SetSolo(pExtended->m_Flags & CHARACTERFLAG_SOLO);
|
|
|
|
m_Super = pExtended->m_Flags & CHARACTERFLAG_SUPER;
|
|
|
|
if(m_Super)
|
|
|
|
TeamsCore()->Team(GetCID(), TeamsCore()->m_IsDDRace16 ? VANILLA_TEAM_SUPER : TEAM_SUPER);
|
|
|
|
|
|
|
|
m_EndlessHook = pExtended->m_Flags & CHARACTERFLAG_ENDLESS_HOOK;
|
|
|
|
m_SuperJump = pExtended->m_Flags & CHARACTERFLAG_ENDLESS_JUMP;
|
|
|
|
m_Jetpack = pExtended->m_Flags & CHARACTERFLAG_JETPACK;
|
|
|
|
m_TeleCheckpoint = pExtended->m_TeleCheckpoint;
|
|
|
|
m_StrongWeakID = pExtended->m_StrongWeakID;
|
|
|
|
|
|
|
|
m_Hit = HIT_ALL;
|
|
|
|
if(pExtended->m_Flags & CHARACTERFLAG_NO_GRENADE_HIT)
|
|
|
|
m_Hit |= DISABLE_HIT_GRENADE;
|
|
|
|
if(pExtended->m_Flags & CHARACTERFLAG_NO_HAMMER_HIT)
|
|
|
|
m_Hit |= DISABLE_HIT_HAMMER;
|
|
|
|
if(pExtended->m_Flags & CHARACTERFLAG_NO_RIFLE_HIT)
|
|
|
|
m_Hit |= DISABLE_HIT_RIFLE;
|
|
|
|
if(pExtended->m_Flags & CHARACTERFLAG_NO_SHOTGUN_HIT)
|
|
|
|
m_Hit |= DISABLE_HIT_SHOTGUN;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
m_aWeapons[WEAPON_HAMMER].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_HAMMER) != 0;
|
|
|
|
m_aWeapons[WEAPON_GUN].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_GUN) != 0;
|
|
|
|
m_aWeapons[WEAPON_SHOTGUN].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_SHOTGUN) != 0;
|
|
|
|
m_aWeapons[WEAPON_GRENADE].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_GRENADE) != 0;
|
|
|
|
m_aWeapons[WEAPON_RIFLE].m_Got = (pExtended->m_Flags & CHARACTERFLAG_WEAPON_LASER) != 0;
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
else
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-06-30 21:47:33 +00:00
|
|
|
// 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)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-06-30 21:47:33 +00:00
|
|
|
if(pChar->m_Weapon == WEAPON_NINJA)
|
|
|
|
m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo = 0;
|
|
|
|
else
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
2019-06-30 21:47:33 +00:00
|
|
|
if(m_Core.m_ActiveWeapon == WEAPON_NINJA)
|
|
|
|
{
|
|
|
|
SetNinjaActivationDir(vec2(0,0));
|
|
|
|
SetNinjaActivationTick(-500);
|
|
|
|
SetNinjaCurrentMoveTime(0);
|
|
|
|
}
|
|
|
|
if(pChar->m_Weapon == m_LastSnapWeapon)
|
|
|
|
m_aWeapons[m_Core.m_ActiveWeapon].m_Got = false;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-30 21:47:33 +00:00
|
|
|
// add weapon
|
|
|
|
if(pChar->m_Weapon != WEAPON_NINJA)
|
|
|
|
m_aWeapons[pChar->m_Weapon].m_Got = true;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
// jetpack
|
|
|
|
if(GameWorld()->m_WorldConfig.m_PredictWeapons && Tuning()->m_JetpackStrength > 0)
|
|
|
|
{
|
|
|
|
m_LastJetpackStrength = Tuning()->m_JetpackStrength;
|
|
|
|
m_Jetpack = true;
|
|
|
|
m_aWeapons[WEAPON_GUN].m_Got = true;
|
|
|
|
m_aWeapons[WEAPON_GUN].m_Ammo = -1;
|
|
|
|
m_NinjaJetpack = pChar->m_Weapon == WEAPON_NINJA;
|
|
|
|
}
|
|
|
|
else if(pChar->m_Weapon != WEAPON_NINJA)
|
|
|
|
m_Jetpack = false;
|
|
|
|
|
|
|
|
// number of jumps
|
|
|
|
if(GameWorld()->m_WorldConfig.m_PredictTiles)
|
|
|
|
{
|
|
|
|
if(pChar->m_Jumped&2)
|
|
|
|
{
|
|
|
|
m_SuperJump = 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_Collision = Tuning()->m_PlayerCollision;
|
|
|
|
m_Core.m_Hook = Tuning()->m_PlayerHooking;
|
|
|
|
|
|
|
|
if(m_Core.m_HookTick != 0)
|
|
|
|
m_EndlessHook = false;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
// detect unfreeze (in case the player was frozen in the tile prediction and not correclty unfrozen)
|
2019-04-24 13:56:56 +00:00
|
|
|
if(pChar->m_Emote != EMOTE_PAIN && pChar->m_Emote != EMOTE_NORMAL)
|
|
|
|
m_DeepFreeze = false;
|
2019-06-30 21:47:33 +00:00
|
|
|
if(pChar->m_Weapon != WEAPON_NINJA || pChar->m_AttackTick > m_FreezeTick || absolute(pChar->m_VelX) == 256*10 || !GameWorld()->m_WorldConfig.m_PredictFreeze)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_DeepFreeze = false;
|
|
|
|
UnFreeze();
|
|
|
|
}
|
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
vec2 PosBefore = m_Pos;
|
|
|
|
m_Pos = m_Core.m_Pos;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
if(distance(PosBefore, m_Pos) > 2.f) // misprediction, don't use prevpos
|
|
|
|
m_PrevPos = m_Pos;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
if(distance(m_PrevPos, m_Pos) > 10.f * 32.f) // reset prevpos if the distance is high
|
|
|
|
m_PrevPos = m_Pos;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
if(pChar->m_Jumped&2)
|
|
|
|
m_Core.m_JumpedTotal = m_Core.m_Jumps;
|
|
|
|
m_AttackTick = pChar->m_AttackTick;
|
|
|
|
m_LastSnapWeapon = pChar->m_Weapon;
|
|
|
|
m_Alive = true;
|
|
|
|
|
2019-09-08 22:53:07 +00:00
|
|
|
m_TuneZone = GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0;
|
|
|
|
|
2019-06-30 21:47:33 +00:00
|
|
|
// set the current weapon
|
|
|
|
if(pChar->m_Weapon != WEAPON_NINJA)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
SetActiveWeapon(pChar->m_Weapon);
|
|
|
|
}
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
// 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);
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Input.m_TargetX = cosf(pChar->m_Angle/256.0f);
|
|
|
|
m_Input.m_TargetY = sinf(pChar->m_Angle/256.0f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if(distance(pChar->m_Core.m_Pos, m_Core.m_Pos) > 32.f)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|