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 | |
16 | CDragger::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 | |
35 | void 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 | |
61 | void 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 | |
159 | void CDragger::RemoveDraggerBeam(int ClientId) |
160 | { |
161 | m_apDraggerBeam[ClientId] = nullptr; |
162 | } |
163 | |
164 | bool 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 | |
189 | void CDragger::Reset() |
190 | { |
191 | m_MarkedForDestroy = true; |
192 | } |
193 | |
194 | void 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 | |
244 | void 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 | |