1/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
2#include "dragger.h"
3
4#include "character.h"
5
6#include <engine/shared/config.h>
7
8#include <generated/protocol.h>
9
10#include <game/client/laser_data.h>
11#include <game/collision.h>
12#include <game/mapitems.h>
13
14void CDragger::Tick()
15{
16 if(GameWorld()->GameTick() % (int)(GameWorld()->GameTickSpeed() * 0.15f) == 0)
17 {
18 Collision()->MoverSpeed(x: m_Pos.x, y: m_Pos.y, pSpeed: &m_Core);
19 m_Pos += m_Core;
20
21 LookForPlayersToDrag();
22 }
23
24 DraggerBeamTick();
25}
26
27void CDragger::LookForPlayersToDrag()
28{
29 // Create a list of players who are in the range of the dragger
30 CEntity *apPlayersInRange[MAX_CLIENTS];
31 std::fill(first: std::begin(arr&: apPlayersInRange), last: std::end(arr&: apPlayersInRange), value: nullptr);
32
33 int NumPlayersInRange = GameWorld()->FindEntities(Pos: m_Pos,
34 Radius: g_Config.m_SvDraggerRange - CCharacterCore::PhysicalSize(),
35 ppEnts: apPlayersInRange, Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER);
36
37 // The closest player (within range) in a team is selected as the target
38 int ClosestTargetId = -1;
39 bool CanStillBeTeamTarget = false;
40 int MinDistInTeam = 0;
41
42 for(int i = 0; i < NumPlayersInRange; i++)
43 {
44 CCharacter *pTarget = static_cast<CCharacter *>(apPlayersInRange[i]);
45 const int &TargetTeam = pTarget->Team();
46
47 // Do not create a dragger beam for super player
48 if(TargetTeam == TEAM_SUPER)
49 {
50 continue;
51 }
52 // If the dragger is disabled for the target's team, no dragger beam will be generated
53 if(m_Layer == LAYER_SWITCH &&
54 m_Number > 0 &&
55 m_Number < (int)Switchers().size() &&
56 !Switchers()[m_Number].m_aStatus[TargetTeam])
57 {
58 continue;
59 }
60
61 // Dragger beams can be created only for reachable, alive players
62 int IsReachable =
63 m_IgnoreWalls ?
64 !Collision()->IntersectNoLaserNoWalls(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr) :
65 !Collision()->IntersectNoLaser(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr);
66 if(IsReachable)
67 {
68 const int &TargetClientId = pTarget->GetCid();
69 int Distance = distance(a: pTarget->m_Pos, b: m_Pos);
70 if(MinDistInTeam == 0 || MinDistInTeam > Distance)
71 {
72 MinDistInTeam = Distance;
73 ClosestTargetId = TargetClientId;
74 }
75 if(TargetClientId == m_TargetId)
76 {
77 CanStillBeTeamTarget = true;
78 }
79 }
80 }
81
82 // Set the closest player for each team as a target if the team does not have a target player yet
83 if((m_TargetId != -1 && !CanStillBeTeamTarget) || m_TargetId == -1)
84 {
85 m_TargetId = ClosestTargetId;
86 }
87}
88
89void CDragger::DraggerBeamReset()
90{
91 m_TargetId = -1;
92}
93
94void CDragger::DraggerBeamTick()
95{
96 CCharacter *pTarget = GameWorld()->GetCharacterById(Id: m_TargetId);
97 if(!pTarget)
98 {
99 DraggerBeamReset();
100 return;
101 }
102
103 if(GameWorld()->GameTick() % (int)(GameWorld()->GameTickSpeed() * 0.15f) == 0)
104 {
105 if(m_Layer == LAYER_SWITCH &&
106 m_Number > 0 &&
107 m_Number < (int)Switchers().size() &&
108 !Switchers()[m_Number].m_aStatus[pTarget->Team()])
109 {
110 DraggerBeamReset();
111 return;
112 }
113 }
114
115 // When the dragger can no longer reach the target player, the dragger beam dissolves
116 int IsReachable =
117 m_IgnoreWalls ?
118 !Collision()->IntersectNoLaserNoWalls(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr) :
119 !Collision()->IntersectNoLaser(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr);
120 if(!IsReachable || distance(a: pTarget->m_Pos, b: m_Pos) >= g_Config.m_SvDraggerRange)
121 {
122 DraggerBeamReset();
123 return;
124 }
125 // In the center of the dragger a tee does not experience speed-up
126 else if(distance(a: pTarget->m_Pos, b: m_Pos) > 28)
127 {
128 pTarget->AddVelocity(Addition: normalize(v: m_Pos - pTarget->m_Pos) * m_Strength);
129 }
130}
131
132CDragger::CDragger(CGameWorld *pGameWorld, int Id, const CLaserData *pData) :
133 CEntity(pGameWorld, CGameWorld::ENTTYPE_DRAGGER)
134{
135 m_Core = vec2(0.f, 0.f);
136 m_Id = Id;
137 m_TargetId = -1;
138
139 m_Strength = 0;
140 m_IgnoreWalls = false;
141 if(0 <= pData->m_Subtype && pData->m_Subtype < NUM_LASERDRAGGERTYPES)
142 {
143 m_IgnoreWalls = (pData->m_Subtype & 1);
144 m_Strength = (pData->m_Subtype >> 1) + 1;
145 }
146 m_Number = pData->m_SwitchNumber;
147 m_Layer = m_Number > 0 ? LAYER_SWITCH : LAYER_GAME;
148
149 Read(pData);
150}
151
152void CDragger::Read(const CLaserData *pData)
153{
154 m_Pos = pData->m_From;
155 m_TargetId = pData->m_Owner;
156}
157
158bool CDragger::Match(CDragger *pDragger)
159{
160 return pDragger->m_Strength == m_Strength && pDragger->m_Number == m_Number && pDragger->m_IgnoreWalls == m_IgnoreWalls;
161}
162