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