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