1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 
6 /**
7  * @file pilot_weapon.c
8  *
9  * @brief Handles pilot weapon sets.
10  *
11  * Cheat sheet: how this works (it's complicated).
12  *
13  * KEYPRESS
14  * 1) weapSetPress
15  * 2) weapSetFire
16  * 2.1) Modifications get turned on/off
17  * 2.2) Weapons go to shootWeaponSetOutfit
18  *
19  * UPDATE
20  * 1) weapSetUpdate
21  * 2.1) fire set => weapSetFire
22  * 2.1.1) Modifications get turned on/off
23  * 2.1.2) Weapons go to shootWeaponSetOutfit
24  * 2.2) weapsetUpdateOutfits
25  *
26  * So to actually modify stuff, chances are you want to go to pilot_weapSetFire.
27  */
28 
29 
30 #include "pilot.h"
31 
32 #include "naev.h"
33 
34 #include "nxml.h"
35 
36 #include "log.h"
37 #include "array.h"
38 #include "weapon.h"
39 #include "escort.h"
40 
41 #include "player.h"
42 #include "spfx.h"
43 
44 
45 /*
46  * Prototypes.
47  */
48 static void pilot_weapSetUpdateOutfits( Pilot* p, PilotWeaponSet *ws );
49 static PilotWeaponSet* pilot_weapSet( Pilot* p, int id );
50 static int pilot_weapSetFire( Pilot *p, PilotWeaponSet *ws, int level );
51 static int pilot_shootWeaponSetOutfit( Pilot* p, PilotWeaponSet *ws, Outfit *o, int level, double time );
52 static int pilot_shootWeapon( Pilot* p, PilotOutfitSlot* w, double time );
53 static void pilot_weapSetUpdateRange( PilotWeaponSet *ws );
54 
55 
56 /**
57  * @brief Gets a weapon set from id.
58  *
59  *    @param p Pilot to get weapon set from.
60  *    @param id ID of the weapon set.
61  *    @return The weapon set matching id.
62  */
pilot_weapSet(Pilot * p,int id)63 static PilotWeaponSet* pilot_weapSet( Pilot* p, int id )
64 {
65    return &p->weapon_sets[ id ];
66 }
67 
68 
69 /**
70  * @brief Fires a weapon set.
71  *
72  *    @param p Pilot firing weaponsets.
73  *    @param ws Weapon set to fire.
74  *    @param level Level of the firing weapon set.
75  */
pilot_weapSetFire(Pilot * p,PilotWeaponSet * ws,int level)76 static int pilot_weapSetFire( Pilot *p, PilotWeaponSet *ws, int level )
77 {
78    int i, j, ret, s;
79    Pilot *pt;
80    double time;
81    Outfit *o;
82 
83    /* Case no outfits. */
84    if (ws->slots == NULL)
85       return 0;
86 
87    /* Fire. */
88    ret    = 0;
89    for (i=0; i<array_size(ws->slots); i++) {
90       o = ws->slots[i].slot->outfit;
91 
92       /* Ignore NULL outfits. */
93       if (o == NULL)
94          continue;
95 
96       /* Only "active" outfits. */
97       if ((level != -1) && (ws->slots[i].level != level))
98          continue;
99 
100       /* Only run once for each weapon type in the group. */
101       s = 0;
102       for (j=0; j<i; j++) {
103          /* Only active outfits. */
104          if ((level != -1) && (ws->slots[j].level != level))
105             continue;
106          /* Found a match. */
107          if (ws->slots[j].slot->outfit == o) {
108             s = 1;
109             break;
110          }
111       }
112       if (s!=0)
113          continue;
114 
115       /* Only "locked on" outfits. */
116       if (outfit_isSeeker(o) &&
117             (ws->slots[i].slot->u.ammo.lockon_timer > 0.))
118          continue;
119 
120       /* If inrange is set we only fire at targets in range. */
121       time = INFINITY;  /* With no target we just set time to infinity. */
122       if (p->target != p->id){
123          pt = pilot_get( p->target );
124          if (pt != NULL)
125             time = pilot_weapFlyTime( o, p, pt);
126          }
127 
128       /* Only "inrange" outfits. */
129       if ( ws->inrange && outfit_duration(o) < time)
130          continue;
131 
132       /* Shoot the weapon of the weaponset. */
133       ret += pilot_shootWeaponSetOutfit( p, ws, o, level, time );
134    }
135 
136    return ret;
137 }
138 
139 
140 /**
141  * @brief Useful function for AI, clears activeness of all weapon sets.
142  */
pilot_weapSetAIClear(Pilot * p)143 void pilot_weapSetAIClear( Pilot* p )
144 {
145    int i;
146    PilotWeaponSet *ws;
147    for (i=0; i<PILOT_WEAPON_SETS; i++) {
148       ws = &p->weapon_sets[i];
149       ws->active = 0;
150    }
151 }
152 
153 
154 /**
155  * @brief Handles a weapon set press.
156  *
157  *    @param p Pilot the weapon set belongs to.
158  *    @param id ID of the weapon set.
159  *    @param type Is +1 if it's a press or -1 if it's a release.
160  */
pilot_weapSetPress(Pilot * p,int id,int type)161 void pilot_weapSetPress( Pilot* p, int id, int type )
162 {
163    int i, l, on, n;
164    PilotWeaponSet *ws;
165 
166    ws = pilot_weapSet(p,id);
167    /* Case no outfits. */
168    if (ws->slots == NULL)
169       return;
170 
171    /* Handle fire groups. */
172    switch (ws->type) {
173       case WEAPSET_TYPE_CHANGE:
174          /* On press just change active weapon set to whatever is available. */
175          if (type > 0) {
176             if (id != p->active_set)
177                pilot_weapSetUpdateOutfits( p, ws );
178             p->active_set = id;
179          }
180          break;
181 
182       case WEAPSET_TYPE_WEAPON:
183          /* Activation philosophy here is to turn on while pressed and off
184           * when it's not held anymore. */
185          if (type > 0)
186             ws->active = 1;
187          else if (type < 0)
188             ws->active = 0;
189          break;
190 
191       case WEAPSET_TYPE_ACTIVE:
192          /* The behaviour here is more complex. What we do is consider a group
193           * to be entirely off if not all outfits are either on or cooling down.
194           * In the case it's deemed to be off, all outfits that are off get turned
195           * on, otherwise all outfits that are on are turrned to cooling down. */
196          /* Only care about presses. */
197          if (type < 0)
198             break;
199 
200          /* Must not be disabled or cooling down. */
201          if ((pilot_isDisabled(p)) || (pilot_isFlag(p, PILOT_COOLDOWN)))
202             return;
203 
204          /* Decide what to do. */
205          on = 1;
206          l  = array_size(ws->slots);
207          for (i=0; i<l; i++) {
208             if (ws->slots[i].slot->state == PILOT_OUTFIT_OFF) {
209                on = 0;
210                break;
211             }
212          }
213 
214          /* Turn them off. */
215          n = 0;
216          if (on) {
217             for (i=0; i<l; i++) {
218                if (ws->slots[i].slot->state != PILOT_OUTFIT_ON)
219                   continue;
220 
221                n += pilot_outfitOff( p, ws->slots[i].slot );
222             }
223          }
224          /* Turn them on. */
225          else {
226             for (i=0; i<l; i++) {
227                if (ws->slots[i].slot->state != PILOT_OUTFIT_OFF)
228                   continue;
229                if (outfit_isAfterburner(ws->slots[i].slot->outfit))
230                   pilot_afterburn( p );
231                else {
232                   ws->slots[i].slot->state  = PILOT_OUTFIT_ON;
233                   ws->slots[i].slot->stimer = outfit_duration( ws->slots[i].slot->outfit );
234                }
235                n++;
236             }
237          }
238          /* Must recalculate stats. */
239          if (n > 0)
240             pilot_calcStats( p );
241 
242          break;
243    }
244 }
245 
246 
247 /**
248  * @brief Updates the pilot's weapon sets.
249  *
250  *    @param p Pilot to update.
251  */
pilot_weapSetUpdate(Pilot * p)252 void pilot_weapSetUpdate( Pilot* p )
253 {
254    PilotWeaponSet *ws;
255    int i;
256 
257    /* Must not be doing hyperspace procedures. */
258    if (pilot_isFlag( p, PILOT_HYP_BEGIN))
259       return;
260 
261    for (i=0; i<PILOT_WEAPON_SETS; i++) {
262       ws = &p->weapon_sets[i];
263       if (ws->slots == NULL)
264          continue;
265 
266       /* Weapons must get "fired" every turn. */
267       if (ws->type == WEAPSET_TYPE_WEAPON) {
268          if (ws->active)
269             pilot_weapSetFire( p, ws, -1 );
270       }
271    }
272 }
273 
274 
275 /**
276  * @brief Updates the outfits with their current weapon set level.
277  */
pilot_weapSetUpdateOutfits(Pilot * p,PilotWeaponSet * ws)278 static void pilot_weapSetUpdateOutfits( Pilot* p, PilotWeaponSet *ws )
279 {
280    int i;
281 
282    for (i=0; i<p->noutfits; i++)
283       p->outfits[i]->level = -1;
284 
285    if (ws->slots != NULL)
286       for (i=0; i<array_size(ws->slots); i++)
287          ws->slots[i].slot->level = ws->slots[i].level;
288 }
289 
290 
291 /**
292  * @brief Checks the current weapon set type.
293  *
294  *    @param p Pilot to manipulate.
295  *    @param id ID of the weapon set to check.
296  *    @return The type of the weapon set.
297  */
pilot_weapSetTypeCheck(Pilot * p,int id)298 int pilot_weapSetTypeCheck( Pilot* p, int id )
299 {
300    PilotWeaponSet *ws;
301    ws = pilot_weapSet(p,id);
302    return ws->type;
303 }
304 
305 
306 /**
307  * @brief Changes the weapon sets mode.
308  *
309  *    @param p Pilot to manipulate.
310  *    @param id ID of the weapon set.
311  *    @param fire Whether or not to enable fire mode.
312  */
pilot_weapSetType(Pilot * p,int id,int type)313 void pilot_weapSetType( Pilot* p, int id, int type )
314 {
315    int i;
316    PilotWeaponSet *ws;
317 
318    ws = pilot_weapSet(p,id);
319    ws->type = type;
320 
321    /* Set levels just in case. */
322    if (ws->slots == NULL)
323       return;
324 
325    /* See if we must overwrite levels. */
326    if ((ws->type == WEAPSET_TYPE_WEAPON) ||
327          (ws->type == WEAPSET_TYPE_ACTIVE))
328       for (i=0; i<array_size(ws->slots); i++)
329          ws->slots[i].level = 0;
330 }
331 
332 
333 /**
334  * @brief Checks the current weapon set inrange property.
335  *
336  *    @param p Pilot to manipulate.
337  *    @param id ID of the weapon set to check.
338  *    @return The inrange mode of the weapon set.
339  */
pilot_weapSetInrangeCheck(Pilot * p,int id)340 int pilot_weapSetInrangeCheck( Pilot* p, int id )
341 {
342    PilotWeaponSet *ws;
343    ws = pilot_weapSet(p,id);
344    return ws->inrange;
345 }
346 
347 
348 /**
349  * @brief Changes the weapon set inrange property.
350  *
351  *    @param p Pilot to manipulate.
352  *    @param id ID of the weapon set.
353  *    @param inrange Whether or not to only fire at stuff in range.
354  */
pilot_weapSetInrange(Pilot * p,int id,int inrange)355 void pilot_weapSetInrange( Pilot* p, int id, int inrange )
356 {
357    PilotWeaponSet *ws;
358    ws = pilot_weapSet(p,id);
359    ws->inrange = inrange;
360 }
361 
362 
363 /**
364  * @brief Gets the name of a weapon set.
365  */
pilot_weapSetName(Pilot * p,int id)366 const char *pilot_weapSetName( Pilot* p, int id )
367 {
368    PilotWeaponSet *ws;
369    ws = pilot_weapSet(p,id);
370    if ((ws->slots == NULL) || (array_size(ws->slots)==0))
371       return "Unused";
372    switch (ws->type) {
373       case WEAPSET_TYPE_CHANGE: return "Weapons - Switched";  break;
374       case WEAPSET_TYPE_WEAPON: return "Weapons - Instant";   break;
375       case WEAPSET_TYPE_ACTIVE: return "Abilities - Toggled"; break;
376    }
377    return NULL;
378 }
379 
380 
381 /**
382  * @brief Removes slots by type from the weapon set.
383  */
pilot_weapSetRmSlot(Pilot * p,int id,OutfitSlotType type)384 void pilot_weapSetRmSlot( Pilot *p, int id, OutfitSlotType type )
385 {
386    int i, n, l;
387    PilotWeaponSet *ws;
388 
389    /* We must clean up the slots. */
390    n  = 0; /* Number to remove. */
391    ws = pilot_weapSet(p,id);
392    if (ws->slots == NULL)
393       return;
394    l  = array_size(ws->slots);
395    for (i=0; i<l; i++) {
396       if (ws->slots->slot->sslot->slot.type != type)
397          continue;
398 
399       /* Move down. */
400       memmove( &ws->slots[i], &ws->slots[i+1], sizeof(PilotWeaponSetOutfit) * (l-i-1) );
401       n++;
402    }
403 
404    /* Remove surplus. */
405    array_erase( &ws->slots, &ws->slots[l-n], &ws->slots[l] );
406 }
407 
408 
409 /**
410  * @brief Adds an outfit to a weapon set.
411  *
412  *    @param p Pilot to manipulate.
413  *    @param id ID of the weapon set.
414  *    @param o Outfit to add.
415  *    @param level Level of the trigger.
416  */
pilot_weapSetAdd(Pilot * p,int id,PilotOutfitSlot * o,int level)417 void pilot_weapSetAdd( Pilot* p, int id, PilotOutfitSlot *o, int level )
418 {
419    PilotWeaponSet *ws;
420    PilotWeaponSetOutfit *slot;
421    Outfit *oo;
422    int i, j;
423    double r;
424 
425    ws = pilot_weapSet(p,id);
426 
427    /* Make sure outfit is valid. */
428    oo = o->outfit;
429    if (oo == NULL)
430       return;
431 
432    /* Make sure outfit type is weapon (or usable). */
433    if (!outfit_isActive(oo))
434       return;
435 
436    /* Create if needed. */
437    if (ws->slots == NULL)
438       ws->slots = array_create( PilotWeaponSetOutfit );
439 
440    /* Check if already there. */
441    for (i=0; i<array_size(ws->slots); i++) {
442       if (ws->slots[i].slot == o) {
443          ws->slots[i].level = level;
444 
445          /* Update if needed. */
446          if (id == p->active_set)
447             pilot_weapSetUpdateOutfits( p, ws );
448          return;
449       }
450    }
451 
452    /* Add it. */
453    slot        = &array_grow( &ws->slots );
454    slot->level = level;
455    slot->slot  = o;
456    r           = outfit_range(oo);
457    if (r > 0)
458       slot->range2 = pow2(r);
459 
460    /* Updated cached weapset. */
461    o->weapset = -1;
462    for (j=0; j<PILOT_WEAPON_SETS; j++) {
463       if (pilot_weapSetCheck(p, j, o) != -1) {
464          o->weapset = j;
465          break;
466       }
467    }
468 
469    /* Update range. */
470    pilot_weapSetUpdateRange( ws );
471 
472    /* Update if needed. */
473    if (id == p->active_set)
474       pilot_weapSetUpdateOutfits( p, ws );
475 }
476 
477 
478 /**
479  * @brief Removes a slot from a weapon set.
480  *
481  *    @param p Pilot who owns the weapon set.
482  *    @param id ID of the weapon set.
483  *    @param o Outfit to remove.
484  */
pilot_weapSetRm(Pilot * p,int id,PilotOutfitSlot * o)485 void pilot_weapSetRm( Pilot* p, int id, PilotOutfitSlot *o )
486 {
487    PilotWeaponSet *ws;
488    int i, j;
489 
490    /* Make sure it has slots. */
491    ws = pilot_weapSet(p,id);
492    if (ws->slots == NULL)
493       return;
494 
495    /* Find the slot. */
496    for (i=0; i<array_size(ws->slots); i++) {
497       if (ws->slots[i].slot != o)
498          continue;
499 
500       array_erase( &ws->slots, &ws->slots[i], &ws->slots[i+1] );
501 
502       /* Update range. */
503       pilot_weapSetUpdateRange( ws );
504 
505       /* Update if needed. */
506       if (id == p->active_set)
507          pilot_weapSetUpdateOutfits( p, ws );
508 
509       /* Updated cached weapset. */
510       o->weapset = -1;
511       for (j=0; j<PILOT_WEAPON_SETS; j++) {
512          if (pilot_weapSetCheck(p, j, o) != -1) {
513             o->weapset = j;
514             break;
515          }
516       }
517 
518       return;
519    }
520 }
521 
522 
523 /**
524  * @brief Checks to see if a slot is in a weapon set.
525  *
526  *    @param p Pilot to check.
527  *    @param id ID of the weapon set.
528  *    @param o Outfit slot to check.
529  *    @return The level to which it belongs (or -1 if it isn't set).
530  */
pilot_weapSetCheck(Pilot * p,int id,PilotOutfitSlot * o)531 int pilot_weapSetCheck( Pilot* p, int id, PilotOutfitSlot *o )
532 {
533    PilotWeaponSet *ws;
534    int i;
535 
536    /* Make sure it has slots. */
537    ws = pilot_weapSet(p,id);
538    if (ws->slots == NULL)
539       return -1;
540 
541    /* Find the slot. */
542    for (i=0; i<array_size(ws->slots); i++)
543       if (ws->slots[i].slot == o)
544          return ws->slots[i].level;
545 
546    /* Not found. */
547    return -1;
548 }
549 
550 
551 /**
552  * @brief Updates the weapon range for a pilot weapon set.
553  *
554  *    @param ws Weapon Set to update range for.
555  */
pilot_weapSetUpdateRange(PilotWeaponSet * ws)556 static void pilot_weapSetUpdateRange( PilotWeaponSet *ws )
557 {
558    int i, lev;
559    double range, speed;
560    double range_accum[PILOT_WEAPSET_MAX_LEVELS];
561    int range_num[PILOT_WEAPSET_MAX_LEVELS];
562    double speed_accum[PILOT_WEAPSET_MAX_LEVELS];
563    int speed_num[PILOT_WEAPSET_MAX_LEVELS];
564 
565    /* No slots. */
566    if (ws->slots == NULL) {
567       for (i=0; i<PILOT_WEAPSET_MAX_LEVELS; i++) {
568          ws->range[i] = 0.;
569          ws->speed[i] = 0.;
570       }
571       return;
572    }
573 
574    /* Calculate ranges. */
575    for (i=0; i<PILOT_WEAPSET_MAX_LEVELS; i++) {
576       range_accum[i] = 0.;
577       range_num[i]   = 0;
578       speed_accum[i] = 0.;
579       speed_num[i]   = 0;
580    }
581    for (i=0; i<array_size(ws->slots); i++) {
582       if (ws->slots[i].slot->outfit == NULL)
583          continue;
584 
585       /* Get level. */
586       lev = ws->slots[i].level;
587       if (lev >= PILOT_WEAPSET_MAX_LEVELS)
588          continue;
589 
590       /* Empty Launchers aren't valid */
591       if (outfit_isLauncher(ws->slots[i].slot->outfit) && ws->slots[i].slot->u.ammo.quantity <= 0)
592          continue;
593 
594       /* Get range. */
595       range = outfit_range(ws->slots[i].slot->outfit);
596       if (range >= 0.) {
597          /* Calculate. */
598          range_accum[ lev ] += range;
599          range_num[ lev ]++;
600       }
601 
602       /* Get speed. */
603       speed = outfit_speed(ws->slots[i].slot->outfit);
604       if (speed >= 0.) {
605          /* Calculate. */
606          speed_accum[ lev ] += speed;
607          speed_num[ lev ]++;
608       }
609    }
610 
611    /* Postprocess. */
612    for (i=0; i<PILOT_WEAPSET_MAX_LEVELS; i++) {
613       /* Postprocess range. */
614       if (range_num[i] == 0)
615          ws->range[i] = 0;
616       else
617          ws->range[i] = range_accum[i] / (double) range_num[i];
618 
619       /* Postprocess speed. */
620       if (speed_num[i] == 0)
621          ws->speed[i] = 0;
622       else
623          ws->speed[i] = speed_accum[i] / (double) speed_num[i];
624    }
625 }
626 
627 
628 /**
629  * @brief Gets the range of the current pilot weapon set.
630  *
631  *    @param p Pilot to get the range of.
632  *    @param id ID of weapon set to get the range of.
633  *    @param Level of the weapons to get the range of (-1 for all).
634  */
pilot_weapSetRange(Pilot * p,int id,int level)635 double pilot_weapSetRange( Pilot* p, int id, int level )
636 {
637    PilotWeaponSet *ws;
638    int i;
639    double range;
640 
641    ws = pilot_weapSet(p,id);
642    if (level < 0) {
643       range = 0;
644       for (i=0; i<PILOT_WEAPSET_MAX_LEVELS; i++)
645          range += ws->range[i];
646    }
647    else
648       range = ws->range[ level ];
649 
650    return range;
651 }
652 
653 
654 /**
655  * @brief Gets the speed of the current pilot weapon set.
656  *
657  *    @param p Pilot to get the speed of.
658  *    @param id ID of weapon set to get the speed of.
659  *    @param Level of the weapons to get the speed of (-1 for all).
660  */
pilot_weapSetSpeed(Pilot * p,int id,int level)661 double pilot_weapSetSpeed( Pilot* p, int id, int level )
662 {
663    PilotWeaponSet *ws;
664    int i;
665    double speed;
666 
667    ws = pilot_weapSet(p,id);
668    if (level < 0) {
669       speed = 0;
670       for (i=0; i<PILOT_WEAPSET_MAX_LEVELS; i++)
671          speed += ws->speed[i];
672    }
673    else
674       speed = ws->speed[ level ];
675 
676    return speed;
677 }
678 
679 
680 /**
681  * @brief Cleans up a weapon set.
682  *
683  *    @param p Pilot who owns the weapon set.
684  *    @param id ID of the weapon set to clean up.
685  */
pilot_weapSetCleanup(Pilot * p,int id)686 void pilot_weapSetCleanup( Pilot* p, int id )
687 {
688    PilotWeaponSet *ws;
689 
690    ws = pilot_weapSet(p,id);
691 
692    if (ws->slots != NULL)
693       array_free( ws->slots );
694    ws->slots = NULL;
695 
696    /* Update range. */
697    pilot_weapSetUpdateRange( ws );
698 }
699 
700 
701 /**
702  * @brief Frees a pilot's weapon sets.
703  */
pilot_weapSetFree(Pilot * p)704 void pilot_weapSetFree( Pilot* p )
705 {
706    int i;
707    for (i=0; i<PILOT_WEAPON_SETS; i++)
708       pilot_weapSetCleanup( p, i );
709 }
710 
711 
712 
713 /**
714  * @brief Lists the items in a pilot weapon set.
715  *
716  *    @param p Pilot who owns the weapon set.
717  *    @param id ID of the weapon set.
718  *    @param[out] n Number of elements in the list.
719  *    @return The array of pilot weaponset outfits.
720  */
pilot_weapSetList(Pilot * p,int id,int * n)721 PilotWeaponSetOutfit* pilot_weapSetList( Pilot* p, int id, int *n )
722 {
723    PilotWeaponSet *ws;
724 
725    ws = pilot_weapSet(p,id);
726    if (ws->slots == NULL) {
727       *n = 0;
728       return NULL;
729    }
730 
731    *n = array_size(ws->slots);
732    return ws->slots;
733 }
734 
735 
736 /**
737  * @brief Makes the pilot shoot.
738  *
739  *    @param p The pilot which is shooting.
740  *    @param level Level of the shot.
741  *    @return The number of shots fired.
742  */
pilot_shoot(Pilot * p,int level)743 int pilot_shoot( Pilot* p, int level )
744 {
745    PilotWeaponSet *ws;
746    int ret;
747 
748    /* Get active set. */
749    ws = pilot_weapSet( p, p->active_set );
750 
751    /* Fire weapons. */
752    if (ws->type == WEAPSET_TYPE_CHANGE) { /* Must be a change set or a weaponset. */
753       ret = pilot_weapSetFire( p, ws, level );
754 
755       /* Firing weapons aborts active cooldown. */
756       if (pilot_isFlag(p, PILOT_COOLDOWN) && ret)
757          pilot_cooldownEnd(p, NULL);
758 
759       return ret;
760    }
761 
762    return 0;
763 }
764 
765 
766 /**
767  * @brief Have pilot stop shooting their weapon.
768  *
769  * Only really deals with beam weapons.
770  *
771  *    @param p Pilot that was shooting.
772  *    @param level Level of the shot.
773  */
pilot_shootStop(Pilot * p,int level)774 void pilot_shootStop( Pilot* p, int level )
775 {
776    int i, recalc;
777    PilotWeaponSet *ws;
778    PilotOutfitSlot *slot;
779 
780    /* Get active set. */
781    ws = pilot_weapSet( p, p->active_set );
782 
783    /* Case no outfits. */
784    if (ws->slots == NULL)
785       return;
786 
787    /* Stop all beams. */
788    recalc = 0;
789    for (i=0; i<array_size(ws->slots); i++) {
790       slot = ws->slots[i].slot;
791 
792       /* Must have associated outfit. */
793       if (ws->slots[i].slot->outfit == NULL)
794          continue;
795 
796       /* Must match level. */
797       if ((level != -1) && (ws->slots[i].level != level))
798          continue;
799 
800       /* Only handle beams. */
801       if (!outfit_isBeam(slot->outfit)) {
802          /* Turn off the state. */
803          if (outfit_isMod( slot->outfit )) {
804             slot->state = PILOT_OUTFIT_OFF;
805             recalc = 1;
806          }
807          continue;
808       }
809 
810       /* Stop beam. */
811       if (ws->slots[i].slot->u.beamid > 0) {
812          /* Enforce minimum duration if set. */
813          if (slot->outfit->u.bem.min_duration > 0.) {
814 
815             slot->stimer = slot->outfit->u.bem.min_duration -
816                   (slot->outfit->u.bem.duration - slot->timer);
817 
818             if (slot->stimer > 0.)
819                continue;
820          }
821 
822          beam_end( p->id, slot->u.beamid );
823          pilot_stopBeam(p, slot);
824       }
825    }
826 
827    /* Must recalculate. */
828    if (recalc)
829       pilot_calcStats( p );
830 }
831 
832 
833 /**
834  * @brief Stops a beam outfit and sets delay as appropriate.
835  *
836  *    @param p Pilot that is firing.
837  *    @param w Pilot's beam outfit.
838  */
pilot_stopBeam(Pilot * p,PilotOutfitSlot * w)839 void pilot_stopBeam( Pilot *p, PilotOutfitSlot *w )
840 {
841    double rate_mod, energy_mod, used;
842 
843    /* There's nothing to do if the beam isn't active. */
844    if (w->u.beamid == 0)
845       return;
846 
847   /* Safeguard against a nasty race condition. */
848   if (w->outfit == NULL) {
849       w->u.beamid = 0;
850      return;
851   }
852 
853    /* Calculate rate modifier. */
854    pilot_getRateMod( &rate_mod, &energy_mod, p, w->outfit );
855 
856    /* Beam duration used. */
857    used = w->outfit->u.bem.duration - w->timer;
858 
859    w->timer = rate_mod * (used / w->outfit->u.bem.duration) * outfit_delay( w->outfit );
860    w->u.beamid = 0;
861 }
862 
863 
864 /**
865  * @brief Computes an estimation of ammo flying time
866  *
867  *    @param w the weapon that shoot
868  *    @param parent Parent of the weapon
869  *    @param target Target of the weapon
870  */
pilot_weapFlyTime(Outfit * o,Pilot * parent,Pilot * target)871 double pilot_weapFlyTime( Outfit *o, Pilot *parent, Pilot *target)
872 {
873    Vector2d approach_vector, relative_location, orthoradial_vector;
874    double speed, radial_speed, orthoradial_speed, dist, t;
875 
876    dist = vect_dist( &parent->solid->pos, &target->solid->pos );
877 
878    /* Beam weapons */
879    if (outfit_isBeam(o))
880       {
881       if (dist > o->u.bem.range)
882          return INFINITY;
883       return 0.;
884       }
885 
886    /* A bay doesn't have range issues */
887    if (outfit_isFighterBay(o))
888       return 0.;
889 
890    /* Rockets use absolute velocity while bolt use relative vel */
891    if (outfit_isLauncher(o))
892          vect_cset( &approach_vector, - VX(target->solid->vel), - VY(target->solid->vel) );
893    else
894          vect_cset( &approach_vector, VX(parent->solid->vel) - VX(target->solid->vel),
895                VY(parent->solid->vel) - VY(target->solid->vel) );
896 
897    speed = outfit_speed(o);
898 
899    /* Get the vector : shooter -> target*/
900    vect_cset( &relative_location, VX(target->solid->pos) - VX(parent->solid->pos),
901          VY(target->solid->pos) - VY(parent->solid->pos) );
902 
903    /* Get the orthogonal vector*/
904    vect_cset(&orthoradial_vector, VY(parent->solid->pos) - VY(target->solid->pos),
905          VX(target->solid->pos) -  VX(parent->solid->pos) );
906 
907    radial_speed = vect_dot( &approach_vector, &relative_location );
908    radial_speed = radial_speed / VMOD(relative_location);
909 
910    orthoradial_speed = vect_dot(&approach_vector, &orthoradial_vector);
911    orthoradial_speed = orthoradial_speed / VMOD(relative_location);
912 
913    if( ((speed*speed - VMOD(approach_vector)*VMOD(approach_vector)) != 0) && (speed*speed - orthoradial_speed*orthoradial_speed) > 0)
914       t = dist * (sqrt( speed*speed - orthoradial_speed*orthoradial_speed ) - radial_speed) /
915             (speed*speed - VMOD(approach_vector)*VMOD(approach_vector));
916    else
917       return INFINITY;
918 
919    /* if t < 0, try the other solution*/
920    if (t < 0)
921       t = - dist * (sqrt( speed*speed - orthoradial_speed*orthoradial_speed ) + radial_speed) /
922             (speed*speed - VMOD(approach_vector)*VMOD(approach_vector));
923 
924    /* if t still < 0, no solution*/
925    if (t < 0)
926       return INFINITY;
927 
928    return t;
929 }
930 
931 
932 /**
933  * @brief Calculates and shoots the appropriate weapons in a weapon set matching an outfit.
934  */
pilot_shootWeaponSetOutfit(Pilot * p,PilotWeaponSet * ws,Outfit * o,int level,double time)935 static int pilot_shootWeaponSetOutfit( Pilot* p, PilotWeaponSet *ws, Outfit *o, int level, double time )
936 {
937    int i, ret;
938    int is_launcher, is_bay;
939    double rate_mod, energy_mod;
940    PilotOutfitSlot *w;
941    int maxp, minh;
942    double q, maxt;
943 
944    /* Store number of shots. */
945    ret = 0;
946 
947    /** @TODO Make beams not fire all at once. */
948    if (outfit_isBeam(o)) {
949       for (i=0; i<array_size(ws->slots); i++)
950          if (ws->slots[i].slot->outfit == o)
951             ret += pilot_shootWeapon( p, ws->slots[i].slot, 0 );
952       return ret;
953    }
954 
955    /* Stores if it is a launcher. */
956    is_launcher = outfit_isLauncher(o);
957 
958    is_bay = outfit_isFighterBay(o);
959 
960    /* Calculate rate modifier. */
961    pilot_getRateMod( &rate_mod, &energy_mod, p, o );
962 
963    /* Find optimal outfit, coolest that can fire. */
964    minh  = -1;
965    maxt  = 0.;
966    maxp  = -1;
967    q     = 0.;
968    for (i=0; i<array_size(ws->slots); i++) {
969       /* Only matching outfits. */
970       if (ws->slots[i].slot->outfit != o)
971          continue;
972 
973       /* Only match levels. */
974       if ((level != -1) && (ws->slots[i].level != level))
975          continue;
976 
977       /* Simplicity. */
978       w = ws->slots[i].slot;
979 
980       /* Launcher only counts with ammo. */
981       if ((is_launcher || is_bay) && ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)))
982          continue;
983 
984       /* Get coolest that can fire. */
985       if (w->timer <= 0.) {
986          if (is_launcher) {
987             if ((minh < 0) || (ws->slots[minh].slot->u.ammo.quantity < w->u.ammo.quantity))
988                minh = i;
989          }
990          else {
991             if ((minh < 0) || (ws->slots[minh].slot->heat_T > w->heat_T))
992                minh = i;
993          }
994       }
995 
996       /* Save some stuff. */
997       if ((maxp < 0) || (w->timer > maxt)) {
998          maxp = i;
999          maxt = w->timer;
1000       }
1001       q += 1.;
1002    }
1003 
1004    /* No weapon can fire. */
1005    if (minh < 0)
1006       return 0;
1007 
1008    /* Only fire if the last weapon to fire fired more than (q-1)/q ago. */
1009    if (maxt > rate_mod * outfit_delay(o) * ((q-1.) / q))
1010       return 0;
1011 
1012    /* Shoot the weapon. */
1013    ret += pilot_shootWeapon( p, ws->slots[minh].slot, time );
1014 
1015    return ret;
1016 }
1017 
1018 
1019 /**
1020  * @brief Actually handles the shooting, how often the player.p can shoot and such.
1021  *
1022  *    @param p Pilot that is shooting.
1023  *    @param w Pilot's outfit to shoot.
1024  *    @param time Expected flight time.
1025  *    @return 0 if nothing was shot and 1 if something was shot.
1026  */
pilot_shootWeapon(Pilot * p,PilotOutfitSlot * w,double time)1027 static int pilot_shootWeapon( Pilot* p, PilotOutfitSlot* w, double time )
1028 {
1029    Vector2d vp, vv;
1030    double rate_mod, energy_mod;
1031    double energy;
1032    int j;
1033 
1034    /* Make sure weapon has outfit. */
1035    if (w->outfit == NULL)
1036       return 0;
1037 
1038    /* Reset beam shut-off if needed. */
1039    if (outfit_isBeam(w->outfit) && w->outfit->u.bem.min_duration)
1040       w->stimer = INFINITY;
1041 
1042    /* check to see if weapon is ready */
1043    if (w->timer > 0.)
1044       return 0;
1045 
1046    /* Calculate rate modifier. */
1047    pilot_getRateMod( &rate_mod, &energy_mod, p, w->outfit );
1048 
1049    /* Get weapon mount position. */
1050    pilot_getMount( p, w, &vp );
1051    vp.x += p->solid->pos.x;
1052    vp.y += p->solid->pos.y;
1053 
1054    /* Modify velocity to take into account the rotation. */
1055    vect_cset( &vv, p->solid->vel.x + vp.x*p->solid->dir_vel,
1056          p->solid->vel.y + vp.y*p->solid->dir_vel );
1057 
1058    /*
1059     * regular bolt weapons
1060     */
1061    if (outfit_isBolt(w->outfit)) {
1062 
1063       /* enough energy? */
1064       if (outfit_energy(w->outfit)*energy_mod > p->energy)
1065          return 0;
1066 
1067       energy      = outfit_energy(w->outfit)*energy_mod;
1068       p->energy  -= energy;
1069       pilot_heatAddSlot( p, w );
1070       weapon_add( w->outfit, w->heat_T, p->solid->dir,
1071             &vp, &p->solid->vel, p, p->target, time );
1072    }
1073 
1074    /*
1075     * Beam weapons.
1076     */
1077    else if (outfit_isBeam(w->outfit)) {
1078       /* Don't fire if the existing beam hasn't been destroyed yet. */
1079       if (w->u.beamid > 0)
1080          return 0;
1081 
1082       /* Check if enough energy to last a second. */
1083       if (outfit_energy(w->outfit)*energy_mod > p->energy)
1084          return 0;
1085 
1086       /** @todo Handle warmup stage. */
1087       w->state = PILOT_OUTFIT_ON;
1088       w->u.beamid = beam_start( w->outfit, p->solid->dir,
1089             &vp, &p->solid->vel, p, p->target, w );
1090 
1091       w->timer = w->outfit->u.bem.duration;
1092 
1093       return 1; /* Return early due to custom timer logic. */
1094    }
1095 
1096    /*
1097     * missile launchers
1098     *
1099     * must be a secondary weapon
1100     */
1101    else if (outfit_isLauncher(w->outfit)) {
1102 
1103       /* Shooter can't be the target - sanity check for the player.p */
1104       if ((w->outfit->u.lau.ammo->u.amm.ai != AMMO_AI_DUMB) && (p->id==p->target))
1105          return 0;
1106 
1107       /* Must have ammo left. */
1108       if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0))
1109          return 0;
1110 
1111       /* enough energy? */
1112       if (outfit_energy(w->u.ammo.outfit)*energy_mod > p->energy)
1113          return 0;
1114 
1115       energy      = outfit_energy(w->u.ammo.outfit)*energy_mod;
1116       p->energy  -= energy;
1117       pilot_heatAddSlot( p, w );
1118       weapon_add( w->outfit, w->heat_T, p->solid->dir,
1119             &vp, &p->solid->vel, p, p->target, time );
1120 
1121       w->u.ammo.quantity -= 1; /* we just shot it */
1122       p->mass_outfit     -= w->u.ammo.outfit->mass;
1123       p->solid->mass     -= w->u.ammo.outfit->mass;
1124 
1125       pilot_updateMass( p );
1126 
1127       /* If last ammo was shot, update the range */
1128       if (w->u.ammo.quantity <= 0) {
1129          for (j=0; j<PILOT_WEAPON_SETS; j++)
1130             pilot_weapSetUpdateRange( &p->weapon_sets[j] );
1131       }
1132    }
1133 
1134    /*
1135     * Fighter bays.
1136     */
1137    else if (outfit_isFighterBay(w->outfit)) {
1138 
1139       /* Must have ammo left. */
1140       if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0))
1141          return 0;
1142 
1143       /* Create the escort. */
1144       escort_create( p, w->u.ammo.outfit->u.fig.ship,
1145             &vp, &p->solid->vel, p->solid->dir, ESCORT_TYPE_BAY, 1 );
1146 
1147       w->u.ammo.quantity -= 1; /* we just shot it */
1148       p->mass_outfit     -= w->u.ammo.outfit->mass;
1149       w->u.ammo.deployed += 1; /* Mark as deployed. */
1150       pilot_updateMass( p );
1151    }
1152    else
1153       WARN("Shooting unknown weapon type: %s", w->outfit->name);
1154 
1155 
1156    /* Reset timer. */
1157    w->timer += rate_mod * outfit_delay( w->outfit );
1158 
1159    return 1;
1160 }
1161 
1162 
1163 /**
1164  * @brief Gets applicable fire rate and energy modifications for a pilot's weapon.
1165  *
1166  *    @param[out] rate_mod Fire rate multiplier.
1167  *    @param[out] energy_mod Energy use multiplier.
1168  *    @param p Pilot who owns the outfit.
1169  *    @param w Pilot's outfit.
1170  */
pilot_getRateMod(double * rate_mod,double * energy_mod,Pilot * p,Outfit * o)1171 void pilot_getRateMod( double *rate_mod, double* energy_mod,
1172       Pilot* p, Outfit *o )
1173 {
1174    switch (o->type) {
1175       case OUTFIT_TYPE_BOLT:
1176          *rate_mod   = 2. - p->stats.fwd_firerate; /* Invert. */
1177          *energy_mod = p->stats.fwd_energy;
1178          break;
1179       case OUTFIT_TYPE_TURRET_BOLT:
1180          *rate_mod   = 2. - p->stats.tur_firerate; /* Invert. */
1181          *energy_mod = p->stats.tur_energy;
1182          break;
1183 
1184       default:
1185          *rate_mod   = 1.;
1186          *energy_mod = 1.;
1187          break;
1188    }
1189 }
1190 
1191 
1192 /**
1193  * @brief Clears the pilots weapon settings.
1194  *
1195  *    @param p Pilot whose weapons we're clearing.
1196  */
pilot_weaponClear(Pilot * p)1197 void pilot_weaponClear( Pilot *p )
1198 {
1199    int i;
1200    PilotWeaponSet *ws;
1201 
1202    for (i=0; i<PILOT_WEAPON_SETS; i++) {
1203       ws = pilot_weapSet( p, i );
1204       if (ws->slots != NULL)
1205          array_erase( &ws->slots, &ws->slots[0], &ws->slots[ array_size(ws->slots) ] );
1206    }
1207 }
1208 
1209 
1210 /**
1211  * @brief Tries to automatically set and create the pilot's weapon set.
1212  *
1213  * Weapon set 0 is for all weapons. <br />
1214  * Weapon set 1 is for forward weapons. Ammo using weapons are secondaries. <br />
1215  * Weapon set 2 is for turret weapons. Ammo using weapons are secondaries. <br />
1216  * Weapon set 3 is for all weapons. Forwards are primaries and turrets are secondaries. <br />
1217  * Weapon set 4 is for seeking weapons. High payload variants are secondaries. <br />
1218  * Weapon set 5 is for fighter bays. <br />
1219  *
1220  *    @param p Pilot to automagically generate weapon lists.
1221  */
pilot_weaponAuto(Pilot * p)1222 void pilot_weaponAuto( Pilot *p )
1223 {
1224    PilotOutfitSlot *slot;
1225    Outfit *o;
1226    int i, level, id;
1227 
1228    /* Clear weapons. */
1229    pilot_weaponClear( p );
1230 
1231    /* Set modes. */
1232    pilot_weapSetType( p, 0, WEAPSET_TYPE_CHANGE );
1233    pilot_weapSetType( p, 1, WEAPSET_TYPE_CHANGE );
1234    pilot_weapSetType( p, 2, WEAPSET_TYPE_CHANGE );
1235    pilot_weapSetType( p, 3, WEAPSET_TYPE_CHANGE );
1236    pilot_weapSetType( p, 4, WEAPSET_TYPE_WEAPON );
1237    pilot_weapSetType( p, 5, WEAPSET_TYPE_WEAPON );
1238    pilot_weapSetType( p, 6, WEAPSET_TYPE_ACTIVE );
1239    pilot_weapSetType( p, 7, WEAPSET_TYPE_ACTIVE );
1240    pilot_weapSetType( p, 8, WEAPSET_TYPE_ACTIVE );
1241    pilot_weapSetType( p, 9, WEAPSET_TYPE_ACTIVE );
1242 
1243    /* All should be inrange. */
1244    if (!pilot_isPlayer(p))
1245       for (i=0; i<PILOT_WEAPON_SETS; i++){
1246          pilot_weapSetInrange( p, i, 1 );
1247          /* Update range and speed (at 0)*/
1248          pilot_weapSetUpdateRange( &p->weapon_sets[i] );
1249       }
1250 
1251    /* Iterate through all the outfits. */
1252    for (i=0; i<p->noutfits; i++) {
1253       slot = p->outfits[i];
1254       o    = slot->outfit;
1255 
1256       /* Must be non-empty, and a weapon or active outfit. */
1257       if ((o == NULL) || !outfit_isActive(o)) {
1258          slot->level   = -1; /* Clear level. */
1259          slot->weapset = -1;
1260          continue;
1261       }
1262 
1263       /* Manually defined group preempts others. */
1264       if (o->group) {
1265          id    = o->group;
1266       }
1267       /* Bolts and beams. */
1268       else if (outfit_isBolt(o) || outfit_isBeam(o) ||
1269             (outfit_isLauncher(o) && !outfit_isSeeker(o->u.lau.ammo))) {
1270          id    = outfit_isTurret(o) ? 2 : 1;
1271       }
1272       /* Seekers. */
1273       else if (outfit_isLauncher(o) && outfit_isSeeker(o->u.lau.ammo)) {
1274          id    = 4;
1275       }
1276       /* Fighter bays. */
1277       else if (outfit_isFighterBay(o)) {
1278          id    = 5;
1279       }
1280       /* Ignore rest. */
1281       else {
1282          slot->level = -1;
1283          continue;
1284       }
1285 
1286       /* Set level based on secondary flag. */
1287       level = outfit_isSecondary(o);
1288 
1289       /* Add to its base group. */
1290       pilot_weapSetAdd( p, id, slot, level );
1291 
1292       /* Also add another copy to another group. */
1293       if (id == 1) { /* Forward. */
1294          pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */
1295          pilot_weapSetAdd( p, 3, slot, 0 );     /* Also get added to 'Fwd/Tur'. */
1296       }
1297       else if (id == 2) { /* Turrets. */
1298          pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */
1299          pilot_weapSetAdd( p, 3, slot, 1 );     /* Also get added to 'Fwd/Tur'. */
1300       }
1301       else if (id == 4) /* Seekers */
1302          pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */
1303    }
1304 
1305    /* Update active weapon set. */
1306    pilot_weapSetUpdateOutfits( p, &p->weapon_sets[ p->active_set ] );
1307 }
1308 
1309 
1310 /**
1311  * @brief Gives the pilot a default weapon set.
1312  */
pilot_weaponSetDefault(Pilot * p)1313 void pilot_weaponSetDefault( Pilot *p )
1314 {
1315    int i;
1316 
1317    /* If current set isn't a fire group no need to worry. */
1318    if (p->weapon_sets[ p->active_set ].type != WEAPSET_TYPE_CHANGE) {
1319       /* Update active weapon set. */
1320       pilot_weapSetUpdateOutfits( p, &p->weapon_sets[ p->active_set ] );
1321       return;
1322    }
1323 
1324    /* Find first fire group. */
1325    for (i=0; i<PILOT_WEAPON_SETS; i++)
1326       if (p->weapon_sets[i].type == WEAPSET_TYPE_CHANGE)
1327          break;
1328 
1329    /* Set active set to first if all fire groups or first non-fire group. */
1330    if (i >= PILOT_WEAPON_SETS)
1331       p->active_set = 0;
1332    else
1333       p->active_set = i;
1334 
1335    /* Update active weapon set. */
1336    pilot_weapSetUpdateOutfits( p, &p->weapon_sets[ p->active_set ] );
1337 }
1338 
1339 
1340 /**
1341  * @brief Sets the weapon set as sane.
1342  *
1343  *    @param p Pilot to set weapons as sane.
1344  */
pilot_weaponSane(Pilot * p)1345 void pilot_weaponSane( Pilot *p )
1346 {
1347    int i, j;
1348    int n, l;
1349    PilotWeaponSet *ws;
1350 
1351    for (j=0; j<PILOT_WEAPON_SETS; j++) {
1352       ws = &p->weapon_sets[j];
1353       if (ws->slots == NULL)
1354          continue;
1355 
1356       l = array_size(ws->slots);
1357       n = 0;
1358       for (i=0; i<l; i++) {
1359          if (ws->slots[i].slot->outfit != NULL)
1360             continue;
1361 
1362          /* Move down. */
1363          memmove( &ws->slots[i], &ws->slots[i+1], sizeof(PilotWeaponSetOutfit) * (l-i-1) );
1364          n++;
1365       }
1366       /* Remove surplus. */
1367       if (n > 0)
1368          array_erase( &ws->slots, &ws->slots[l-n], &ws->slots[l] );
1369 
1370       /* See if we must overwrite levels. */
1371       if ((ws->type == WEAPSET_TYPE_WEAPON) ||
1372             (ws->type == WEAPSET_TYPE_ACTIVE))
1373          for (i=0; i<array_size(ws->slots); i++)
1374             ws->slots[i].level = 0;
1375    }
1376 
1377    /* Update range. */
1378    pilot_weapSetUpdateRange( ws );
1379 }
1380 
1381 /**
1382  * @brief Disables a given active outfit.
1383  *
1384  * @param p Pilot whose outfit we are disabling.
1385  * @return Whether the outfit was actually disabled.
1386  */
pilot_outfitOff(Pilot * p,PilotOutfitSlot * o)1387 int pilot_outfitOff( Pilot *p, PilotOutfitSlot *o )
1388 {
1389    /* Must not be disabled or cooling down. */
1390    if ((pilot_isDisabled(p)) || (pilot_isFlag(p, PILOT_COOLDOWN)))
1391       return 0;
1392 
1393    if (outfit_isAfterburner( o->outfit )) /* Afterburners */
1394       pilot_afterburnOver( p );
1395    else if (outfit_isBeam( o->outfit )) {
1396       /* Beams use stimer to represent minimum time until shutdown. */
1397       o->stimer = -1;
1398    }
1399    else {
1400       o->stimer = outfit_cooldown( o->outfit );
1401       o->state  = PILOT_OUTFIT_COOLDOWN;
1402    }
1403 
1404    return 1;
1405 }
1406 
1407 /**
1408  * @brief Disables all active outfits for a pilot.
1409  *
1410  * @param p Pilot whose outfits we are disabling.
1411  * @return Whether any outfits were actually disabled.
1412  */
pilot_outfitOffAll(Pilot * p)1413 int pilot_outfitOffAll( Pilot *p )
1414 {
1415    PilotOutfitSlot *o;
1416    int nchg;
1417    int i;
1418 
1419    nchg = 0;
1420    for (i=0; i<p->noutfits; i++) {
1421       o = p->outfits[i];
1422       /* Picky about our outfits. */
1423       if (o->outfit == NULL)
1424          continue;
1425       if (!o->active)
1426          continue;
1427       if (o->state == PILOT_OUTFIT_ON)
1428          nchg += pilot_outfitOff( p, o );
1429    }
1430    return (nchg > 0);
1431 }
1432 
1433 /**
1434  * @brief Activate the afterburner.
1435  */
pilot_afterburn(Pilot * p)1436 void pilot_afterburn (Pilot *p)
1437 {
1438    double afb_mod;
1439 
1440    if (p == NULL)
1441       return;
1442 
1443    if (pilot_isFlag(p, PILOT_HYP_PREP) || pilot_isFlag(p, PILOT_HYPERSPACE) ||
1444          pilot_isFlag(p, PILOT_LANDING) || pilot_isFlag(p, PILOT_TAKEOFF) ||
1445          pilot_isDisabled(p) || pilot_isFlag(p, PILOT_COOLDOWN))
1446       return;
1447 
1448    /* Not under manual control if is player. */
1449    if (pilot_isFlag( p, PILOT_MANUAL_CONTROL ) && pilot_isFlag( p, PILOT_PLAYER ))
1450       return;
1451 
1452    /** @todo fancy effect? */
1453    if (p->afterburner == NULL)
1454       return;
1455 
1456    /* The afterburner only works if its efficiency is high enough. */
1457    if (pilot_heatEfficiencyMod( p->afterburner->heat_T,
1458          p->afterburner->outfit->u.afb.heat_base,
1459          p->afterburner->outfit->u.afb.heat_cap ) < 0.3)
1460       return;
1461 
1462    if (p->afterburner->state == PILOT_OUTFIT_OFF) {
1463       p->afterburner->state  = PILOT_OUTFIT_ON;
1464       p->afterburner->stimer = outfit_duration( p->afterburner->outfit );
1465       pilot_setFlag(p,PILOT_AFTERBURNER);
1466       pilot_calcStats( p );
1467 
1468       /* @todo Make this part of a more dynamic activated outfit sound system. */
1469       sound_playPos(p->afterburner->outfit->u.afb.sound_on,
1470             p->solid->pos.x, p->solid->pos.y, p->solid->vel.x, p->solid->vel.y);
1471    }
1472 
1473    if (pilot_isPlayer(p)) {
1474       afb_mod = MIN( 1., player.p->afterburner->outfit->u.afb.mass_limit / player.p->solid->mass );
1475       spfx_shake( afb_mod * player.p->afterburner->outfit->u.afb.rumble * SHAKE_MAX );
1476    }
1477 }
1478 
1479 
1480 /**
1481  * @brief Deactivates the afterburner.
1482  */
pilot_afterburnOver(Pilot * p)1483 void pilot_afterburnOver (Pilot *p)
1484 {
1485    if (p == NULL)
1486       return;
1487    if (p->afterburner == NULL)
1488       return;
1489 
1490    if (p->afterburner->state == PILOT_OUTFIT_ON) {
1491       p->afterburner->state  = PILOT_OUTFIT_OFF;
1492       pilot_rmFlag(p,PILOT_AFTERBURNER);
1493       pilot_calcStats( p );
1494 
1495       /* @todo Make this part of a more dynamic activated outfit sound system. */
1496       sound_playPos(p->afterburner->outfit->u.afb.sound_off,
1497             p->solid->pos.x, p->solid->pos.y, p->solid->vel.x, p->solid->vel.y);
1498    }
1499 }
1500