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