1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
3 | #include "laser.h" |
4 | #include "character.h" |
5 | |
6 | #include <engine/shared/config.h> |
7 | |
8 | #include <game/generated/protocol.h> |
9 | #include <game/mapitems.h> |
10 | |
11 | #include <game/server/gamecontext.h> |
12 | #include <game/server/gamemodes/DDRace.h> |
13 | |
14 | CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEnergy, int Owner, int Type) : |
15 | CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER) |
16 | { |
17 | m_Pos = Pos; |
18 | m_Owner = Owner; |
19 | m_Energy = StartEnergy; |
20 | m_Dir = Direction; |
21 | m_Bounces = 0; |
22 | m_EvalTick = 0; |
23 | m_TelePos = vec2(0, 0); |
24 | m_WasTele = false; |
25 | m_Type = Type; |
26 | m_TeleportCancelled = false; |
27 | m_IsBlueTeleport = false; |
28 | m_ZeroEnergyBounceInLastTick = false; |
29 | m_TuneZone = GameServer()->Collision()->IsTune(Index: GameServer()->Collision()->GetMapIndex(Pos: m_Pos)); |
30 | CCharacter *pOwnerChar = GameServer()->GetPlayerChar(ClientId: m_Owner); |
31 | m_TeamMask = pOwnerChar ? pOwnerChar->TeamMask() : CClientMask(); |
32 | m_BelongsToPracticeTeam = pOwnerChar && pOwnerChar->Teams()->IsPractice(Team: pOwnerChar->Team()); |
33 | |
34 | GameWorld()->InsertEntity(pEntity: this); |
35 | DoBounce(); |
36 | } |
37 | |
38 | bool CLaser::HitCharacter(vec2 From, vec2 To) |
39 | { |
40 | static const vec2 StackedLaserShotgunBugSpeed = vec2(-2147483648.0f, -2147483648.0f); |
41 | vec2 At; |
42 | CCharacter *pOwnerChar = GameServer()->GetPlayerChar(ClientId: m_Owner); |
43 | CCharacter *pHit; |
44 | bool pDontHitSelf = g_Config.m_SvOldLaser || (m_Bounces == 0 && !m_WasTele); |
45 | |
46 | if(pOwnerChar ? (!pOwnerChar->LaserHitDisabled() && m_Type == WEAPON_LASER) || (!pOwnerChar->ShotgunHitDisabled() && m_Type == WEAPON_SHOTGUN) : g_Config.m_SvHit) |
47 | pHit = GameWorld()->IntersectCharacter(Pos0: m_Pos, Pos1: To, Radius: 0.f, NewPos&: At, pNotThis: pDontHitSelf ? pOwnerChar : 0, CollideWith: m_Owner); |
48 | else |
49 | pHit = GameWorld()->IntersectCharacter(Pos0: m_Pos, Pos1: To, Radius: 0.f, NewPos&: At, pNotThis: pDontHitSelf ? pOwnerChar : 0, CollideWith: m_Owner, pThisOnly: pOwnerChar); |
50 | |
51 | if(!pHit || (pHit == pOwnerChar && g_Config.m_SvOldLaser) || (pHit != pOwnerChar && pOwnerChar ? (pOwnerChar->LaserHitDisabled() && m_Type == WEAPON_LASER) || (pOwnerChar->ShotgunHitDisabled() && m_Type == WEAPON_SHOTGUN) : !g_Config.m_SvHit)) |
52 | return false; |
53 | m_From = From; |
54 | m_Pos = At; |
55 | m_Energy = -1; |
56 | if(m_Type == WEAPON_SHOTGUN) |
57 | { |
58 | float Strength; |
59 | if(!m_TuneZone) |
60 | Strength = Tuning()->m_ShotgunStrength; |
61 | else |
62 | Strength = TuningList()[m_TuneZone].m_ShotgunStrength; |
63 | |
64 | const vec2 &HitPos = pHit->Core()->m_Pos; |
65 | if(!g_Config.m_SvOldLaser) |
66 | { |
67 | if(m_PrevPos != HitPos) |
68 | { |
69 | pHit->AddVelocity(Addition: normalize(v: m_PrevPos - HitPos) * Strength); |
70 | } |
71 | else |
72 | { |
73 | pHit->SetRawVelocity(StackedLaserShotgunBugSpeed); |
74 | } |
75 | } |
76 | else if(g_Config.m_SvOldLaser && pOwnerChar) |
77 | { |
78 | if(pOwnerChar->Core()->m_Pos != HitPos) |
79 | { |
80 | pHit->AddVelocity(Addition: normalize(v: pOwnerChar->Core()->m_Pos - HitPos) * Strength); |
81 | } |
82 | else |
83 | { |
84 | pHit->SetRawVelocity(StackedLaserShotgunBugSpeed); |
85 | } |
86 | } |
87 | else |
88 | { |
89 | // Re-apply move restrictions as a part of 'shotgun bug' reproduction |
90 | pHit->ApplyMoveRestrictions(); |
91 | } |
92 | } |
93 | else if(m_Type == WEAPON_LASER) |
94 | { |
95 | pHit->UnFreeze(); |
96 | } |
97 | return true; |
98 | } |
99 | |
100 | void CLaser::DoBounce() |
101 | { |
102 | m_EvalTick = Server()->Tick(); |
103 | |
104 | if(m_Energy < 0) |
105 | { |
106 | m_MarkedForDestroy = true; |
107 | return; |
108 | } |
109 | m_PrevPos = m_Pos; |
110 | vec2 Coltile; |
111 | |
112 | int Res; |
113 | int z; |
114 | |
115 | if(m_WasTele) |
116 | { |
117 | m_PrevPos = m_TelePos; |
118 | m_Pos = m_TelePos; |
119 | m_TelePos = vec2(0, 0); |
120 | } |
121 | |
122 | vec2 To = m_Pos + m_Dir * m_Energy; |
123 | |
124 | Res = GameServer()->Collision()->IntersectLineTeleWeapon(Pos0: m_Pos, Pos1: To, pOutCollision: &Coltile, pOutBeforeCollision: &To, pTeleNr: &z); |
125 | |
126 | if(Res) |
127 | { |
128 | if(!HitCharacter(From: m_Pos, To)) |
129 | { |
130 | // intersected |
131 | m_From = m_Pos; |
132 | m_Pos = To; |
133 | |
134 | vec2 TempPos = m_Pos; |
135 | vec2 TempDir = m_Dir * 4.0f; |
136 | |
137 | int f = 0; |
138 | if(Res == -1) |
139 | { |
140 | f = GameServer()->Collision()->GetTile(x: round_to_int(f: Coltile.x), y: round_to_int(f: Coltile.y)); |
141 | GameServer()->Collision()->SetCollisionAt(x: round_to_int(f: Coltile.x), y: round_to_int(f: Coltile.y), id: TILE_SOLID); |
142 | } |
143 | GameServer()->Collision()->MovePoint(pInoutPos: &TempPos, pInoutVel: &TempDir, Elasticity: 1.0f, pBounces: 0); |
144 | if(Res == -1) |
145 | { |
146 | GameServer()->Collision()->SetCollisionAt(x: round_to_int(f: Coltile.x), y: round_to_int(f: Coltile.y), id: f); |
147 | } |
148 | m_Pos = TempPos; |
149 | m_Dir = normalize(v: TempDir); |
150 | |
151 | const float Distance = distance(a: m_From, b: m_Pos); |
152 | // Prevent infinite bounces |
153 | if(Distance == 0.0f && m_ZeroEnergyBounceInLastTick) |
154 | { |
155 | m_Energy = -1; |
156 | } |
157 | else if(!m_TuneZone) |
158 | { |
159 | m_Energy -= Distance + Tuning()->m_LaserBounceCost; |
160 | } |
161 | else |
162 | { |
163 | m_Energy -= distance(a: m_From, b: m_Pos) + GameServer()->TuningList()[m_TuneZone].m_LaserBounceCost; |
164 | } |
165 | m_ZeroEnergyBounceInLastTick = Distance == 0.0f; |
166 | |
167 | if(Res == TILE_TELEINWEAPON && !GameServer()->Collision()->TeleOuts(Number: z - 1).empty()) |
168 | { |
169 | int TeleOut = GameServer()->m_World.m_Core.RandomOr0(BelowThis: GameServer()->Collision()->TeleOuts(Number: z - 1).size()); |
170 | m_TelePos = GameServer()->Collision()->TeleOuts(Number: z - 1)[TeleOut]; |
171 | m_WasTele = true; |
172 | } |
173 | else |
174 | { |
175 | m_Bounces++; |
176 | m_WasTele = false; |
177 | } |
178 | |
179 | int BounceNum = Tuning()->m_LaserBounceNum; |
180 | if(m_TuneZone) |
181 | BounceNum = TuningList()[m_TuneZone].m_LaserBounceNum; |
182 | |
183 | if(m_Bounces > BounceNum) |
184 | m_Energy = -1; |
185 | |
186 | GameServer()->CreateSound(Pos: m_Pos, Sound: SOUND_LASER_BOUNCE, Mask: m_TeamMask); |
187 | } |
188 | } |
189 | else |
190 | { |
191 | if(!HitCharacter(From: m_Pos, To)) |
192 | { |
193 | m_From = m_Pos; |
194 | m_Pos = To; |
195 | m_Energy = -1; |
196 | } |
197 | } |
198 | |
199 | CCharacter *pOwnerChar = GameServer()->GetPlayerChar(ClientId: m_Owner); |
200 | if(m_Owner >= 0 && m_Energy <= 0 && !m_TeleportCancelled && pOwnerChar && |
201 | pOwnerChar->IsAlive() && pOwnerChar->HasTelegunLaser() && m_Type == WEAPON_LASER) |
202 | { |
203 | vec2 PossiblePos; |
204 | bool Found = false; |
205 | |
206 | // Check if the laser hits a player. |
207 | bool pDontHitSelf = g_Config.m_SvOldLaser || (m_Bounces == 0 && !m_WasTele); |
208 | vec2 At; |
209 | CCharacter *pHit; |
210 | if(pOwnerChar ? (!pOwnerChar->LaserHitDisabled() && m_Type == WEAPON_LASER) : g_Config.m_SvHit) |
211 | pHit = GameServer()->m_World.IntersectCharacter(Pos0: m_Pos, Pos1: To, Radius: 0.f, NewPos&: At, pNotThis: pDontHitSelf ? pOwnerChar : 0, CollideWith: m_Owner); |
212 | else |
213 | pHit = GameServer()->m_World.IntersectCharacter(Pos0: m_Pos, Pos1: To, Radius: 0.f, NewPos&: At, pNotThis: pDontHitSelf ? pOwnerChar : 0, CollideWith: m_Owner, pThisOnly: pOwnerChar); |
214 | |
215 | if(pHit) |
216 | Found = GetNearestAirPosPlayer(PlayerPos: pHit->m_Pos, pOutPos: &PossiblePos); |
217 | else |
218 | Found = GetNearestAirPos(Pos: m_Pos, PrevPos: m_From, pOutPos: &PossiblePos); |
219 | |
220 | if(Found) |
221 | { |
222 | pOwnerChar->m_TeleGunPos = PossiblePos; |
223 | pOwnerChar->m_TeleGunTeleport = true; |
224 | pOwnerChar->m_IsBlueTeleGunTeleport = m_IsBlueTeleport; |
225 | } |
226 | } |
227 | else if(m_Owner >= 0) |
228 | { |
229 | int MapIndex = GameServer()->Collision()->GetPureMapIndex(Pos: Coltile); |
230 | int TileFIndex = GameServer()->Collision()->GetFTileIndex(Index: MapIndex); |
231 | bool IsSwitchTeleGun = GameServer()->Collision()->GetSwitchType(Index: MapIndex) == TILE_ALLOW_TELE_GUN; |
232 | bool IsBlueSwitchTeleGun = GameServer()->Collision()->GetSwitchType(Index: MapIndex) == TILE_ALLOW_BLUE_TELE_GUN; |
233 | int IsTeleInWeapon = GameServer()->Collision()->IsTeleportWeapon(Index: MapIndex); |
234 | |
235 | if(!IsTeleInWeapon) |
236 | { |
237 | if(IsSwitchTeleGun || IsBlueSwitchTeleGun) |
238 | { |
239 | // Delay specifies which weapon the tile should work for. |
240 | // Delay = 0 means all. |
241 | int delay = GameServer()->Collision()->GetSwitchDelay(Index: MapIndex); |
242 | |
243 | if((delay != 3 && delay != 0) && m_Type == WEAPON_LASER) |
244 | { |
245 | IsSwitchTeleGun = IsBlueSwitchTeleGun = false; |
246 | } |
247 | } |
248 | |
249 | m_IsBlueTeleport = TileFIndex == TILE_ALLOW_BLUE_TELE_GUN || IsBlueSwitchTeleGun; |
250 | |
251 | // Teleport is canceled if the last bounce tile is not a TILE_ALLOW_TELE_GUN. |
252 | // Teleport also works if laser didn't bounce. |
253 | m_TeleportCancelled = |
254 | m_Type == WEAPON_LASER && (TileFIndex != TILE_ALLOW_TELE_GUN && TileFIndex != TILE_ALLOW_BLUE_TELE_GUN && !IsSwitchTeleGun && !IsBlueSwitchTeleGun); |
255 | } |
256 | } |
257 | |
258 | //m_Owner = -1; |
259 | } |
260 | |
261 | void CLaser::Reset() |
262 | { |
263 | m_MarkedForDestroy = true; |
264 | } |
265 | |
266 | void CLaser::Tick() |
267 | { |
268 | if((g_Config.m_SvDestroyLasersOnDeath || m_BelongsToPracticeTeam) && m_Owner >= 0) |
269 | { |
270 | CCharacter *pOwnerChar = GameServer()->GetPlayerChar(ClientId: m_Owner); |
271 | if(!(pOwnerChar && pOwnerChar->IsAlive())) |
272 | { |
273 | Reset(); |
274 | } |
275 | } |
276 | |
277 | float Delay; |
278 | if(m_TuneZone) |
279 | Delay = TuningList()[m_TuneZone].m_LaserBounceDelay; |
280 | else |
281 | Delay = Tuning()->m_LaserBounceDelay; |
282 | |
283 | if((Server()->Tick() - m_EvalTick) > (Server()->TickSpeed() * Delay / 1000.0f)) |
284 | DoBounce(); |
285 | } |
286 | |
287 | void CLaser::TickPaused() |
288 | { |
289 | ++m_EvalTick; |
290 | } |
291 | |
292 | void CLaser::Snap(int SnappingClient) |
293 | { |
294 | if(NetworkClipped(SnappingClient) && NetworkClipped(SnappingClient, CheckPos: m_From)) |
295 | return; |
296 | CCharacter *pOwnerChar = 0; |
297 | if(m_Owner >= 0) |
298 | pOwnerChar = GameServer()->GetPlayerChar(ClientId: m_Owner); |
299 | if(!pOwnerChar) |
300 | return; |
301 | |
302 | pOwnerChar = nullptr; |
303 | CClientMask TeamMask = CClientMask().set(); |
304 | |
305 | if(m_Owner >= 0) |
306 | pOwnerChar = GameServer()->GetPlayerChar(ClientId: m_Owner); |
307 | |
308 | if(pOwnerChar && pOwnerChar->IsAlive()) |
309 | TeamMask = pOwnerChar->TeamMask(); |
310 | |
311 | if(SnappingClient != SERVER_DEMO_CLIENT && !TeamMask.test(position: SnappingClient)) |
312 | return; |
313 | |
314 | int SnappingClientVersion = GameServer()->GetClientVersion(ClientId: SnappingClient); |
315 | int LaserType = m_Type == WEAPON_LASER ? LASERTYPE_RIFLE : m_Type == WEAPON_SHOTGUN ? LASERTYPE_SHOTGUN : -1; |
316 | |
317 | GameServer()->SnapLaserObject(Context: CSnapContext(SnappingClientVersion), SnapId: GetId(), |
318 | To: m_Pos, From: m_From, StartTick: m_EvalTick, Owner: m_Owner, LaserType, Subtype: 0, SwitchNumber: m_Number); |
319 | } |
320 | |
321 | void CLaser::SwapClients(int Client1, int Client2) |
322 | { |
323 | m_Owner = m_Owner == Client1 ? Client2 : m_Owner == Client2 ? Client1 : m_Owner; |
324 | } |
325 | |