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