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