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