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