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