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