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 "laser.h"
|
2020-09-26 19:41:58 +00:00
|
|
|
#include "character.h"
|
|
|
|
#include <game/generated/protocol.h>
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
#include <engine/shared/config.h>
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEnergy, int Owner, int Type) :
|
|
|
|
CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_Pos = Pos;
|
|
|
|
m_Owner = Owner;
|
|
|
|
m_Energy = StartEnergy;
|
|
|
|
if(pGameWorld->m_WorldConfig.m_IsFNG && m_Energy < 10.f)
|
|
|
|
m_Energy = 800.0f;
|
|
|
|
m_Dir = Direction;
|
|
|
|
m_Bounces = 0;
|
|
|
|
m_EvalTick = 0;
|
|
|
|
m_WasTele = false;
|
|
|
|
m_Type = Type;
|
2021-04-23 03:01:38 +00:00
|
|
|
m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0;
|
2019-04-11 22:46:54 +00:00
|
|
|
GameWorld()->InsertEntity(this);
|
|
|
|
DoBounce();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CLaser::HitCharacter(vec2 From, vec2 To)
|
|
|
|
{
|
2022-05-27 22:58:33 +00:00
|
|
|
static const vec2 StackedLaserShotgunBugSpeed = vec2(-2147483648.0f, -2147483648.0f);
|
2019-04-11 22:46:54 +00:00
|
|
|
vec2 At;
|
|
|
|
CCharacter *pOwnerChar = GameWorld()->GetCharacterByID(m_Owner);
|
|
|
|
CCharacter *pHit;
|
2020-10-03 01:10:54 +00:00
|
|
|
bool DontHitSelf = (g_Config.m_SvOldLaser || !GameWorld()->m_WorldConfig.m_IsDDRace) || (m_Bounces == 0 && !m_WasTele);
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(pOwnerChar ? (!(pOwnerChar->m_Hit & CCharacter::DISABLE_HIT_LASER) && m_Type == WEAPON_LASER) || (!(pOwnerChar->m_Hit & CCharacter::DISABLE_HIT_SHOTGUN) && m_Type == WEAPON_SHOTGUN) : g_Config.m_SvHit)
|
2020-10-03 01:10:54 +00:00
|
|
|
pHit = GameWorld()->IntersectCharacter(m_Pos, To, 0.f, At, DontHitSelf ? pOwnerChar : 0, m_Owner);
|
2019-04-11 22:46:54 +00:00
|
|
|
else
|
2020-10-03 01:10:54 +00:00
|
|
|
pHit = GameWorld()->IntersectCharacter(m_Pos, To, 0.f, At, DontHitSelf ? pOwnerChar : 0, m_Owner, pOwnerChar);
|
2019-04-11 22:46:54 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(!pHit || (pHit == pOwnerChar && g_Config.m_SvOldLaser) || (pHit != pOwnerChar && pOwnerChar ? (pOwnerChar->m_Hit & CCharacter::DISABLE_HIT_LASER && m_Type == WEAPON_LASER) || (pOwnerChar->m_Hit & CCharacter::DISABLE_HIT_SHOTGUN && m_Type == WEAPON_SHOTGUN) : !g_Config.m_SvHit))
|
2019-04-11 22:46:54 +00:00
|
|
|
return false;
|
|
|
|
m_From = From;
|
|
|
|
m_Pos = At;
|
|
|
|
m_Energy = -1;
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_Type == WEAPON_SHOTGUN)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
vec2 Temp;
|
2019-09-15 22:07:42 +00:00
|
|
|
float Strength = GetTuning(m_TuneZone)->m_ShotgunStrength;
|
2022-05-27 22:58:33 +00:00
|
|
|
vec2 &HitPos = pHit->Core()->m_Pos;
|
|
|
|
if(!g_Config.m_SvOldLaser)
|
|
|
|
{
|
|
|
|
if(m_PrevPos != HitPos)
|
|
|
|
{
|
|
|
|
Temp = pHit->Core()->m_Vel + normalize(m_PrevPos - HitPos) * Strength;
|
|
|
|
pHit->Core()->m_Vel = ClampVel(pHit->m_MoveRestrictions, Temp);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pHit->Core()->m_Vel = StackedLaserShotgunBugSpeed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(g_Config.m_SvOldLaser && pOwnerChar)
|
|
|
|
{
|
|
|
|
if(pOwnerChar->Core()->m_Pos != HitPos)
|
|
|
|
{
|
|
|
|
Temp = pHit->Core()->m_Vel + normalize(pOwnerChar->Core()->m_Pos - HitPos) * Strength;
|
|
|
|
pHit->Core()->m_Vel = ClampVel(pHit->m_MoveRestrictions, Temp);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pHit->Core()->m_Vel = StackedLaserShotgunBugSpeed;
|
|
|
|
}
|
|
|
|
}
|
2020-10-12 16:50:23 +00:00
|
|
|
else
|
2022-05-27 22:58:33 +00:00
|
|
|
{
|
|
|
|
pHit->Core()->m_Vel = ClampVel(pHit->m_MoveRestrictions, pHit->Core()->m_Vel);
|
|
|
|
}
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
else if(m_Type == WEAPON_LASER)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
pHit->UnFreeze();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CLaser::DoBounce()
|
|
|
|
{
|
|
|
|
m_EvalTick = GameWorld()->GameTick();
|
|
|
|
|
|
|
|
if(m_Energy < 0)
|
|
|
|
{
|
2021-05-22 18:02:00 +00:00
|
|
|
m_MarkedForDestroy = true;
|
2019-04-11 22:46:54 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_PrevPos = m_Pos;
|
|
|
|
vec2 Coltile;
|
|
|
|
|
|
|
|
int Res;
|
|
|
|
int z;
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_WasTele)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_PrevPos = m_TelePos;
|
|
|
|
m_Pos = m_TelePos;
|
2020-09-26 19:41:58 +00:00
|
|
|
m_TelePos = vec2(0, 0);
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
vec2 To = m_Pos + m_Dir * m_Energy;
|
|
|
|
|
|
|
|
Res = Collision()->IntersectLineTeleWeapon(m_Pos, To, &Coltile, &To, &z);
|
|
|
|
|
|
|
|
if(Res)
|
|
|
|
{
|
|
|
|
if(!HitCharacter(m_Pos, To))
|
|
|
|
{
|
|
|
|
// intersected
|
|
|
|
m_From = m_Pos;
|
|
|
|
m_Pos = To;
|
|
|
|
|
|
|
|
vec2 TempPos = m_Pos;
|
|
|
|
vec2 TempDir = m_Dir * 4.0f;
|
|
|
|
|
|
|
|
int f = 0;
|
|
|
|
if(Res == -1)
|
|
|
|
{
|
|
|
|
f = Collision()->GetTile(round_to_int(Coltile.x), round_to_int(Coltile.y));
|
|
|
|
Collision()->SetCollisionAt(round_to_int(Coltile.x), round_to_int(Coltile.y), TILE_SOLID);
|
|
|
|
}
|
|
|
|
Collision()->MovePoint(&TempPos, &TempDir, 1.0f, 0);
|
|
|
|
if(Res == -1)
|
|
|
|
{
|
|
|
|
Collision()->SetCollisionAt(round_to_int(Coltile.x), round_to_int(Coltile.y), f);
|
|
|
|
}
|
|
|
|
m_Pos = TempPos;
|
|
|
|
m_Dir = normalize(TempDir);
|
|
|
|
|
2022-03-12 23:02:42 +00:00
|
|
|
const float Distance = distance(m_From, m_Pos);
|
|
|
|
// Prevent infinite bounces
|
|
|
|
if(Distance == 0.0f)
|
|
|
|
m_Energy = -1;
|
|
|
|
else
|
|
|
|
m_Energy -= Distance + GetTuning(m_TuneZone)->m_LaserBounceCost;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
m_Bounces++;
|
|
|
|
m_WasTele = false;
|
|
|
|
|
2019-09-15 22:07:42 +00:00
|
|
|
int BounceNum = GetTuning(m_TuneZone)->m_LaserBounceNum;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
if(m_Bounces > BounceNum)
|
|
|
|
m_Energy = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(!HitCharacter(m_Pos, To))
|
|
|
|
{
|
|
|
|
m_From = m_Pos;
|
|
|
|
m_Pos = To;
|
|
|
|
m_Energy = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CLaser::Tick()
|
|
|
|
{
|
2019-09-15 22:07:42 +00:00
|
|
|
float Delay = GetTuning(m_TuneZone)->m_LaserBounceDelay;
|
2019-04-11 22:46:54 +00:00
|
|
|
|
|
|
|
if(GameWorld()->m_WorldConfig.m_IsVanilla) // predict old physics on vanilla 0.6 servers
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(GameWorld()->GameTick() > m_EvalTick + (GameWorld()->GameTickSpeed() * Delay / 1000.0f))
|
2019-04-11 22:46:54 +00:00
|
|
|
DoBounce();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if((GameWorld()->GameTick() - m_EvalTick) > (GameWorld()->GameTickSpeed() * Delay / 1000.0f))
|
2019-04-11 22:46:54 +00:00
|
|
|
DoBounce();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
CLaser::CLaser(CGameWorld *pGameWorld, int ID, CNetObj_Laser *pLaser) :
|
|
|
|
CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
|
2019-04-11 22:46:54 +00:00
|
|
|
{
|
|
|
|
m_Pos.x = pLaser->m_X;
|
|
|
|
m_Pos.y = pLaser->m_Y;
|
|
|
|
m_From.x = pLaser->m_FromX;
|
|
|
|
m_From.y = pLaser->m_FromY;
|
|
|
|
m_EvalTick = pLaser->m_StartTick;
|
2021-04-23 03:01:38 +00:00
|
|
|
m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0;
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Owner = -2;
|
2019-09-15 22:07:42 +00:00
|
|
|
m_Energy = GetTuning(m_TuneZone)->m_LaserReach;
|
2019-04-11 22:46:54 +00:00
|
|
|
if(pGameWorld->m_WorldConfig.m_IsFNG && m_Energy < 10.f)
|
|
|
|
m_Energy = 800.0f;
|
|
|
|
|
|
|
|
m_Dir = m_Pos - m_From;
|
2019-07-08 21:08:42 +00:00
|
|
|
if(length(m_Pos - m_From) > 0.001f)
|
2019-04-11 22:46:54 +00:00
|
|
|
m_Dir = normalize(m_Dir);
|
|
|
|
else
|
|
|
|
m_Energy = 0;
|
2019-11-22 14:37:18 +00:00
|
|
|
m_Type = WEAPON_LASER;
|
2019-04-11 22:46:54 +00:00
|
|
|
m_PrevPos = m_From;
|
|
|
|
m_ID = ID;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CLaser::FillInfo(CNetObj_Laser *pLaser)
|
|
|
|
{
|
|
|
|
pLaser->m_X = (int)m_Pos.x;
|
|
|
|
pLaser->m_Y = (int)m_Pos.y;
|
|
|
|
pLaser->m_FromX = (int)m_From.x;
|
|
|
|
pLaser->m_FromY = (int)m_From.y;
|
|
|
|
pLaser->m_StartTick = m_EvalTick;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CLaser::Match(CLaser *pLaser)
|
|
|
|
{
|
|
|
|
if(pLaser->m_EvalTick != m_EvalTick)
|
|
|
|
return false;
|
|
|
|
if(distance(pLaser->m_From, m_From) > 2.f)
|
|
|
|
return false;
|
|
|
|
const vec2 ThisDiff = m_Pos - m_From;
|
|
|
|
const vec2 OtherDiff = pLaser->m_Pos - pLaser->m_From;
|
2020-09-26 19:41:58 +00:00
|
|
|
const float DirError = distance(normalize(OtherDiff) * length(ThisDiff), ThisDiff);
|
2022-01-22 16:34:23 +00:00
|
|
|
return DirError <= 2.f;
|
2019-04-11 22:46:54 +00:00
|
|
|
}
|