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 | |
16 | CGun::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 | |
32 | void 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 | |
51 | void 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 | |
141 | void CGun::Reset() |
142 | { |
143 | m_MarkedForDestroy = true; |
144 | } |
145 | |
146 | void 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 | |