1/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
2#include "gun.h"
3
4#include "character.h"
5#include "plasma.h"
6
7#include <engine/server.h>
8#include <engine/shared/config.h>
9
10#include <generated/protocol.h>
11
12#include <game/mapitems.h>
13#include <game/server/gamecontext.h>
14#include <game/server/player.h>
15#include <game/server/teams.h>
16
17CGun::CGun(CGameWorld *pGameWorld, vec2 Pos, bool Freeze, bool Explosive, int Layer, int Number) :
18 CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
19{
20 m_Core = vec2(0.0f, 0.0f);
21 m_Pos = Pos;
22 m_Freeze = Freeze;
23 m_Explosive = Explosive;
24 m_Layer = Layer;
25 m_Number = Number;
26 m_EvalTick = Server()->Tick();
27
28 std::fill(first: std::begin(arr&: m_aLastFireTeam), last: std::end(arr&: m_aLastFireTeam), value: 0);
29 std::fill(first: std::begin(arr&: m_aLastFireSolo), last: std::end(arr&: m_aLastFireSolo), value: 0);
30 GameWorld()->InsertEntity(pEntity: this);
31}
32
33void CGun::Tick()
34{
35 if(Server()->Tick() % (int)(Server()->TickSpeed() * 0.15f) == 0)
36 {
37 m_EvalTick = Server()->Tick();
38 GameServer()->Collision()->MoverSpeed(x: m_Pos.x, y: m_Pos.y, pSpeed: &m_Core);
39 m_Pos += m_Core;
40 }
41 if(g_Config.m_SvPlasmaPerSec > 0)
42 {
43 Fire();
44 }
45}
46
47void CGun::Fire()
48{
49 // Create a list of players who are in the range of the turret
50 CEntity *apPlayersInRange[MAX_CLIENTS];
51 std::fill(first: std::begin(arr&: apPlayersInRange), last: std::end(arr&: apPlayersInRange), value: nullptr);
52
53 int NumPlayersInRange = GameServer()->m_World.FindEntities(Pos: m_Pos, Radius: g_Config.m_SvPlasmaRange,
54 ppEnts: apPlayersInRange, Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER);
55
56 // The closest player (within range) in a team is selected as the target
57 int aTargetIdInTeam[MAX_CLIENTS];
58 bool aIsTarget[MAX_CLIENTS];
59 int aMinDistInTeam[MAX_CLIENTS];
60 std::fill(first: std::begin(arr&: aMinDistInTeam), last: std::end(arr&: aMinDistInTeam), value: 0);
61 std::fill(first: std::begin(arr&: aIsTarget), last: std::end(arr&: aIsTarget), value: false);
62 std::fill(first: std::begin(arr&: aTargetIdInTeam), last: std::end(arr&: aTargetIdInTeam), value: -1);
63
64 for(int i = 0; i < NumPlayersInRange; i++)
65 {
66 CCharacter *pTarget = static_cast<CCharacter *>(apPlayersInRange[i]);
67 const int &TargetTeam = pTarget->Team();
68 // Do not fire at super players
69 if(TargetTeam == TEAM_SUPER)
70 {
71 continue;
72 }
73 // If the turret is disabled for the target's team, the turret will not fire
74 if(m_Layer == LAYER_SWITCH && m_Number > 0 &&
75 !Switchers()[m_Number].m_aStatus[TargetTeam])
76 {
77 continue;
78 }
79
80 // Turrets can only shoot at a speed of sv_plasma_per_sec
81 const int &TargetClientId = pTarget->GetPlayer()->GetCid();
82 const bool &TargetIsSolo = pTarget->Teams()->m_Core.GetSolo(ClientId: TargetClientId);
83 if((TargetIsSolo &&
84 m_aLastFireSolo[TargetClientId] + Server()->TickSpeed() / g_Config.m_SvPlasmaPerSec > Server()->Tick()) ||
85 (!TargetIsSolo &&
86 m_aLastFireTeam[TargetTeam] + Server()->TickSpeed() / g_Config.m_SvPlasmaPerSec > Server()->Tick()))
87 {
88 continue;
89 }
90
91 // Turrets can shoot only at reachable, alive players
92 int IsReachable = !GameServer()->Collision()->IntersectLine(Pos0: m_Pos, Pos1: pTarget->m_Pos, pOutCollision: nullptr, pOutBeforeCollision: nullptr);
93 if(IsReachable && pTarget->IsAlive())
94 {
95 // Turrets fire on solo players regardless of the rest of the team
96 if(TargetIsSolo)
97 {
98 aIsTarget[TargetClientId] = true;
99 m_aLastFireSolo[TargetClientId] = Server()->Tick();
100 }
101 else
102 {
103 int Distance = distance(a: pTarget->m_Pos, b: m_Pos);
104 if(aMinDistInTeam[TargetTeam] == 0 || aMinDistInTeam[TargetTeam] > Distance)
105 {
106 aMinDistInTeam[TargetTeam] = Distance;
107 aTargetIdInTeam[TargetTeam] = TargetClientId;
108 }
109 }
110 }
111 }
112
113 // Set the closest player for each team as a target
114 for(int i = 0; i < MAX_CLIENTS; i++)
115 {
116 if(aTargetIdInTeam[i] != -1)
117 {
118 aIsTarget[aTargetIdInTeam[i]] = true;
119 m_aLastFireTeam[i] = Server()->Tick();
120 }
121 }
122
123 for(int i = 0; i < MAX_CLIENTS; i++)
124 {
125 // Fire at each target
126 if(aIsTarget[i])
127 {
128 CCharacter *pTarget = GameServer()->GetPlayerChar(ClientId: i);
129 new CPlasma(&GameServer()->m_World, m_Pos, normalize(v: pTarget->m_Pos - m_Pos), m_Freeze, m_Explosive, i);
130 }
131 }
132}
133
134void CGun::Reset()
135{
136 m_MarkedForDestroy = true;
137}
138
139void CGun::Snap(int SnappingClient)
140{
141 if(NetworkClipped(SnappingClient))
142 return;
143
144 int SnappingClientVersion = GameServer()->GetClientVersion(ClientId: SnappingClient);
145
146 int Subtype = (m_Explosive ? 1 : 0) | (m_Freeze ? 2 : 0);
147
148 int StartTick;
149 if(SnappingClientVersion >= VERSION_DDNET_ENTITY_NETOBJS)
150 {
151 StartTick = -1;
152 }
153 else
154 {
155 // Emulate turned off blinking turret for old clients
156 CCharacter *pChar = GameServer()->GetPlayerChar(ClientId: SnappingClient);
157
158 if(SnappingClient != SERVER_DEMO_CLIENT &&
159 (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == TEAM_SPECTATORS ||
160 GameServer()->m_apPlayers[SnappingClient]->IsPaused()) &&
161 GameServer()->m_apPlayers[SnappingClient]->SpectatorId() != SPEC_FREEVIEW)
162 pChar = GameServer()->GetPlayerChar(ClientId: GameServer()->m_apPlayers[SnappingClient]->SpectatorId());
163
164 int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
165 if(pChar && m_Layer == LAYER_SWITCH && m_Number > 0 &&
166 !Switchers()[m_Number].m_aStatus[pChar->Team()] && (!Tick))
167 return;
168
169 StartTick = m_EvalTick;
170 }
171
172 GameServer()->SnapLaserObject(Context: CSnapContext(SnappingClientVersion, Server()->IsSixup(ClientId: SnappingClient), SnappingClient), SnapId: GetId(),
173 To: m_Pos, From: m_Pos, StartTick, Owner: -1, LaserType: LASERTYPE_GUN, Subtype, SwitchNumber: m_Number);
174}
175