]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_seeker.qc
Merge remote branch 'origin/master' into samual/seeker_updates
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_seeker.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(SEEKER, w_seeker, IT_ROCKETS, 8, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_MID, "seeker", "seeker", _("T.A.G. Seeker"))
3 #else
4 #ifdef SVQC
5 //.float proxytime; = autoswitch
6 //.float tl; = wait
7 .entity tag_target, wps_tag_tracker;
8 .float tag_time;
9
10 void Seeker_Missile_Explode ()
11 {
12         self.event_damage = SUB_Null;
13         RadiusDamage (self, self.owner, autocvar_g_balance_seeker_missile_damage, autocvar_g_balance_seeker_missile_edgedamage, autocvar_g_balance_seeker_missile_radius, world, autocvar_g_balance_seeker_missile_force, self.projectiledeathtype, other);
14
15         remove (self);
16 }
17
18 void Seeker_Missile_Touch()
19 {
20         PROJECTILE_TOUCH;
21
22         Seeker_Missile_Explode();
23 }
24
25 void Seeker_Missile_Think()
26 {
27         entity e;
28         vector desireddir, olddir, newdir, eorg;
29         float turnrate;
30         float dist;
31         float spd;
32
33         if (time > self.cnt)
34         {
35                 self.projectiledeathtype |= HITTYPE_SPLASH;
36                 Seeker_Missile_Explode();
37         }
38
39         spd = vlen(self.velocity);
40         spd = bound(
41                 spd - autocvar_g_balance_seeker_missile_decel * frametime,
42                 autocvar_g_balance_seeker_missile_speed_max,
43                 spd + autocvar_g_balance_seeker_missile_accel * frametime
44         );
45
46         if (self.enemy != world)
47                 if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
48                         self.enemy = world;
49
50         if (self.enemy != world)
51         {
52                 e               = self.enemy;
53                 eorg            = 0.5 * (e.absmin + e.absmax);
54                 turnrate        = autocvar_g_balance_seeker_missile_turnrate; // how fast to turn
55                 desireddir      = normalize(eorg - self.origin);
56                 olddir          = normalize(self.velocity); // get my current direction
57                 dist            = vlen(eorg - self.origin);
58
59                 // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
60                 if (autocvar_g_balance_seeker_missile_smart && (dist > autocvar_g_balance_seeker_missile_smart_mindist))
61                 {
62                         // Is it a better idea (shorter distance) to trace to the target itself?
63                         if ( vlen(self.origin + olddir * self.wait) < dist)
64                                 traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
65                         else
66                                 traceline(self.origin, eorg, FALSE, self);
67
68                         // Setup adaptive tracelength
69                         self.wait = bound(autocvar_g_balance_seeker_missile_smart_trace_min, vlen(self.origin - trace_endpos), self.wait = autocvar_g_balance_seeker_missile_smart_trace_max);
70
71                         // Calc how important it is that we turn and add this to the desierd (enemy) dir.
72                         desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
73                 }
74                 
75                 newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
76                 self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
77         }
78
79         // Proxy
80         if (autocvar_g_balance_seeker_missile_proxy)
81         {
82                 if ( dist <= autocvar_g_balance_seeker_missile_proxy_maxrange)
83                 {
84                         if (self.autoswitch == 0)
85                         {
86                                 self.autoswitch = time + autocvar_g_balance_seeker_missile_proxy_delay;
87                         }
88                         else
89                         {
90                                 if (self.autoswitch <= time)
91                                 {
92                                         Seeker_Missile_Explode();
93                                         self.autoswitch = 0;
94                                 }
95                         }
96                 }
97                 else
98                 {
99                         if (self.autoswitch != 0)
100                                 self.autoswitch = 0;
101                 }
102         }
103         ///////////////
104
105         if (self.enemy.deadflag != DEAD_NO)
106         {
107                 self.enemy = world;
108                 self.cnt = time + 1 + (random() * 4);
109                 self.nextthink = self.cnt;
110                 return;
111         }
112
113         //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
114         self.nextthink = time;// + 0.05; // csqc projectiles
115         UpdateCSQCProjectile(self);
116 }
117
118
119
120 void Seeker_Missile_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
121 {
122         if (self.health <= 0)
123                 return;
124
125         if (self.owner == attacker)
126                 self.health = self.health - (damage * 0.25);
127         else
128                 self.health = self.health - damage;
129                 
130         if (self.health <= 0)
131                 W_PrepareExplosionByDamage(attacker, Seeker_Missile_Explode);
132 }
133
134 /*
135 void Seeker_Missile_Animate()
136 {
137         self.frame = self.frame +1;
138         self.nextthink = time + 0.05;
139
140         if (self.enemy != world)
141                 if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
142                         self.enemy = world;
143
144         if(self.frame == 5)
145         {
146                 self.think           = Seeker_Missile_Think;
147                 self.nextthink       = time;// + cvar("g_balance_seeker_missile_activate_delay"); // cant dealy with csqc projectiles
148
149                 if (autocvar_g_balance_seeker_missile_proxy)
150                         self.movetype    = MOVETYPE_BOUNCEMISSILE;
151                 else
152                         self.movetype    = MOVETYPE_FLYMISSILE;
153         }
154
155         UpdateCSQCProjectile(self);
156 }
157 */
158
159 void Seeker_Fire_Missile(vector f_diff, entity m_target)
160 {
161         local entity missile;
162
163         W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_reload_ammo);
164
165         makevectors(self.v_angle);
166         W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/seeker_fire.wav", CHAN_WEAPON, 0);
167         w_shotorg += f_diff;
168         pointparticles(particleeffectnum("seeker_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
169
170         //self.detornator         = FALSE;
171
172         missile                 = spawn();
173         missile.owner           = self;
174         missile.classname       = "seeker_missile";
175         missile.bot_dodge       = TRUE;
176         missile.bot_dodgerating = autocvar_g_balance_seeker_missile_damage;
177
178         missile.think           = Seeker_Missile_Think;
179         missile.touch           = Seeker_Missile_Touch;
180         missile.event_damage    = Seeker_Missile_Damage;
181         missile.nextthink       = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay");
182         missile.cnt             = time + autocvar_g_balance_seeker_missile_lifetime;
183         missile.enemy           = m_target;
184         missile.solid           = SOLID_BBOX;
185         missile.scale           = 2;
186         missile.takedamage      = DAMAGE_YES;
187         missile.health          = autocvar_g_balance_seeker_missile_health;
188         missile.damageforcescale = autocvar_g_balance_seeker_missile_damageforcescale;
189         //missile.think           = Seeker_Missile_Animate; // csqc projectiles.
190         
191         if (missile.enemy != world)
192                 missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY;
193         else 
194                 missile.projectiledeathtype = WEP_SEEKER;
195
196
197         setorigin (missile, w_shotorg);
198         setsize (missile, '-4 -4 -4', '4 4 4');
199         missile.movetype    = MOVETYPE_FLYMISSILE;
200         missile.flags       = FL_PROJECTILE;
201         W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_missile);
202
203         missile.angles = vectoangles (missile.velocity);
204
205         CSQCProjectile(missile, FALSE, PROJECTILE_SEEKER, TRUE);
206
207         other = missile; MUTATOR_CALLHOOK(EditProjectile);
208 }
209
210 entity Seeker_Tagged_Info(entity isowner, entity istarget)
211 {
212         entity tag;
213         for(tag = world; (tag = find(tag, classname, "tag_tracker")); ) 
214                 if ((tag.owner == isowner) && (tag.tag_target == istarget))
215                         return tag;
216                 
217         return world;
218 }
219
220 void Seeker_Attack()
221 {
222         entity tracker, closest_target;
223         
224         for(tracker = world; (tracker = find(tracker, classname, "tag_tracker")); ) if (tracker.owner == self)
225         {
226                 if (closest_target)
227                 {
228                         if (vlen(self.origin - tracker.tag_target.origin) < vlen(self.origin - closest_target.origin))
229                                 closest_target = tracker.tag_target;
230                 }
231                 else 
232                         closest_target = tracker.tag_target;
233         }
234                 
235         traceline(self.origin + self.view_ofs, closest_target.origin, MOVE_NOMONSTERS, self);
236         if ((!closest_target) || ((trace_fraction < 1) && (trace_ent != closest_target)))
237                 closest_target = world;
238         
239         Seeker_Fire_Missile('0 0 0', closest_target);
240         /*
241         switch(mod(self.bulletcounter, 4))
242         {
243                 case 0:
244                         Seeker_Fire_Missile('-1.25 -3.75 0', closest_target);
245                         break;
246                 case 1:
247                         Seeker_Fire_Missile('+1.25 -3.75 0', closest_target);
248                         break;
249                 case 2:
250                         Seeker_Fire_Missile('-1.25 +3.75 0', closest_target);
251                         break;
252                 case 3:
253                 default:
254                         Seeker_Fire_Missile('+1.25 +3.75 0', closest_target);
255                         break;
256         }
257         */
258
259 }
260
261 void Seeker_Tracker_Think() 
262 {
263         // commit suicide if: You die OR target dies OR you switch away from the seeker OR commit suicide if lifetime is up
264         if ((self.owner.deadflag != DEAD_NO) || (self.tag_target.deadflag != DEAD_NO) || (self.owner.switchweapon != WEP_SEEKER)
265         || (time > self.tag_time + autocvar_g_balance_seeker_tag_tracker_lifetime))
266         {
267                 if (self)
268                 {
269                         WaypointSprite_Kill(self.tag_target.wps_tag_tracker);
270                         remove(self);
271                 }
272                 return;
273         }
274         
275         // Update the think method information
276         self.nextthink = time;
277 }
278
279 void Seeker_Tag_Explode ()
280 {
281         //if(other==self.owner)
282         //    return;
283         Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE, self);
284
285         remove (self);
286 }
287
288 void Seeker_Tag_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
289 {
290         if (self.health <= 0)
291                 return;
292         self.health = self.health - damage;
293         if (self.health <= 0)
294                 Seeker_Tag_Explode();
295 }
296
297 void Seeker_Tag_Touch()
298 {
299         vector dir;
300         vector org2;
301         entity e;
302         
303         dir     = normalize (self.owner.origin - self.origin);
304         org2    = findbetterlocation (self.origin, 8);
305
306         te_knightspike(org2);
307
308         self.event_damage = SUB_Null;
309         Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_HEADSHOT, self);
310
311         if (other.takedamage == DAMAGE_AIM && other.deadflag == DEAD_NO)
312         {
313                 // check to see if this person is already tagged by me
314                 entity tag = Seeker_Tagged_Info(self.owner, other);
315                 
316                 if (tag != world)
317                 {
318                         if (other.wps_tag_tracker) // don't attach another waypointsprite without killing the old one first
319                                 WaypointSprite_Kill(other.wps_tag_tracker);
320                                 
321                         tag.tag_time = time;
322                 }
323                 else
324                 {               
325                         sprint(self.owner, strcat("You just tagged ^2", other.netname, "^7 with a tracking device!\n"));
326                         e             = spawn();
327                         e.classname   = "tag_tracker";
328                         e.owner       = self.owner;
329                         e.think       = Seeker_Tracker_Think;
330                         e.nextthink   = time;
331                         e.tag_target  = other;
332                         e.tag_time    = time;
333                 }
334                         
335                 WaypointSprite_Spawn("nb-ball", autocvar_g_balance_seeker_tag_tracker_lifetime, 0, other, '0 0 64', self.owner, 0, other, wps_tag_tracker, TRUE);
336                 WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT);
337         }
338
339         remove(self);
340         return;
341 }
342
343 void Seeker_Fire_Tag()
344 {
345         local entity missile;
346         W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_tag_ammo, autocvar_g_balance_seeker_reload_ammo);
347
348         W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/tag_fire.wav", CHAN_WEAPON, autocvar_g_balance_seeker_missile_damage * autocvar_g_balance_seeker_missile_count);
349
350         missile                 = spawn();
351         missile.owner           = self;
352         missile.classname       = "seeker_tag";
353         missile.bot_dodge       = TRUE;
354         missile.bot_dodgerating = 50;
355         missile.touch           = Seeker_Tag_Touch;
356         missile.think           = SUB_Remove;
357         missile.nextthink       = time + autocvar_g_balance_seeker_tag_lifetime;
358         missile.movetype        = MOVETYPE_FLY;
359         missile.solid           = SOLID_BBOX;
360         missile.owner           = self;
361
362         missile.takedamage       = DAMAGE_YES;
363         missile.event_damage    = Seeker_Tag_Explode;
364         missile.health          = autocvar_g_balance_seeker_tag_health;
365         missile.damageforcescale = autocvar_g_balance_seeker_tag_damageforcescale;
366
367         setorigin (missile, w_shotorg);
368         setsize (missile, '-2 -2 -2', '2 2 2');
369
370         missile.flags       = FL_PROJECTILE;
371
372         missile.movetype    = MOVETYPE_FLY;
373         W_SETUPPROJECTILEVELOCITY(missile, g_balance_seeker_tag);
374         missile.angles = vectoangles (missile.velocity);
375
376         CSQCProjectile(missile, TRUE, PROJECTILE_TAG, FALSE); // has sound
377
378         other = missile; MUTATOR_CALLHOOK(EditProjectile);
379 }
380
381 void spawnfunc_weapon_seeker (void)
382 {
383         weapon_defaultspawnfunc(WEP_SEEKER);
384 }
385
386 float w_seeker(float req)
387 {
388         float ammo_amount;
389
390         if (req == WR_AIM)
391         {
392                 if (Seeker_Tagged_Info(self, self.enemy) != world)
393                         self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_seeker_missile_speed_max, 0, autocvar_g_balance_seeker_missile_lifetime, FALSE);
394                 else
395                         self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE);
396         }
397         else if (req == WR_THINK)
398         {
399                 if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo)) // forced reload
400                         weapon_action(self.weapon, WR_RELOAD);
401                         
402                 else if (self.BUTTON_ATCK)
403                 {
404                         if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire))
405                         {
406                                 Seeker_Fire_Tag();
407                                 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_seeker_tag_animtime, w_ready);
408                         }
409                 }
410
411                 else if (self.BUTTON_ATCK2)
412                 {
413                         if (weapon_prepareattack(1, autocvar_g_balance_seeker_missile_refire))
414                         {
415                                 Seeker_Attack();
416                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_missile_animtime, w_ready);
417                         }
418                 }
419         }
420         else if (req == WR_PRECACHE)
421         {
422                 precache_model ("models/weapons/g_seeker.md3");
423                 precache_model ("models/weapons/v_seeker.md3");
424                 precache_model ("models/weapons/h_seeker.iqm");
425                 precache_sound ("weapons/tag_fire.wav");
426                 //precache_sound ("weapons/flac_fire.wav");
427                 precache_sound ("weapons/seeker_fire.wav");
428                 //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
429         }
430         else if (req == WR_SETUP)
431         {
432                 weapon_setup(WEP_SEEKER);
433                 self.current_ammo = ammo_rockets;
434         }
435         else if (req == WR_CHECKAMMO1)
436         {
437                 ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo;
438                 ammo_amount += self.weapon_load[WEP_SEEKER] >= autocvar_g_balance_seeker_tag_ammo;
439                 return ammo_amount;
440         }
441         else if (req == WR_CHECKAMMO2)
442         {
443                 ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_missile_ammo;
444                 ammo_amount += self.weapon_load[WEP_SEEKER] >= autocvar_g_balance_seeker_missile_ammo;
445                 return ammo_amount;
446         }
447         else if (req == WR_RELOAD)
448         {
449                 W_Reload(min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo), autocvar_g_balance_seeker_reload_ammo, autocvar_g_balance_seeker_reload_time, "weapons/reload.wav");
450         }
451         return TRUE;
452 };
453 #endif
454 #ifdef CSQC
455 float w_seeker(float req)
456 {
457         if(req == WR_IMPACTEFFECT)
458         {
459                 vector org2;
460                 org2 = w_org + w_backoff * 6;
461                 if(w_deathtype & HITTYPE_BOUNCE)
462                 {
463                         pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
464                         if(!w_issilent)
465                         {
466                                 if (w_random<0.15)
467                                         sound(self, CHAN_PROJECTILE, "weapons/tagexp1.wav", 1, ATTN_NORM);
468                                 else if (w_random<0.7)
469                                         sound(self, CHAN_PROJECTILE, "weapons/tagexp2.wav", 1, ATTN_NORM);
470                                 else
471                                         sound(self, CHAN_PROJECTILE, "weapons/tagexp3.wav", 1, ATTN_NORM);
472                         }
473                 }
474                 else if(w_deathtype & HITTYPE_HEADSHOT)
475                 {
476                         if(!w_issilent)
477                                 sound(self, CHAN_PROJECTILE, "weapons/tag_impact.wav", 1, ATTN_NORM);
478                 }
479                 else
480                 {
481                         pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
482                         if(!w_issilent)
483                         {
484                                 if (w_random<0.15)
485                                         sound(self, CHAN_PROJECTILE, "weapons/seekerexp1.wav", 1, ATTN_NORM);
486                                 else if (w_random<0.7)
487                                         sound(self, CHAN_PROJECTILE, "weapons/seekerexp2.wav", 1, ATTN_NORM);
488                                 else
489                                         sound(self, CHAN_PROJECTILE, "weapons/seekerexp3.wav", 1, ATTN_NORM);
490                         }
491                 }
492         }
493         else if(req == WR_PRECACHE)
494         {
495                 precache_sound("weapons/seekerexp1.wav");
496                 precache_sound("weapons/seekerexp2.wav");
497                 precache_sound("weapons/seekerexp3.wav");
498                 precache_sound("weapons/tagexp1.wav");
499                 precache_sound("weapons/tagexp2.wav");
500                 precache_sound("weapons/tagexp3.wav");
501                 precache_sound("weapons/tag_impact.wav");
502         }
503         else if (req == WR_SUICIDEMESSAGE)
504                 w_deathtypestring = _("%s played with tiny rockets");
505         else if (req == WR_KILLMESSAGE)
506         {
507                 if(w_deathtype & HITTYPE_SECONDARY)
508                         w_deathtypestring = _("%s was tagged by %s");
509                 else
510                         w_deathtypestring = _("%s was pummeled by %s");
511         }
512         return TRUE;
513 }
514 #endif
515 #endif