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