| 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 | |
| 15 | CProjectile::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 | |
| 47 | vec2 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 | |
| 74 | void 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 | |
| 155 | void CProjectile::SetBouncing(int Value) |
| 156 | { |
| 157 | m_Bouncing = Value; |
| 158 | } |
| 159 | |
| 160 | CProjectile::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 | |
| 200 | CProjectileData 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 | |
| 217 | bool 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 | |