Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
devastator.qc
Go to the documentation of this file.
1#include "devastator.qh"
2
3#ifdef SVQC
4
5.entity lastrocket;
6
8{
9 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
10 {
11 .entity weaponentity = weaponentities[slot];
12 if (this.realowner.(weaponentity).lastrocket == this)
13 this.realowner.(weaponentity).lastrocket = NULL;
14 }
15}
16
17void W_Devastator_Explode(entity this, entity directhitentity)
18{
20
21 if (directhitentity.takedamage == DAMAGE_AIM
22 && IS_PLAYER(directhitentity) && DIFF_TEAM(this.realowner, directhitentity) && !IS_DEAD(directhitentity)
23 && IsFlying(directhitentity))
24 Send_Notification(NOTIF_ONE, this.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
25
26 this.event_damage = func_null;
27 this.takedamage = DAMAGE_NO;
28
29 vector force_xyzscale = '1 1 1';
30 force_xyzscale.x = WEP_CVAR(WEP_DEVASTATOR, force_xyscale);
31 force_xyzscale.y = WEP_CVAR(WEP_DEVASTATOR, force_xyscale);
32
33 RadiusDamageForSource(this, this.origin, this.velocity, this.realowner,
34 WEP_CVAR(WEP_DEVASTATOR, damage),
35 WEP_CVAR(WEP_DEVASTATOR, edgedamage),
36 WEP_CVAR(WEP_DEVASTATOR, radius),
37 NULL,
38 NULL,
39 false,
40 WEP_CVAR(WEP_DEVASTATOR, force),
41 force_xyzscale,
44 directhitentity
45 );
46
47 Weapon thiswep = WEP_DEVASTATOR;
48 .entity weaponentity = this.weaponentity_fld;
49 if (this.realowner.(weaponentity).m_weapon == thiswep
50 && GetResource(this.realowner, thiswep.ammo_type) < WEP_CVAR(WEP_DEVASTATOR, ammo)
51 && !(this.realowner.items & IT_UNLIMITED_AMMO))
52 {
53 this.realowner.cnt = thiswep.m_id;
54 ATTACK_FINISHED(this.realowner, weaponentity) = time;
55 this.realowner.(weaponentity).m_switchweapon = w_getbestweapon(this.realowner, weaponentity);
56 }
57 delete(this);
58}
59
64
66{
68
69 this.event_damage = func_null;
70 this.takedamage = DAMAGE_NO;
71
72 bool handled_as_rocketjump = false;
73 entity head = NULL;
74 bool allow_rocketjump = WEP_CVAR(WEP_DEVASTATOR, remote_jump);
75 MUTATOR_CALLHOOK(AllowRocketJumping, allow_rocketjump);
76 allow_rocketjump = M_ARGV(0, bool);
77
78 if (allow_rocketjump && WEP_CVAR(WEP_DEVASTATOR, remote_jump_radius))
79 {
80 head = WarpZone_FindRadius(this.origin, WEP_CVAR(WEP_DEVASTATOR, remote_jump_radius), false);
81 for (; head; head = head.chain)
82 if (head.takedamage && head == this.realowner
83 && vdist(this.origin - head.WarpZone_findradius_nearest, <=, WEP_CVAR(WEP_DEVASTATOR, remote_jump_radius)))
84 {
85 // we handled this as a rocketjump :)
86 handled_as_rocketjump = true;
87
88 // modify velocity
89 if (WEP_CVAR(WEP_DEVASTATOR, remote_jump_velocity_z_add))
90 {
91 head.velocity.x *= 0.9;
92 head.velocity.y *= 0.9;
93 head.velocity.z = bound(
94 WEP_CVAR(WEP_DEVASTATOR, remote_jump_velocity_z_min),
95 head.velocity.z + WEP_CVAR(WEP_DEVASTATOR, remote_jump_velocity_z_add),
96 WEP_CVAR(WEP_DEVASTATOR, remote_jump_velocity_z_max)
97 );
98 }
99
100 // now do the damage
101 RadiusDamage(this, head,
102 WEP_CVAR(WEP_DEVASTATOR, remote_jump_damage),
103 WEP_CVAR(WEP_DEVASTATOR, remote_jump_damage),
104 WEP_CVAR(WEP_DEVASTATOR, remote_jump_radius),
105 NULL,
106 head,
107 (WEP_CVAR(WEP_DEVASTATOR, remote_jump_force) ? WEP_CVAR(WEP_DEVASTATOR, remote_jump_force) : 0),
109 this.weaponentity_fld,
110 NULL
111 );
112 break;
113 }
114 }
115
116 RadiusDamage(this, this.realowner,
117 WEP_CVAR(WEP_DEVASTATOR, remote_damage),
118 WEP_CVAR(WEP_DEVASTATOR, remote_edgedamage),
119 WEP_CVAR(WEP_DEVASTATOR, remote_radius),
120 (handled_as_rocketjump ? head : NULL),
121 NULL,
122 WEP_CVAR(WEP_DEVASTATOR, remote_force),
124 this.weaponentity_fld,
125 NULL
126 );
127
128 Weapon thiswep = WEP_DEVASTATOR;
129 if (this.realowner.(weaponentity).m_weapon == thiswep
130 && GetResource(this.realowner, thiswep.ammo_type) < WEP_CVAR(WEP_DEVASTATOR, ammo)
131 && !(this.realowner.items & IT_UNLIMITED_AMMO))
132 {
133 this.realowner.cnt = thiswep.m_id;
134 ATTACK_FINISHED(this.realowner, weaponentity) = time;
135 this.realowner.(weaponentity).m_switchweapon = w_getbestweapon(this.realowner, weaponentity);
136 }
137 delete(this);
138}
139
140void W_Devastator_RemoteExplode(entity this, .entity weaponentity)
141{
142 if (!IS_DEAD(this.realowner)
143 && this.realowner.(weaponentity).lastrocket)
144 {
145 if ((this.spawnshieldtime >= 0)
146 ? (time >= this.spawnshieldtime) // timer
147 : vdist(NearestPointOnBox(this.realowner, this.origin) - this.origin, >, WEP_CVAR(WEP_DEVASTATOR, remote_radius))) // safety device
148 {
149 W_Devastator_DoRemoteExplode(this, weaponentity);
150 }
151 }
152}
153
154vector W_Devastator_SteerTo(vector thisdir, vector goaldir, float maxturn_cos)
155{
156 if (thisdir * goaldir > maxturn_cos)
157 return goaldir;
158 if (thisdir * goaldir < -0.9998) // less than 1 degree and opposite
159 return thisdir; // refuse to guide (better than letting a numerical error happen)
160
161 // solve:
162 // g = normalize(thisdir + goaldir * X)
163 // thisdir * g = maxturn
164 //
165 // gg = thisdir + goaldir * X
166 // (thisdir * gg)^2 = maxturn^2 * (gg * gg)
167 //
168 // (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
169 float f = thisdir * goaldir;
170 // (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
171 // 0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
172 float m2 = maxturn_cos * maxturn_cos;
173 vector v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
174 return normalize(thisdir + goaldir * v.y); // the larger solution!
175}
176// assume thisdir == -goaldir:
177// f == -1
178// v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
179// (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
180// x^2 - 2 * x + 1 = 0
181// (x - 1)^2 = 0
182// x = 1
183// normalize(thisdir + goaldir)
184// normalize(0)
185
187{
188 this.nextthink = time;
189 if (time > this.cnt)
190 {
193 return;
194 }
195
196 // accelerate
197 makevectors(this.angles.x * '-1 0 0' + this.angles.y * '0 1 0');
198 float velspeed = WEP_CVAR(WEP_DEVASTATOR, speed) * W_WeaponSpeedFactor(this.realowner) - (this.velocity * v_forward);
199 if (velspeed > 0)
200 this.velocity += v_forward * min(WEP_CVAR(WEP_DEVASTATOR, speedaccel) * W_WeaponSpeedFactor(this.realowner) * frametime, velspeed);
201
202 // laser guided, or remote detonation
203 .entity weaponentity = this.weaponentity_fld;
204 if (this.realowner.(weaponentity).m_weapon == WEP_DEVASTATOR)
205 {
206 if (this == this.realowner.(weaponentity).lastrocket
207 && !this.realowner.(weaponentity).rl_release
209 && WEP_CVAR(WEP_DEVASTATOR, guiderate)
210 && time > this.pushltime
211 && !IS_DEAD(this.realowner))
212 {
213 vector desireddir, olddir, newdir, desiredorigin, goal;
214 float f = WEP_CVAR(WEP_DEVASTATOR, guideratedelay);
215 if (f)
216 f = bound(0, (time - this.pushltime) / f, 1);
217 else
218 f = 1;
219
220 vector md = this.realowner.(weaponentity).movedir;
221 vector dv = v_right * -md.y + v_up * md.z;
222
223 if (!W_DualWielding(this.realowner))
224 dv = '0 0 0'; // don't override!
225
226 velspeed = vlen(this.velocity);
227
228 makevectors(this.realowner.v_angle);
229 desireddir = WarpZone_RefSys_TransformVelocity(this.realowner, this, v_forward);
230 desiredorigin = WarpZone_RefSys_TransformOrigin(this.realowner, this, this.realowner.origin + this.realowner.view_ofs + dv);
231 olddir = normalize(this.velocity);
232
233 // now it gets tricky... we want to move like some curve to approximate the target direction
234 // but we are limiting the rate at which we can turn!
235 goal = desiredorigin + ((this.origin - desiredorigin) * desireddir + WEP_CVAR(WEP_DEVASTATOR, guidegoal)) * desireddir;
236 newdir = W_Devastator_SteerTo(olddir, normalize(goal - this.origin), cos(WEP_CVAR(WEP_DEVASTATOR, guiderate) * f * frametime * DEG2RAD));
237
238 this.velocity = newdir * velspeed;
239 this.angles = vectoangles(this.velocity);
240
241 if (!this.count)
242 {
243 Send_Effect(EFFECT_ROCKET_GUIDE, this.origin, this.velocity, 1);
244 // TODO add a better sound here
245 sound(this.realowner, CH_WEAPON_B, SND_DEVASTATOR_MODE, VOL_BASE, ATTN_NORM);
246 this.count = 1;
247 }
248 }
249
250 if (this.rl_detonate_later)
251 W_Devastator_RemoteExplode(this, weaponentity);
252 }
253
254 if (this.csqcprojectile_clientanimate == 0)
256}
257
259{
261 {
262 if (wasfreed(this))
264 return;
265 }
268}
269
270void W_Devastator_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
271{
272 if (GetResource(this, RES_HEALTH) <= 0)
273 return;
274 if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
275 return; // g_projectiles_damage says to halt
276
277 TakeResource(this, RES_HEALTH, damage);
278 this.angles = vectoangles(this.velocity);
279
280 if (GetResource(this, RES_HEALTH) <= 0)
282}
283
284void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
285{
286 W_DecreaseAmmo(thiswep, actor, WEP_CVAR(WEP_DEVASTATOR, ammo), weaponentity);
287
288 W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_DEVASTATOR_FIRE, CH_WEAPON_A, WEP_CVAR(WEP_DEVASTATOR, damage), thiswep.m_id);
289 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
290
292 missile.weaponentity_fld = weaponentity;
293 missile.owner = missile.realowner = actor;
294 actor.(weaponentity).lastrocket = missile;
295 if (WEP_CVAR(WEP_DEVASTATOR, detonatedelay) >= 0)
296 missile.spawnshieldtime = time + WEP_CVAR(WEP_DEVASTATOR, detonatedelay);
297 else
298 missile.spawnshieldtime = -1; // NOTE: proximity based when rocket jumping
299 missile.pushltime = time + WEP_CVAR(WEP_DEVASTATOR, guidedelay);
300 missile.classname = "rocket";
301 missile.bot_dodge = true;
302 missile.bot_dodgerating = WEP_CVAR(WEP_DEVASTATOR, damage) * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
303
304 missile.takedamage = DAMAGE_YES;
305 missile.damageforcescale = WEP_CVAR(WEP_DEVASTATOR, damageforcescale);
306 SetResourceExplicit(missile, RES_HEALTH, WEP_CVAR(WEP_DEVASTATOR, health));
307 missile.event_damage = W_Devastator_Damage;
308 missile.damagedbycontents = true;
310
311 set_movetype(missile, MOVETYPE_FLY);
312 PROJECTILE_MAKETRIGGER(missile);
313 missile.projectiledeathtype = thiswep.m_id;
314 setsize(missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
315
316 setorigin(missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
317 W_SetupProjVelocity_Basic(missile, WEP_CVAR(WEP_DEVASTATOR, speedstart), 0);
318 missile.angles = vectoangles(missile.velocity);
319
322 missile.nextthink = time;
323 missile.cnt = time + WEP_CVAR(WEP_DEVASTATOR, lifetime);
324 missile.rl_detonate_later = (fire & 2); // allow instant detonation
325 missile.flags = FL_PROJECTILE;
326 IL_PUSH(g_projectiles, missile);
327 IL_PUSH(g_bot_dodge, missile);
328 missile.missile_flags = MIF_SPLASH;
329
330 CSQCProjectile(missile, WEP_CVAR(WEP_DEVASTATOR, guiderate) == 0 && WEP_CVAR(WEP_DEVASTATOR, speedaccel) == 0, PROJECTILE_ROCKET, false); // because of fly sound
331
332 // common properties
333 MUTATOR_CALLHOOK(EditProjectile, actor, missile);
334
335 if (time >= missile.nextthink)
336 getthink(missile)(missile);
337}
338
339METHOD(Devastator, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
340{
341 if (!WEP_CVAR(WEP_DEVASTATOR, guidestop) && !actor.(weaponentity).rl_release)
342 {
343 int fired_rockets = 0;
344 IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket", ++fired_rockets);
345 // release PHYS_INPUT_BUTTON_ATCK after all fired rocket exploded otherwise bot can't fire again
346 if (!fired_rockets)
347 return;
348 }
349
350 // aim and decide to fire if appropriate
351 float spd = WEP_CVAR(WEP_DEVASTATOR, speed);
352 // simulate rocket guide by calculating rocket trajectory with higher speed
353 // 20 times faster at 90 degrees guide rate
354 if (WEP_CVAR(WEP_DEVASTATOR, guiderate) > 0)
355 spd *= sqrt(WEP_CVAR(WEP_DEVASTATOR, guiderate)) * (20 / 9.489); // 9.489 ~= sqrt(90)
356 // no need to fire with high accuracy on large distances if rockets can be guided
357 bool shot_accurate = (WEP_CVAR(WEP_DEVASTATOR, guiderate) < 50);
358 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, spd, 0, WEP_CVAR(WEP_DEVASTATOR, lifetime), false, shot_accurate);
359 float pred_time = bound(0.02, 0.02 + (8 - skill) * 0.01, 0.1);
360 if (skill >= 2) // skill 0 and 1 bots won't detonate rockets!
361 {
362 // decide whether to detonate rockets
363 float selfdamage = 0, teamdamage = 0, enemydamage = 0;
364 float pred_selfdamage = 0, pred_teamdamage = 0, pred_enemydamage = 0;
365 float edgedamage = WEP_CVAR(WEP_DEVASTATOR, edgedamage);
366 float coredamage = WEP_CVAR(WEP_DEVASTATOR, damage);
367 float edgeradius = WEP_CVAR(WEP_DEVASTATOR, radius);
368 IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
369 {
370 entity rocket = it;
371 IL_EACH(g_bot_targets, it.bot_attack,
372 {
373 // code to calculate damage is similar to the one used in RadiusDamageForSource with some simplifications
374 vector target_pos = it.origin + (it.maxs - it.mins) * 0.5;
375
376 float f;
377 float dist = vlen(target_pos - rocket.origin);
378 float dmg = 0;
379 if (dist <= edgeradius)
380 {
381 f = (edgeradius > 0) ? max(0, 1 - (dist / edgeradius)) : 1;
382 dmg = coredamage * f + edgedamage * (1 - f);
383 }
384
385 float pred_dist = vlen(target_pos + it.velocity * pred_time - (rocket.origin + rocket.velocity * pred_time));
386 float pred_dmg = 0;
387 if (pred_dist <= edgeradius)
388 {
389 f = (edgeradius > 0) ? max(0, 1 - (pred_dist / edgeradius)) : 1;
390 pred_dmg = coredamage * f + edgedamage * (1 - f);
391 }
392
393 // count potential damage according to type of target
394 if (it == actor)
395 {
396 if (StatusEffects_active(STATUSEFFECT_Strength, it))
397 dmg *= autocvar_g_balance_powerup_strength_damage;
398 if (StatusEffects_active(STATUSEFFECT_Shield, it))
399 dmg *= autocvar_g_balance_powerup_invincible_takedamage;
400 // self damage reduction factor will be applied later to the total damage
401 selfdamage += dmg;
402 pred_selfdamage += pred_dmg;
403 }
404 else if (SAME_TEAM(it, actor))
405 {
406 if (StatusEffects_active(STATUSEFFECT_Shield, it))
407 dmg *= autocvar_g_balance_powerup_invincible_takedamage;
408 // bot strength factor will be applied later to the total damage
409 teamdamage += dmg;
410 pred_teamdamage += pred_dmg;
411 }
412 else if (bot_shouldattack(actor, it))
413 {
414 if (StatusEffects_active(STATUSEFFECT_Shield, it))
415 dmg *= autocvar_g_balance_powerup_invincible_takedamage;
416 // bot strength factor will be applied later to the total damage
417 enemydamage += dmg;
418 pred_enemydamage += pred_dmg;
419 }
420 });
421 });
422
424 pred_selfdamage *= autocvar_g_balance_selfdamagepercent;
425 if (StatusEffects_active(STATUSEFFECT_Strength, actor))
426 {
427 // FIXME bots don't know whether team damage is enabled or not
432 }
433
434 float good_damage = enemydamage;
435 float pred_good_damage = pred_enemydamage;
436 float bad_damage = selfdamage + teamdamage;
437 float pred_bad_damage = pred_selfdamage + pred_teamdamage;
438
439 // detonate if predicted good damage is lower (current good damage is maximum)
440 // or if predicted bad damage is too much
441 if (good_damage > coredamage * 0.1 && good_damage > bad_damage * 1.5
442 && (pred_good_damage < good_damage + 2 || pred_good_damage < pred_bad_damage * 1.5))
443 PHYS_INPUT_BUTTON_ATCK2(actor) = true;
444 if (skill >= 7 && selfdamage > GetResource(actor, RES_HEALTH))
445 PHYS_INPUT_BUTTON_ATCK2(actor) = false;
446
447 // don't fire a new shot at the same time!
448 if (PHYS_INPUT_BUTTON_ATCK2(actor))
449 PHYS_INPUT_BUTTON_ATCK(actor) = false;
450 }
451}
452
453METHOD(Devastator, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
454{
455 if (WEP_CVAR(WEP_DEVASTATOR, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR(WEP_DEVASTATOR, ammo))
456 { // forced reload
457 thiswep.wr_reload(thiswep, actor, weaponentity);
458 return;
459 }
460
461 if (fire & 1)
462 {
463 if ((actor.(weaponentity).rl_release || WEP_CVAR(WEP_DEVASTATOR, guidestop))
464 && weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(WEP_DEVASTATOR, refire)))
465 {
466 W_Devastator_Attack(thiswep, actor, weaponentity, fire);
467 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(WEP_DEVASTATOR, animtime), w_ready);
468 actor.(weaponentity).rl_release = 0;
469 }
470 }
471 else
472 actor.(weaponentity).rl_release = 1;
473
474 if ((fire & 2)
475 && actor.(weaponentity).m_switchweapon == thiswep)
476 {
477 bool rockfound = false;
478 IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
479 {
480 if (!it.rl_detonate_later)
481 {
482 it.rl_detonate_later = true;
483 rockfound = true;
484 }
485 });
486 if (rockfound)
487 sound(actor, CH_WEAPON_B, SND_DEVASTATOR_DET, VOL_BASE, ATTN_NORM);
488 }
489}
490
491METHOD(Devastator, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
492{
493 actor.(weaponentity).rl_release = 1;
494}
495
496METHOD(Devastator, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
497{
498 #if 0
499 // don't switch while guiding a missile
500 if (ATTACK_FINISHED(actor, weaponentity) <= time || PS(actor).m_weapon != WEP_DEVASTATOR)
501 {
502 ammo_amount = false;
503 if (WEP_CVAR(WEP_DEVASTATOR, reload_ammo))
504 {
505 if (GetResource(actor, thiswep.ammo_type) < WEP_CVAR(WEP_DEVASTATOR, ammo) && actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(WEP_DEVASTATOR, ammo))
506 ammo_amount = true;
507 }
508 else if (GetResource(actor, thiswep.ammo_type) < WEP_CVAR(WEP_DEVASTATOR, ammo))
509 ammo_amount = true;
510 return !ammo_amount;
511 }
512 #endif
513 #if 0
514 if (actor.rl_release == 0)
515 {
516 LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE", actor.rl_release, GetResource(actor, thiswep.ammo_type), WEP_CVAR(WEP_DEVASTATOR, ammo));
517 return true;
518 }
519 else
520 {
521 ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR(WEP_DEVASTATOR, ammo);
522 ammo_amount += actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(WEP_DEVASTATOR, ammo);
523 LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s", actor.rl_release, GetResource(actor, thiswep.ammo_type), WEP_CVAR(WEP_DEVASTATOR, ammo), (ammo_amount ? "TRUE" : "FALSE"));
524 return ammo_amount;
525 }
526 #else
527 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR(WEP_DEVASTATOR, ammo);
528 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(WEP_DEVASTATOR, ammo);
529 return ammo_amount;
530 #endif
531}
532
533METHOD(Devastator, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
534{
535 return false;
536}
537
538METHOD(Devastator, wr_resetplayer, void(entity thiswep, entity actor))
539{
540 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
541 {
542 .entity weaponentity = weaponentities[slot];
543 actor.(weaponentity).lastrocket = NULL; // stop rocket guiding, no revenge from the grave!
544 actor.(weaponentity).rl_release = 0;
545 }
546}
547
548METHOD(Devastator, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
549{
550 W_Reload(actor, weaponentity, WEP_CVAR(WEP_DEVASTATOR, ammo), SND_RELOAD);
551}
552
553METHOD(Devastator, wr_suicidemessage, Notification(entity thiswep))
554{
555 return WEAPON_DEVASTATOR_SUICIDE;
556}
557
558METHOD(Devastator, wr_killmessage, Notification(entity thiswep))
559{
561 return WEAPON_DEVASTATOR_MURDER_SPLASH;
562 else
563 return WEAPON_DEVASTATOR_MURDER_DIRECT;
564}
565
566#endif // SVQC
567#ifdef CSQC
568
569METHOD(Devastator, wr_impacteffect, void(entity thiswep, entity actor))
570{
571 vector org2 = w_org + w_backoff * 2;
572 pointparticles(EFFECT_ROCKET_EXPLODE, org2, '0 0 0', 1);
573 if (!w_issilent)
574 sound(actor, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTN_NORM);
575}
576
577#endif // CSQC
578#ifdef MENUQC
581#include "vortex.qh"
582
583METHOD(Devastator, describe, string(Devastator this))
584{
585 TC(Devastator, this);
587 PAR(_("The %s launches a remote controlled rocket, dealing significant damage when it explodes on impact. "
588 "If the primary fire is held, the rocket can be guided by the user's aim, allowing steering it towards enemies."), COLORED_NAME(this));
589 PAR(_("The secondary fire can be used to immediately detonate rockets, allowing dealing damage to enemies even if the rocket barely missed colliding with them."));
590 PAR(_("It consumes a bunch of %s ammo for each rocket, which can end up being depleted quickly, so often players alternate with another weapon like %s."), COLORED_NAME(ITEM_Rockets), COLORED_NAME(WEP_VORTEX));
591 PAR(_("Due to its high damage output, the %s is one of the most commonly used weapons. "
592 "It can be used in almost any scenario, working best in medium range combat. "
593 "In close range combat, the large splash radius often results in rockets damaging both you and your enemy."), COLORED_NAME(this));
594 PAR(_("Due to the ability to remotely detonate rockets, a common usage is \"rocket flying,\" where you fire a rocket and immediately detonate it to boost yourself while mid-air, "
595 "much more effective with the %s mutator enabled."), COLORED_NAME(MUTATOR_rocketflying));
596 PAR(W_Guide_Keybinds(this));
598 return PAGE_TEXT;
599}
600
601#endif // MENUQC
float skill
Definition api.qh:35
bool bot_aim(entity this,.entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity, bool shot_accurate)
IntrusiveList g_bot_dodge
Definition api.qh:150
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
void TakeResource(entity receiver, Resource res_type, float amount)
Takes an entity some resource.
bool SetResourceExplicit(entity e, Resource res_type, float amount)
Sets the resource amount of an entity without calling any hooks.
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition weapon.qh:42
Resource ammo_type
M: ammotype : main ammo type.
Definition weapon.qh:56
int m_id
Definition weapon.qh:43
string netname
Definition powerups.qc:20
float lifetime
Definition powerups.qc:23
float cnt
Definition powerups.qc:24
float count
Definition powerups.qc:22
#define COLORED_NAME(this)
Definition color.qh:195
const int IT_UNLIMITED_AMMO
Definition item.qh:23
float radius
Definition impulse.qh:11
#define M_ARGV(x, type)
Definition events.qh:17
bool IsFlying(entity this)
Definition player.qc:843
#define IS_DEAD(s)
Definition player.qh:244
#define IS_PLAYER(s)
Definition player.qh:242
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition player.qh:152
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition player.qh:154
const int FL_PROJECTILE
Definition constants.qh:85
vector v_up
float DEG2RAD
float frametime
vector velocity
float time
vector v_right
float nextthink
vector v_forward
vector origin
const float ATTN_NORM
void UpdateCSQCProjectile(entity e)
void CSQCProjectile(entity e, float clientanimate, int type, float docull)
float csqcprojectile_clientanimate
float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype,.entity weaponentity, entity directhitentity)
Definition damage.qc:943
float RadiusDamageForSource(entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, bool inflictorselfdamage, float forceintensity, vector forcexyzscale, int deathtype,.entity weaponentity, entity directhitentity)
Definition damage.qc:718
IntrusiveList g_damagedbycontents
Definition damage.qh:143
float spawnshieldtime
Definition damage.qh:61
float autocvar_g_balance_selfdamagepercent
Definition damage.qh:25
vector w_org
int w_deathtype
float damageforcescale
vector w_backoff
float w_issilent
const int HITTYPE_BOUNCE
Definition all.qh:31
const int HITTYPE_SPLASH
Definition all.qh:30
void W_Devastator_Explode(entity this, entity directhitentity)
Definition devastator.qc:17
void W_Devastator_Explode_think(entity this)
Definition devastator.qc:60
void W_Devastator_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
void W_Devastator_DoRemoteExplode(entity this,.entity weaponentity)
Definition devastator.qc:65
void W_Devastator_Attack(Weapon thiswep, entity actor,.entity weaponentity, int fire)
void W_Devastator_Touch(entity this, entity toucher)
void W_Devastator_RemoteExplode(entity this,.entity weaponentity)
void W_Devastator_Unregister(entity this)
Definition devastator.qc:7
vector W_Devastator_SteerTo(vector thisdir, vector goaldir, float maxturn_cos)
void W_Devastator_Think(entity this)
entity lastrocket
Definition devastator.qc:5
float rl_release
Definition devastator.qh:91
float rl_detonate_later
Definition devastator.qh:92
float speed
Definition dynlight.qc:9
#define pointparticles(effect, org, vel, howmany)
Definition effect.qh:7
void Send_Effect(entity eff, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:120
ent angles
Definition ent_cs.qc:121
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define IL_EACH(this, cond, body)
float pushltime
Definition jumppads.qh:21
#define TC(T, sym)
Definition _all.inc:82
vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel)
Definition common.qc:771
entity WarpZone_FindRadius(vector org, float rad, bool needlineofsight)
Definition common.qc:684
entity WarpZone_RefSys_SpawnSameRefSys(entity me)
Definition common.qc:806
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Definition common.qc:763
#define LOG_INFOF(...)
Definition log.qh:66
vector movedir
Definition viewloc.qh:18
ERASEABLE vector solve_quadratic(float a, float b, float c)
ax^2 + bx + c = 0
Definition math.qh:304
float bound(float min, float value, float max)
float cos(float f)
float vlen(vector v)
vector vectoangles(vector v)
float sqrt(float f)
float min(float f,...)
vector normalize(vector v)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_FLY
Definition movetypes.qh:134
var void func_null()
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1573
entity Notification
always last
Definition all.qh:81
#define METHOD(cname, name, prototype)
Definition oo.qh:269
#define NULL
Definition post.qh:14
#define makevectors
Definition post.qh:21
const int PROJECTILE_ROCKET
Definition projectiles.qh:4
float health
Legacy fields for the resources. To be removed.
Definition resources.qh:9
#define w_getbestweapon(ent, wepent)
Definition selection.qh:23
#define setthink(e, f)
#define getthink(e)
vector
Definition self.qh:92
entity entity toucher
Definition self.qh:72
#define settouch(e, f)
Definition self.qh:73
float W_CheckProjectileDamage(entity inflictor, entity projowner, int deathtype, float exception)
Definition common.qc:45
void W_PrepareExplosionByDamage(entity this, entity attacker, void(entity this) explode)
Definition common.qc:87
bool W_DualWielding(entity player)
Definition common.qc:20
const int MIF_SPLASH
Definition common.qh:46
int projectiledeathtype
Definition common.qh:21
IntrusiveList g_projectiles
Definition common.qh:58
#define PROJECTILE_MAKETRIGGER(e)
Definition common.qh:34
float WarpZone_Projectile_Touch(entity this, entity toucher)
Definition server.qc:378
const float VOL_BASE
Definition sound.qh:36
const int CH_SHOTS
Definition sound.qh:14
const int CH_WEAPON_A
Definition sound.qh:7
#define sound(e, c, s, v, a)
Definition sound.qh:52
const int CH_WEAPON_B
Definition sound.qh:8
#define PS(this)
Definition state.qh:18
bool StatusEffects_active(StatusEffect this, entity actor)
float autocvar_g_balance_powerup_strength_damage
Definition strength.qh:18
#define PAGE_TEXT
Definition string.qh:642
#define PAR(...)
Adds an individually translatable paragraph to PAGE_TEXT without having to deal with strcat and sprin...
Definition string.qh:648
#define PAGE_TEXT_INIT()
Definition string.qh:641
const int DAMAGE_YES
Definition subs.qh:80
const int DAMAGE_NO
Definition subs.qh:79
const int DAMAGE_AIM
Definition subs.qh:81
float takedamage
Definition subs.qh:78
float ammo
Definition sv_turrets.qh:43
#define DIFF_TEAM(a, b)
Definition teams.qh:242
entity realowner
vector w_shotdir
Definition tracing.qh:20
#define W_SetupShot_ProjectileSize(ent, wepent, mi, ma, antilag, recoil, snd, chan, maxdamage, deathtype)
Definition tracing.qh:30
vector w_shotorg
Definition tracing.qh:19
#define W_SetupProjVelocity_Basic(ent, pspeed, pspread)
Definition tracing.qh:49
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
ERASEABLE vector NearestPointOnBox(entity box, vector org)
Definition vector.qh:181
string W_Guide_Keybinds(Weapon wep)
Definition all.qc:824
void W_MuzzleFlash(Weapon thiswep, entity actor,.entity weaponentity, vector shotorg, vector shotdir)
Definition all.qc:715
string W_Guide_DPS_onlyOne_unnamed(string name)
Definition all.qc:901
#define WEP_CVAR(wep, name)
Definition all.qh:337
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
entity weaponentities[MAX_WEAPONSLOTS]
Definition weapon.qh:17
void W_DecreaseAmmo(Weapon wep, entity actor, float ammo_use,.entity weaponentity)
float W_WeaponSpeedFactor(entity this)
void W_Reload(entity actor,.entity weaponentity, float sent_ammo_min, Sound sent_sound)
void weapon_thinkf(entity actor,.entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor,.entity weaponentity, int fire) func)
bool weapon_prepareattack(Weapon thiswep, entity actor,.entity weaponentity, bool secondary, float attacktime)
void w_ready(Weapon thiswep, entity actor,.entity weaponentity, int fire)
#define ATTACK_FINISHED(ent, w)
entity weaponentity_fld
float weapon_load[REGISTRY_MAX(Weapons)]
Weapon m_weapon
Definition wepent.qh:26
Weapon m_switchweapon
Definition wepent.qh:25