1 /*
2  * See Licensing and Copyright notice in naev.h
3  */
4 
5 /**
6  * @file board.c
7  *
8  * @brief Deals with boarding ships.
9  */
10 
11 
12 #include "board.h"
13 
14 #include "naev.h"
15 
16 #include "log.h"
17 #include "pilot.h"
18 #include "player.h"
19 #include "toolkit.h"
20 #include "space.h"
21 #include "rng.h"
22 #include "economy.h"
23 #include "hook.h"
24 #include "damagetype.h"
25 #include "nstring.h"
26 
27 
28 #define BOARDING_WIDTH  380 /**< Boarding window width. */
29 #define BOARDING_HEIGHT 200 /**< Boarding window height. */
30 
31 #define BUTTON_WIDTH     50 /**< Boarding button width. */
32 #define BUTTON_HEIGHT    30 /**< Boarding button height. */
33 
34 
35 static int board_stopboard = 0; /**< Whether or not to unboard. */
36 static int board_boarded   = 0;
37 
38 
39 /*
40  * prototypes
41  */
42 static void board_stealCreds( unsigned int wdw, char* str );
43 static void board_stealCargo( unsigned int wdw, char* str );
44 static void board_stealFuel( unsigned int wdw, char* str );
45 static void board_stealAmmo( unsigned int wdw, char* str );
46 static int board_trySteal( Pilot *p );
47 static int board_fail( unsigned int wdw );
48 static void board_update( unsigned int wdw );
49 
50 
51 /**
52  * @brief Gets if the player is boarded.
53  */
player_isBoarded(void)54 int player_isBoarded (void)
55 {
56    return board_boarded;
57 }
58 
59 
60 /**
61  * @fn void player_board (void)
62  *
63  * @brief Attempt to board the player's target.
64  *
65  * Creates the window on success.
66  */
player_board(void)67 void player_board (void)
68 {
69    Pilot *p;
70    unsigned int wdw;
71    char c;
72    HookParam hparam[2];
73 
74    /* Not disabled. */
75    if (pilot_isDisabled(player.p))
76       return;
77 
78    if (player.p->target==PLAYER_ID) {
79       /* We don't try to find far away targets, only nearest and see if it matches.
80        * However, perhaps looking for first boardable target within a certain range
81        * could be more interesting. */
82       player_targetNearest();
83       p = pilot_get(player.p->target);
84       if ((!pilot_isDisabled(p) && !pilot_isFlag(p,PILOT_BOARDABLE)) ||
85             pilot_isFlag(p,PILOT_NOBOARD)) {
86          player_targetClear();
87          player_message("\erYou need a target to board first!");
88          return;
89       }
90    }
91    else
92       p = pilot_get(player.p->target);
93    c = pilot_getFactionColourChar( p );
94 
95    /* More checks. */
96    if (pilot_isFlag(p,PILOT_NOBOARD)) {
97       player_message("\erTarget ship can not be boarded.");
98       return;
99    }
100    else if (pilot_isFlag(p,PILOT_BOARDED)) {
101       player_message("\erYour target cannot be boarded again.");
102       return;
103    }
104    else if (!pilot_isDisabled(p) && !pilot_isFlag(p,PILOT_BOARDABLE)) {
105       player_message("\erYou cannot board a ship that isn't disabled!");
106       return;
107    }
108    else if (vect_dist(&player.p->solid->pos,&p->solid->pos) >
109          p->ship->gfx_space->sw * PILOT_SIZE_APROX) {
110       player_message("\erYou are too far away to board your target.");
111       return;
112    }
113    else if ((pow2(VX(player.p->solid->vel)-VX(p->solid->vel)) +
114             pow2(VY(player.p->solid->vel)-VY(p->solid->vel))) >
115          (double)pow2(MAX_HYPERSPACE_VEL)) {
116       player_message("\erYou are going too fast to board the ship.");
117       return;
118    }
119    /* We'll recover it if it's the pilot's ex-escort. */
120    else if (p->parent == PLAYER_ID) {
121       /* Try to recover. */
122       pilot_dock( p, player.p, 0 );
123       if (pilot_isFlag(p, PILOT_DELETE )) { /* Hack to see if it boarded. */
124          player_message("\epYou recover \eg%s\ep into your fighter bay.", p->name);
125          return;
126       }
127    }
128 
129    /* Is boarded. */
130    board_boarded = 1;
131 
132    /* pilot will be boarded */
133    pilot_setFlag(p,PILOT_BOARDED);
134    player_message("\epBoarding ship \e%c%s\e0.", c, p->name);
135 
136    /* Don't unboard. */
137    board_stopboard = 0;
138 
139    /*
140     * run hook if needed
141     */
142    hparam[0].type       = HOOK_PARAM_PILOT;
143    hparam[0].u.lp       = p->id;
144    hparam[1].type       = HOOK_PARAM_SENTINEL;
145    hooks_runParam( "board", hparam );
146    pilot_runHookParam(p, PILOT_HOOK_BOARD, hparam, 1);
147    hparam[0].u.lp       = PLAYER_ID;
148    pilot_runHookParam(p, PILOT_HOOK_BOARDING, hparam, 1);
149 
150    if (board_stopboard) {
151       board_boarded = 0;
152       return;
153    }
154 
155    /*
156     * create the boarding window
157     */
158    wdw = window_create( "Boarding", -1, -1, BOARDING_WIDTH, BOARDING_HEIGHT );
159 
160    window_addText( wdw, 20, -30, 120, 60,
161          0, "txtCargo", &gl_smallFont, &cDConsole,
162          "Credits:\n"
163          "Cargo:\n"
164          "Fuel:\n"
165          "Ammo:\n"
166          );
167    window_addText( wdw, 80, -30, 120, 60,
168          0, "txtData", &gl_smallFont, &cBlack, NULL );
169 
170    window_addButton( wdw, 20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
171          "btnStealCredits", "Credits", board_stealCreds);
172    window_addButton( wdw, 20+BUTTON_WIDTH+20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
173          "btnStealCargo", "Cargo", board_stealCargo);
174    window_addButton( wdw, 20+2*(BUTTON_WIDTH+20), 20, BUTTON_WIDTH, BUTTON_HEIGHT,
175          "btnStealFuel", "Fuel", board_stealFuel);
176    window_addButton( wdw, 20+3*(BUTTON_WIDTH+20), 20, BUTTON_WIDTH, BUTTON_HEIGHT,
177          "btnStealAmmo", "Ammo", board_stealAmmo);
178    window_addButton( wdw, -20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
179          "btnBoardingClose", "Leave", board_exit );
180 
181    board_update(wdw);
182 }
183 
184 
185 /**
186  * @brief Forces unboarding of the pilot.
187  */
board_unboard(void)188 void board_unboard (void)
189 {
190    board_stopboard = 1;
191 }
192 
193 
194 /**
195  * @brief Closes the boarding window.
196  *
197  *    @param wdw Window triggering the function.
198  *    @param str Unused.
199  */
board_exit(unsigned int wdw,char * str)200 void board_exit( unsigned int wdw, char* str )
201 {
202    (void) str;
203    window_destroy( wdw );
204 
205    /* Is not boarded. */
206    board_boarded = 0;
207 }
208 
209 
210 /**
211  * @brief Attempt to steal the boarded ship's credits.
212  *
213  *    @param wdw Window triggering the function.
214  *    @param str Unused.
215  */
board_stealCreds(unsigned int wdw,char * str)216 static void board_stealCreds( unsigned int wdw, char* str )
217 {
218    (void)str;
219    Pilot* p;
220 
221    p = pilot_get(player.p->target);
222 
223    if (p->credits==0) { /* you can't steal from the poor */
224       player_message("\epThe ship has no credits.");
225       return;
226    }
227 
228    if (board_fail(wdw)) return;
229 
230    player_modCredits( p->credits );
231    p->credits = 0;
232    board_update( wdw ); /* update the lack of credits */
233    player_message("\epYou manage to steal the ship's credits.");
234 }
235 
236 
237 /**
238  * @brief Attempt to steal the boarded ship's cargo.
239  *
240  *    @param wdw Window triggering the function.
241  *    @param str Unused.
242  */
board_stealCargo(unsigned int wdw,char * str)243 static void board_stealCargo( unsigned int wdw, char* str )
244 {
245    (void)str;
246    int q;
247    Pilot* p;
248 
249    p = pilot_get(player.p->target);
250 
251    if (p->ncommodities==0) { /* no cargo */
252       player_message("\epThe ship has no cargo.");
253       return;
254    }
255    else if (pilot_cargoFree(player.p) <= 0) {
256       player_message("\erYou have no room for the ship's cargo.");
257       return;
258    }
259 
260    if (board_fail(wdw)) return;
261 
262    /** steal as much as possible until full - @todo let player choose */
263    q = 1;
264    while ((p->ncommodities > 0) && (q!=0)) {
265       q = pilot_cargoAdd( player.p, p->commodities[0].commodity,
266             p->commodities[0].quantity, 0 );
267       pilot_cargoRm( p, p->commodities[0].commodity, q );
268    }
269 
270    board_update( wdw );
271    player_message("\epYou manage to steal the ship's cargo.");
272 }
273 
274 
275 /**
276  * @brief Attempt to steal the boarded ship's fuel.
277  *
278  *    @param wdw Window triggering the function.
279  *    @param str Unused.
280  */
board_stealFuel(unsigned int wdw,char * str)281 static void board_stealFuel( unsigned int wdw, char* str )
282 {
283    (void)str;
284    Pilot* p;
285 
286    p = pilot_get(player.p->target);
287 
288    if (p->fuel <= 0.) { /* no fuel. */
289       player_message("\epThe ship has no fuel.");
290       return;
291    }
292    else if (player.p->fuel == player.p->fuel_max) {
293       player_message("\erYour ship is at maximum fuel capacity.");
294       return;
295    }
296 
297    if (board_fail(wdw))
298       return;
299 
300    /* Steal fuel. */
301    player.p->fuel += p->fuel;
302    p->fuel = 0.;
303 
304    /* Make sure doesn't overflow. */
305    if (player.p->fuel > player.p->fuel_max) {
306       p->fuel      = player.p->fuel - player.p->fuel_max;
307       player.p->fuel = player.p->fuel_max;
308    }
309 
310    board_update( wdw );
311    player_message("\epYou manage to steal the ship's fuel.");
312 }
313 
314 
315 /**
316  * @brief Attempt to steal the boarded ship's ammo.
317  *
318  *    @param wdw Window triggering the function.
319  *    @param str Unused.
320  */
board_stealAmmo(unsigned int wdw,char * str)321 static void board_stealAmmo( unsigned int wdw, char* str )
322 {
323      Pilot* p;
324      int nreloaded, i, nammo, x;
325      PilotOutfitSlot *target_outfit_slot, *player_outfit_slot;
326      Outfit *target_outfit, *ammo, *player_outfit;
327      (void)str;
328      nreloaded = 0;
329      p = pilot_get(player.p->target);
330      /* Target has no ammo */
331      if (pilot_countAmmo(p) <= 0) {
332         player_message("\erThe ship has no ammo.");
333         return;
334      }
335      /* Player is already at max ammo */
336      if (pilot_countAmmo(player.p) >= pilot_maxAmmo(player.p)) {
337         player_message("\erYou are already at max ammo.");
338         return;
339      }
340      if (board_fail(wdw))
341         return;
342      /* Steal the ammo */
343      for (i=0; i<p->noutfits; i++) {
344         target_outfit_slot = p->outfits[i];
345         if (target_outfit_slot == NULL)
346            continue;
347         target_outfit = target_outfit_slot->outfit;
348         if (target_outfit == NULL)
349            continue;
350         /* outfit isn't a launcher */
351         if (!outfit_isLauncher(target_outfit)) {
352            continue;
353         }
354         nammo = target_outfit_slot->u.ammo.quantity;
355         ammo = target_outfit_slot->u.ammo.outfit;
356         /* launcher has no ammo */
357         if (ammo == NULL)
358            continue;
359         if (nammo <= 0) {
360            continue;
361         }
362         for (x=0; x<player.p->noutfits; x++) {
363            int nadded = 0;
364            player_outfit_slot = player.p->outfits[x];
365            if (player_outfit_slot == NULL)
366               continue;
367            player_outfit = player_outfit_slot->outfit;
368            if (player_outfit == NULL)
369               continue;
370            if (!outfit_isLauncher(player_outfit)) {
371               continue;
372            }
373            if (strcmp(ammo->name, player_outfit_slot->u.ammo.outfit->name) != 0) {
374               continue;
375            }
376            /* outfit's ammo matches; try to add to player and remove from target */
377            nadded = pilot_addAmmo(player.p, player_outfit_slot, ammo, nammo);
378            nammo -= nadded;
379            pilot_rmAmmo(p, target_outfit_slot, nadded);
380            nreloaded += nadded;
381            if (nadded > 0)
382               player_message("\epYou looted %d %s(s)", nadded, ammo->name);
383            if (nammo <= 0) {
384               break;
385            }
386         }
387         if (nammo <= 0) {
388            continue;
389         }
390      }
391      if (nreloaded <= 0)
392         player_message("\erThere is no ammo compatible with your launchers on board.");
393      pilot_updateMass(player.p);
394      pilot_weaponSane(player.p);
395      pilot_updateMass(p);
396      pilot_weaponSane(p);
397      board_update(wdw);
398 }
399 
400 
401 /**
402  * @brief Checks to see if the pilot can steal from its target.
403  *
404  *    @param p Pilot stealing from its target.
405  *    @return 0 if successful, 1 if fails, -1 if fails and kills target.
406  */
board_trySteal(Pilot * p)407 static int board_trySteal( Pilot *p )
408 {
409    Pilot *target;
410    Damage dmg;
411 
412    /* Get the target. */
413    target = pilot_get(p->target);
414    if (target == NULL)
415       return 1;
416 
417    /* See if was successful. */
418    if (RNGF() > (0.5 * (10. + target->crew)/(10. + p->crew)))
419       return 0;
420 
421    /* Triggered self destruct. */
422    if (RNGF() < 0.4) {
423       /* Don't actually kill. */
424       target->shield = 0.;
425       target->armour = 1.;
426       /* This will make the boarding ship take the possible faction hit. */
427       dmg.type        = dtype_get("normal");
428       dmg.damage      = 100.;
429       dmg.penetration = 1.;
430       dmg.disable     = 0.;
431       pilot_hit( target, NULL, p->id, &dmg, 1 );
432       /* Return ship dead. */
433       return -1;
434    }
435 
436    return 1;
437 }
438 
439 
440 /**
441  * @brief Checks to see if the hijack attempt failed.
442  *
443  *    @return 1 on failure to board, otherwise 0.
444  */
board_fail(unsigned int wdw)445 static int board_fail( unsigned int wdw )
446 {
447    int ret;
448 
449    ret = board_trySteal( player.p );
450 
451    if (ret == 0)
452       return 0;
453    else if (ret < 0) /* killed ship. */
454       player_message("\epYou have tripped the ship's self-destruct mechanism!");
455    else /* you just got locked out */
456       player_message("\epThe ship's security system locks %s out.",
457             (player.p->ship->crew > 0) ? "your crew" : "you" );
458 
459    board_exit( wdw, NULL);
460    return 1;
461 }
462 
463 
464 /**
465  * @brief Updates the boarding window fields.
466  *
467  *    @param wdw Window to update.
468  */
board_update(unsigned int wdw)469 static void board_update( unsigned int wdw )
470 {
471    int i, j;
472    char str[PATH_MAX];
473    char cred[ECON_CRED_STRLEN];
474    Pilot* p;
475 
476    p = pilot_get(player.p->target);
477    j = 0;
478 
479    /* Credits. */
480    credits2str( cred, p->credits, 2 );
481    j += nsnprintf( &str[j], PATH_MAX, "%s\n", cred );
482 
483    /* Commodities. */
484    if ((p->ncommodities==0) && (j < PATH_MAX))
485       j += snprintf( &str[j], PATH_MAX-j, "none\n" );
486    else {
487      for (i=0; i<p->ncommodities; i++) {
488          if (j > PATH_MAX)
489             break;
490          if (p->commodities[i].commodity == NULL)
491             continue;
492          j += snprintf( &str[j], PATH_MAX-j, "%d %s\n",
493                         p->commodities[i].quantity, p->commodities[i].commodity->name );
494      }
495    }
496 
497    /* Fuel. */
498    if (p->fuel <= 0.) {
499       if (j < PATH_MAX)
500          j += snprintf( &str[j], PATH_MAX-j, "none\n" );
501    }
502    else {
503       if (j < PATH_MAX)
504          j += snprintf( &str[j], PATH_MAX-j, "%.0f Units\n", p->fuel );
505    }
506 
507    /* Missiles */
508    int nmissiles = pilot_countAmmo(p);
509    if (nmissiles <= 0) {
510       if (j < PATH_MAX)
511         j += snprintf( &str[j], PATH_MAX-j, "none\n" );
512    }
513    else {
514       if (j < PATH_MAX)
515         j += snprintf( &str[j], PATH_MAX-j, "%d missiles\n", nmissiles );
516    }
517 
518    window_modifyText( wdw, "txtData", str );
519 }
520 
521 
522 /**
523  * @brief Has a pilot attempt to board another pilot.
524  *
525  *    @param p Pilot doing the boarding.
526  *    @return 1 if target was boarded.
527  */
pilot_board(Pilot * p)528 int pilot_board( Pilot *p )
529 {
530    Pilot *target;
531    HookParam hparam[2];
532 
533    /* Make sure target is sane. */
534    target = pilot_get(p->target);
535    if (target == NULL) {
536       DEBUG("NO TARGET");
537       return 0;
538    }
539 
540    /* Check if can board. */
541    if (!pilot_isDisabled(target))
542       return 0;
543    else if (vect_dist(&p->solid->pos, &target->solid->pos) >
544          target->ship->gfx_space->sw * PILOT_SIZE_APROX )
545       return 0;
546    else if ((pow2(VX(p->solid->vel)-VX(target->solid->vel)) +
547             pow2(VY(p->solid->vel)-VY(target->solid->vel))) >
548             (double)pow2(MAX_HYPERSPACE_VEL))
549       return 0;
550    else if (pilot_isFlag(target,PILOT_BOARDED))
551       return 0;
552 
553    /* Set the boarding flag. */
554    pilot_setFlag(target, PILOT_BOARDED);
555    pilot_setFlag(p, PILOT_BOARDING);
556 
557    /* Set time it takes to board. */
558    p->ptimer = 3.;
559 
560    /* Run pilot board hook. */
561    hparam[0].type       = HOOK_PARAM_PILOT;
562    hparam[0].u.lp       = p->id;
563    hparam[1].type       = HOOK_PARAM_SENTINEL;
564    pilot_runHookParam(target, PILOT_HOOK_BOARDING, hparam, 1);
565    hparam[0].u.lp       = target->id;
566    pilot_runHookParam(target, PILOT_HOOK_BOARD, hparam, 1);
567 
568    return 1;
569 }
570 
571 
572 /**
573  * @brief Finishes the boarding.
574  *
575  *    @param p Pilot to finish the boarding.
576  */
pilot_boardComplete(Pilot * p)577 void pilot_boardComplete( Pilot *p )
578 {
579    int ret;
580    Pilot *target;
581    credits_t worth;
582    char creds[ ECON_CRED_STRLEN ];
583 
584    /* Make sure target is sane. */
585    target = pilot_get(p->target);
586    if (target == NULL)
587       return;
588 
589    /* In the case of the player take fewer credits. */
590    if (pilot_isPlayer(target)) {
591       worth = MIN( 0.1*pilot_worth(target), target->credits );
592       p->credits       += worth;
593       target->credits  -= worth;
594       credits2str( creds, worth, 2 );
595       player_message( "\e%c%s\e0 has plundered %s credits from your ship!",
596             pilot_getFactionColourChar(p), p->name, creds );
597    }
598    else {
599       /* Steal stuff, we only do credits for now. */
600       ret = board_trySteal(p);
601       if (ret == 0) {
602          /* Normally just plunder it all. */
603          p->credits += target->credits;
604          target->credits = 0.;
605       }
606    }
607 
608    /* Finish the boarding. */
609    pilot_rmFlag(p, PILOT_BOARDING);
610 }
611 
612 
613