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
14CLaser::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
38bool 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
100void 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
261void CLaser::Reset()
262{
263 m_MarkedForDestroy = true;
264}
265
266void 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
287void CLaser::TickPaused()
288{
289 ++m_EvalTick;
290}
291
292void 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
321void CLaser::SwapClients(int Client1, int Client2)
322{
323 m_Owner = m_Owner == Client1 ? Client2 : m_Owner == Client2 ? Client1 : m_Owner;
324}
325