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 <engine/shared/config.h> |
4 | |
5 | #include <game/client/projectile_data.h> |
6 | #include <game/collision.h> |
7 | #include <game/generated/protocol.h> |
8 | #include <game/mapitems.h> |
9 | |
10 | #include "character.h" |
11 | #include "projectile.h" |
12 | |
13 | CProjectile::CProjectile( |
14 | CGameWorld *pGameWorld, |
15 | int Type, |
16 | int Owner, |
17 | vec2 Pos, |
18 | vec2 Dir, |
19 | int Span, |
20 | bool Freeze, |
21 | bool Explosive, |
22 | int SoundImpact, |
23 | int Layer, |
24 | int Number) : |
25 | CEntity(pGameWorld, CGameWorld::ENTTYPE_PROJECTILE) |
26 | { |
27 | m_Type = Type; |
28 | m_Pos = Pos; |
29 | m_Direction = Dir; |
30 | m_LifeSpan = Span; |
31 | m_Owner = Owner; |
32 | m_SoundImpact = SoundImpact; |
33 | m_StartTick = GameWorld()->GameTick(); |
34 | m_Explosive = Explosive; |
35 | |
36 | m_Layer = Layer; |
37 | m_Number = Number; |
38 | m_Freeze = Freeze; |
39 | |
40 | m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Index: Collision()->GetMapIndex(Pos: m_Pos)) : 0; |
41 | |
42 | GameWorld()->InsertEntity(pEntity: this); |
43 | } |
44 | |
45 | vec2 CProjectile::GetPos(float Time) |
46 | { |
47 | float Curvature = 0; |
48 | float Speed = 0; |
49 | CTuningParams *pTuning = GetTuning(i: m_TuneZone); |
50 | |
51 | switch(m_Type) |
52 | { |
53 | case WEAPON_GRENADE: |
54 | Curvature = pTuning->m_GrenadeCurvature; |
55 | Speed = pTuning->m_GrenadeSpeed; |
56 | break; |
57 | |
58 | case WEAPON_SHOTGUN: |
59 | Curvature = pTuning->m_ShotgunCurvature; |
60 | Speed = pTuning->m_ShotgunSpeed; |
61 | break; |
62 | |
63 | case WEAPON_GUN: |
64 | Curvature = pTuning->m_GunCurvature; |
65 | Speed = pTuning->m_GunSpeed; |
66 | break; |
67 | } |
68 | |
69 | return CalcPos(Pos: m_Pos, Velocity: m_Direction, Curvature, Speed, Time); |
70 | } |
71 | |
72 | void CProjectile::Tick() |
73 | { |
74 | float Pt = (GameWorld()->GameTick() - m_StartTick - 1) / (float)GameWorld()->GameTickSpeed(); |
75 | float Ct = (GameWorld()->GameTick() - m_StartTick) / (float)GameWorld()->GameTickSpeed(); |
76 | vec2 PrevPos = GetPos(Time: Pt); |
77 | vec2 CurPos = GetPos(Time: Ct); |
78 | vec2 ColPos; |
79 | vec2 NewPos; |
80 | int Collide = Collision()->IntersectLine(Pos0: PrevPos, Pos1: CurPos, pOutCollision: &ColPos, pOutBeforeCollision: &NewPos); |
81 | CCharacter *pOwnerChar = GameWorld()->GetCharacterById(Id: m_Owner); |
82 | |
83 | CCharacter *pTargetChr = GameWorld()->IntersectCharacter(Pos0: PrevPos, Pos1: ColPos, Radius: m_Freeze ? 1.0f : 6.0f, NewPos&: ColPos, pNotThis: pOwnerChar, CollideWith: m_Owner); |
84 | |
85 | if(GameWorld()->m_WorldConfig.m_IsSolo && !(m_Type == WEAPON_SHOTGUN && GameWorld()->m_WorldConfig.m_IsDDRace)) |
86 | pTargetChr = 0; |
87 | |
88 | if(m_LifeSpan > -1) |
89 | m_LifeSpan--; |
90 | |
91 | bool isWeaponCollide = false; |
92 | if( |
93 | pOwnerChar && |
94 | pTargetChr && |
95 | !pTargetChr->CanCollide(ClientId: m_Owner)) |
96 | { |
97 | isWeaponCollide = true; |
98 | } |
99 | |
100 | if(((pTargetChr && (pOwnerChar ? !pOwnerChar->GrenadeHitDisabled() : g_Config.m_SvHit || m_Owner == -1 || pTargetChr == pOwnerChar)) || Collide || GameLayerClipped(CheckPos: CurPos)) && !isWeaponCollide) |
101 | { |
102 | if(m_Explosive && (!pTargetChr || (!m_Freeze || (m_Type == WEAPON_SHOTGUN && Collide)))) |
103 | { |
104 | GameWorld()->CreateExplosion(Pos: ColPos, Owner: m_Owner, Weapon: m_Type, NoDamage: m_Owner == -1, ActivatedTeam: (!pTargetChr ? -1 : pTargetChr->Team()), Mask: CClientMask().set()); |
105 | } |
106 | else if(m_Freeze) |
107 | { |
108 | CEntity *apEnts[MAX_CLIENTS]; |
109 | int Num = GameWorld()->FindEntities(Pos: CurPos, Radius: 1.0f, ppEnts: apEnts, Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER); |
110 | for(int i = 0; i < Num; ++i) |
111 | { |
112 | auto *pChr = static_cast<CCharacter *>(apEnts[i]); |
113 | if(pChr && (m_Layer != LAYER_SWITCH || (m_Layer == LAYER_SWITCH && m_Number > 0 && m_Number < (int)Switchers().size() && Switchers()[m_Number].m_aStatus[pChr->Team()]))) |
114 | pChr->Freeze(); |
115 | } |
116 | } |
117 | if(Collide && m_Bouncing != 0) |
118 | { |
119 | m_StartTick = GameWorld()->GameTick(); |
120 | m_Pos = NewPos + (-(m_Direction * 4)); |
121 | if(m_Bouncing == 1) |
122 | m_Direction.x = -m_Direction.x; |
123 | else if(m_Bouncing == 2) |
124 | m_Direction.y = -m_Direction.y; |
125 | if(absolute(a: m_Direction.x) < 1e-6f) |
126 | m_Direction.x = 0; |
127 | if(absolute(a: m_Direction.y) < 1e-6f) |
128 | m_Direction.y = 0; |
129 | m_Pos += m_Direction; |
130 | } |
131 | else if(m_Type == WEAPON_GUN) |
132 | { |
133 | m_MarkedForDestroy = true; |
134 | } |
135 | else if(!m_Freeze) |
136 | m_MarkedForDestroy = true; |
137 | } |
138 | if(m_LifeSpan == -1) |
139 | { |
140 | if(m_Explosive) |
141 | { |
142 | if(m_Owner >= 0) |
143 | pOwnerChar = GameWorld()->GetCharacterById(Id: m_Owner); |
144 | |
145 | GameWorld()->CreateExplosion(Pos: ColPos, Owner: m_Owner, Weapon: m_Type, NoDamage: m_Owner == -1, ActivatedTeam: (!pOwnerChar ? -1 : pOwnerChar->Team()), Mask: CClientMask().set()); |
146 | } |
147 | m_MarkedForDestroy = true; |
148 | } |
149 | } |
150 | |
151 | // DDRace |
152 | |
153 | void CProjectile::SetBouncing(int Value) |
154 | { |
155 | m_Bouncing = Value; |
156 | } |
157 | |
158 | CProjectile::CProjectile(CGameWorld *pGameWorld, int Id, const CProjectileData *pProj) : |
159 | CEntity(pGameWorld, CGameWorld::ENTTYPE_PROJECTILE) |
160 | { |
161 | m_Pos = pProj->m_StartPos; |
162 | m_Direction = pProj->m_StartVel; |
163 | if(pProj->m_ExtraInfo) |
164 | { |
165 | m_Owner = pProj->m_Owner; |
166 | m_Explosive = pProj->m_Explosive; |
167 | m_Bouncing = pProj->m_Bouncing; |
168 | m_Freeze = pProj->m_Freeze; |
169 | } |
170 | else |
171 | { |
172 | m_Owner = -1; |
173 | m_Bouncing = 0; |
174 | m_Freeze = false; |
175 | m_Explosive = (pProj->m_Type == WEAPON_GRENADE) && (absolute(a: 1.0f - length(a: m_Direction)) < 0.015f); |
176 | } |
177 | m_Type = pProj->m_Type; |
178 | m_StartTick = pProj->m_StartTick; |
179 | m_TuneZone = pProj->m_TuneZone; |
180 | |
181 | int Lifetime = 20 * GameWorld()->GameTickSpeed(); |
182 | m_SoundImpact = -1; |
183 | if(m_Type == WEAPON_GRENADE) |
184 | { |
185 | Lifetime = GetTuning(i: m_TuneZone)->m_GrenadeLifetime * GameWorld()->GameTickSpeed(); |
186 | m_SoundImpact = SOUND_GRENADE_EXPLODE; |
187 | } |
188 | else if(m_Type == WEAPON_GUN) |
189 | Lifetime = GetTuning(i: m_TuneZone)->m_GunLifetime * GameWorld()->GameTickSpeed(); |
190 | else if(m_Type == WEAPON_SHOTGUN && !GameWorld()->m_WorldConfig.m_IsDDRace) |
191 | Lifetime = GetTuning(i: m_TuneZone)->m_ShotgunLifetime * GameWorld()->GameTickSpeed(); |
192 | m_LifeSpan = Lifetime - (pGameWorld->GameTick() - m_StartTick); |
193 | m_Id = Id; |
194 | m_Number = pProj->m_SwitchNumber; |
195 | m_Layer = m_Number > 0 ? LAYER_SWITCH : LAYER_GAME; |
196 | } |
197 | |
198 | CProjectileData CProjectile::GetData() const |
199 | { |
200 | CProjectileData Result; |
201 | Result.m_StartPos = m_Pos; |
202 | Result.m_StartVel = m_Direction; |
203 | Result.m_Type = m_Type; |
204 | Result.m_StartTick = m_StartTick; |
205 | Result.m_ExtraInfo = true; |
206 | Result.m_Owner = m_Owner; |
207 | Result.m_Explosive = m_Explosive; |
208 | Result.m_Bouncing = m_Bouncing; |
209 | Result.m_Freeze = m_Freeze; |
210 | Result.m_TuneZone = m_TuneZone; |
211 | Result.m_SwitchNumber = m_Number; |
212 | return Result; |
213 | } |
214 | |
215 | bool CProjectile::Match(CProjectile *pProj) |
216 | { |
217 | if(pProj->m_Type != m_Type) |
218 | return false; |
219 | if(pProj->m_StartTick != m_StartTick) |
220 | return false; |
221 | if(distance(a: pProj->m_Pos, b: m_Pos) > 2.f) |
222 | return false; |
223 | if(distance(a: pProj->m_Direction, b: m_Direction) > 2.f) |
224 | return false; |
225 | return true; |
226 | } |
227 | |