1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 
6 /**
7  * @file pilot_outfit.c
8  *
9  * @brief Handles pilot outfits.
10  */
11 
12 
13 #include "pilot.h"
14 
15 #include "naev.h"
16 
17 #include "nxml.h"
18 
19 #include "log.h"
20 #include "player.h"
21 #include "space.h"
22 #include "gui.h"
23 #include "slots.h"
24 #include "nstring.h"
25 
26 
27 /*
28  * Prototypes.
29  */
30 static int pilot_hasOutfitLimit( Pilot *p, const char *limit );
31 
32 
33 /**
34  * @brief Updates the lockons on the pilot's launchers
35  *
36  *    @param p Pilot being updated.
37  *    @param o Slot being updated.
38  *    @param t Pilot that is currently the target of p (or NULL if not applicable).
39  *    @param a Angle to update if necessary. Should be initialized to -1 before the loop.
40  *    @param dt Current delta tick.
41  */
pilot_lockUpdateSlot(Pilot * p,PilotOutfitSlot * o,Pilot * t,double * a,double dt)42 void pilot_lockUpdateSlot( Pilot *p, PilotOutfitSlot *o, Pilot *t, double *a, double dt )
43 {
44    double max, old;
45    double x,y, ang, arc;
46    int locked;
47 
48    /* No target. */
49    if (t == NULL)
50       return;
51 
52    /* Nota  seeker. */
53    if (!outfit_isSeeker(o->outfit))
54       return;
55 
56    /* Check arc. */
57    arc = o->outfit->u.lau.arc;
58    if (arc > 0.) {
59 
60       /* We use an external variable to set and update the angle if necessary. */
61       if (*a < 0.) {
62          x     = t->solid->pos.x - p->solid->pos.x;
63          y     = t->solid->pos.y - p->solid->pos.y;
64          ang   = ANGLE( x, y );
65          *a    = fabs( angle_diff( ang, p->solid->dir ) );
66       }
67 
68       /* Decay if not in arc. */
69       if (*a > arc) {
70          /* Limit decay to the lockon time for this launcher. */
71          max = o->outfit->u.lau.lockon;
72 
73          /* When a lock is lost, immediately gain half the lock timer.
74           * This is meant as an incentive for the aggressor not to lose the lock,
75           * and for the target to try and break the lock. */
76          old = o->u.ammo.lockon_timer;
77          o->u.ammo.lockon_timer += dt;
78          if ((old <= 0.) && (o->u.ammo.lockon_timer > 0.))
79             o->u.ammo.lockon_timer += o->outfit->u.lau.lockon / 2.;
80 
81          /* Cap at max. */
82          if (o->u.ammo.lockon_timer > max)
83             o->u.ammo.lockon_timer = max;
84 
85          /* Out of arc. */
86          o->u.ammo.in_arc = 0;
87          return;
88       }
89    }
90 
91    /* In arc. */
92    o->u.ammo.in_arc = 1;
93    locked = (o->u.ammo.lockon_timer < 0.);
94 
95    /* Lower timer. When the timer reaches zero, the lock is established. */
96    max = -o->outfit->u.lau.lockon/3.;
97    if (o->u.ammo.lockon_timer > max) {
98       /* Compensate for enemy hide factor. */
99       o->u.ammo.lockon_timer -= dt * (o->outfit->u.lau.ew_target2 / t->ew_hide);
100 
101       /* Cap at -max/3. */
102       if (o->u.ammo.lockon_timer < max)
103          o->u.ammo.lockon_timer = max;
104 
105       /* Trigger lockon hook. */
106       if (!locked && (o->u.ammo.lockon_timer < 0.))
107          pilot_runHook( p, PILOT_HOOK_LOCKON );
108    }
109 }
110 
111 
112 /**
113  * @brief Clears pilot's missile lockon timers.
114  *
115  *    @param p Pilot to clear missile lockon timers.
116  */
pilot_lockClear(Pilot * p)117 void pilot_lockClear( Pilot *p )
118 {
119    int i;
120    PilotOutfitSlot *o;
121 
122    for (i=0; i<p->noutfits; i++) {
123       o = p->outfits[i];
124       if (o->outfit == NULL)
125          continue;
126       if (!outfit_isSeeker(o->outfit))
127          continue;
128 
129       /* Clear timer. */
130       o->u.ammo.lockon_timer = o->outfit->u.lau.lockon;
131 
132       /* Clear arc. */
133       o->u.ammo.in_arc = 0;
134    }
135 }
136 
137 
138 /**
139  * @brief Gets the mount position of a pilot.
140  *
141  * Position is relative to the pilot.
142  *
143  *    @param p Pilot to get mount position of.
144  *    @param id ID of the mount.
145  *    @param[out] v Position of the mount.
146  *    @return 0 on success.
147  */
pilot_getMount(const Pilot * p,const PilotOutfitSlot * w,Vector2d * v)148 int pilot_getMount( const Pilot *p, const PilotOutfitSlot *w, Vector2d *v )
149 {
150    double a, x, y;
151    double cm, sm;
152    const ShipMount *m;
153 
154    /* Calculate the sprite angle. */
155    a  = (double)(p->tsy * p->ship->gfx_space->sx + p->tsx);
156    a *= p->ship->mangle;
157 
158    /* 2d rotation matrix
159     * [ x' ]   [  cos  sin  ]   [ x ]
160     * [ y' ] = [ -sin  cos  ] * [ y ]
161     *
162     * dir is inverted so that rotation is counter-clockwise.
163     */
164    m = &w->sslot->mount;
165    cm = cos(-a);
166    sm = sin(-a);
167    x = m->x * cm + m->y * sm;
168    y = m->x *-sm + m->y * cm;
169 
170    /* Correction for ortho perspective. */
171    y *= M_SQRT1_2;
172 
173    /* Don't forget to add height. */
174    y += m->h;
175 
176    /* Get the mount and add the player.p offset. */
177    vect_cset( v, x, y );
178 
179    return 0;
180 }
181 
182 
183 /**
184  * @brief Docks the pilot on its target pilot.
185  *
186  *    @param p Pilot that wants to dock.
187  *    @param target Pilot to dock on.
188  *    @param deployed Was pilot already deployed?
189  *    @return 0 on successful docking.
190  */
pilot_dock(Pilot * p,Pilot * target,int deployed)191 int pilot_dock( Pilot *p, Pilot *target, int deployed )
192 {
193    int i;
194    Outfit *o = NULL;
195 
196    /* Must be close. */
197    if (vect_dist(&p->solid->pos, &target->solid->pos) >
198          target->ship->gfx_space->sw * PILOT_SIZE_APROX )
199       return -1;
200 
201    /* Cannot be going much faster. */
202    if ((pow2(VX(p->solid->vel)-VX(target->solid->vel)) +
203             pow2(VY(p->solid->vel)-VY(target->solid->vel))) >
204          (double)pow2(MAX_HYPERSPACE_VEL))
205       return -1;
206 
207    /* Check to see if target has an available bay. */
208    for (i=0; i<target->noutfits; i++) {
209 
210       /* Must have outfit. */
211       if (target->outfits[i]->outfit == NULL)
212          continue;
213 
214       /* Must be fighter bay. */
215       if (!outfit_isFighterBay(target->outfits[i]->outfit))
216          continue;
217 
218       /* Must have deployed. */
219       if (deployed && (target->outfits[i]->u.ammo.deployed <= 0))
220          continue;
221 
222       o = outfit_ammo(target->outfits[i]->outfit);
223 
224       /* Try to add fighter. */
225       if (outfit_isFighter(o) &&
226             (strcmp(p->ship->name,o->u.fig.ship)==0)) {
227          if (deployed)
228             target->outfits[i]->u.ammo.deployed -= 1;
229          break;
230       }
231    }
232    if ((o==NULL) || (i >= target->noutfits))
233       return -1;
234 
235    /* Add the pilot's outfit. */
236    if (pilot_addAmmo(target, target->outfits[i], o, 1) != 1)
237       return -1;
238 
239    /* Remove from pilot's escort list. */
240    if (deployed) {
241       for (i=0; i<target->nescorts; i++) {
242          if ((target->escorts[i].type == ESCORT_TYPE_BAY) &&
243                (target->escorts[i].id == p->id))
244             break;
245       }
246       /* Not found as pilot's escorts. */
247       if (i >= target->nescorts)
248          return -1;
249       /* Free if last pilot. */
250       if (target->nescorts == 1) {
251          free(target->escorts);
252          target->escorts   = NULL;
253          target->nescorts  = 0;
254       }
255       else {
256          memmove( &target->escorts[i], &target->escorts[i+1],
257                sizeof(Escort_t) * (target->nescorts-i-1) );
258          target->nescorts--;
259       }
260    }
261 
262    /* Destroy the pilot. */
263    pilot_delete(p);
264 
265    return 0;
266 }
267 
268 
269 /**
270  * @brief Checks to see if the pilot has deployed ships.
271  *
272  *    @param p Pilot to see if has deployed ships.
273  *    @return 1 if pilot has deployed ships, 0 otherwise.
274  */
pilot_hasDeployed(Pilot * p)275 int pilot_hasDeployed( Pilot *p )
276 {
277    int i;
278    for (i=0; i<p->noutfits; i++) {
279       if (p->outfits[i]->outfit == NULL)
280          continue;
281       if (outfit_isFighterBay(p->outfits[i]->outfit))
282          if (p->outfits[i]->u.ammo.deployed > 0)
283             return 1;
284    }
285    return 0;
286 }
287 
288 
289 /**
290  * @brief Adds an outfit to the pilot, ignoring CPU or other limits.
291  *
292  * @note Does not call pilot_calcStats().
293  *
294  *    @param pilot Pilot to add the outfit to.
295  *    @param outfit Outfit to add to the pilot.
296  *    @param s Slot to add ammo to.
297  *    @return 0 on success.
298  */
pilot_addOutfitRaw(Pilot * pilot,Outfit * outfit,PilotOutfitSlot * s)299 int pilot_addOutfitRaw( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s )
300 {
301    Outfit *o;
302 
303    /* Set the outfit. */
304    s->outfit   = outfit;
305 
306    /* Set some default parameters. */
307    s->timer    = 0.;
308 
309    /* Some per-case scenarios. */
310    if (outfit_isFighterBay(outfit)) {
311       s->u.ammo.outfit   = NULL;
312       s->u.ammo.quantity = 0;
313       s->u.ammo.deployed = 0;
314    }
315    if (outfit_isTurret(outfit)) /* used to speed up AI */
316       pilot->nturrets++;
317    else if (outfit_isBolt(outfit))
318       pilot->ncannons++;
319    else if (outfit_isJammer(outfit))
320       pilot->njammers++;
321    else if (outfit_isAfterburner(outfit))
322       pilot->nafterburners++;
323 
324    if (outfit_isBeam(outfit)) { /* Used to speed up some calculations. */
325       s->u.beamid = 0;
326       pilot->nbeams++;
327    }
328    if (outfit_isLauncher(outfit)) {
329       s->u.ammo.outfit   = NULL;
330       s->u.ammo.quantity = 0;
331       s->u.ammo.deployed = 0; /* Just in case. */
332    }
333 
334    /* Check if active. */
335    o = s->outfit;
336    s->active = outfit_isActive(o);
337 
338    /* Update heat. */
339    pilot_heatCalcSlot( s );
340 
341    return 0;
342 }
343 
344 
345 /**
346  * @brief Tests to see if an outfit can be added.
347  *
348  *    @param pilot Pilot to add outfit to.
349  *    @param outfit Outfit to add.
350  *    @param s Slot adding outfit to.
351  *    @param warn Whether or not should generate a warning.
352  *    @return 0 if can add, -1 if can't.
353  */
pilot_addOutfitTest(Pilot * pilot,Outfit * outfit,PilotOutfitSlot * s,int warn)354 int pilot_addOutfitTest( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s, int warn )
355 {
356    const char *str;
357 
358    /* See if slot has space. */
359    if (s->outfit != NULL) {
360       if (warn)
361          WARN( "Pilot '%s': trying to add outfit '%s' to slot that already has an outfit",
362                pilot->name, outfit->name );
363       return -1;
364    }
365    else if ((outfit_cpu(outfit) < 0) &&
366          (pilot->cpu < ABS( outfit_cpu(outfit) ))) {
367       if (warn)
368          WARN( "Pilot '%s': Not enough CPU to add outfit '%s'",
369                pilot->name, outfit->name );
370       return -1;
371    }
372    else if ((str = pilot_canEquip( pilot, s, outfit)) != NULL) {
373       if (warn)
374          WARN( "Pilot '%s': Trying to add outfit but %s",
375                pilot->name, str );
376       return -1;
377    }
378    return 0;
379 }
380 
381 
382 
383 /**
384  * @brief Adds an outfit to the pilot.
385  *
386  *    @param pilot Pilot to add the outfit to.
387  *    @param outfit Outfit to add to the pilot.
388  *    @param s Slot to add ammo to.
389  *    @return 0 on success.
390  */
pilot_addOutfit(Pilot * pilot,Outfit * outfit,PilotOutfitSlot * s)391 int pilot_addOutfit( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s )
392 {
393    int ret;
394 
395    /* Test to see if outfit can be added. */
396    ret = pilot_addOutfitTest( pilot, outfit, s, 1 );
397    if (ret != 0)
398       return -1;
399 
400    /* Add outfit. */
401    ret = pilot_addOutfitRaw( pilot, outfit, s );
402 
403    /* Recalculate the stats */
404    pilot_calcStats(pilot);
405 
406    return ret;
407 }
408 
409 
410 /**
411  * @brief Removes an outfit from the pilot without doing any checks.
412  *
413  * @note Does not run pilot_calcStats().
414  *
415  *    @param pilot Pilot to remove the outfit from.
416  *    @param s Slot to remove.
417  *    @return 0 on success.
418  */
pilot_rmOutfitRaw(Pilot * pilot,PilotOutfitSlot * s)419 int pilot_rmOutfitRaw( Pilot* pilot, PilotOutfitSlot *s )
420 {
421    int ret;
422 
423    /* Decrement counters if necessary. */
424    if (s->outfit != NULL) {
425       if (outfit_isTurret(s->outfit))
426          pilot->nturrets--;
427       else if (outfit_isBolt(s->outfit))
428          pilot->ncannons--;
429       if (outfit_isBeam(s->outfit))
430          pilot->nbeams--;
431    }
432 
433    /* Remove the outfit. */
434    ret         = (s->outfit==NULL);
435    s->outfit   = NULL;
436 
437    /* Remove secondary and such if necessary. */
438    if (pilot->afterburner == s)
439       pilot->afterburner = NULL;
440 
441    return ret;
442 }
443 
444 
445 /**
446  * @brief Removes an outfit from the pilot.
447  *
448  *    @param pilot Pilot to remove the outfit from.
449  *    @param s Slot to remove.
450  *    @return 0 on success.
451  */
pilot_rmOutfit(Pilot * pilot,PilotOutfitSlot * s)452 int pilot_rmOutfit( Pilot* pilot, PilotOutfitSlot *s )
453 {
454    const char *str;
455    int ret;
456 
457    str = pilot_canEquip( pilot, s, NULL );
458    if (str != NULL) {
459       WARN("Pilot '%s': Trying to remove outfit but %s",
460             pilot->name, str );
461       return -1;
462    }
463 
464    ret = pilot_rmOutfitRaw( pilot, s );
465 
466    /* recalculate the stats */
467    pilot_calcStats(pilot);
468 
469    return ret;
470 }
471 //TODO: fix comment to conform to Naev's style and represent changes
472 /**
473  * @brief Pilot slot sanity check - makes sure stats are sane.
474  *
475  *    @param p Pilot to check.
476  *    @return 0 if a slot doesn't fit, !0 otherwise.
477  */
pilot_slotsCheckSanity(Pilot * p)478 int pilot_slotsCheckSanity( Pilot *p )
479 {
480    int i;
481    for (i=0; i<p->noutfits; i++)
482       if ((p->outfits[i]->outfit != NULL) &&
483             !outfit_fitsSlot( p->outfits[i]->outfit, &p->outfits[i]->sslot->slot ))
484          return 0;
485    return 1;
486 }
487 //TODO: fix comment to conform to Naev's style and represent changes
488 /**
489  * @brief Pilot required (core) slot filled check - makes sure they are filled.
490  *
491  *    @param p Pilot to check.
492  *    @return 0 if a slot is missing, !0 otherwise.
493  */
pilot_slotsCheckRequired(Pilot * p)494 int pilot_slotsCheckRequired( Pilot *p )
495 {
496    int i;
497 
498    for (i=0; i < p->outfit_nstructure; i++)
499       if (p->outfit_structure[i].sslot->required && p->outfit_structure[i].outfit == NULL)
500          return 0;
501 
502    for (i=0; i < p->outfit_nutility; i++)
503       if (p->outfit_utility[i].sslot->required && p->outfit_utility[i].outfit == NULL)
504          return 0;
505 
506    for (i=0; i < p->outfit_nweapon; i++)
507       if (p->outfit_weapon[i].sslot->required && p->outfit_weapon[i].outfit == NULL)
508          return 0;
509 
510    return 1;
511 }
512 //TODO: fix comment to conform to Naev's style and represent change
513 /**
514  * @brief Pilot sanity check - makes sure stats are sane.
515  *
516  *    @param p Pilot to check.
517  *    @return The reason why the pilot is not sane (or NULL if sane).
518  */
pilot_checkSpaceworthy(Pilot * p)519 const char* pilot_checkSpaceworthy( Pilot *p )
520 {
521    if (!pilot_slotsCheckSanity(p))
522       return "Doesn't fit slot";
523 
524    /* CPU. */
525    if (p->cpu < 0)
526       return "Insufficient CPU";
527 
528    /* Movement. */
529    if (p->thrust < 0.)
530       return "Insufficient Thrust";
531    if (p->speed < 0.)
532       return "Insufficient Speed";
533    if (p->turn < 0.)
534       return "Insufficient Turn";
535 
536    /* Health. */
537    if (p->armour_max < 0.)
538       return "Insufficient Armour";
539    if (p->armour_regen < 0.)
540       return "Insufficient Armour Regeneration";
541    if (p->shield_max < 0.)
542       return "Insufficient Shield";
543    if (p->shield_regen < 0.)
544       return "Insufficient Shield Regeneration";
545    if (p->energy_max < 0.)
546       return "Insufficient Energy";
547    if (p->energy_regen < 0.)
548       return "Insufficient Energy Regeneration";
549 
550    /* Misc. */
551    if (p->fuel_max < 0.)
552       return "Insufficient Fuel Maximum";
553    if (p->fuel_consumption < 0.)
554       return "Insufficient Fuel Consumption";
555    if (p->cargo_free < 0)
556       return "Insufficient Free Cargo Space";
557 
558    /* Core Slots */
559    if (!pilot_slotsCheckRequired(p))
560       return "Not All Core Slots are equipped";
561 
562    /* All OK. */
563    return NULL;
564 }
565 /**
566  * @brief Pilot sanity report - makes sure stats are sane.
567  *
568  *    @param p Pilot to check.
569  *    @param buf Buffer to fill.
570  *    @param bufSize Size of the buffer.
571  *    @return Number of issues encountered.
572  */
573 #define SPACEWORTHY_CHECK(cond,msg) \
574 if (cond){ ret++; \
575    if (pos < bufSize) pos += snprintf( &buf[pos], bufSize-pos, (msg) ); }
pilot_reportSpaceworthy(Pilot * p,char buf[],int bufSize)576 int pilot_reportSpaceworthy( Pilot *p, char buf[], int bufSize )
577 {
578    int pos = 0;
579    int ret = 0;
580 
581    /* Core Slots */
582    SPACEWORTHY_CHECK( !pilot_slotsCheckRequired(p), "Not All Core Slots are equipped\n" );
583    /* CPU. */
584    SPACEWORTHY_CHECK( p->cpu < 0, "Insufficient CPU\n" );
585 
586    /* Movement. */
587    SPACEWORTHY_CHECK( p->thrust < 0, "Insufficient Thrust\n" );
588    SPACEWORTHY_CHECK( p->speed < 0,  "Insufficient Speed\n" );
589    SPACEWORTHY_CHECK( p->turn < 0,   "Insufficient Turn\n" );
590 
591    /* Health. */
592    SPACEWORTHY_CHECK( p->armour < 0.,       "Insufficient Armour\n" );
593    SPACEWORTHY_CHECK( p->armour_regen < 0., "Insufficient Armour Regeneration\n" );
594    SPACEWORTHY_CHECK( p->shield < 0.,       "Insufficient Shield\n" );
595    SPACEWORTHY_CHECK( p->shield_regen < 0., "Insufficient Shield Regeneration\n" );
596    SPACEWORTHY_CHECK( p->energy_max < 0.,   "Insufficient Energy\n" );
597    SPACEWORTHY_CHECK( p->energy_regen < 0., "Insufficient Energy Regeneration\n" );
598 
599    /* Misc. */
600    SPACEWORTHY_CHECK( p->fuel_max < 0.,         "Insufficient Fuel Maximum\n" );
601    SPACEWORTHY_CHECK( p->fuel_consumption < 0., "Insufficient Fuel Consumption\n" );
602    SPACEWORTHY_CHECK( p->cargo_free < 0,        "Insufficient Free Cargo Space\n" );
603 
604    /*buffer is full, lets write that there is more then what's copied */
605    if (pos > bufSize-1) {
606       buf[bufSize-4]='.';
607       buf[bufSize-3]='.';
608       buf[bufSize-2]='.';
609       /* buf[bufSize-1]='\0'; already done for us */
610    }
611    else {
612       if (pos == 0)
613          /*string is empty so no errors encountered */
614          nsnprintf( buf, bufSize, "Spaceworthy");
615       else
616          /*string is not empty, so trunc the last newline */
617          buf[pos-1]='\0';
618    }
619 
620    return ret;
621 }
622 #undef SPACEWORTHY_CHECK
623 
624 /**
625  * @brief Checks to see if a pilot has an outfit with a specific outfit type.
626  *
627  *    @param p Pilot to check.
628  *    @param t Outfit type to check.
629  *    @return the amount of outfits of this type the pilot has.
630  */
pilot_hasOutfitLimit(Pilot * p,const char * limit)631 static int pilot_hasOutfitLimit( Pilot *p, const char *limit )
632 {
633    int i;
634    Outfit *o;
635    for (i = 0; i<p->noutfits; i++) {
636       o = p->outfits[i]->outfit;
637       if (o == NULL)
638          continue;
639       if ((o->limit != NULL) && (strcmp(o->limit,limit)==0))
640          return 1;
641    }
642    return 0;
643 }
644 
645 /**
646  * @brief Checks to see if can equip/remove an outfit from a slot.
647  *
648  *    @param p Pilot to check if can equip.
649  *    @param s Slot being checked to see if it can equip/remove an outfit.
650  *    @param o Outfit to check (NULL if being removed).
651  *    @return NULL if can swap, or error message if can't.
652  */
pilot_canEquip(Pilot * p,PilotOutfitSlot * s,Outfit * o)653 const char* pilot_canEquip( Pilot *p, PilotOutfitSlot *s, Outfit *o )
654 {
655    /* Just in case. */
656    if ((p==NULL) || (s==NULL))
657       return "Nothing selected.";
658 
659    if (o!=NULL) {
660       /* Check slot type. */
661       if (!outfit_fitsSlot( o, &s->sslot->slot ))
662          return "Does not fit slot.";
663       /* Check outfit limit. */
664       if ((o->limit != NULL) && pilot_hasOutfitLimit( p, o->limit ))
665          return "Already have an outfit of this type installed";
666    }
667    else {
668       /* Check fighter bay. */
669       if ((o==NULL) && (s!=NULL) && (s->u.ammo.deployed > 0))
670          return "Recall the fighters first";
671    }
672 
673    return NULL;
674 }
675 
676 
677 /**
678  * @brief Adds some ammo to the pilot stock.
679  *
680  *    @param pilot Pilot to add ammo to.
681  *    @param s Slot to add ammo to.
682  *    @param ammo Ammo to add.
683  *    @param quantity Amount to add.
684  *    @return Amount actually added.
685  */
pilot_addAmmo(Pilot * pilot,PilotOutfitSlot * s,Outfit * ammo,int quantity)686 int pilot_addAmmo( Pilot* pilot, PilotOutfitSlot *s, Outfit* ammo, int quantity )
687 {
688    int q, max;
689    (void) pilot;
690 
691    /* Failure cases. */
692    if (s->outfit == NULL) {
693       WARN("Pilot '%s': Trying to add ammo to unequiped slot.", pilot->name );
694       return 0;
695    }
696    else if (!outfit_isLauncher(s->outfit) && !outfit_isFighterBay(s->outfit)) {
697       WARN("Pilot '%s': Trying to add ammo to non-launcher/fighterbay type outfit '%s'",
698             pilot->name, s->outfit->name);
699       return 0;
700    }
701    else if (!outfit_isAmmo(ammo) && !outfit_isFighter(ammo)) {
702       WARN( "Pilot '%s': Trying to add non-ammo/fighter type outfit '%s' as ammo.",
703             pilot->name, ammo->name );
704       return 0;
705    }
706    else if (outfit_isLauncher(s->outfit) && outfit_isFighter(ammo)) {
707       WARN("Pilot '%s': Trying to add fighter '%s' as launcher '%s' ammo",
708             pilot->name, ammo->name, s->outfit->name );
709       return 0;
710    }
711    else if (outfit_isFighterBay(s->outfit) && outfit_isAmmo(ammo)) {
712       WARN("Pilot '%s': Trying to add ammo '%s' as fighter bay '%s' ammo",
713             pilot->name, ammo->name, s->outfit->name );
714       return 0;
715    }
716    else if ((s->u.ammo.outfit != NULL) && (s->u.ammo.quantity > 0) &&
717          (s->u.ammo.outfit != ammo)) {
718       WARN("Pilot '%s': Trying to add ammo to outfit that already has ammo.",
719             pilot->name );
720       return 0;
721    }
722 
723    /* Set the ammo type. */
724    s->u.ammo.outfit    = ammo;
725 
726    /* Add the ammo. */
727    max                 = outfit_amount(s->outfit) - s->u.ammo.deployed;
728    q                   = s->u.ammo.quantity; /* Amount have. */
729    s->u.ammo.quantity += quantity;
730    s->u.ammo.quantity  = MIN( max, s->u.ammo.quantity );
731    q                   = s->u.ammo.quantity - q; /* Amount actually added. */
732    pilot->mass_outfit += q * s->u.ammo.outfit->mass;
733    pilot_updateMass( pilot );
734 
735    return q;
736 }
737 
738 
739 /**
740  * @brief Removes some ammo from the pilot stock.
741  *
742  *    @param pilot Pilot to remove ammo from.
743  *    @param s Slot to remove ammo from.
744  *    @param quantity Amount to remove.
745  *    @return Amount actually removed.
746  */
pilot_rmAmmo(Pilot * pilot,PilotOutfitSlot * s,int quantity)747 int pilot_rmAmmo( Pilot* pilot, PilotOutfitSlot *s, int quantity )
748 {
749    (void) pilot;
750    int q;
751 
752    /* Failure cases. */
753    if (s->outfit == NULL) {
754       WARN("Pilot '%s': Trying to remove ammo from unequiped slot.", pilot->name );
755       return 0;
756    }
757    else if (!outfit_isLauncher(s->outfit) && !outfit_isFighterBay(s->outfit)) {
758       WARN("Pilot '%s': Trying to remove ammo from non-launcher/fighter bay type outfit '%s'",
759             pilot->name, s->outfit->name);
760       return 0;
761    }
762 
763    /* No ammo already. */
764    if (s->u.ammo.outfit == NULL)
765       return 0;
766 
767    /* Remove ammo. */
768    q                   = MIN( quantity, s->u.ammo.quantity );
769    s->u.ammo.quantity -= q;
770    pilot->mass_outfit -= q * s->u.ammo.outfit->mass;
771    pilot_updateMass( pilot );
772    /* We don't set the outfit to null so it "remembers" old ammo. */
773 
774    return q;
775 }
776 
777 
778 /**
779  * @brief Gets the number of ammo units on the ship
780  *
781  *    @param pilot Pilot to count the ammo on
782  *    @@return The integer count of ammo units on pilot
783  */
pilot_countAmmo(Pilot * pilot)784 int pilot_countAmmo( Pilot* pilot )
785 {
786    int nammo = 0, i;
787    PilotOutfitSlot* po;
788    Outfit* outfit;
789    for (i=0; i<pilot->noutfits; i++) {
790      po = pilot->outfits[i];
791      if (po == NULL)
792         continue;
793      outfit = po->outfit;
794      if (outfit == NULL)
795         continue;
796      if (!outfit_isLauncher(po->outfit))
797         continue;
798      nammo += po->u.ammo.quantity;
799    }
800    return nammo;
801 }
802 
803 
804 /**
805  * @brief The maximum amount of ammo the pilot's current ship can hold.
806  *
807  *    @param pilot Pilot to get the count from
808  *    @@return An integer, the max amount of ammo that can be held.
809  */
pilot_maxAmmo(Pilot * pilot)810 int pilot_maxAmmo( Pilot* pilot )
811 {
812   int max = 0, i;
813   PilotOutfitSlot* po;
814   Outfit* outfit;
815   for (i=0; i<pilot->noutfits; i++) {
816      po = pilot->outfits[i];
817      if (po == NULL)
818         continue;
819      outfit = po->outfit;
820      if (outfit == NULL)
821         continue;
822      if (!outfit_isLauncher(outfit))
823         continue;
824      max += outfit->u.lau.amount;
825   }
826   return max;
827 }
828 
829 
830 /**
831  * @brief Gets all the outfits in nice text form.
832  *
833  *    @param pilot Pilot to get the outfits from.
834  *    @@return A list of all the outfits in a nice form.
835  */
pilot_getOutfits(const Pilot * pilot)836 char* pilot_getOutfits( const Pilot* pilot )
837 {
838    int i;
839    char *buf;
840    int p, len;
841 
842    len = 1024;
843 
844    buf = malloc(len);
845    buf[0] = '\0';
846    p = 0;
847    for (i=1; i<pilot->noutfits; i++) {
848       if (pilot->outfits[i]->outfit == NULL)
849          continue;
850       p += nsnprintf( &buf[p], len-p, (p==0) ? "%s" : ", %s",
851             pilot->outfits[i]->outfit->name );
852    }
853 
854    if (p==0)
855       p += nsnprintf( &buf[p], len-p, "None" );
856 
857    return buf;
858 }
859 
860 
861 /**
862  * @brief Recalculates the pilot's stats based on his outfits.
863  *
864  *    @param pilot Pilot to recalculate his stats.
865  */
pilot_calcStats(Pilot * pilot)866 void pilot_calcStats( Pilot* pilot )
867 {
868    int i;
869    Outfit* o;
870    PilotOutfitSlot *slot;
871    double ac, sc, ec, fc; /* temporary health coefficients to set */
872    ShipStats amount, *s, *default_s;
873 
874    /*
875     * set up the basic stuff
876     */
877    /* mass */
878    pilot->solid->mass   = pilot->ship->mass;
879    pilot->base_mass     = pilot->solid->mass;
880    /* cpu */
881    pilot->cpu           = 0.;
882    /* movement */
883    pilot->thrust_base   = pilot->ship->thrust;
884    pilot->turn_base     = pilot->ship->turn;
885    pilot->speed_base    = pilot->ship->speed;
886    /* crew */
887    pilot->crew          = pilot->ship->crew;
888    /* cargo */
889    pilot->cap_cargo     = pilot->ship->cap_cargo;
890    /* fuel_consumption. */
891    pilot->fuel_consumption = pilot->ship->fuel_consumption;
892    /* health */
893    ac = (pilot->armour_max > 0.) ? pilot->armour / pilot->armour_max : 0.;
894    sc = (pilot->shield_max > 0.) ? pilot->shield / pilot->shield_max : 0.;
895    ec = (pilot->energy_max > 0.) ? pilot->energy / pilot->energy_max : 0.;
896    fc = (pilot->fuel_max   > 0.) ? pilot->fuel   / pilot->fuel_max   : 0.;
897    pilot->armour_max    = pilot->ship->armour;
898    pilot->shield_max    = pilot->ship->shield;
899    pilot->fuel_max      = pilot->ship->fuel;
900    pilot->armour_regen  = pilot->ship->armour_regen;
901    pilot->shield_regen  = pilot->ship->shield_regen;
902    /* Absorption. */
903    pilot->dmg_absorb    = pilot->ship->dmg_absorb;
904    /* Energy. */
905    pilot->energy_max    = pilot->ship->energy;
906    pilot->energy_regen  = pilot->ship->energy_regen;
907    pilot->energy_loss   = 0.; /* Initially no net loss. */
908    /* Stats. */
909    s = &pilot->stats;
910    *s = pilot->ship->stats_array;
911    memset( &amount, 0, sizeof(ShipStats) );
912 
913    /*
914     * Now add outfit changes
915     */
916    pilot->mass_outfit   = 0.;
917    pilot->jamming       = 0;
918    for (i=0; i<pilot->noutfits; i++) {
919       slot = pilot->outfits[i];
920       o    = slot->outfit;
921 
922       /* Outfit must exist. */
923       if (o==NULL)
924          continue;
925 
926       /* Modify CPU. */
927       pilot->cpu           += outfit_cpu(o);
928 
929       /* Add mass. */
930       pilot->mass_outfit   += o->mass;
931 
932       /* Keep a separate counter for required (core) outfits. */
933       if (sp_required( o->slot.spid ))
934          pilot->base_mass += o->mass;
935 
936       /* Add ammo mass. */
937       if (outfit_ammo(o) != NULL)
938          if (slot->u.ammo.outfit != NULL)
939             pilot->mass_outfit += slot->u.ammo.quantity * slot->u.ammo.outfit->mass;
940 
941       if (outfit_isAfterburner(o)) /* Afterburner */
942          pilot->afterburner = pilot->outfits[i]; /* Set afterburner */
943 
944       /* Active outfits must be on to affect stuff. */
945       if (slot->active && !(slot->state==PILOT_OUTFIT_ON))
946          continue;
947 
948       if (outfit_isMod(o)) { /* Modification */
949          /* Movement. */
950          pilot->thrust_base   += o->u.mod.thrust;
951          pilot->turn_base     += o->u.mod.turn;
952          pilot->speed_base    += o->u.mod.speed;
953          /* Health. */
954          pilot->dmg_absorb    += o->u.mod.absorb;
955          pilot->armour_max    += o->u.mod.armour;
956          pilot->armour_regen  += o->u.mod.armour_regen;
957          pilot->shield_max    += o->u.mod.shield;
958          pilot->shield_regen  += o->u.mod.shield_regen;
959          pilot->energy_max    += o->u.mod.energy;
960          pilot->energy_regen  += o->u.mod.energy_regen;
961          pilot->energy_loss   += o->u.mod.energy_loss;
962          /* Fuel. */
963          pilot->fuel_max      += o->u.mod.fuel;
964          /* Misc. */
965          pilot->cap_cargo     += o->u.mod.cargo;
966          pilot->mass_outfit   += o->u.mod.mass_rel * pilot->ship->mass;
967          pilot->crew          += o->u.mod.crew_rel * pilot->ship->crew;
968          /*
969           * Stats.
970           */
971          ss_statsModFromList( s, o->u.mod.stats, &amount );
972 
973       }
974       else if (outfit_isAfterburner(o)) { /* Afterburner */
975          pilot_setFlag( pilot, PILOT_AFTERBURNER ); /* We use old school flags for this still... */
976          pilot->energy_loss += pilot->afterburner->outfit->u.afb.energy; /* energy loss */
977       }
978       else if (outfit_isJammer(o)) { /* Jammer */
979          pilot->jamming        = 1;
980          pilot->energy_loss   += o->u.jam.energy;
981       }
982    }
983 
984    if (!pilot_isFlag( pilot, PILOT_AFTERBURNER ))
985       pilot->solid->speed_max = pilot->speed;
986 
987    /* Slot voodoo. */
988    s = &pilot->stats;
989    default_s = &pilot->ship->stats_array;
990 
991    /* Fire rate:
992     *  amount = p * exp( -0.15 * (n-1) )
993     *  1x 15% -> 15%
994     *  2x 15% -> 25.82%
995     *  3x 15% -> 33.33%
996     *  6x 15% -> 42.51%
997     */
998    if (amount.fwd_firerate > 0) {
999       s->fwd_firerate = default_s->fwd_firerate + (s->fwd_firerate-default_s->fwd_firerate) * exp( -0.15 * (double)(MAX(amount.fwd_firerate-1.,0)) );
1000    }
1001    /* Cruiser. */
1002    if (amount.tur_firerate > 0) {
1003       s->tur_firerate = default_s->tur_firerate + (s->tur_firerate-default_s->tur_firerate) * exp( -0.15 * (double)(MAX(amount.tur_firerate-1.,0)) );
1004    }
1005    /*
1006     * Electronic warfare setting base parameters.
1007     */
1008    s->ew_hide           = default_s->ew_hide + (s->ew_hide-default_s->ew_hide)                      * exp( -0.2 * (double)(MAX(amount.ew_hide-1.,0)) );
1009    s->ew_detect         = default_s->ew_detect + (s->ew_detect-default_s->ew_detect)                * exp( -0.2 * (double)(MAX(amount.ew_detect-1.,0)) );
1010    s->ew_jump_detect    = default_s->ew_jump_detect + (s->ew_jump_detect-default_s->ew_jump_detect) * exp( -0.2 * (double)(MAX(amount.ew_jump_detect-1.,0)) );
1011 
1012    /* Square the internal values to speed up comparisons. */
1013    pilot->ew_base_hide   = pow2( s->ew_hide );
1014    pilot->ew_detect      = pow2( s->ew_detect );
1015    pilot->ew_jump_detect = pow2( s->ew_jump_detect );
1016 
1017    /*
1018     * Relative increases.
1019     */
1020    /* Movement. */
1021    pilot->thrust_base  *= s->thrust_mod;
1022    pilot->turn_base    *= s->turn_mod;
1023    pilot->speed_base   *= s->speed_mod;
1024    /* Health. */
1025    pilot->armour_max   *= s->armour_mod;
1026    pilot->armour_regen *= s->armour_regen_mod;
1027    pilot->shield_max   *= s->shield_mod;
1028    pilot->shield_regen *= s->shield_regen_mod;
1029    pilot->energy_max   *= s->energy_mod;
1030    pilot->energy_regen *= s->energy_regen_mod;
1031    /* cpu */
1032    pilot->cpu_max       = (int)floor((float)(pilot->ship->cpu + s->cpu_max)*s->cpu_mod);
1033    pilot->cpu          += pilot->cpu_max; /* CPU is negative, this just sets it so it's based off of cpu_max. */
1034    /* Misc. */
1035    pilot->dmg_absorb    = MAX( 0., pilot->dmg_absorb );
1036    pilot->crew         *= s->crew_mod;
1037    pilot->cap_cargo    *= s->cargo_mod;
1038    s->engine_limit     *= s->engine_limit_rel;
1039 
1040    /*
1041     * Flat increases.
1042     */
1043    pilot->energy_max   += s->energy_flat;
1044    pilot->energy       += s->energy_flat;
1045    pilot->energy_regen -= s->energy_usage;
1046 
1047    /* Give the pilot his health proportion back */
1048    pilot->armour = ac * pilot->armour_max;
1049    pilot->shield = sc * pilot->shield_max;
1050    pilot->energy = ec * pilot->energy_max;
1051    pilot->fuel   = fc * pilot->fuel_max;
1052 
1053    /* Set final energy tau. */
1054    pilot->energy_tau = pilot->energy_max / pilot->energy_regen;
1055 
1056    /* Cargo has to be reset. */
1057    pilot_cargoCalc(pilot);
1058 
1059    /* Calculate mass. */
1060    pilot->solid->mass = s->mass_mod*pilot->ship->mass + pilot->stats.cargo_inertia*pilot->mass_cargo + pilot->mass_outfit;
1061 
1062    /* Calculate the heat. */
1063    pilot_heatCalc( pilot );
1064 
1065    /* Modulate by mass. */
1066    pilot_updateMass( pilot );
1067 
1068    /* Update GUI as necessary. */
1069    gui_setGeneric( pilot );
1070 }
1071 
1072 
1073 /**
1074  * @brief Cures the pilot as if he was landed.
1075  */
pilot_healLanded(Pilot * pilot)1076 void pilot_healLanded( Pilot *pilot )
1077 {
1078    pilot->armour = pilot->armour_max;
1079    pilot->shield = pilot->shield_max;
1080    pilot->energy = pilot->energy_max;
1081 
1082    pilot->stress = 0.;
1083    pilot->stimer = 0.;
1084    pilot->sbonus = 0.;
1085 }
1086 
1087 
1088 /**
1089  * @brief Updates the pilot stats after mass change.
1090  *
1091  *    @param pilot Pilot to update his mass.
1092  */
pilot_updateMass(Pilot * pilot)1093 void pilot_updateMass( Pilot *pilot )
1094 {
1095    double mass, factor;
1096 
1097    /* Set limit. */
1098    mass = pilot->solid->mass;
1099    if ((pilot->stats.engine_limit > 0.) && (mass > pilot->stats.engine_limit))
1100       factor = pilot->stats.engine_limit / mass;
1101    else
1102       factor = 1.;
1103 
1104    pilot->thrust  = factor * pilot->thrust_base * mass;
1105    pilot->turn    = factor * pilot->turn_base;
1106    pilot->speed   = factor * pilot->speed_base;
1107 
1108 /* limit the maximum speed if limiter is active */
1109    if (pilot_isFlag(pilot, PILOT_HASSPEEDLIMIT)) {
1110       pilot->speed = pilot->speed_limit - pilot->thrust / (mass * 3.);
1111       /* Sanity: speed must never go negative. */
1112       if (pilot->speed < 0.) {
1113          /* If speed DOES go negative, we have to lower thrust. */
1114          pilot->thrust = 3 * pilot->speed_limit * mass;
1115          pilot->speed = 0.;
1116       }
1117    }
1118    /* Need to recalculate electronic warfare mass change. */
1119    pilot_ewUpdateStatic( pilot );
1120 }
1121 
1122 
1123