ddnet/src/game/server/entities/projectile.cpp
bors[bot] 2b5723cdef
Merge #3492 #3559 #3561 #3565
3492: Support F-Client and replace non-extended extension messages r=Learath2 a=heinrich5991

This continues what #3439 started, and also replaces non-extended messages that are DDRace extensions with extended messages. The DDNet server still sends the old message to old clients and the DDNet client still understands the old messages.

Supersedes #3439.

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [x] Considered possible null pointers and out of bounds array indexing
- [x] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


3559: Don't highlight us if we have no name (alternative fix for #3554) r=heinrich5991 a=def-

<!-- What is the motivation for the changes of this pull request -->

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [x] Considered possible null pointers and out of bounds array indexing
- [x] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


3561: Fix input message position in teehistorian files r=heinrich5991 a=Zwelf

Previously the message was recorded when incoming. But the time when the input should apply wasn't recorded. Now inputs are recorded right before they get applied in the game world.

And record exact tick where load/save result is returned.

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [x] Considered possible null pointers and out of bounds array indexing
- [x] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


3565: Add Arabic language translation by .Yukki r=heinrich5991 a=def-

![screenshot-20210131@093216](https://user-images.githubusercontent.com/2335377/106378755-747d7780-63a7-11eb-9553-6a1898907441.png)

## Checklist

- [x] Tested the change ingame
- [x] Provided screenshots if it is a visual change
- [x] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [x] Considered possible null pointers and out of bounds array indexing
- [x] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: fokkonaut <35420825+fokkonaut@users.noreply.github.com>
Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
Co-authored-by: def <dennis@felsin9.de>
Co-authored-by: Zwelf <zwelf@strct.cc>
2021-01-31 14:37:48 +00:00

386 lines
11 KiB
C++

/* (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 "projectile.h"
#include <game/generated/protocol.h>
#include <game/server/gamecontext.h>
#include <game/server/gamemodes/DDRace.h>
#include <game/server/player.h>
#include <game/version.h>
#include <engine/shared/config.h>
#include <game/server/teams.h>
#include "character.h"
CProjectile::CProjectile(
CGameWorld *pGameWorld,
int Type,
int Owner,
vec2 Pos,
vec2 Dir,
int Span,
bool Freeze,
bool Explosive,
float Force,
int SoundImpact,
int Layer,
int Number) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_PROJECTILE)
{
m_Type = Type;
m_Pos = Pos;
m_Direction = Dir;
m_LifeSpan = Span;
m_Owner = Owner;
m_Force = Force;
//m_Damage = Damage;
m_SoundImpact = SoundImpact;
m_StartTick = Server()->Tick();
m_Explosive = Explosive;
m_Layer = Layer;
m_Number = Number;
m_Freeze = Freeze;
m_TuneZone = GameServer()->Collision()->IsTune(GameServer()->Collision()->GetMapIndex(m_Pos));
GameWorld()->InsertEntity(this);
}
void CProjectile::Reset()
{
if(m_LifeSpan > -2)
GameServer()->m_World.DestroyEntity(this);
}
vec2 CProjectile::GetPos(float Time)
{
float Curvature = 0;
float Speed = 0;
switch(m_Type)
{
case WEAPON_GRENADE:
if(!m_TuneZone)
{
Curvature = GameServer()->Tuning()->m_GrenadeCurvature;
Speed = GameServer()->Tuning()->m_GrenadeSpeed;
}
else
{
Curvature = GameServer()->TuningList()[m_TuneZone].m_GrenadeCurvature;
Speed = GameServer()->TuningList()[m_TuneZone].m_GrenadeSpeed;
}
break;
case WEAPON_SHOTGUN:
if(!m_TuneZone)
{
Curvature = GameServer()->Tuning()->m_ShotgunCurvature;
Speed = GameServer()->Tuning()->m_ShotgunSpeed;
}
else
{
Curvature = GameServer()->TuningList()[m_TuneZone].m_ShotgunCurvature;
Speed = GameServer()->TuningList()[m_TuneZone].m_ShotgunSpeed;
}
break;
case WEAPON_GUN:
if(!m_TuneZone)
{
Curvature = GameServer()->Tuning()->m_GunCurvature;
Speed = GameServer()->Tuning()->m_GunSpeed;
}
else
{
Curvature = GameServer()->TuningList()[m_TuneZone].m_GunCurvature;
Speed = GameServer()->TuningList()[m_TuneZone].m_GunSpeed;
}
break;
}
return CalcPos(m_Pos, m_Direction, Curvature, Speed, Time);
}
void CProjectile::Tick()
{
float Pt = (Server()->Tick() - m_StartTick - 1) / (float)Server()->TickSpeed();
float Ct = (Server()->Tick() - m_StartTick) / (float)Server()->TickSpeed();
vec2 PrevPos = GetPos(Pt);
vec2 CurPos = GetPos(Ct);
vec2 ColPos;
vec2 NewPos;
int Collide = GameServer()->Collision()->IntersectLine(PrevPos, CurPos, &ColPos, &NewPos);
CCharacter *pOwnerChar = 0;
if(m_Owner >= 0)
pOwnerChar = GameServer()->GetPlayerChar(m_Owner);
CCharacter *pTargetChr = 0;
if(pOwnerChar ? !(pOwnerChar->m_Hit & CCharacter::DISABLE_HIT_GRENADE) : g_Config.m_SvHit)
pTargetChr = GameServer()->m_World.IntersectCharacter(PrevPos, ColPos, m_Freeze ? 1.0f : 6.0f, ColPos, pOwnerChar, m_Owner);
if(m_LifeSpan > -1)
m_LifeSpan--;
int64 TeamMask = -1LL;
bool IsWeaponCollide = false;
if(
pOwnerChar &&
pTargetChr &&
pOwnerChar->IsAlive() &&
pTargetChr->IsAlive() &&
!pTargetChr->CanCollide(m_Owner))
{
IsWeaponCollide = true;
}
if(pOwnerChar && pOwnerChar->IsAlive())
{
TeamMask = pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner);
}
else if(m_Owner >= 0 && (m_Type != WEAPON_GRENADE || g_Config.m_SvDestroyBulletsOnDeath))
{
GameServer()->m_World.DestroyEntity(this);
return;
}
if(((pTargetChr && (pOwnerChar ? !(pOwnerChar->m_Hit & CCharacter::DISABLE_HIT_GRENADE) : g_Config.m_SvHit || m_Owner == -1 || pTargetChr == pOwnerChar)) || Collide || GameLayerClipped(CurPos)) && !IsWeaponCollide)
{
if(m_Explosive /*??*/ && (!pTargetChr || (pTargetChr && (!m_Freeze || (m_Type == WEAPON_SHOTGUN && Collide)))))
{
int Number = 1;
if(GameServer()->EmulateBug(BUG_GRENADE_DOUBLEEXPLOSION) && m_LifeSpan == -1)
{
Number = 2;
}
for(int i = 0; i < Number; i++)
{
GameServer()->CreateExplosion(ColPos, m_Owner, m_Type, m_Owner == -1, (!pTargetChr ? -1 : pTargetChr->Team()),
(m_Owner != -1) ? TeamMask : -1LL);
GameServer()->CreateSound(ColPos, m_SoundImpact,
(m_Owner != -1) ? TeamMask : -1LL);
}
}
else if(m_Freeze)
{
CCharacter *apEnts[MAX_CLIENTS];
int Num = GameWorld()->FindEntities(CurPos, 1.0f, (CEntity **)apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
for(int i = 0; i < Num; ++i)
if(apEnts[i] && (m_Layer != LAYER_SWITCH || (m_Layer == LAYER_SWITCH && m_Number > 0 && GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[apEnts[i]->Team()])))
apEnts[i]->Freeze();
}
if(pOwnerChar && !GameLayerClipped(ColPos) &&
((m_Type == WEAPON_GRENADE && pOwnerChar->HasTelegunGrenade()) || (m_Type == WEAPON_GUN && pOwnerChar->HasTelegunGun())))
{
int MapIndex = GameServer()->Collision()->GetPureMapIndex(pTargetChr ? pTargetChr->m_Pos : ColPos);
int TileFIndex = GameServer()->Collision()->GetFTileIndex(MapIndex);
bool IsSwitchTeleGun = GameServer()->Collision()->IsSwitch(MapIndex) == TILE_ALLOW_TELE_GUN;
bool IsBlueSwitchTeleGun = GameServer()->Collision()->IsSwitch(MapIndex) == TILE_ALLOW_BLUE_TELE_GUN;
if(IsSwitchTeleGun || IsBlueSwitchTeleGun)
{
// Delay specifies which weapon the tile should work for.
// Delay = 0 means all.
int delay = GameServer()->Collision()->GetSwitchDelay(MapIndex);
if(delay == 1 && m_Type != WEAPON_GUN)
IsSwitchTeleGun = IsBlueSwitchTeleGun = false;
if(delay == 2 && m_Type != WEAPON_GRENADE)
IsSwitchTeleGun = IsBlueSwitchTeleGun = false;
if(delay == 3 && m_Type != WEAPON_LASER)
IsSwitchTeleGun = IsBlueSwitchTeleGun = false;
}
if(TileFIndex == TILE_ALLOW_TELE_GUN || TileFIndex == TILE_ALLOW_BLUE_TELE_GUN || IsSwitchTeleGun || IsBlueSwitchTeleGun || pTargetChr)
{
bool Found;
vec2 PossiblePos;
if(!Collide)
Found = GetNearestAirPosPlayer(pTargetChr ? pTargetChr->m_Pos : ColPos, &PossiblePos);
else
Found = GetNearestAirPos(NewPos, CurPos, &PossiblePos);
if(Found)
{
pOwnerChar->m_TeleGunPos = PossiblePos;
pOwnerChar->m_TeleGunTeleport = true;
pOwnerChar->m_IsBlueTeleGunTeleport = TileFIndex == TILE_ALLOW_BLUE_TELE_GUN || IsBlueSwitchTeleGun;
}
}
}
if(Collide && m_Bouncing != 0)
{
m_StartTick = Server()->Tick();
m_Pos = NewPos + (-(m_Direction * 4));
if(m_Bouncing == 1)
m_Direction.x = -m_Direction.x;
else if(m_Bouncing == 2)
m_Direction.y = -m_Direction.y;
if(fabs(m_Direction.x) < 1e-6)
m_Direction.x = 0;
if(fabs(m_Direction.y) < 1e-6)
m_Direction.y = 0;
m_Pos += m_Direction;
}
else if(m_Type == WEAPON_GUN)
{
GameServer()->CreateDamageInd(CurPos, -atan2(m_Direction.x, m_Direction.y), 10, (m_Owner != -1) ? TeamMask : -1LL);
GameServer()->m_World.DestroyEntity(this);
return;
}
else
{
if(!m_Freeze)
{
GameServer()->m_World.DestroyEntity(this);
return;
}
}
}
if(m_LifeSpan == -1)
{
if(m_Explosive)
{
if(m_Owner >= 0)
pOwnerChar = GameServer()->GetPlayerChar(m_Owner);
int64 TeamMask = -1LL;
if(pOwnerChar && pOwnerChar->IsAlive())
{
TeamMask = pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner);
}
GameServer()->CreateExplosion(ColPos, m_Owner, m_Type, m_Owner == -1, (!pOwnerChar ? -1 : pOwnerChar->Team()),
(m_Owner != -1) ? TeamMask : -1LL);
GameServer()->CreateSound(ColPos, m_SoundImpact,
(m_Owner != -1) ? TeamMask : -1LL);
}
GameServer()->m_World.DestroyEntity(this);
return;
}
int x = GameServer()->Collision()->GetIndex(PrevPos, CurPos);
int z;
if(g_Config.m_SvOldTeleportWeapons)
z = GameServer()->Collision()->IsTeleport(x);
else
z = GameServer()->Collision()->IsTeleportWeapon(x);
CGameControllerDDRace *pControllerDDRace = (CGameControllerDDRace *)GameServer()->m_pController;
if(z && pControllerDDRace->m_TeleOuts[z - 1].size())
{
int TeleOut = GameServer()->m_World.m_Core.RandomOr0(pControllerDDRace->m_TeleOuts[z - 1].size());
m_Pos = pControllerDDRace->m_TeleOuts[z - 1][TeleOut];
m_StartTick = Server()->Tick();
}
}
void CProjectile::TickPaused()
{
++m_StartTick;
}
void CProjectile::FillInfo(CNetObj_Projectile *pProj)
{
pProj->m_X = (int)m_Pos.x;
pProj->m_Y = (int)m_Pos.y;
pProj->m_VelX = (int)(m_Direction.x * 100.0f);
pProj->m_VelY = (int)(m_Direction.y * 100.0f);
pProj->m_StartTick = m_StartTick;
pProj->m_Type = m_Type;
}
void CProjectile::Snap(int SnappingClient)
{
float Ct = (Server()->Tick() - m_StartTick) / (float)Server()->TickSpeed();
if(NetworkClipped(SnappingClient, GetPos(Ct)))
return;
CCharacter *pSnapChar = GameServer()->GetPlayerChar(SnappingClient);
int Tick = (Server()->Tick() % Server()->TickSpeed()) % ((m_Explosive) ? 6 : 20);
if(pSnapChar && pSnapChar->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pSnapChar->Team()] && (!Tick)))
return;
CCharacter *pOwnerChar = 0;
int64 TeamMask = -1LL;
if(m_Owner >= 0)
pOwnerChar = GameServer()->GetPlayerChar(m_Owner);
if(pOwnerChar && pOwnerChar->IsAlive())
TeamMask = pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner);
if(m_Owner != -1 && !CmaskIsSet(TeamMask, SnappingClient))
return;
int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
CNetObj_DDNetProjectile DDNetProjectile;
if(SnappingClientVersion >= VERSION_DDNET_ANTIPING_PROJECTILE && FillExtraInfo(&DDNetProjectile))
{
int Type = SnappingClientVersion <= VERSION_DDNET_MSG_LEGACY ? (int)NETOBJTYPE_PROJECTILE : NETOBJTYPE_DDNETPROJECTILE;
void *pProj = Server()->SnapNewItem(Type, GetID(), sizeof(DDNetProjectile));
if(!pProj)
{
return;
}
mem_copy(pProj, &DDNetProjectile, sizeof(DDNetProjectile));
}
else
{
CNetObj_Projectile *pProj = static_cast<CNetObj_Projectile *>(Server()->SnapNewItem(NETOBJTYPE_PROJECTILE, GetID(), sizeof(CNetObj_Projectile)));
if(!pProj)
{
return;
}
FillInfo(pProj);
}
}
// DDRace
void CProjectile::SetBouncing(int Value)
{
m_Bouncing = Value;
}
bool CProjectile::FillExtraInfo(CNetObj_DDNetProjectile *pProj)
{
const int MaxPos = 0x7fffffff / 100;
if(abs((int)m_Pos.y) + 1 >= MaxPos || abs((int)m_Pos.x) + 1 >= MaxPos)
{
//If the modified data would be too large to fit in an integer, send normal data instead
return false;
}
//Send additional/modified info, by modifiying the fields of the netobj
float Angle = -atan2f(m_Direction.x, m_Direction.y);
int Data = 0;
Data |= (abs(m_Owner) & 255) << 0;
if(m_Owner < 0)
Data |= PROJECTILEFLAG_NO_OWNER;
//This bit tells the client to use the extra info
Data |= PROJECTILEFLAG_IS_DDNET;
// PROJECTILEFLAG_BOUNCE_HORIZONTAL, PROJECTILEFLAG_BOUNCE_VERTICAL
Data |= (m_Bouncing & 3) << 10;
if(m_Explosive)
Data |= PROJECTILEFLAG_EXPLOSIVE;
if(m_Freeze)
Data |= PROJECTILEFLAG_FREEZE;
pProj->m_X = (int)(m_Pos.x * 100.0f);
pProj->m_Y = (int)(m_Pos.y * 100.0f);
pProj->m_Angle = (int)(Angle * 1000000.0f);
pProj->m_Data = Data;
pProj->m_StartTick = m_StartTick;
pProj->m_Type = m_Type;
return true;
}