1/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
2#include "dragger.h"
3
4#include "character.h"
5#include "dragger_beam.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/player.h>
15#include <game/server/teams.h>
16
17CDragger::CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool IgnoreWalls, int Layer, int Number) :
18 CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
19{
20 m_Core = vec2(0.0f, 0.0f);
21 m_Pos = Pos;
22 m_Strength = Strength;
23 m_IgnoreWalls = IgnoreWalls;
24 m_Layer = Layer;
25 m_Number = Number;
26 m_EvalTick = Server()->Tick();
27
28 for(auto &TargetId : m_aTargetIdInTeam)
29 {
30 TargetId = -1;
31 }
32 std::fill(first: std::begin(arr&: m_apDraggerBeam), last: std::end(arr&: m_apDraggerBeam), value: nullptr);
33 GameWorld()->InsertEntity(pEntity: this);
34}
35
36void CDragger::Tick()
37{
38 if(Server()->Tick() % (int)(Server()->TickSpeed() * 0.15f) == 0)
39 {
40 m_EvalTick = Server()->Tick();
41 GameServer()->Collision()->MoverSpeed(x: m_Pos.x, y: m_Pos.y, pSpeed: &m_Core);
42 m_Pos += m_Core;
43
44 // Adopt the new position for all outgoing laser beams
45 for(auto &DraggerBeam : m_apDraggerBeam)
46 {
47 if(DraggerBeam != nullptr)
48 {
49 DraggerBeam->SetPos(m_Pos);
50 }
51 }
52
53 LookForPlayersToDrag();
54 }
55}
56
57void CDragger::LookForPlayersToDrag()
58{
59 // Create a list of players who are in the range of the dragger
60 CEntity *apPlayersInRange[MAX_CLIENTS];
61 std::fill(first: std::begin(arr&: apPlayersInRange), last: std::end(arr&: apPlayersInRange), value: nullptr);
62
63 int NumPlayersInRange = GameServer()->m_World.FindEntities(Pos: m_Pos,
64 Radius: g_Config.m_SvDraggerRange - CCharacterCore::PhysicalSize(),
65 ppEnts: apPlayersInRange, Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER);
66
67 // The closest player (within range) in a team is selected as the target
68 int aClosestTargetIdInTeam[MAX_CLIENTS];
69 bool aCanStillBeTeamTarget[MAX_CLIENTS];
70 bool aIsTarget[MAX_CLIENTS];
71 int aMinDistInTeam[MAX_CLIENTS];
72 std::fill(first: std::begin(arr&: aCanStillBeTeamTarget), last: std::end(arr&: aCanStillBeTeamTarget), value: false);
73 std::fill(first: std::begin(arr&: aMinDistInTeam), last: std::end(arr&: aMinDistInTeam), value: 0);
74 std::fill(first: std::begin(arr&: aIsTarget), last: std::end(arr&: aIsTarget), value: false);
75 std::fill(first: std::begin(arr&: aClosestTargetIdInTeam), last: std::end(arr&: aClosestTargetIdInTeam), value: -1);
76
77 for(int i = 0; i < NumPlayersInRange; i++)
78 {
79 CCharacter *pTarget = static_cast<CCharacter *>(apPlayersInRange[i]);
80 const int &TargetTeam = pTarget->Team();
81
82 // Do not create a dragger beam for super player
83 if(TargetTeam == TEAM_SUPER)
84 {
85 continue;
86 }
87 // If the dragger is disabled for the target's team, no dragger beam will be generated
88 if(m_Layer == LAYER_SWITCH && m_Number > 0 &&
89 !Switchers()[m_Number].m_aStatus[TargetTeam])
90 {
91 continue;
92 }
93
94 // Dragger beams can be created only for reachable, alive players
95 int IsReachable =
96 m_IgnoreWalls ?
97 !GameServer()->Collision()->IntersectNoLaserNoWalls(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr) :
98 !GameServer()->Collision()->IntersectNoLaser(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr);
99 if(IsReachable && pTarget->IsAlive())
100 {
101 const int &TargetClientId = pTarget->GetPlayer()->GetCid();
102 // Solo players are dragged independently from the rest of the team
103 if(pTarget->Teams()->m_Core.GetSolo(ClientId: TargetClientId))
104 {
105 aIsTarget[TargetClientId] = true;
106 }
107 else
108 {
109 int Distance = distance(a: pTarget->m_Pos, b: m_Pos);
110 if(aMinDistInTeam[TargetTeam] == 0 || aMinDistInTeam[TargetTeam] > Distance)
111 {
112 aMinDistInTeam[TargetTeam] = Distance;
113 aClosestTargetIdInTeam[TargetTeam] = TargetClientId;
114 }
115 aCanStillBeTeamTarget[TargetClientId] = true;
116 }
117 }
118 }
119
120 // Set the closest player for each team as a target if the team does not have a target player yet
121 for(int i = 0; i < MAX_CLIENTS; i++)
122 {
123 if((m_aTargetIdInTeam[i] != -1 && !aCanStillBeTeamTarget[m_aTargetIdInTeam[i]]) || m_aTargetIdInTeam[i] == -1)
124 {
125 m_aTargetIdInTeam[i] = aClosestTargetIdInTeam[i];
126 }
127 if(m_aTargetIdInTeam[i] != -1)
128 {
129 aIsTarget[m_aTargetIdInTeam[i]] = true;
130 }
131 }
132
133 for(int i = 0; i < MAX_CLIENTS; i++)
134 {
135 // Create Dragger Beams which have not been created yet
136 if(aIsTarget[i] && m_apDraggerBeam[i] == nullptr)
137 {
138 m_apDraggerBeam[i] = new CDraggerBeam(&GameServer()->m_World, this, m_Pos, m_Strength, m_IgnoreWalls, i, m_Layer, m_Number);
139 // The generated dragger beam is placed in the first position in the tick sequence and would therefore
140 // no longer be executed automatically in this tick. To execute the dragger beam nevertheless already
141 // this tick we call it manually (we do this to keep the old game logic)
142 m_apDraggerBeam[i]->Tick();
143 }
144 // Remove dragger beams that have not yet been deleted
145 else if(!aIsTarget[i] && m_apDraggerBeam[i] != nullptr)
146 {
147 m_apDraggerBeam[i]->Reset();
148 }
149 }
150}
151
152void CDragger::RemoveDraggerBeam(int ClientId)
153{
154 m_apDraggerBeam[ClientId] = nullptr;
155}
156
157bool CDragger::WillDraggerBeamUseDraggerId(int TargetClientId, int SnappingClientId)
158{
159 // For each snapping client, this must return true for at most one target (i.e. only one of the dragger beams),
160 // in which case the dragger itself must not be snapped
161 CCharacter *pTargetChar = GameServer()->GetPlayerChar(ClientId: TargetClientId);
162 CCharacter *pSnapChar = GameServer()->GetPlayerChar(ClientId: SnappingClientId);
163 if(pTargetChar && pSnapChar && m_apDraggerBeam[TargetClientId] != nullptr)
164 {
165 const int SnapTeam = pSnapChar->Team();
166 const int TargetTeam = pTargetChar->Team();
167 if(SnapTeam == TargetTeam && SnapTeam < MAX_CLIENTS)
168 {
169 if(pSnapChar->Teams()->m_Core.GetSolo(ClientId: SnappingClientId) || m_aTargetIdInTeam[SnapTeam] < 0)
170 {
171 return SnappingClientId == TargetClientId;
172 }
173 else
174 {
175 return m_aTargetIdInTeam[SnapTeam] == TargetClientId;
176 }
177 }
178 }
179 return false;
180}
181
182void CDragger::Reset()
183{
184 m_MarkedForDestroy = true;
185}
186
187void CDragger::Snap(int SnappingClient)
188{
189 // Only players with the dragger in their field of view or who want to see everything will receive the snap
190 if(NetworkClipped(SnappingClient))
191 return;
192
193 // Send the dragger in its resting position if the player would not otherwise see a dragger beam within its own team
194 for(int i = 0; i < MAX_CLIENTS; i++)
195 {
196 if(WillDraggerBeamUseDraggerId(TargetClientId: i, SnappingClientId: SnappingClient))
197 {
198 return;
199 }
200 }
201
202 int SnappingClientVersion = GameServer()->GetClientVersion(ClientId: SnappingClient);
203
204 int Subtype = (m_IgnoreWalls ? 1 : 0) | (std::clamp(val: round_to_int(f: m_Strength - 1.f), lo: 0, hi: 2) << 1);
205
206 int StartTick;
207 if(SnappingClientVersion >= VERSION_DDNET_ENTITY_NETOBJS)
208 {
209 StartTick = -1;
210 }
211 else
212 {
213 // Emulate turned off blinking dragger for old clients
214 CCharacter *pChar = GameServer()->GetPlayerChar(ClientId: SnappingClient);
215 if(SnappingClient != SERVER_DEMO_CLIENT &&
216 (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == TEAM_SPECTATORS ||
217 GameServer()->m_apPlayers[SnappingClient]->IsPaused()) &&
218 GameServer()->m_apPlayers[SnappingClient]->SpectatorId() != SPEC_FREEVIEW)
219 pChar = GameServer()->GetPlayerChar(ClientId: GameServer()->m_apPlayers[SnappingClient]->SpectatorId());
220
221 int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
222 if(pChar && m_Layer == LAYER_SWITCH && m_Number > 0 &&
223 !Switchers()[m_Number].m_aStatus[pChar->Team()] && !Tick)
224 return;
225
226 StartTick = m_EvalTick;
227 if(StartTick < Server()->Tick() - 4)
228 StartTick = Server()->Tick() - 4;
229 else if(StartTick > Server()->Tick())
230 StartTick = Server()->Tick();
231 }
232
233 GameServer()->SnapLaserObject(Context: CSnapContext(SnappingClientVersion, Server()->IsSixup(ClientId: SnappingClient), SnappingClient), SnapId: GetId(),
234 To: m_Pos, From: m_Pos, StartTick, Owner: -1, LaserType: LASERTYPE_DRAGGER, Subtype, SwitchNumber: m_Number);
235}
236
237void CDragger::SwapClients(int Client1, int Client2)
238{
239 std::swap(a&: m_apDraggerBeam[Client1], b&: m_apDraggerBeam[Client2]);
240 for(int &TargetId : m_aTargetIdInTeam)
241 {
242 TargetId = TargetId == Client1 ? Client2 : (TargetId == Client2 ? Client1 : TargetId);
243 }
244}
245