1 /* ShipInfoPanel.cpp
2 Copyright (c) 2014 by Michael Zahniser
3
4 Endless Sky is free software: you can redistribute it and/or modify it under the
5 terms of the GNU General Public License as published by the Free Software
6 Foundation, either version 3 of the License, or (at your option) any later version.
7
8 Endless Sky is distributed in the hope that it will be useful, but WITHOUT ANY
9 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10 PARTICULAR PURPOSE. See the GNU General Public License for more details.
11 */
12
13 #include "ShipInfoPanel.h"
14
15 #include "text/alignment.hpp"
16 #include "Command.h"
17 #include "Dialog.h"
18 #include "text/DisplayText.h"
19 #include "text/Font.h"
20 #include "text/FontSet.h"
21 #include "text/Format.h"
22 #include "GameData.h"
23 #include "Information.h"
24 #include "Interface.h"
25 #include "LineShader.h"
26 #include "LogbookPanel.h"
27 #include "Messages.h"
28 #include "MissionPanel.h"
29 #include "OutlineShader.h"
30 #include "PlayerInfo.h"
31 #include "PlayerInfoPanel.h"
32 #include "Rectangle.h"
33 #include "Ship.h"
34 #include "Sprite.h"
35 #include "SpriteShader.h"
36 #include "text/Table.h"
37 #include "text/truncate.hpp"
38 #include "UI.h"
39
40 #include <algorithm>
41
42 using namespace std;
43
44 namespace {
45 constexpr double WIDTH = 250.;
46 constexpr int COLUMN_WIDTH = static_cast<int>(WIDTH) - 20;
47 }
48
49
ShipInfoPanel(PlayerInfo & player,int index)50 ShipInfoPanel::ShipInfoPanel(PlayerInfo &player, int index)
51 : player(player), shipIt(player.Ships().begin()), canEdit(player.GetPlanet())
52 {
53 SetInterruptible(false);
54
55 // If a valid ship index was given, show that ship.
56 if(static_cast<unsigned>(index) < player.Ships().size())
57 shipIt += index;
58 else if(player.Flagship())
59 {
60 // Find the player's flagship. It may not be first in the list, if the
61 // first item in the list cannot be a flagship.
62 while(shipIt != player.Ships().end() && shipIt->get() != player.Flagship())
63 ++shipIt;
64 }
65
66 UpdateInfo();
67 }
68
69
70
Step()71 void ShipInfoPanel::Step ()
72 {
73 DoHelp("ship info");
74 }
75
76
77
Draw()78 void ShipInfoPanel::Draw()
79 {
80 // Dim everything behind this panel.
81 DrawBackdrop();
82
83 // Fill in the information for how this interface should be drawn.
84 Information interfaceInfo;
85 interfaceInfo.SetCondition("ship tab");
86 if(canEdit && (shipIt != player.Ships().end())
87 && (shipIt->get() != player.Flagship() || (*shipIt)->IsParked()))
88 {
89 if(!(*shipIt)->IsDisabled())
90 interfaceInfo.SetCondition("can park");
91 interfaceInfo.SetCondition((*shipIt)->IsParked() ? "show unpark" : "show park");
92 interfaceInfo.SetCondition("show disown");
93 }
94 else if(!canEdit)
95 {
96 interfaceInfo.SetCondition("show dump");
97 if(CanDump())
98 interfaceInfo.SetCondition("enable dump");
99 }
100 if(player.Ships().size() > 1)
101 interfaceInfo.SetCondition("five buttons");
102 else
103 interfaceInfo.SetCondition("three buttons");
104 if(player.HasLogs())
105 interfaceInfo.SetCondition("enable logbook");
106
107 // Draw the interface.
108 const Interface *infoPanelUi = GameData::Interfaces().Get("info panel");
109 infoPanelUi->Draw(interfaceInfo, this);
110
111 // Draw all the different information sections.
112 ClearZones();
113 Rectangle cargoBounds = infoPanelUi->GetBox("cargo");
114 DrawShipStats(infoPanelUi->GetBox("stats"));
115 DrawOutfits(infoPanelUi->GetBox("outfits"), cargoBounds);
116 DrawWeapons(infoPanelUi->GetBox("weapons"));
117 DrawCargo(cargoBounds);
118
119 // If the player hovers their mouse over a ship attribute, show its tooltip.
120 info.DrawTooltips();
121 }
122
123
124
KeyDown(SDL_Keycode key,Uint16 mod,const Command & command,bool isNewPress)125 bool ShipInfoPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command, bool isNewPress)
126 {
127 bool shift = (mod & KMOD_SHIFT);
128 if(key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
129 GetUI()->Pop(this);
130 else if(!player.Ships().empty() && ((key == 'p' && !shift) || key == SDLK_LEFT || key == SDLK_UP))
131 {
132 if(shipIt == player.Ships().begin())
133 shipIt = player.Ships().end();
134 --shipIt;
135 UpdateInfo();
136 }
137 else if(!player.Ships().empty() && (key == 'n' || key == SDLK_RIGHT || key == SDLK_DOWN))
138 {
139 ++shipIt;
140 if(shipIt == player.Ships().end())
141 shipIt = player.Ships().begin();
142 UpdateInfo();
143 }
144 else if(key == 'i' || command.Has(Command::INFO))
145 {
146 GetUI()->Pop(this);
147 GetUI()->Push(new PlayerInfoPanel(player));
148 }
149 else if(key == 'R' || (key == 'r' && shift))
150 GetUI()->Push(new Dialog(this, &ShipInfoPanel::Rename, "Change this ship's name?", (*shipIt)->Name()));
151 else if(canEdit && (key == 'P' || (key == 'p' && shift)))
152 {
153 if(shipIt->get() != player.Flagship() || (*shipIt)->IsParked())
154 player.ParkShip(shipIt->get(), !(*shipIt)->IsParked());
155 }
156 else if(canEdit && key == 'D')
157 {
158 if(shipIt->get() != player.Flagship())
159 GetUI()->Push(new Dialog(this, &ShipInfoPanel::Disown, "Are you sure you want to disown \""
160 + shipIt->get()->Name()
161 + "\"? Disowning a ship rather than selling it means you will not get any money for it."));
162 }
163 else if(key == 'c' && CanDump())
164 {
165 int commodities = (*shipIt)->Cargo().CommoditiesSize();
166 int amount = (*shipIt)->Cargo().Get(selectedCommodity);
167 int plunderAmount = (*shipIt)->Cargo().Get(selectedPlunder);
168 if(amount)
169 {
170 GetUI()->Push(new Dialog(this, &ShipInfoPanel::DumpCommodities,
171 "How many tons of " + Format::LowerCase(selectedCommodity)
172 + " do you want to jettison?", amount));
173 }
174 else if(plunderAmount > 0 && selectedPlunder->Get("installable") < 0.)
175 {
176 GetUI()->Push(new Dialog(this, &ShipInfoPanel::DumpPlunder,
177 "How many tons of " + Format::LowerCase(selectedPlunder->Name())
178 + " do you want to jettison?", plunderAmount));
179 }
180 else if(plunderAmount == 1)
181 {
182 GetUI()->Push(new Dialog(this, &ShipInfoPanel::Dump,
183 "Are you sure you want to jettison a " + selectedPlunder->Name() + "?"));
184 }
185 else if(plunderAmount > 1)
186 {
187 GetUI()->Push(new Dialog(this, &ShipInfoPanel::DumpPlunder,
188 "How many " + selectedPlunder->PluralName() + " do you want to jettison?",
189 plunderAmount));
190 }
191 else if(commodities)
192 {
193 GetUI()->Push(new Dialog(this, &ShipInfoPanel::Dump,
194 "Are you sure you want to jettison all of this ship's regular cargo?"));
195 }
196 else
197 {
198 GetUI()->Push(new Dialog(this, &ShipInfoPanel::Dump,
199 "Are you sure you want to jettison all of this ship's cargo?"));
200 }
201 }
202 else if(command.Has(Command::MAP) || key == 'm')
203 GetUI()->Push(new MissionPanel(player));
204 else if(key == 'l' && player.HasLogs())
205 GetUI()->Push(new LogbookPanel(player));
206 else
207 return false;
208
209 return true;
210 }
211
212
213
Click(int x,int y,int clicks)214 bool ShipInfoPanel::Click(int x, int y, int clicks)
215 {
216 if(shipIt == player.Ships().end())
217 return true;
218
219 draggingIndex = -1;
220 if(canEdit && hoverIndex >= 0 && (**shipIt).GetSystem() == player.GetSystem() && !(**shipIt).IsDisabled())
221 draggingIndex = hoverIndex;
222
223 selectedCommodity.clear();
224 selectedPlunder = nullptr;
225 Point point(x, y);
226 for(const auto &zone : commodityZones)
227 if(zone.Contains(point))
228 selectedCommodity = zone.Value();
229 for(const auto &zone : plunderZones)
230 if(zone.Contains(point))
231 selectedPlunder = zone.Value();
232
233 return true;
234 }
235
236
237
Hover(int x,int y)238 bool ShipInfoPanel::Hover(int x, int y)
239 {
240 Point point(x, y);
241 info.Hover(point);
242 return Hover(point);
243 }
244
245
246
Drag(double dx,double dy)247 bool ShipInfoPanel::Drag(double dx, double dy)
248 {
249 return Hover(hoverPoint + Point(dx, dy));
250 }
251
252
253
Release(int x,int y)254 bool ShipInfoPanel::Release(int x, int y)
255 {
256 if(draggingIndex >= 0 && hoverIndex >= 0 && hoverIndex != draggingIndex)
257 (**shipIt).GetArmament().Swap(hoverIndex, draggingIndex);
258
259 draggingIndex = -1;
260 return true;
261 }
262
263
264
UpdateInfo()265 void ShipInfoPanel::UpdateInfo()
266 {
267 draggingIndex = -1;
268 hoverIndex = -1;
269 ClearZones();
270 if(shipIt == player.Ships().end())
271 return;
272
273 const Ship &ship = **shipIt;
274 info.Update(ship, player.FleetDepreciation(), player.GetDate().DaysSinceEpoch());
275 if(player.Flagship() && ship.GetSystem() == player.GetSystem() && &ship != player.Flagship())
276 player.Flagship()->SetTargetShip(*shipIt);
277
278 outfits.clear();
279 for(const auto &it : ship.Outfits())
280 outfits[it.first->Category()].push_back(it.first);
281 }
282
283
284
ClearZones()285 void ShipInfoPanel::ClearZones()
286 {
287 zones.clear();
288 commodityZones.clear();
289 plunderZones.clear();
290 }
291
292
293
DrawShipStats(const Rectangle & bounds)294 void ShipInfoPanel::DrawShipStats(const Rectangle &bounds)
295 {
296 // Check that the specified area is big enough.
297 if(bounds.Width() < WIDTH)
298 return;
299
300 // Colors to draw with.
301 Color dim = *GameData::Colors().Get("medium");
302 Color bright = *GameData::Colors().Get("bright");
303 const Ship &ship = **shipIt;
304
305 // Two columns of opposite alignment are used to simulate a single visual column.
306 Table table;
307 table.AddColumn(0, {COLUMN_WIDTH, Alignment::LEFT});
308 table.AddColumn(COLUMN_WIDTH, {COLUMN_WIDTH, Alignment::RIGHT, Truncate::MIDDLE});
309 table.SetUnderline(0, COLUMN_WIDTH);
310 table.DrawAt(bounds.TopLeft() + Point(10., 8.));
311
312 table.DrawTruncatedPair("ship:", dim, ship.Name(), bright, Truncate::MIDDLE, true);
313 table.DrawTruncatedPair("model:", dim, ship.ModelName(), bright, Truncate::MIDDLE, true);
314
315 info.DrawAttributes(table.GetRowBounds().TopLeft() - Point(10., 10.));
316 }
317
318
319
DrawOutfits(const Rectangle & bounds,Rectangle & cargoBounds)320 void ShipInfoPanel::DrawOutfits(const Rectangle &bounds, Rectangle &cargoBounds)
321 {
322 // Check that the specified area is big enough.
323 if(bounds.Width() < WIDTH)
324 return;
325
326 // Colors to draw with.
327 Color dim = *GameData::Colors().Get("medium");
328 Color bright = *GameData::Colors().Get("bright");
329 const Ship &ship = **shipIt;
330
331 // Two columns of opposite alignment are used to simulate a single visual column.
332 Table table;
333 table.AddColumn(0, {COLUMN_WIDTH, Alignment::LEFT});
334 table.AddColumn(COLUMN_WIDTH, {COLUMN_WIDTH, Alignment::RIGHT});
335 table.SetUnderline(0, COLUMN_WIDTH);
336 Point start = bounds.TopLeft() + Point(10., 8.);
337 table.DrawAt(start);
338
339 // Draw the outfits in the same order used in the outfitter.
340 for(const string &category : Outfit::CATEGORIES)
341 {
342 auto it = outfits.find(category);
343 if(it == outfits.end())
344 continue;
345
346 // Skip to the next column if there is not space for this category label
347 // plus at least one outfit.
348 if(table.GetRowBounds().Bottom() + 40. > bounds.Bottom())
349 {
350 start += Point(WIDTH, 0.);
351 if(start.X() + COLUMN_WIDTH > bounds.Right())
352 break;
353 table.DrawAt(start);
354 }
355
356 // Draw the category label.
357 table.Draw(category, bright);
358 table.Advance();
359 for(const Outfit *outfit : it->second)
360 {
361 // Check if we've gone below the bottom of the bounds.
362 if(table.GetRowBounds().Bottom() > bounds.Bottom())
363 {
364 start += Point(WIDTH, 0.);
365 if(start.X() + COLUMN_WIDTH > bounds.Right())
366 break;
367 table.DrawAt(start);
368 table.Draw(category, bright);
369 table.Advance();
370 }
371
372 // Draw the outfit name and count.
373 table.DrawTruncatedPair(outfit->Name(), dim,
374 to_string(ship.OutfitCount(outfit)), bright, Truncate::BACK, false);
375 }
376 // Add an extra gap in between categories.
377 table.DrawGap(10.);
378 }
379
380 // Check if this information spilled over into the cargo column.
381 if(table.GetPoint().X() >= cargoBounds.Left())
382 {
383 double startY = table.GetRowBounds().Top() - 8.;
384 cargoBounds = Rectangle::WithCorners(
385 Point(cargoBounds.Left(), startY),
386 Point(cargoBounds.Right(), max(startY, cargoBounds.Bottom())));
387 }
388 }
389
390
391
DrawWeapons(const Rectangle & bounds)392 void ShipInfoPanel::DrawWeapons(const Rectangle &bounds)
393 {
394 // Colors to draw with.
395 Color dim = *GameData::Colors().Get("medium");
396 Color bright = *GameData::Colors().Get("bright");
397 const Font &font = FontSet::Get(14);
398 const Ship &ship = **shipIt;
399
400 // Figure out how much to scale the sprite by.
401 const Sprite *sprite = ship.GetSprite();
402 double scale = 0.;
403 if(sprite)
404 scale = min(1., min((WIDTH - 10) / sprite->Width(), (WIDTH - 10) / sprite->Height()));
405
406 // Figure out the left- and right-most hardpoints on the ship. If they are
407 // too far apart, the scale may need to be reduced.
408 // Also figure out how many weapons of each type are on each side.
409 double maxX = 0.;
410 int count[2][2] = {{0, 0}, {0, 0}};
411 for(const Hardpoint &hardpoint : ship.Weapons())
412 {
413 // Multiply hardpoint X by 2 to convert to sprite pixels.
414 maxX = max(maxX, fabs(2. * hardpoint.GetPoint().X()));
415 ++count[hardpoint.GetPoint().X() >= 0.][hardpoint.IsTurret()];
416 }
417 // If necessary, shrink the sprite to keep the hardpoints inside the labels.
418 // The width of this UI block will be 2 * (LABEL_WIDTH + HARDPOINT_DX).
419 static const double LABEL_WIDTH = 150.;
420 static const double LABEL_DX = 95.;
421 static const double LABEL_PAD = 5.;
422 if(maxX > (LABEL_DX - LABEL_PAD))
423 scale = min(scale, (LABEL_DX - LABEL_PAD) / (2. * maxX));
424
425 // Draw the ship, using the black silhouette swizzle.
426 SpriteShader::Draw(sprite, bounds.Center(), scale, 8);
427 OutlineShader::Draw(sprite, bounds.Center(), scale * Point(sprite->Width(), sprite->Height()), Color(.5f));
428
429 // Figure out how tall each part of the weapon listing will be.
430 int gunRows = max(count[0][0], count[1][0]);
431 int turretRows = max(count[0][1], count[1][1]);
432 // If there are both guns and turrets, add a gap of ten pixels.
433 double height = 20. * (gunRows + turretRows) + 10. * (gunRows && turretRows);
434
435 double gunY = bounds.Top() + .5 * (bounds.Height() - height);
436 double turretY = gunY + 20. * gunRows + 10. * (gunRows != 0);
437 double nextY[2][2] = {
438 {gunY + 20. * (gunRows - count[0][0]), turretY + 20. * (turretRows - count[0][1])},
439 {gunY + 20. * (gunRows - count[1][0]), turretY + 20. * (turretRows - count[1][1])}};
440
441 int index = 0;
442 const double centerX = bounds.Center().X();
443 const double labelCenter[2] = {-.5 * LABEL_WIDTH - LABEL_DX, LABEL_DX + .5 * LABEL_WIDTH};
444 const double fromX[2] = {-LABEL_DX + LABEL_PAD, LABEL_DX - LABEL_PAD};
445 static const double LINE_HEIGHT = 20.;
446 static const double TEXT_OFF = .5 * (LINE_HEIGHT - font.Height());
447 static const Point LINE_SIZE(LABEL_WIDTH, LINE_HEIGHT);
448 Point topFrom;
449 Point topTo;
450 Color topColor;
451 bool hasTop = false;
452 auto layout = Layout(static_cast<int>(LABEL_WIDTH), Truncate::BACK);
453 for(const Hardpoint &hardpoint : ship.Weapons())
454 {
455 string name = "[empty]";
456 if(hardpoint.GetOutfit())
457 name = hardpoint.GetOutfit()->Name();
458
459 bool isRight = (hardpoint.GetPoint().X() >= 0.);
460 bool isTurret = hardpoint.IsTurret();
461
462 double &y = nextY[isRight][isTurret];
463 double x = centerX + (isRight ? LABEL_DX : -LABEL_DX - LABEL_WIDTH);
464 bool isHover = (index == hoverIndex);
465 layout.align = isRight ? Alignment::LEFT : Alignment::RIGHT;
466 font.Draw({name, layout}, Point(x, y + TEXT_OFF), isHover ? bright : dim);
467 Point zoneCenter(labelCenter[isRight], y + .5 * LINE_HEIGHT);
468 zones.emplace_back(zoneCenter, LINE_SIZE, index);
469
470 // Determine what color to use for the line.
471 float high = (index == hoverIndex ? .8f : .5f);
472 Color color(high, .75f * high, 0.f, 1.f);
473 if(isTurret)
474 color = Color(0.f, .75f * high, high, 1.f);
475
476 // Draw the line.
477 Point from(fromX[isRight], zoneCenter.Y());
478 Point to = bounds.Center() + (2. * scale) * hardpoint.GetPoint();
479 DrawLine(from, to, color);
480 if(isHover)
481 {
482 topFrom = from;
483 topTo = to;
484 topColor = color;
485 hasTop = true;
486 }
487
488 y += LINE_HEIGHT;
489 ++index;
490 }
491 // Make sure the line for whatever hardpoint we're hovering is always on top.
492 if(hasTop)
493 DrawLine(topFrom, topTo, topColor);
494
495 // Re-positioning weapons.
496 if(draggingIndex >= 0)
497 {
498 const Outfit *outfit = ship.Weapons()[draggingIndex].GetOutfit();
499 string name = outfit ? outfit->Name() : "[empty]";
500 Point pos(hoverPoint.X() - .5 * font.Width(name), hoverPoint.Y());
501 font.Draw(name, pos + Point(1., 1.), Color(0., 1.));
502 font.Draw(name, pos, bright);
503 }
504 }
505
506
507
DrawCargo(const Rectangle & bounds)508 void ShipInfoPanel::DrawCargo(const Rectangle &bounds)
509 {
510 Color dim = *GameData::Colors().Get("medium");
511 Color bright = *GameData::Colors().Get("bright");
512 Color backColor = *GameData::Colors().Get("faint");
513 const Ship &ship = **shipIt;
514
515 // Cargo list.
516 const CargoHold &cargo = (player.Cargo().Used() ? player.Cargo() : ship.Cargo());
517 Table table;
518 table.AddColumn(0, {COLUMN_WIDTH, Alignment::LEFT});
519 table.AddColumn(COLUMN_WIDTH, {COLUMN_WIDTH, Alignment::RIGHT});
520 table.SetUnderline(-5, COLUMN_WIDTH + 5);
521 table.DrawAt(bounds.TopLeft() + Point(10., 8.));
522
523 double endY = bounds.Bottom() - 30. * (cargo.Passengers() != 0);
524 bool hasSpace = (table.GetRowBounds().Bottom() < endY);
525 if((cargo.CommoditiesSize() || cargo.HasOutfits() || cargo.MissionCargoSize()) && hasSpace)
526 {
527 table.Draw("Cargo", bright);
528 table.Advance();
529 hasSpace = (table.GetRowBounds().Bottom() < endY);
530 }
531 if(cargo.CommoditiesSize() && hasSpace)
532 {
533 for(const auto &it : cargo.Commodities())
534 {
535 if(!it.second)
536 continue;
537
538 commodityZones.emplace_back(table.GetCenterPoint(), table.GetRowSize(), it.first);
539 if(it.first == selectedCommodity)
540 table.DrawHighlight(backColor);
541
542 table.Draw(it.first, dim);
543 table.Draw(to_string(it.second), bright);
544
545 // Truncate the list if there is not enough space.
546 if(table.GetRowBounds().Bottom() >= endY)
547 {
548 hasSpace = false;
549 break;
550 }
551 }
552 table.DrawGap(10.);
553 }
554 if(cargo.HasOutfits() && hasSpace)
555 {
556 for(const auto &it : cargo.Outfits())
557 {
558 if(!it.second)
559 continue;
560
561 plunderZones.emplace_back(table.GetCenterPoint(), table.GetRowSize(), it.first);
562 if(it.first == selectedPlunder)
563 table.DrawHighlight(backColor);
564
565 // For outfits, show how many of them you have and their total mass.
566 bool isSingular = (it.second == 1 || it.first->Get("installable") < 0.);
567 string name = (isSingular ? it.first->Name() : it.first->PluralName());
568 if(!isSingular)
569 name += " (" + to_string(it.second) + "x)";
570 table.Draw(name, dim);
571
572 double mass = it.first->Mass() * it.second;
573 table.Draw(Format::Number(mass), bright);
574
575 // Truncate the list if there is not enough space.
576 if(table.GetRowBounds().Bottom() >= endY)
577 {
578 hasSpace = false;
579 break;
580 }
581 }
582 table.DrawGap(10.);
583 }
584 if(cargo.HasMissionCargo() && hasSpace)
585 {
586 for(const auto &it : cargo.MissionCargo())
587 {
588 // Capitalize the name of the cargo.
589 table.Draw(Format::Capitalize(it.first->Cargo()), dim);
590 table.Draw(to_string(it.second), bright);
591
592 // Truncate the list if there is not enough space.
593 if(table.GetRowBounds().Bottom() >= endY)
594 break;
595 }
596 table.DrawGap(10.);
597 }
598 if(cargo.Passengers() && endY >= bounds.Top())
599 {
600 table.DrawAt(Point(bounds.Left(), endY) + Point(10., 8.));
601 table.Draw("passengers:", dim);
602 table.Draw(to_string(cargo.Passengers()), bright);
603 }
604 }
605
606
607
DrawLine(const Point & from,const Point & to,const Color & color) const608 void ShipInfoPanel::DrawLine(const Point &from, const Point &to, const Color &color) const
609 {
610 Color black(0.f, 1.f);
611 Point mid(to.X(), from.Y());
612
613 LineShader::Draw(from, mid, 3.5f, black);
614 LineShader::Draw(mid, to, 3.5f, black);
615 LineShader::Draw(from, mid, 1.5f, color);
616 LineShader::Draw(mid, to, 1.5f, color);
617 }
618
619
620
Hover(const Point & point)621 bool ShipInfoPanel::Hover(const Point &point)
622 {
623 if(shipIt == player.Ships().end())
624 return true;
625
626 hoverPoint = point;
627
628 hoverIndex = -1;
629 const vector<Hardpoint> &weapons = (**shipIt).Weapons();
630 bool dragIsTurret = (draggingIndex >= 0 && weapons[draggingIndex].IsTurret());
631 for(const auto &zone : zones)
632 {
633 bool isTurret = weapons[zone.Value()].IsTurret();
634 if(zone.Contains(hoverPoint) && (draggingIndex == -1 || isTurret == dragIsTurret))
635 hoverIndex = zone.Value();
636 }
637
638 return true;
639 }
640
641
642
Rename(const string & name)643 void ShipInfoPanel::Rename(const string &name)
644 {
645 if(shipIt != player.Ships().end() && !name.empty())
646 {
647 player.RenameShip(shipIt->get(), name);
648 UpdateInfo();
649 }
650 }
651
652
653
CanDump() const654 bool ShipInfoPanel::CanDump() const
655 {
656 if(canEdit || shipIt == player.Ships().end())
657 return false;
658
659 CargoHold &cargo = (*shipIt)->Cargo();
660 return (selectedPlunder && cargo.Get(selectedPlunder) > 0) || cargo.CommoditiesSize() || cargo.OutfitsSize();
661 }
662
663
664
Dump()665 void ShipInfoPanel::Dump()
666 {
667 if(!CanDump())
668 return;
669
670 CargoHold &cargo = (*shipIt)->Cargo();
671 int commodities = (*shipIt)->Cargo().CommoditiesSize();
672 int amount = cargo.Get(selectedCommodity);
673 int plunderAmount = cargo.Get(selectedPlunder);
674 int64_t loss = 0;
675 if(amount)
676 {
677 int64_t basis = player.GetBasis(selectedCommodity, amount);
678 loss += basis;
679 player.AdjustBasis(selectedCommodity, -basis);
680 (*shipIt)->Jettison(selectedCommodity, amount);
681 }
682 else if(plunderAmount > 0)
683 {
684 loss += plunderAmount * selectedPlunder->Cost();
685 (*shipIt)->Jettison(selectedPlunder, plunderAmount);
686 }
687 else if(commodities)
688 {
689 for(const auto &it : cargo.Commodities())
690 {
691 int64_t basis = player.GetBasis(it.first, it.second);
692 loss += basis;
693 player.AdjustBasis(it.first, -basis);
694 (*shipIt)->Jettison(it.first, it.second);
695 }
696 }
697 else
698 {
699 for(const auto &it : cargo.Outfits())
700 {
701 loss += it.first->Cost() * max(0, it.second);
702 (*shipIt)->Jettison(it.first, it.second);
703 }
704 }
705 selectedCommodity.clear();
706 selectedPlunder = nullptr;
707
708 info.Update(**shipIt, player.FleetDepreciation(), player.GetDate().DaysSinceEpoch());
709 if(loss)
710 Messages::Add("You jettisoned " + Format::Credits(loss) + " credits worth of cargo.");
711 }
712
713
714
DumpPlunder(int count)715 void ShipInfoPanel::DumpPlunder(int count)
716 {
717 int64_t loss = 0;
718 count = min(count, (*shipIt)->Cargo().Get(selectedPlunder));
719 if(count > 0)
720 {
721 loss += count * selectedPlunder->Cost();
722 (*shipIt)->Jettison(selectedPlunder, count);
723 info.Update(**shipIt, player.FleetDepreciation(), player.GetDate().DaysSinceEpoch());
724
725 if(loss)
726 Messages::Add("You jettisoned " + Format::Credits(loss) + " credits worth of cargo.");
727 }
728 }
729
730
731
DumpCommodities(int count)732 void ShipInfoPanel::DumpCommodities(int count)
733 {
734 int64_t loss = 0;
735 count = min(count, (*shipIt)->Cargo().Get(selectedCommodity));
736 if(count > 0)
737 {
738 int64_t basis = player.GetBasis(selectedCommodity, count);
739 loss += basis;
740 player.AdjustBasis(selectedCommodity, -basis);
741 (*shipIt)->Jettison(selectedCommodity, count);
742 info.Update(**shipIt, player.FleetDepreciation(), player.GetDate().DaysSinceEpoch());
743
744 if(loss)
745 Messages::Add("You jettisoned " + Format::Credits(loss) + " credits worth of cargo.");
746 }
747 }
748
749
750
Disown()751 void ShipInfoPanel::Disown()
752 {
753 // Make sure a ship really is selected.
754 if(shipIt == player.Ships().end() || shipIt->get() == player.Flagship())
755 return;
756
757 // Because you can never disown your flagship, the player's ship list will
758 // never become empty as a result of disowning a ship.
759 const Ship *ship = shipIt->get();
760 if(shipIt != player.Ships().begin())
761 --shipIt;
762
763 player.DisownShip(ship);
764 UpdateInfo();
765 }
766