Rename t_items.qc to items.qc
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / weapons / selection.qc
1 #include "selection.qh"
2
3 #include "weaponsystem.qh"
4 #include <common/items.qh>
5 #include <server/items.qh>
6 #include <common/constants.qh>
7 #include <common/net_linked.qh>
8 #include <common/util.qh>
9 #include <common/items/item.qh>
10 #include <common/weapons/_all.qh>
11 #include <common/state.qh>
12 #include <common/mutators/mutator/waypoints/waypointsprites.qh>
13 #include <common/wepent.qh>
14
15 // switch between weapons
16 void Send_WeaponComplain(entity e, float wpn, float type)
17 {
18         msg_entity = e;
19         WriteHeader(MSG_ONE, TE_CSQC_WEAPONCOMPLAIN);
20         WriteByte(MSG_ONE, wpn);
21         WriteByte(MSG_ONE, type);
22 }
23
24 void Weapon_whereis(Weapon this, entity cl)
25 {
26         if (!autocvar_g_showweaponspawns) return;
27         IL_EACH(g_items, it.weapon == this.m_id && (!it.team || (it.ItemStatus & ITS_AVAILABLE)),
28         {
29                 if (Item_IsLoot(it) && (autocvar_g_showweaponspawns < 2))
30                 {
31                         continue;
32                 }
33                 entity wp = WaypointSprite_Spawn(
34                         WP_Weapon,
35                         -2, 0,
36                         NULL, it.origin + ('0 0 1' * it.maxs.z) * 1.2,
37                         cl, 0,
38                         NULL, enemy,
39                         0,
40                         RADARICON_NONE
41                 );
42                 wp.wp_extra = this.m_id;
43         });
44 }
45
46 bool client_hasweapon(entity this, Weapon wpn, .entity weaponentity, float andammo, bool complain)
47 {
48         float f = 0;
49
50         if (time < CS(this).hasweapon_complain_spam)
51                 complain = 0;
52
53         // ignore hook button when using other offhand equipment
54         if (this.offhand != OFFHAND_HOOK)
55         if (wpn == WEP_HOOK && !((STAT(WEAPONS, this) | weaponsInMap) & WepSet_FromWeapon(wpn)))
56             complain = 0;
57
58         if (complain)
59                 CS(this).hasweapon_complain_spam = time + 0.2;
60
61         if (wpn == WEP_Null)
62         {
63                 if (complain)
64                         sprint(this, "Invalid weapon\n");
65                 return false;
66         }
67         if (autocvar_g_weaponswitch_debug == 2 && weaponslot(weaponentity) > 0 && !(wpn.spawnflags & WEP_FLAG_DUALWIELD) && !(PS(this).dual_weapons & wpn.m_wepset))
68                 return false; // no complaints needed
69         if (STAT(WEAPONS, this) & WepSet_FromWeapon(wpn))
70         {
71                 if (andammo)
72                 {
73                         if(this.items & IT_UNLIMITED_AMMO)
74                         {
75                                 f = 1;
76                         }
77                         else
78                         {
79                                 f = wpn.wr_checkammo1(wpn, this, weaponentity) + wpn.wr_checkammo2(wpn, this, weaponentity);
80
81                                 // always allow selecting the Mine Layer if we placed mines, so that we can detonate them
82                                 if(wpn == WEP_MINE_LAYER)
83                                         IL_EACH(g_mines, it.owner == this && it.weaponentity_fld == weaponentity,
84                                         {
85                                                 f = 1;
86                                                 break; // no need to continue
87                                         });
88                         }
89                         if (!f)
90                         {
91                                 if (complain)
92                                 if(IS_REAL_CLIENT(this))
93                                 {
94                                         play2(this, SND(UNAVAILABLE));
95                                         Send_WeaponComplain (this, wpn.m_id, 0);
96                                 }
97                                 return false;
98                         }
99                 }
100                 return true;
101         }
102         if (complain)
103         {
104                 // DRESK - 3/16/07
105                 // Report Proper Weapon Status / Modified Weapon Ownership Message
106                 if (weaponsInMap & WepSet_FromWeapon(wpn))
107                 {
108                         Send_WeaponComplain(this, wpn.m_id, 1);
109                         if(autocvar_g_showweaponspawns < 3)
110                                 Weapon_whereis(wpn, this);
111                         else
112                         {
113                                 FOREACH(Weapons, it.impulse == wpn.impulse,
114                                 {
115                                         Weapon_whereis(it, this);
116                                 });
117                         }
118                 }
119                 else
120                 {
121                         Send_WeaponComplain (this, wpn.m_id, 2);
122                 }
123
124                 play2(this, SND(UNAVAILABLE));
125         }
126         return false;
127 }
128
129 float W_GetCycleWeapon(entity this, string weaponorder, float dir, float imp, float complain, float skipmissing, .entity weaponentity)
130 {
131         // We cannot tokenize in this function, as GiveItems calls this
132         // function. Thus we must use car/cdr.
133         float weaponwant, first_valid, prev_valid, switchtonext, switchtolast;
134         WepSet wepset = '0 0 0';
135         switchtonext = switchtolast = 0;
136         first_valid = prev_valid = 0;
137         float weaponcur;
138         entity wep;
139
140         if(skipmissing || this.(weaponentity).selectweapon == 0)
141                 weaponcur = this.(weaponentity).m_switchweapon.m_id;
142         else
143                 weaponcur = this.(weaponentity).selectweapon;
144
145         if(dir == 0)
146                 switchtonext = 1;
147
148         int c = 0;
149
150         string rest = weaponorder;
151         while(rest != "")
152         {
153                 weaponwant = stof(car(rest)); rest = cdr(rest);
154                 wep = REGISTRY_GET(Weapons, weaponwant);
155                 wepset = wep.m_wepset;
156                 if(imp >= 0)
157                 if(wep.impulse != imp)
158                         continue;
159
160                 bool have_other = false;
161                 FOREACH(Weapons, it != WEP_Null, {
162                         if(i != weaponwant)
163                         if(it.impulse == imp || imp < 0)
164                         if((STAT(WEAPONS, this) & (it.m_wepset)) || (weaponsInMap & (it.m_wepset)))
165                                 have_other = true;
166                 });
167
168                 // skip weapons we don't own that aren't normal and aren't in the map
169                 if(!(STAT(WEAPONS, this) & wepset))
170                 if(!(weaponsInMap & wepset))
171                 if((wep.spawnflags & WEP_FLAG_MUTATORBLOCKED) || have_other)
172                         continue;
173
174                 ++c;
175
176                 if(!skipmissing || client_hasweapon(this, wep, weaponentity, true, false))
177                 {
178                         if(switchtonext)
179                                 return weaponwant;
180                         if(!first_valid)
181                                 first_valid = weaponwant;
182                         if(weaponwant == weaponcur)
183                         {
184                                 if(dir >= 0)
185                                         switchtonext = 1;
186                                 else if(prev_valid)
187                                         return prev_valid;
188                                 else
189                                         switchtolast = 1;
190                         }
191                         prev_valid = weaponwant;
192                 }
193         }
194         if(first_valid)
195         {
196                 if(switchtolast)
197                         return prev_valid;
198                 else
199                         return first_valid;
200         }
201         // complain (but only for one weapon on the button that has been pressed)
202         if(complain)
203         {
204                 this.weaponcomplainindex += 1;
205                 c = (this.weaponcomplainindex % c) + 1;
206                 rest = weaponorder;
207                 while(rest != "")
208                 {
209                         weaponwant = stof(car(rest)); rest = cdr(rest);
210                         wep = REGISTRY_GET(Weapons, weaponwant);
211                         wepset = wep.m_wepset;
212                         if(imp >= 0)
213                                 if(wep.impulse != imp)
214                                         continue;
215
216                         bool have_other = false;
217                         FOREACH(Weapons, it != WEP_Null, {
218                                 if(i != weaponwant)
219                                 if(it.impulse == imp || imp < 0)
220                                 if((STAT(WEAPONS, this) & (it.m_wepset)) || (weaponsInMap & (it.m_wepset)))
221                                         have_other = true;
222                         });
223
224                         // skip weapons we don't own that aren't normal and aren't in the map
225                         if(!(STAT(WEAPONS, this) & wepset))
226                         if(!(weaponsInMap & wepset))
227                         if((wep.spawnflags & WEP_FLAG_MUTATORBLOCKED) || have_other)
228                                 continue;
229
230                         --c;
231                         if(c == 0)
232                         {
233                                 client_hasweapon(this, wep, weaponentity, true, true);
234                                 break;
235                         }
236                 }
237         }
238         return 0;
239 }
240
241 void W_SwitchWeapon_Force(Player this, Weapon wep, .entity weaponentity)
242 {
243         TC(Weapon, wep);
244         this.(weaponentity).cnt = this.(weaponentity).m_switchweapon.m_id;
245         this.(weaponentity).m_switchweapon = wep;
246         this.(weaponentity).selectweapon = wep.m_id;
247 }
248
249 // perform weapon to attack (weaponstate and attack_finished check is here)
250 void W_SwitchToOtherWeapon(entity this, .entity weaponentity)
251 {
252         // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway)
253         Weapon ww;
254         WepSet set = WepSet_FromWeapon(this.(weaponentity).m_weapon);
255         if (STAT(WEAPONS, this) & set)
256         {
257                 STAT(WEAPONS, this) &= ~set;
258                 ww = w_getbestweapon(this, weaponentity);
259                 STAT(WEAPONS, this) |= set;
260         }
261         else
262         {
263                 ww = w_getbestweapon(this, weaponentity);
264         }
265         if (ww == WEP_Null) return;
266         W_SwitchWeapon_Force(this, ww, weaponentity);
267 }
268
269 bool W_SwitchWeapon(entity this, Weapon w, .entity weaponentity)
270 {
271         if(this.(weaponentity).m_switchweapon != w)
272         {
273                 if(client_hasweapon(this, w, weaponentity, true, true))
274                 {
275                         W_SwitchWeapon_Force(this, w, weaponentity);
276                         return true;
277                 }
278                 else
279                 {
280                         this.(weaponentity).selectweapon = w.m_id; // update selectweapon anyway
281                         return false;
282                 }
283         }
284         else if(!weaponLocked(this) && CS(this).cvar_cl_weapon_switch_reload)
285         {
286                 entity actor = this;
287                 w.wr_reload(w, actor, weaponentity);
288         }
289
290         return true; // player already has the weapon out or needs to reload
291 }
292
293 void W_SwitchWeapon_TryOthers(entity this, Weapon w, .entity weaponentity)
294 {
295         if(!W_SwitchWeapon(this, w, weaponentity) && CS(this).cvar_cl_weapon_switch_fallback_to_impulse)
296                 W_NextWeaponOnImpulse(this, w.impulse, weaponentity);
297 }
298
299 void W_CycleWeapon(entity this, string weaponorder, float dir, .entity weaponentity)
300 {
301         float w;
302         w = W_GetCycleWeapon(this, weaponorder, dir, -1, 1, true, weaponentity);
303         if(w > 0)
304                 W_SwitchWeapon(this, REGISTRY_GET(Weapons, w), weaponentity);
305 }
306
307 void W_NextWeaponOnImpulse(entity this, float imp, .entity weaponentity)
308 {
309         float w;
310         w = W_GetCycleWeapon(this, CS(this).cvar_cl_weaponpriority, +1, imp, 1, (CS(this).cvar_cl_weaponimpulsemode == 0), weaponentity);
311         if(w > 0)
312                 W_SwitchWeapon(this, REGISTRY_GET(Weapons, w), weaponentity);
313 }
314
315 // next weapon
316 void W_NextWeapon(entity this, int list, .entity weaponentity)
317 {
318         if(list == 0)
319                 W_CycleWeapon(this, weaponorder_byid, -1, weaponentity);
320         else if(list == 1)
321                 W_CycleWeapon(this, CS(this).weaponorder_byimpulse, -1, weaponentity);
322         else if(list == 2)
323                 W_CycleWeapon(this, CS(this).cvar_cl_weaponpriority, -1, weaponentity);
324 }
325
326 // prev weapon
327 void W_PreviousWeapon(entity this, float list, .entity weaponentity)
328 {
329         if(list == 0)
330                 W_CycleWeapon(this, weaponorder_byid, +1, weaponentity);
331         else if(list == 1)
332                 W_CycleWeapon(this, CS(this).weaponorder_byimpulse, +1, weaponentity);
333         else if(list == 2)
334                 W_CycleWeapon(this, CS(this).cvar_cl_weaponpriority, +1, weaponentity);
335 }
336
337 // previously used if exists and has ammo, (second) best otherwise
338 void W_LastWeapon(entity this, .entity weaponentity)
339 {
340         Weapon wep = REGISTRY_GET(Weapons, this.(weaponentity).cnt);
341         if (client_hasweapon(this, wep, weaponentity, true, false))
342                 W_SwitchWeapon(this, wep, weaponentity);
343         else
344                 W_SwitchToOtherWeapon(this, weaponentity);
345 }