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
13CProjectile::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
45vec2 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
72void 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
153void CProjectile::SetBouncing(int Value)
154{
155 m_Bouncing = Value;
156}
157
158CProjectile::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
198CProjectileData 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
215bool 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