1/* See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
2#include "dragger_beam.h"
3
4#include "character.h"
5#include "dragger.h"
6
7#include <engine/server.h>
8#include <engine/shared/config.h>
9
10#include <generated/protocol.h>
11
12#include <game/mapitems.h>
13#include <game/server/gamecontext.h>
14#include <game/server/save.h>
15
16CDraggerBeam::CDraggerBeam(CGameWorld *pGameWorld, CDragger *pDragger, vec2 Pos, float Strength, bool IgnoreWalls,
17 int ForClientId, int Layer, int Number) :
18 CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
19{
20 m_pDragger = pDragger;
21 m_Pos = Pos;
22 m_Strength = Strength;
23 m_IgnoreWalls = IgnoreWalls;
24 m_ForClientId = ForClientId;
25 m_Active = true;
26 m_Layer = Layer;
27 m_Number = Number;
28 m_EvalTick = Server()->Tick();
29
30 GameWorld()->InsertEntity(pEntity: this);
31}
32
33void CDraggerBeam::Tick()
34{
35 if(!m_Active)
36 {
37 return;
38 }
39
40 // Drag only if the player is reachable and alive
41 CCharacter *pTarget = GameServer()->GetPlayerChar(ClientId: m_ForClientId);
42 if(!pTarget)
43 {
44 Reset();
45 return;
46 }
47
48 // The following checks are necessary, because the checks in CDragger::LookForPlayersToDrag only take place
49 // after CDraggerBeam::Tick and only every 150ms
50 // When the dragger is disabled for the target player's team, the dragger beam dissolves. The check if a dragger
51 // is disabled is only executed every 150ms, so the beam can stay activated up to 6 extra ticks
52 if(Server()->Tick() % (int)(Server()->TickSpeed() * 0.15f) == 0)
53 {
54 if(m_Layer == LAYER_SWITCH && m_Number > 0 &&
55 !Switchers()[m_Number].m_aStatus[pTarget->Team()])
56 {
57 Reset();
58 return;
59 }
60 }
61
62 // When the dragger can no longer reach the target player, the dragger beam dissolves
63 int IsReachable =
64 m_IgnoreWalls ?
65 !GameServer()->Collision()->IntersectNoLaserNoWalls(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr) :
66 !GameServer()->Collision()->IntersectNoLaser(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr);
67 if(!IsReachable ||
68 distance(a: pTarget->m_Pos, b: m_Pos) >= g_Config.m_SvDraggerRange || !pTarget->IsAlive())
69 {
70 Reset();
71 return;
72 }
73 // In the center of the dragger a tee does not experience speed-up
74 else if(distance(a: pTarget->m_Pos, b: m_Pos) > 28)
75 {
76 pTarget->AddVelocity(Addition: normalize(v: m_Pos - pTarget->m_Pos) * m_Strength);
77 }
78}
79
80void CDraggerBeam::SetPos(vec2 Pos)
81{
82 m_Pos = Pos;
83}
84
85void CDraggerBeam::Reset()
86{
87 m_MarkedForDestroy = true;
88 m_Active = false;
89
90 m_pDragger->RemoveDraggerBeam(ClientId: m_ForClientId);
91}
92
93void CDraggerBeam::Snap(int SnappingClient)
94{
95 if(!m_Active)
96 {
97 return;
98 }
99
100 // Only players who can see the player attached to the dragger can see the dragger beam
101 CCharacter *pTarget = GameServer()->GetPlayerChar(ClientId: m_ForClientId);
102 if(!pTarget || !pTarget->CanSnapCharacter(SnappingClient))
103 {
104 return;
105 }
106 // Only players with the dragger beam in their field of view or who want to see everything will receive the snap
107 vec2 TargetPos = vec2(pTarget->m_Pos.x, pTarget->m_Pos.y);
108 if(distance(a: pTarget->m_Pos, b: m_Pos) >= g_Config.m_SvDraggerRange || NetworkClippedLine(SnappingClient, StartPos: m_Pos, EndPos: TargetPos))
109 {
110 return;
111 }
112
113 int Subtype = (m_IgnoreWalls ? 1 : 0) | (std::clamp(val: round_to_int(f: m_Strength - 1.f), lo: 0, hi: 2) << 1);
114
115 int StartTick = m_EvalTick;
116 if(StartTick < Server()->Tick() - 4)
117 {
118 StartTick = Server()->Tick() - 4;
119 }
120 else if(StartTick > Server()->Tick())
121 {
122 StartTick = Server()->Tick();
123 }
124
125 int SnappingClientVersion = GameServer()->GetClientVersion(ClientId: SnappingClient);
126 if(SnappingClientVersion >= VERSION_DDNET_ENTITY_NETOBJS)
127 {
128 StartTick = -1;
129 }
130
131 int SnapObjId = GetId();
132 if(m_pDragger->WillDraggerBeamUseDraggerId(TargetClientId: m_ForClientId, SnappingClientId: SnappingClient))
133 {
134 SnapObjId = m_pDragger->GetId();
135 }
136
137 GameServer()->SnapLaserObject(Context: CSnapContext(SnappingClientVersion, Server()->IsSixup(ClientId: SnappingClient), SnappingClient), SnapId: SnapObjId,
138 To: TargetPos, From: m_Pos, StartTick, Owner: m_ForClientId, LaserType: LASERTYPE_DRAGGER, Subtype, SwitchNumber: m_Number);
139}
140
141void CDraggerBeam::SwapClients(int Client1, int Client2)
142{
143 m_ForClientId = m_ForClientId == Client1 ? Client2 : (m_ForClientId == Client2 ? Client1 : m_ForClientId);
144}
145
146ESaveResult CDraggerBeam::BlocksSave(int ClientId)
147{
148 return m_ForClientId == ClientId ? ESaveResult::DRAGGER_ACTIVE : ESaveResult::SUCCESS;
149}
150