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 if(IS_PLAYER(directhitentity))
23 if(DIFF_TEAM(this.realowner, directhitentity))
24 if(!IS_DEAD(directhitentity))
25 if(IsFlying(directhitentity))
26 Send_Notification(NOTIF_ONE, this.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
27
28 this.event_damage = func_null;
29 this.takedamage = DAMAGE_NO;
30
31 vector force_xyzscale = '1 1 1';
32 force_xyzscale.x = WEP_CVAR(WEP_DEVASTATOR, force_xyscale);
33 force_xyzscale.y = WEP_CVAR(WEP_DEVASTATOR, force_xyscale);
34
36 this,
37 this.origin,
38 this.velocity,
39 this.realowner,
40 WEP_CVAR(WEP_DEVASTATOR, damage),
41 WEP_CVAR(WEP_DEVASTATOR, edgedamage),
42 WEP_CVAR(WEP_DEVASTATOR, radius),
43 NULL,
44 NULL,
45 false,
46 WEP_CVAR(WEP_DEVASTATOR, force),
47 force_xyzscale,
50 directhitentity
51 );
52
53 Weapon thiswep = WEP_DEVASTATOR;
54 .entity weaponentity = this.weaponentity_fld;
55 if(this.realowner.(weaponentity).m_weapon == thiswep)
56 {
57 if(GetResource(this.realowner, thiswep.ammo_type) < WEP_CVAR(WEP_DEVASTATOR, ammo))
58 if(!(this.realowner.items & IT_UNLIMITED_AMMO))
59 {
60 this.realowner.cnt = thiswep.m_id;
61 ATTACK_FINISHED(this.realowner, weaponentity) = time;
62 this.realowner.(weaponentity).m_switchweapon = w_getbestweapon(this.realowner, weaponentity);
63 }
64 }
65 delete(this);
66}
67
72
74{
76
77 this.event_damage = func_null;
78 this.takedamage = DAMAGE_NO;
79
80 bool handled_as_rocketjump = false;
81 entity head = NULL;
82 bool allow_rocketjump = WEP_CVAR(WEP_DEVASTATOR, remote_jump);
83 MUTATOR_CALLHOOK(AllowRocketJumping, allow_rocketjump);
84 allow_rocketjump = M_ARGV(0, bool);
85
86 if(allow_rocketjump && WEP_CVAR(WEP_DEVASTATOR, remote_jump_radius))
87 {
89 this.origin,
90 WEP_CVAR(WEP_DEVASTATOR, remote_jump_radius),
91 false
92 );
93
94 while(head)
95 {
96 if(head.takedamage && (head == this.realowner))
97 {
98 if(vdist(this.origin - head.WarpZone_findradius_nearest, <=, WEP_CVAR(WEP_DEVASTATOR, remote_jump_radius)))
99 {
100 // we handled this as a rocketjump :)
101 handled_as_rocketjump = true;
102
103 // modify velocity
104 if(WEP_CVAR(WEP_DEVASTATOR, remote_jump_velocity_z_add))
105 {
106 head.velocity_x *= 0.9;
107 head.velocity_y *= 0.9;
108 head.velocity_z = bound(
109 WEP_CVAR(WEP_DEVASTATOR, remote_jump_velocity_z_min),
110 head.velocity.z + WEP_CVAR(WEP_DEVASTATOR, remote_jump_velocity_z_add),
111 WEP_CVAR(WEP_DEVASTATOR, remote_jump_velocity_z_max)
112 );
113 }
114
115 // now do the damage
117 this,
118 head,
119 WEP_CVAR(WEP_DEVASTATOR, remote_jump_damage),
120 WEP_CVAR(WEP_DEVASTATOR, remote_jump_damage),
121 WEP_CVAR(WEP_DEVASTATOR, remote_jump_radius),
122 NULL,
123 head,
124 (WEP_CVAR(WEP_DEVASTATOR, remote_jump_force) ? WEP_CVAR(WEP_DEVASTATOR, remote_jump_force) : 0),
126 this.weaponentity_fld,
127 NULL
128 );
129 break;
130 }
131 }
132 head = head.chain;
133 }
134 }
135
137 this,
138 this.realowner,
139 WEP_CVAR(WEP_DEVASTATOR, remote_damage),
140 WEP_CVAR(WEP_DEVASTATOR, remote_edgedamage),
141 WEP_CVAR(WEP_DEVASTATOR, remote_radius),
142 (handled_as_rocketjump ? head : NULL),
143 NULL,
144 WEP_CVAR(WEP_DEVASTATOR, remote_force),
146 this.weaponentity_fld,
147 NULL
148 );
149
150 Weapon thiswep = WEP_DEVASTATOR;
151 if(this.realowner.(weaponentity).m_weapon == thiswep)
152 {
153 if(GetResource(this.realowner, thiswep.ammo_type) < WEP_CVAR(WEP_DEVASTATOR, ammo))
154 if(!(this.realowner.items & IT_UNLIMITED_AMMO))
155 {
156 this.realowner.cnt = thiswep.m_id;
157 ATTACK_FINISHED(this.realowner, weaponentity) = time;
158 this.realowner.(weaponentity).m_switchweapon = w_getbestweapon(this.realowner, weaponentity);
159 }
160 }
161 delete(this);
162}
163
164void W_Devastator_RemoteExplode(entity this, .entity weaponentity)
165{
166 if(!IS_DEAD(this.realowner))
167 if(this.realowner.(weaponentity).lastrocket)
168 {
169 if((this.spawnshieldtime >= 0)
170 ? (time >= this.spawnshieldtime) // timer
171 : (vdist(NearestPointOnBox(this.realowner, this.origin) - this.origin, >, WEP_CVAR(WEP_DEVASTATOR, remote_radius))) // safety device
172 )
173 {
174 W_Devastator_DoRemoteExplode(this, weaponentity);
175 }
176 }
177}
178
179vector W_Devastator_SteerTo(vector thisdir, vector goaldir, float maxturn_cos)
180{
181 if(thisdir * goaldir > maxturn_cos)
182 return goaldir;
183 if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
184 return thisdir; // refuse to guide (better than letting a numerical error happen)
185 float f, m2;
186 vector v;
187 // solve:
188 // g = normalize(thisdir + goaldir * X)
189 // thisdir * g = maxturn
190 //
191 // gg = thisdir + goaldir * X
192 // (thisdir * gg)^2 = maxturn^2 * (gg * gg)
193 //
194 // (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
195 f = thisdir * goaldir;
196 // (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
197 // 0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
198 m2 = maxturn_cos * maxturn_cos;
199 v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
200 return normalize(thisdir + goaldir * v.y); // the larger solution!
201}
202// assume thisdir == -goaldir:
203// f == -1
204// v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
205// (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
206// x^2 - 2 * x + 1 = 0
207// (x - 1)^2 = 0
208// x = 1
209// normalize(thisdir + goaldir)
210// normalize(0)
211
213{
214 vector desireddir, olddir, newdir, desiredorigin, goal;
215 float velspeed, f;
216 this.nextthink = time;
217 if(time > this.cnt)
218 {
221 return;
222 }
223
224 // accelerate
225 makevectors(this.angles.x * '-1 0 0' + this.angles.y * '0 1 0');
226 velspeed = WEP_CVAR(WEP_DEVASTATOR, speed) * W_WeaponSpeedFactor(this.realowner) - (this.velocity * v_forward);
227 if(velspeed > 0)
228 this.velocity = this.velocity + v_forward * min(WEP_CVAR(WEP_DEVASTATOR, speedaccel) * W_WeaponSpeedFactor(this.realowner) * frametime, velspeed);
229
230 // laser guided, or remote detonation
231 .entity weaponentity = this.weaponentity_fld;
232 if(this.realowner.(weaponentity).m_weapon == WEP_DEVASTATOR)
233 {
234 if(this == this.realowner.(weaponentity).lastrocket)
235 if(!this.realowner.(weaponentity).rl_release)
236 if(!PHYS_INPUT_BUTTON_ATCK2(this))
237 if(WEP_CVAR(WEP_DEVASTATOR, guiderate))
238 if(time > this.pushltime)
239 if(!IS_DEAD(this.realowner))
240 {
241 f = WEP_CVAR(WEP_DEVASTATOR, guideratedelay);
242 if(f)
243 f = bound(0, (time - this.pushltime) / f, 1);
244 else
245 f = 1;
246
247 vector md = this.realowner.(weaponentity).movedir;
248 vector dv = v_right * -md.y + v_up * md.z;
249
250 if(!W_DualWielding(this.realowner))
251 dv = '0 0 0'; // don't override!
252
253 velspeed = vlen(this.velocity);
254
255 makevectors(this.realowner.v_angle);
256 desireddir = WarpZone_RefSys_TransformVelocity(this.realowner, this, v_forward);
257 desiredorigin = WarpZone_RefSys_TransformOrigin(this.realowner, this, this.realowner.origin + this.realowner.view_ofs + dv);
258 olddir = normalize(this.velocity);
259
260 // now it gets tricky... we want to move like some curve to approximate the target direction
261 // but we are limiting the rate at which we can turn!
262 goal = desiredorigin + ((this.origin - desiredorigin) * desireddir + WEP_CVAR(WEP_DEVASTATOR, guidegoal)) * desireddir;
263 newdir = W_Devastator_SteerTo(olddir, normalize(goal - this.origin), cos(WEP_CVAR(WEP_DEVASTATOR, guiderate) * f * frametime * DEG2RAD));
264
265 this.velocity = newdir * velspeed;
266 this.angles = vectoangles(this.velocity);
267
268 if(!this.count)
269 {
270 Send_Effect(EFFECT_ROCKET_GUIDE, this.origin, this.velocity, 1);
271 // TODO add a better sound here
272 sound(this.realowner, CH_WEAPON_B, SND_DEVASTATOR_MODE, VOL_BASE, ATTN_NORM);
273 this.count = 1;
274 }
275 }
276
277 if(this.rl_detonate_later)
278 W_Devastator_RemoteExplode(this, weaponentity);
279 }
280
281 if(this.csqcprojectile_clientanimate == 0)
283}
284
286{
288 {
289 if(wasfreed(this))
291 return;
292 }
295}
296
297void W_Devastator_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
298{
299 if(GetResource(this, RES_HEALTH) <= 0)
300 return;
301
302 if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
303 return; // g_projectiles_damage says to halt
304
305 TakeResource(this, RES_HEALTH, damage);
306 this.angles = vectoangles(this.velocity);
307
308 if(GetResource(this, RES_HEALTH) <= 0)
310}
311
312void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
313{
314 W_DecreaseAmmo(thiswep, actor, WEP_CVAR(WEP_DEVASTATOR, ammo), weaponentity);
315
316 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);
317 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
318
320 missile.weaponentity_fld = weaponentity;
321 missile.owner = missile.realowner = actor;
322 actor.(weaponentity).lastrocket = missile;
323 if(WEP_CVAR(WEP_DEVASTATOR, detonatedelay) >= 0)
324 missile.spawnshieldtime = time + WEP_CVAR(WEP_DEVASTATOR, detonatedelay);
325 else
326 missile.spawnshieldtime = -1; // NOTE: proximity based when rocket jumping
327 missile.pushltime = time + WEP_CVAR(WEP_DEVASTATOR, guidedelay);
328 missile.classname = "rocket";
329 missile.bot_dodge = true;
330 missile.bot_dodgerating = WEP_CVAR(WEP_DEVASTATOR, damage) * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
331
332 missile.takedamage = DAMAGE_YES;
333 missile.damageforcescale = WEP_CVAR(WEP_DEVASTATOR, damageforcescale);
334 SetResourceExplicit(missile, RES_HEALTH, WEP_CVAR(WEP_DEVASTATOR, health));
335 missile.event_damage = W_Devastator_Damage;
336 missile.damagedbycontents = true;
338
339 set_movetype(missile, MOVETYPE_FLY);
340 PROJECTILE_MAKETRIGGER(missile);
341 missile.projectiledeathtype = thiswep.m_id;
342 setsize(missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
343
344 setorigin(missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
345 W_SetupProjVelocity_Basic(missile, WEP_CVAR(WEP_DEVASTATOR, speedstart), 0);
346 missile.angles = vectoangles(missile.velocity);
347
350 missile.nextthink = time;
351 missile.cnt = time + WEP_CVAR(WEP_DEVASTATOR, lifetime);
352 missile.rl_detonate_later = (fire & 2); // allow instant detonation
353 missile.flags = FL_PROJECTILE;
354 IL_PUSH(g_projectiles, missile);
355 IL_PUSH(g_bot_dodge, missile);
356 missile.missile_flags = MIF_SPLASH;
357
358 CSQCProjectile(missile, WEP_CVAR(WEP_DEVASTATOR, guiderate) == 0 && WEP_CVAR(WEP_DEVASTATOR, speedaccel) == 0, PROJECTILE_ROCKET, false); // because of fly sound
359
360 // common properties
361 MUTATOR_CALLHOOK(EditProjectile, actor, missile);
362
363 if (time >= missile.nextthink)
364 {
365 getthink(missile)(missile);
366 }
367}
368
369METHOD(Devastator, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
370{
371 if (!WEP_CVAR(WEP_DEVASTATOR, guidestop) && !actor.(weaponentity).rl_release)
372 {
373 int fired_rockets = 0;
374 IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
375 {
376 fired_rockets++;
377 });
378 // release PHYS_INPUT_BUTTON_ATCK after all fired rocket exploded otherwise bot can't fire again
379 if (!fired_rockets)
380 return;
381 }
382
383 // aim and decide to fire if appropriate
384 float spd = WEP_CVAR(WEP_DEVASTATOR, speed);
385 // simulate rocket guide by calculating rocket trajectory with higher speed
386 // 20 times faster at 90 degrees guide rate
387 if (WEP_CVAR(WEP_DEVASTATOR, guiderate) > 0)
388 spd *= sqrt(WEP_CVAR(WEP_DEVASTATOR, guiderate)) * (20 / 9.489); // 9.489 ~= sqrt(90)
389 // no need to fire with high accuracy on large distances if rockets can be guided
390 bool shot_accurate = (WEP_CVAR(WEP_DEVASTATOR, guiderate) < 50);
391 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, spd, 0, WEP_CVAR(WEP_DEVASTATOR, lifetime), false, shot_accurate);
392 float pred_time = bound(0.02, 0.02 + (8 - skill) * 0.01, 0.1);
393 if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
394 {
395 // decide whether to detonate rockets
396 float selfdamage = 0, teamdamage = 0, enemydamage = 0;
397 float pred_selfdamage = 0, pred_teamdamage = 0, pred_enemydamage = 0;
398 float edgedamage = WEP_CVAR(WEP_DEVASTATOR, edgedamage);
399 float coredamage = WEP_CVAR(WEP_DEVASTATOR, damage);
400 float edgeradius = WEP_CVAR(WEP_DEVASTATOR, radius);
401 IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
402 {
403 entity rocket = it;
404 IL_EACH(g_bot_targets, it.bot_attack,
405 {
406 // code to calculate damage is similar to the one used in RadiusDamageForSource with some simplifications
407 vector target_pos = it.origin + (it.maxs - it.mins) * 0.5;
408
409 float dist = vlen(target_pos - rocket.origin);
410 float dmg = 0;
411 if (dist <= edgeradius)
412 {
413 float f = (edgeradius > 0) ? max(0, 1 - (dist / edgeradius)) : 1;
414 dmg = coredamage * f + edgedamage * (1 - f);
415 }
416
417 float pred_dist = vlen(target_pos + it.velocity * pred_time - (rocket.origin + rocket.velocity * pred_time));
418 float pred_dmg = 0;
419 if (pred_dist <= edgeradius)
420 {
421 float f = (edgeradius > 0) ? max(0, 1 - (pred_dist / edgeradius)) : 1;
422 pred_dmg = coredamage * f + edgedamage * (1 - f);
423 }
424
425 // count potential damage according to type of target
426 if(it == actor)
427 {
428 if(StatusEffects_active(STATUSEFFECT_Strength, it))
429 dmg *= autocvar_g_balance_powerup_strength_damage;
430 if(StatusEffects_active(STATUSEFFECT_Shield, it))
431 dmg *= autocvar_g_balance_powerup_invincible_takedamage;
432 // self damage reduction factor will be applied later to the total damage
433 selfdamage += dmg;
434 pred_selfdamage += pred_dmg;
435 }
436 else if(SAME_TEAM(it, actor))
437 {
438 if(StatusEffects_active(STATUSEFFECT_Shield, it))
439 dmg *= autocvar_g_balance_powerup_invincible_takedamage;
440 // bot strength factor will be applied later to the total damage
441 teamdamage += dmg;
442 pred_teamdamage += pred_dmg;
443 }
444 else if(bot_shouldattack(actor, it))
445 {
446 if(StatusEffects_active(STATUSEFFECT_Shield, it))
447 dmg *= autocvar_g_balance_powerup_invincible_takedamage;
448 // bot strength factor will be applied later to the total damage
449 enemydamage += dmg;
450 pred_enemydamage += pred_dmg;
451 }
452 });
453 });
454
456 pred_selfdamage *= autocvar_g_balance_selfdamagepercent;
457 if(StatusEffects_active(STATUSEFFECT_Strength, actor))
458 {
459 // FIXME bots don't know whether team damage is enabled or not
464 }
465
466 float good_damage = enemydamage;
467 float pred_good_damage = pred_enemydamage;
468 float bad_damage = selfdamage + teamdamage;
469 float pred_bad_damage = pred_selfdamage + pred_teamdamage;
470
471 // detonate if predicted good damage is lower (current good damage is maximum)
472 // or if predicted bad damage is too much
473 if(good_damage > coredamage * 0.1 && good_damage > bad_damage * 1.5
474 && (pred_good_damage < good_damage + 2 || pred_good_damage < pred_bad_damage * 1.5))
475 {
476 PHYS_INPUT_BUTTON_ATCK2(actor) = true;
477 }
478 if(skill >= 7 && selfdamage > GetResource(actor, RES_HEALTH))
479 PHYS_INPUT_BUTTON_ATCK2(actor) = false;
480
481 // don't fire a new shot at the same time!
482 if(PHYS_INPUT_BUTTON_ATCK2(actor)) PHYS_INPUT_BUTTON_ATCK(actor) = false;
483 }
484}
485
486METHOD(Devastator, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
487{
488 if(WEP_CVAR(WEP_DEVASTATOR, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR(WEP_DEVASTATOR, ammo)) { // forced reload
489 thiswep.wr_reload(thiswep, actor, weaponentity);
490 } else {
491 if(fire & 1)
492 {
493 if(actor.(weaponentity).rl_release || WEP_CVAR(WEP_DEVASTATOR, guidestop))
494 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(WEP_DEVASTATOR, refire)))
495 {
496 W_Devastator_Attack(thiswep, actor, weaponentity, fire);
497 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(WEP_DEVASTATOR, animtime), w_ready);
498 actor.(weaponentity).rl_release = 0;
499 }
500 }
501 else
502 actor.(weaponentity).rl_release = 1;
503
504 if(fire & 2)
505 if(actor.(weaponentity).m_switchweapon == thiswep)
506 {
507 bool rockfound = false;
508 IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
509 {
510 if(!it.rl_detonate_later)
511 {
512 it.rl_detonate_later = true;
513 rockfound = true;
514 }
515 });
516 if(rockfound)
517 sound(actor, CH_WEAPON_B, SND_DEVASTATOR_DET, VOL_BASE, ATTN_NORM);
518 }
519 }
520}
521
522METHOD(Devastator, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
523{
524 actor.(weaponentity).rl_release = 1;
525}
526
527METHOD(Devastator, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
528{
529 #if 0
530 // don't switch while guiding a missile
531 if(ATTACK_FINISHED(actor, weaponentity) <= time || PS(actor).m_weapon != WEP_DEVASTATOR)
532 {
533 ammo_amount = false;
534 if(WEP_CVAR(WEP_DEVASTATOR, reload_ammo))
535 {
536 if(GetResource(actor, thiswep.ammo_type) < WEP_CVAR(WEP_DEVASTATOR, ammo) && actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(WEP_DEVASTATOR, ammo))
537 ammo_amount = true;
538 }
539 else if(GetResource(actor, thiswep.ammo_type) < WEP_CVAR(WEP_DEVASTATOR, ammo))
540 ammo_amount = true;
541 return !ammo_amount;
542 }
543 #endif
544 #if 0
545 if(actor.rl_release == 0)
546 {
547 LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE", actor.rl_release, GetResource(actor, thiswep.ammo_type), WEP_CVAR(WEP_DEVASTATOR, ammo));
548 return true;
549 }
550 else
551 {
552 ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR(WEP_DEVASTATOR, ammo);
553 ammo_amount += actor.(weaponentity).(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(WEP_DEVASTATOR, ammo);
554 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"));
555 return ammo_amount;
556 }
557 #else
558 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR(WEP_DEVASTATOR, ammo);
559 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR(WEP_DEVASTATOR, ammo);
560 return ammo_amount;
561 #endif
562}
563
564METHOD(Devastator, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
565{
566 return false;
567}
568
569METHOD(Devastator, wr_resetplayer, void(entity thiswep, entity actor))
570{
571 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
572 {
573 .entity weaponentity = weaponentities[slot];
574 actor.(weaponentity).lastrocket = NULL; // stop rocket guiding, no revenge from the grave!
575 actor.(weaponentity).rl_release = 0;
576 }
577}
578
579METHOD(Devastator, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
580{
581 W_Reload(actor, weaponentity, WEP_CVAR(WEP_DEVASTATOR, ammo), SND_RELOAD);
582}
583
584METHOD(Devastator, wr_suicidemessage, Notification(entity thiswep))
585{
586 return WEAPON_DEVASTATOR_SUICIDE;
587}
588
589METHOD(Devastator, wr_killmessage, Notification(entity thiswep))
590{
592 return WEAPON_DEVASTATOR_MURDER_SPLASH;
593 else
594 return WEAPON_DEVASTATOR_MURDER_DIRECT;
595}
596
597#endif
598#ifdef CSQC
599
600METHOD(Devastator, wr_impacteffect, void(entity thiswep, entity actor))
601{
602 vector org2 = w_org + w_backoff * 2;
603 pointparticles(EFFECT_ROCKET_EXPLODE, org2, '0 0 0', 1);
604 if(!w_issilent)
605 sound(actor, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTN_NORM);
606}
607
608#endif
609#ifdef MENUQC
612#include "vortex.qh"
613
614METHOD(Devastator, describe, string(Devastator this))
615{
616 TC(Devastator, this);
618 PAR(_("The %s launches a remote controlled rocket, dealing significant damage when it explodes on impact. "
619 "If the primary fire is held, the rocket can be guided by the user's aim, allowing steering it towards enemies."), COLORED_NAME(this));
620 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."));
621 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));
622 PAR(_("Due to its high damage output, the %s is one of the most commonly used weapons. "
623 "It can be used in almost any scenario, working best in medium range combat. "
624 "In close range combat, the large splash radius often results in rockets damaging both you and your enemy."), COLORED_NAME(this));
625 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, "
626 "much more effective with the %s mutator enabled."), COLORED_NAME(MUTATOR_rocketflying));
627 PAR(W_Guide_Keybinds(this));
629 return PAGE_TEXT;
630}
631
632#endif
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:44
Resource ammo_type
M: ammotype : main ammo type.
Definition weapon.qh:51
int m_id
Definition weapon.qh:45
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:836
#define IS_DEAD(s)
Definition player.qh:245
#define IS_PLAYER(s)
Definition player.qh:243
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition player.qh:150
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition player.qh:152
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:981
float RadiusDamageForSource(entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float inflictorselfdamage, float forceintensity, vector forcexyzscale, int deathtype,.entity weaponentity, entity directhitentity)
Definition damage.qc:741
IntrusiveList g_damagedbycontents
Definition damage.qh:135
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:68
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:73
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:87
float rl_detonate_later
Definition devastator.qh:88
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:124
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:773
entity WarpZone_FindRadius(vector org, float rad, bool needlineofsight)
Definition common.qc:686
entity WarpZone_RefSys_SpawnSameRefSys(entity me)
Definition common.qc:808
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Definition common.qc:765
#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:381
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:643
#define PAR(...)
Adds an individually translatable paragraph to PAGE_TEXT without having to deal with strcat and sprin...
Definition string.qh:649
#define PAGE_TEXT_INIT()
Definition string.qh:642
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:178
string W_Guide_Keybinds(Weapon wep)
Definition all.qc:836
void W_MuzzleFlash(Weapon thiswep, entity actor,.entity weaponentity, vector shotorg, vector shotdir)
Definition all.qc:728
string W_Guide_DPS_onlyOne_unnamed(string name)
Definition all.qc:913
#define WEP_CVAR(wep, name)
Definition all.qh:321
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