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 | #include <game/client/laser_data.h> |
6 | #include <game/collision.h> |
7 | #include <game/generated/protocol.h> |
8 | #include <game/mapitems.h> |
9 | |
10 | #include <engine/shared/config.h> |
11 | |
12 | CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEnergy, int Owner, int Type) : |
13 | CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER) |
14 | { |
15 | m_Pos = Pos; |
16 | m_Owner = Owner; |
17 | m_Energy = StartEnergy; |
18 | if(pGameWorld->m_WorldConfig.m_IsFNG && m_Energy < 10.f) |
19 | m_Energy = 800.0f; |
20 | m_Dir = Direction; |
21 | m_Bounces = 0; |
22 | m_EvalTick = 0; |
23 | m_Type = Type; |
24 | m_ZeroEnergyBounceInLastTick = false; |
25 | m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Index: Collision()->GetMapIndex(Pos: m_Pos)) : 0; |
26 | GameWorld()->InsertEntity(pEntity: this); |
27 | DoBounce(); |
28 | } |
29 | |
30 | bool CLaser::HitCharacter(vec2 From, vec2 To) |
31 | { |
32 | static const vec2 StackedLaserShotgunBugSpeed = vec2(-2147483648.0f, -2147483648.0f); |
33 | vec2 At; |
34 | CCharacter *pOwnerChar = GameWorld()->GetCharacterById(Id: m_Owner); |
35 | CCharacter *pHit; |
36 | bool DontHitSelf = (g_Config.m_SvOldLaser || !GameWorld()->m_WorldConfig.m_IsDDRace) || (m_Bounces == 0); |
37 | |
38 | if(pOwnerChar ? (!pOwnerChar->LaserHitDisabled() && m_Type == WEAPON_LASER) || (!pOwnerChar->ShotgunHitDisabled() && m_Type == WEAPON_SHOTGUN) : g_Config.m_SvHit) |
39 | pHit = GameWorld()->IntersectCharacter(Pos0: m_Pos, Pos1: To, Radius: 0.f, NewPos&: At, pNotThis: DontHitSelf ? pOwnerChar : 0, CollideWith: m_Owner); |
40 | else |
41 | pHit = GameWorld()->IntersectCharacter(Pos0: m_Pos, Pos1: To, Radius: 0.f, NewPos&: At, pNotThis: DontHitSelf ? pOwnerChar : 0, CollideWith: m_Owner, pThisOnly: pOwnerChar); |
42 | |
43 | 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)) |
44 | return false; |
45 | m_From = From; |
46 | m_Pos = At; |
47 | m_Energy = -1; |
48 | if(m_Type == WEAPON_SHOTGUN) |
49 | { |
50 | float Strength; |
51 | if(!m_TuneZone) |
52 | Strength = Tuning()->m_ShotgunStrength; |
53 | else |
54 | Strength = TuningList()[m_TuneZone].m_ShotgunStrength; |
55 | |
56 | const vec2 &HitPos = pHit->Core()->m_Pos; |
57 | if(!g_Config.m_SvOldLaser) |
58 | { |
59 | if(m_PrevPos != HitPos) |
60 | { |
61 | pHit->AddVelocity(Addition: normalize(v: m_PrevPos - HitPos) * Strength); |
62 | } |
63 | else |
64 | { |
65 | pHit->SetRawVelocity(StackedLaserShotgunBugSpeed); |
66 | } |
67 | } |
68 | else if(g_Config.m_SvOldLaser && pOwnerChar) |
69 | { |
70 | if(pOwnerChar->Core()->m_Pos != HitPos) |
71 | { |
72 | pHit->AddVelocity(Addition: normalize(v: pOwnerChar->Core()->m_Pos - HitPos) * Strength); |
73 | } |
74 | else |
75 | { |
76 | pHit->SetRawVelocity(StackedLaserShotgunBugSpeed); |
77 | } |
78 | } |
79 | else |
80 | { |
81 | // Re-apply move restrictions as a part of 'shotgun bug' reproduction |
82 | pHit->ApplyMoveRestrictions(); |
83 | } |
84 | } |
85 | else if(m_Type == WEAPON_LASER) |
86 | { |
87 | pHit->UnFreeze(); |
88 | } |
89 | return true; |
90 | } |
91 | |
92 | void CLaser::DoBounce() |
93 | { |
94 | m_EvalTick = GameWorld()->GameTick(); |
95 | |
96 | if(m_Energy < 0) |
97 | { |
98 | m_MarkedForDestroy = true; |
99 | return; |
100 | } |
101 | m_PrevPos = m_Pos; |
102 | vec2 Coltile; |
103 | |
104 | int Res; |
105 | vec2 To = m_Pos + m_Dir * m_Energy; |
106 | |
107 | Res = Collision()->IntersectLineTeleWeapon(Pos0: m_Pos, Pos1: To, pOutCollision: &Coltile, pOutBeforeCollision: &To); |
108 | |
109 | if(Res) |
110 | { |
111 | if(!HitCharacter(From: m_Pos, To)) |
112 | { |
113 | // intersected |
114 | m_From = m_Pos; |
115 | m_Pos = To; |
116 | |
117 | vec2 TempPos = m_Pos; |
118 | vec2 TempDir = m_Dir * 4.0f; |
119 | |
120 | int f = 0; |
121 | if(Res == -1) |
122 | { |
123 | f = Collision()->GetTile(x: round_to_int(f: Coltile.x), y: round_to_int(f: Coltile.y)); |
124 | Collision()->SetCollisionAt(x: round_to_int(f: Coltile.x), y: round_to_int(f: Coltile.y), id: TILE_SOLID); |
125 | } |
126 | Collision()->MovePoint(pInoutPos: &TempPos, pInoutVel: &TempDir, Elasticity: 1.0f, pBounces: 0); |
127 | if(Res == -1) |
128 | { |
129 | Collision()->SetCollisionAt(x: round_to_int(f: Coltile.x), y: round_to_int(f: Coltile.y), id: f); |
130 | } |
131 | m_Pos = TempPos; |
132 | m_Dir = normalize(v: TempDir); |
133 | |
134 | const float Distance = distance(a: m_From, b: m_Pos); |
135 | // Prevent infinite bounces |
136 | if(Distance == 0.0f && m_ZeroEnergyBounceInLastTick) |
137 | { |
138 | m_Energy = -1; |
139 | } |
140 | else |
141 | { |
142 | m_Energy -= Distance + GetTuning(i: m_TuneZone)->m_LaserBounceCost; |
143 | } |
144 | m_ZeroEnergyBounceInLastTick = Distance == 0.0f; |
145 | |
146 | m_Bounces++; |
147 | |
148 | int BounceNum = GetTuning(i: m_TuneZone)->m_LaserBounceNum; |
149 | |
150 | if(m_Bounces > BounceNum) |
151 | m_Energy = -1; |
152 | } |
153 | } |
154 | else |
155 | { |
156 | if(!HitCharacter(From: m_Pos, To)) |
157 | { |
158 | m_From = m_Pos; |
159 | m_Pos = To; |
160 | m_Energy = -1; |
161 | } |
162 | } |
163 | } |
164 | |
165 | void CLaser::Tick() |
166 | { |
167 | float Delay = GetTuning(i: m_TuneZone)->m_LaserBounceDelay; |
168 | |
169 | if(GameWorld()->m_WorldConfig.m_IsVanilla) // predict old physics on vanilla 0.6 servers |
170 | { |
171 | if(GameWorld()->GameTick() > m_EvalTick + (GameWorld()->GameTickSpeed() * Delay / 1000.0f)) |
172 | DoBounce(); |
173 | } |
174 | else |
175 | { |
176 | if((GameWorld()->GameTick() - m_EvalTick) > (GameWorld()->GameTickSpeed() * Delay / 1000.0f)) |
177 | DoBounce(); |
178 | } |
179 | } |
180 | |
181 | CLaser::CLaser(CGameWorld *pGameWorld, int Id, CLaserData *pLaser) : |
182 | CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER) |
183 | { |
184 | m_Pos = pLaser->m_To; |
185 | m_From = pLaser->m_From; |
186 | m_EvalTick = pLaser->m_StartTick; |
187 | m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Index: Collision()->GetMapIndex(Pos: m_Pos)) : 0; |
188 | m_Owner = -2; |
189 | m_Energy = GetTuning(i: m_TuneZone)->m_LaserReach; |
190 | if(pGameWorld->m_WorldConfig.m_IsFNG && m_Energy < 10.f) |
191 | m_Energy = 800.0f; |
192 | |
193 | m_Dir = m_Pos - m_From; |
194 | if(length(a: m_Pos - m_From) > 0.001f) |
195 | m_Dir = normalize(v: m_Dir); |
196 | else |
197 | m_Energy = 0; |
198 | m_Type = pLaser->m_Type == LASERTYPE_SHOTGUN ? WEAPON_SHOTGUN : WEAPON_LASER; |
199 | m_PrevPos = m_From; |
200 | m_Id = Id; |
201 | } |
202 | |
203 | bool CLaser::Match(CLaser *pLaser) |
204 | { |
205 | if(pLaser->m_EvalTick != m_EvalTick) |
206 | return false; |
207 | if(distance(a: pLaser->m_From, b: m_From) > 2.f) |
208 | return false; |
209 | const vec2 ThisDiff = m_Pos - m_From; |
210 | const vec2 OtherDiff = pLaser->m_Pos - pLaser->m_From; |
211 | const float DirError = distance(a: normalize(v: OtherDiff) * length(a: ThisDiff), b: ThisDiff); |
212 | return DirError <= 2.f; |
213 | } |
214 | |
215 | CLaserData CLaser::GetData() const |
216 | { |
217 | CLaserData Result; |
218 | Result.m_From.x = m_From.x; |
219 | Result.m_From.y = m_From.y; |
220 | Result.m_To.x = m_Pos.x; |
221 | Result.m_To.y = m_Pos.y; |
222 | Result.m_StartTick = m_EvalTick; |
223 | Result.m_ExtraInfo = true; |
224 | Result.m_Owner = m_Owner; |
225 | Result.m_Type = m_Type == WEAPON_SHOTGUN ? LASERTYPE_SHOTGUN : LASERTYPE_RIFLE; |
226 | Result.m_Subtype = -1; |
227 | Result.m_TuneZone = m_TuneZone; |
228 | Result.m_SwitchNumber = m_Number; |
229 | return Result; |
230 | } |
231 | |