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