1 #include "MapWnd.h"
2 
3 #include "CensusBrowseWnd.h"
4 #include "ResourceBrowseWnd.h"
5 #include "ChatWnd.h"
6 #include "PlayerListWnd.h"
7 #include "ClientUI.h"
8 #include "CUIControls.h"
9 #include "CUIDrawUtil.h"
10 #include "CombatReport/CombatReportWnd.h"
11 #include "FleetButton.h"
12 #include "FleetWnd.h"
13 #include "InGameMenu.h"
14 #include "DesignWnd.h"
15 #include "ProductionWnd.h"
16 #include "ResearchWnd.h"
17 #include "EncyclopediaDetailPanel.h"
18 #include "ObjectListWnd.h"
19 #include "ModeratorActionsWnd.h"
20 #include "SidePanel.h"
21 #include "SitRepPanel.h"
22 #include "SystemIcon.h"
23 #include "FieldIcon.h"
24 #include "ShaderProgram.h"
25 #include "Hotkeys.h"
26 #include "Sound.h"
27 #include "TextBrowseWnd.h"
28 #include "../util/Directories.h"
29 #include "../util/i18n.h"
30 #include "../util/Logger.h"
31 #include "../util/GameRules.h"
32 #include "../util/OptionsDB.h"
33 #include "../util/Order.h"
34 #include "../util/Random.h"
35 #include "../util/ModeratorAction.h"
36 #include "../util/ScopedTimer.h"
37 #include "../universe/Enums.h"
38 #include "../universe/Fleet.h"
39 #include "../universe/Planet.h"
40 #include "../universe/Predicates.h"
41 #include "../universe/Ship.h"
42 #include "../universe/ShipDesign.h"
43 #include "../universe/Species.h"
44 #include "../universe/System.h"
45 #include "../universe/Field.h"
46 #include "../universe/Pathfinder.h"
47 #include "../universe/Universe.h"
48 #include "../universe/UniverseObject.h"
49 #include "../Empire/Empire.h"
50 #include "../network/Message.h"
51 #include "../client/ClientNetworking.h"
52 #include "../client/human/HumanClientApp.h"
53 
54 #include <boost/graph/graph_concepts.hpp>
55 #include <boost/optional/optional.hpp>
56 #include <boost/range/numeric.hpp>
57 #include <boost/range/adaptor/map.hpp>
58 
59 #include <GG/Layout.h>
60 #include <GG/MultiEdit.h>
61 #include <GG/PtRect.h>
62 #include <GG/WndEvent.h>
63 
64 #include <deque>
65 #include <unordered_set>
66 #include <valarray>
67 #include <vector>
68 #include <unordered_map>
69 
70 namespace {
71     const double    ZOOM_STEP_SIZE = std::pow(2.0, 1.0/4.0);
72     const double    ZOOM_IN_MAX_STEPS = 12.0;
73     const double    ZOOM_IN_MIN_STEPS = -10.0;//-7.0;   // negative zoom steps indicates zooming out
74     const double    ZOOM_MAX = std::pow(ZOOM_STEP_SIZE, ZOOM_IN_MAX_STEPS);
75     const double    ZOOM_MIN = std::pow(ZOOM_STEP_SIZE, ZOOM_IN_MIN_STEPS);
76 
77     const GG::X     SITREP_PANEL_WIDTH(400);
78     const GG::Y     SITREP_PANEL_HEIGHT(200);
79 
80     const std::string SITREP_WND_NAME = "map.sitrep";
81     const std::string MAP_PEDIA_WND_NAME = "map.pedia";
82     const std::string OBJECT_WND_NAME = "map.object-list";
83     const std::string MODERATOR_WND_NAME = "map.moderator";
84     const std::string COMBAT_REPORT_WND_NAME = "combat.summary";
85     const std::string MAP_SIDEPANEL_WND_NAME = "map.sidepanel";
86 
87     const GG::Y     ZOOM_SLIDER_HEIGHT(200);
88     const GG::Y     SCALE_LINE_HEIGHT(20);
89     const GG::X     SCALE_LINE_MAX_WIDTH(240);
90     const int       MIN_SYSTEM_NAME_SIZE = 10;
91     const int       LAYOUT_MARGIN = 5;
92     const GG::Y     TOOLBAR_HEIGHT(32);
93 
94     const double    TWO_PI = 2.0*3.1415926536;
95 
96     DeclareThreadSafeLogger(effects);
97 
ZoomScaleFactor(double steps_in)98     double ZoomScaleFactor(double steps_in) {
99         if (steps_in > ZOOM_IN_MAX_STEPS) {
100             ErrorLogger() << "ZoomScaleFactor passed steps in (" << steps_in << ") higher than max (" << ZOOM_IN_MAX_STEPS << "), so using max";
101             steps_in = ZOOM_IN_MAX_STEPS;
102         } else if (steps_in < ZOOM_IN_MIN_STEPS) {
103             ErrorLogger() << "ZoomScaleFactor passed steps in (" << steps_in << ") lower than minimum (" << ZOOM_IN_MIN_STEPS << "), so using min";
104             steps_in = ZOOM_IN_MIN_STEPS;
105         }
106         return std::pow(ZOOM_STEP_SIZE, steps_in);
107     }
108 
AddOptions(OptionsDB & db)109     void AddOptions(OptionsDB& db) {
110         db.Add("ui.map.background.gas.shown",               UserStringNop("OPTIONS_DB_GALAXY_MAP_GAS"),                         true,                           Validator<bool>());
111         db.Add("ui.map.background.starfields.shown",        UserStringNop("OPTIONS_DB_GALAXY_MAP_STARFIELDS"),                  true,                           Validator<bool>());
112         db.Add("ui.map.background.starfields.scale",        UserStringNop("OPTIONS_DB_GALAXY_MAP_STARFIELDS_SCALE"),            1.0,                            RangedStepValidator<double>(0.2, 0.2, 4.0));
113 
114         db.Add("ui.map.scale.legend.shown",                 UserStringNop("OPTIONS_DB_GALAXY_MAP_SCALE_LINE"),                  true,                           Validator<bool>());
115         db.Add("ui.map.scale.circle.shown",                 UserStringNop("OPTIONS_DB_GALAXY_MAP_SCALE_CIRCLE"),                false,                          Validator<bool>());
116 
117         db.Add("ui.map.zoom.slider.shown",                  UserStringNop("OPTIONS_DB_GALAXY_MAP_ZOOM_SLIDER"),                 false,                          Validator<bool>());
118 
119         db.Add("ui.map.starlane.thickness",                 UserStringNop("OPTIONS_DB_STARLANE_THICKNESS"),                     2.0,                            RangedStepValidator<double>(0.2, 0.2, 10.0));
120         db.Add("ui.map.starlane.thickness.factor",          UserStringNop("OPTIONS_DB_STARLANE_CORE"),                          2.0,                            RangedStepValidator<double>(1.0, 1.0, 10.0));
121         db.Add("ui.map.starlane.empire.color.shown",        UserStringNop("OPTIONS_DB_RESOURCE_STARLANE_COLOURING"),            true,                           Validator<bool>());
122 
123         db.Add("ui.map.fleet.supply.shown",                 UserStringNop("OPTIONS_DB_FLEET_SUPPLY_LINES"),                     true,                           Validator<bool>());
124         db.Add("ui.map.fleet.supply.width",                 UserStringNop("OPTIONS_DB_FLEET_SUPPLY_LINE_WIDTH"),                3.0,                            RangedStepValidator<double>(0.2, 0.2, 10.0));
125         db.Add("ui.map.fleet.supply.dot.spacing",           UserStringNop("OPTIONS_DB_FLEET_SUPPLY_LINE_DOT_SPACING"),          20,                             RangedStepValidator<int>(1, 3, 40));
126         db.Add("ui.map.fleet.supply.dot.rate",              UserStringNop("OPTIONS_DB_FLEET_SUPPLY_LINE_DOT_RATE"),             0.02,                           RangedStepValidator<double>(0.01, 0.01, 0.1));
127 
128         db.Add("ui.fleet.explore.hostile.ignored",          UserStringNop("OPTIONS_DB_FLEET_EXPLORE_IGNORE_HOSTILE"),           false,                          Validator<bool>());
129         db.Add("ui.fleet.explore.system.route.limit",       UserStringNop("OPTIONS_DB_FLEET_EXPLORE_SYSTEM_ROUTE_LIMIT"),       25,                             StepValidator<int>(1, -1));
130         db.Add("ui.fleet.explore.system.known.multiplier",  UserStringNop("OPTIONS_DB_FLEET_EXPLORE_SYSTEM_KNOWN_MULTIPLIER"),  10.0f,                          Validator<float>());
131 
132         db.Add("ui.map.starlane.color",                     UserStringNop("OPTIONS_DB_UNOWNED_STARLANE_COLOUR"),                GG::Clr(72,  72,  72,  255),    Validator<GG::Clr>());
133 
134         db.Add("ui.map.detection.range.shown",              UserStringNop("OPTIONS_DB_GALAXY_MAP_DETECTION_RANGE"),             true,                           Validator<bool>());
135 
136         db.Add("ui.map.scanlines.shown",                    UserStringNop("OPTIONS_DB_UI_SYSTEM_FOG"),                          true,                           Validator<bool>());
137         db.Add("ui.map.system.scanlines.spacing",           UserStringNop("OPTIONS_DB_UI_SYSTEM_FOG_SPACING"),                  4.0,                            RangedStepValidator<double>(0.2, 1.4, 8.0));
138         db.Add("ui.map.system.scanlines.color",             UserStringNop("OPTIONS_DB_UI_SYSTEM_FOG_CLR"),                      GG::Clr(36, 36, 36, 192),       Validator<GG::Clr>());
139         db.Add("ui.map.field.scanlines.color",              UserStringNop("OPTIONS_DB_UI_FIELD_FOG_CLR"),                       GG::Clr(0, 0, 0, 64),           Validator<GG::Clr>());
140 
141         db.Add("ui.map.system.icon.size",                   UserStringNop("OPTIONS_DB_UI_SYSTEM_ICON_SIZE"),                    14,                             RangedValidator<int>(8, 50));
142 
143         db.Add("ui.map.system.circle.shown",                UserStringNop("OPTIONS_DB_UI_SYSTEM_CIRCLES"),                      true,                           Validator<bool>());
144         db.Add("ui.map.system.circle.size",                 UserStringNop("OPTIONS_DB_UI_SYSTEM_CIRCLE_SIZE"),                  1.5,                            RangedStepValidator<double>(0.1, 1.0, 2.5));
145         db.Add("ui.map.system.circle.inner.width",          UserStringNop("OPTIONS_DB_UI_SYSTEM_INNER_CIRCLE_WIDTH"),           2.0,                            RangedStepValidator<double>(0.5, 1.0, 8.0));
146         db.Add("ui.map.system.circle.outer.width",          UserStringNop("OPTIONS_DB_UI_SYSTEM_OUTER_CIRCLE_WIDTH"),           2.0,                            RangedStepValidator<double>(0.5, 1.0, 8.0));
147         db.Add("ui.map.system.circle.inner.max.width",      UserStringNop("OPTIONS_DB_UI_SYSTEM_INNER_CIRCLE_MAX_WIDTH"),       5.0,                            RangedStepValidator<double>(0.5, 1.0, 12.0));
148         db.Add("ui.map.system.circle.distance",             UserStringNop("OPTIONS_DB_UI_SYSTEM_CIRCLE_DISTANCE"),              2.0,                            RangedStepValidator<double>(0.5, 1.0, 8.0));
149 
150         db.Add("ui.map.system.unexplored.rollover.enabled", UserStringNop("OPTIONS_DB_UI_SYSTEM_UNEXPLORED_OVERLAY"),           true,                           Validator<bool>());
151 
152         db.Add("ui.map.system.icon.tiny.threshold",         UserStringNop("OPTIONS_DB_UI_SYSTEM_TINY_ICON_SIZE_THRESHOLD"),     10,                             RangedValidator<int>(1, 16));
153 
154         db.Add("ui.map.system.select.indicator.size",       UserStringNop("OPTIONS_DB_UI_SYSTEM_SELECTION_INDICATOR_SIZE"),     1.6,                            RangedStepValidator<double>(0.1, 0.5, 5));
155         db.Add("ui.map.system.select.indicator.rpm",        UserStringNop("OPTIONS_DB_UI_SYSTEM_SELECTION_INDICATOR_FPS"),      12,                             RangedValidator<int>(1, 60));
156 
157         db.Add("ui.map.system.unowned.name.color",          UserStringNop("OPTIONS_DB_UI_SYSTEM_NAME_UNOWNED_COLOR"),           GG::Clr(160, 160, 160, 255),    Validator<GG::Clr>());
158 
159         db.Add("ui.map.fleet.button.tiny.zoom.threshold",   UserStringNop("OPTIONS_DB_UI_TINY_FLEET_BUTTON_MIN_ZOOM"),          0.8,                            RangedStepValidator<double>(0.1, 0.1, 4.0));
160         db.Add("ui.map.fleet.button.small.zoom.threshold",  UserStringNop("OPTIONS_DB_UI_SMALL_FLEET_BUTTON_MIN_ZOOM"),         1.50,                           RangedStepValidator<double>(0.1, 0.1, 4.0));
161         db.Add("ui.map.fleet.button.medium.zoom.threshold", UserStringNop("OPTIONS_DB_UI_MEDIUM_FLEET_BUTTON_MIN_ZOOM"),        4.00,                           RangedStepValidator<double>(0.1, 0.1, 4.0));
162 
163         db.Add("ui.map.detection.range.opacity",            UserStringNop("OPTIONS_DB_GALAXY_MAP_DETECTION_RANGE_OPACITY"),     3,                              RangedValidator<int>(0, 8));
164 
165         db.Add("ui.map.menu.enabled",                       UserStringNop("OPTIONS_DB_UI_GALAXY_MAP_POPUP"),                    false,                          Validator<bool>());
166 
167         db.Add("ui.production.mappanels.removed",           UserStringNop("OPTIONS_DB_UI_HIDE_MAP_PANELS"),                     false,                          Validator<bool>());
168 
169         db.Add("ui.map.sidepanel.width",                    UserStringNop("OPTIONS_DB_UI_SIDEPANEL_WIDTH"),                     512,                            Validator<int>());
170 
171         // Register hotkey names/default values for the context "map".
172         Hotkey::AddHotkey("ui.map.open",                    UserStringNop("HOTKEY_MAP_RETURN_TO_MAP"),                          GG::GGK_ESCAPE);
173         Hotkey::AddHotkey("ui.turn.end",                    UserStringNop("HOTKEY_MAP_END_TURN"),                               GG::GGK_RETURN,                 GG::MOD_KEY_CTRL);
174         Hotkey::AddHotkey("ui.map.sitrep",                  UserStringNop("HOTKEY_MAP_SIT_REP"),                                GG::GGK_n,                      GG::MOD_KEY_CTRL);
175         Hotkey::AddHotkey("ui.research",                    UserStringNop("HOTKEY_MAP_RESEARCH"),                               GG::GGK_r,                      GG::MOD_KEY_CTRL);
176         Hotkey::AddHotkey("ui.production",                  UserStringNop("HOTKEY_MAP_PRODUCTION"),                             GG::GGK_p,                      GG::MOD_KEY_CTRL);
177         Hotkey::AddHotkey("ui.design",                      UserStringNop("HOTKEY_MAP_DESIGN"),                                 GG::GGK_d,                      GG::MOD_KEY_CTRL);
178         Hotkey::AddHotkey("ui.map.objects",                 UserStringNop("HOTKEY_MAP_OBJECTS"),                                GG::GGK_o,                      GG::MOD_KEY_CTRL);
179         Hotkey::AddHotkey("ui.map.messages",                UserStringNop("HOTKEY_MAP_MESSAGES"),                               GG::GGK_t,                      GG::MOD_KEY_ALT);
180         Hotkey::AddHotkey("ui.map.empires",                 UserStringNop("HOTKEY_MAP_EMPIRES"),                                GG::GGK_e,                      GG::MOD_KEY_CTRL);
181         Hotkey::AddHotkey("ui.pedia",                       UserStringNop("HOTKEY_MAP_PEDIA"),                                  GG::GGK_F1);
182         Hotkey::AddHotkey("ui.map.graphs",                  UserStringNop("HOTKEY_MAP_GRAPHS"),                                 GG::GGK_NONE);
183         Hotkey::AddHotkey("ui.gamemenu",                    UserStringNop("HOTKEY_MAP_MENU"),                                   GG::GGK_F10);
184         Hotkey::AddHotkey("ui.zoom.in",                     UserStringNop("HOTKEY_MAP_ZOOM_IN"),                                GG::GGK_z,                      GG::MOD_KEY_CTRL);
185         Hotkey::AddHotkey("ui.zoom.in.alt",                 UserStringNop("HOTKEY_MAP_ZOOM_IN_ALT"),                            GG::GGK_KP_PLUS,                GG::MOD_KEY_CTRL);
186         Hotkey::AddHotkey("ui.zoom.out",                    UserStringNop("HOTKEY_MAP_ZOOM_OUT"),                               GG::GGK_x,                      GG::MOD_KEY_CTRL);
187         Hotkey::AddHotkey("ui.zoom.out.alt",                UserStringNop("HOTKEY_MAP_ZOOM_OUT_ALT"),                           GG::GGK_KP_MINUS,               GG::MOD_KEY_CTRL);
188         Hotkey::AddHotkey("ui.map.system.zoom.home",        UserStringNop("HOTKEY_MAP_ZOOM_HOME_SYSTEM"),                       GG::GGK_h,                      GG::MOD_KEY_CTRL);
189         Hotkey::AddHotkey("ui.map.system.zoom.prev",        UserStringNop("HOTKEY_MAP_ZOOM_PREV_SYSTEM"),                       GG::GGK_COMMA,                  GG::MOD_KEY_CTRL);
190         Hotkey::AddHotkey("ui.map.system.zoom.next",        UserStringNop("HOTKEY_MAP_ZOOM_NEXT_SYSTEM"),                       GG::GGK_PERIOD,                 GG::MOD_KEY_CTRL);
191         Hotkey::AddHotkey("ui.map.system.owned.zoom.prev",  UserStringNop("HOTKEY_MAP_ZOOM_PREV_OWNED_SYSTEM"),                 GG::GGK_COMMA,                  GG::MOD_KEY_CTRL | GG::MOD_KEY_SHIFT);
192         Hotkey::AddHotkey("ui.map.system.owned.zoom.next",  UserStringNop("HOTKEY_MAP_ZOOM_NEXT_OWNED_SYSTEM"),                 GG::GGK_PERIOD,                 GG::MOD_KEY_CTRL | GG::MOD_KEY_SHIFT);
193         Hotkey::AddHotkey("ui.map.fleet.zoom.prev",         UserStringNop("HOTKEY_MAP_ZOOM_PREV_FLEET"),                        GG::GGK_f,                      GG::MOD_KEY_CTRL);
194         Hotkey::AddHotkey("ui.map.fleet.zoom.next",         UserStringNop("HOTKEY_MAP_ZOOM_NEXT_FLEET"),                        GG::GGK_g,                      GG::MOD_KEY_CTRL);
195         Hotkey::AddHotkey("ui.map.fleet.idle.zoom.prev",    UserStringNop("HOTKEY_MAP_ZOOM_PREV_IDLE_FLEET"),                   GG::GGK_f,                      GG::MOD_KEY_ALT);
196         Hotkey::AddHotkey("ui.map.fleet.idle.zoom.next",    UserStringNop("HOTKEY_MAP_ZOOM_NEXT_IDLE_FLEET"),                   GG::GGK_g,                      GG::MOD_KEY_ALT);
197 
198         Hotkey::AddHotkey("ui.pan.right",                   UserStringNop("HOTKEY_MAP_PAN_RIGHT"),                              GG::GGK_RIGHT,                  GG::MOD_KEY_CTRL);
199         Hotkey::AddHotkey("ui.pan.left",                    UserStringNop("HOTKEY_MAP_PAN_LEFT"),                               GG::GGK_LEFT,                   GG::MOD_KEY_CTRL);
200         Hotkey::AddHotkey("ui.pan.up",                      UserStringNop("HOTKEY_MAP_PAN_UP"),                                 GG::GGK_UP,                     GG::MOD_KEY_CTRL);
201         Hotkey::AddHotkey("ui.pan.down",                    UserStringNop("HOTKEY_MAP_PAN_DOWN"),                               GG::GGK_DOWN,                   GG::MOD_KEY_CTRL);
202 
203         Hotkey::AddHotkey("ui.map.scale.legend",            UserStringNop("HOTKEY_MAP_TOGGLE_SCALE_LINE"),                      GG::GGK_l,                      GG::MOD_KEY_ALT);
204         Hotkey::AddHotkey("ui.map.scale.circle",            UserStringNop("HOTKEY_MAP_TOGGLE_SCALE_CIRCLE"),                    GG::GGK_c,                      GG::MOD_KEY_ALT);
205 
206         Hotkey::AddHotkey("ui.cut",                         UserStringNop("HOTKEY_CUT"),                                        GG::GGK_x,                      GG::MOD_KEY_CTRL);
207         Hotkey::AddHotkey("ui.copy",                        UserStringNop("HOTKEY_COPY"),                                       GG::GGK_c,                      GG::MOD_KEY_CTRL);
208         Hotkey::AddHotkey("ui.paste",                       UserStringNop("HOTKEY_PASTE"),                                      GG::GGK_v,                      GG::MOD_KEY_CTRL);
209 
210         Hotkey::AddHotkey("ui.select.all",                  UserStringNop("HOTKEY_SELECT_ALL"),                                 GG::GGK_a,                      GG::MOD_KEY_CTRL);
211         Hotkey::AddHotkey("ui.select.none",                 UserStringNop("HOTKEY_DESELECT"),                                   GG::GGK_d,                      GG::MOD_KEY_CTRL);
212 
213         // stepping through UI controls doesn't really work as of this writing, so I'm commenting out these hotkey commands
214         //Hotkey::AddHotkey("ui.focus.prev",                  UserStringNop("HOTKEY_FOCUS_PREV_WND"),                             GG::GGK_NONE);
215         //Hotkey::AddHotkey("ui.focus.next",                  UserStringNop("HOTKEY_FOCUS_NEXT_WND"),                             GG::GGK_NONE);
216     }
217     bool temp_bool = RegisterOptions(&AddOptions);
218 
219     /* Returns fractional distance along line segment between two points that a
220      * third point between them is.assumes the "mid" point is between the
221      * "start" and "end" points, in which case the returned fraction is between
222      * 0.0 and 1.0 */
FractionalDistanceBetweenPoints(double startX,double startY,double midX,double midY,double endX,double endY)223     double FractionalDistanceBetweenPoints(double startX, double startY, double midX, double midY, double endX, double endY) {
224         // get magnitudes of vectors
225         double full_deltaX = endX - startX, full_deltaY = endY - startY;
226         double mid_deltaX = midX - startX, mid_deltaY = midY - startY;
227         double full_length = std::sqrt(full_deltaX*full_deltaX + full_deltaY*full_deltaY);
228         if (full_length == 0.0) // safety check
229             full_length = 1.0;
230         double mid_length = std::sqrt(mid_deltaX*mid_deltaX + mid_deltaY*mid_deltaY);
231         return mid_length / full_length;
232     }
233 
234     /* Returns point that is dist ditance away from (X1, Y1) in the direction
235      * of (X2, Y2) */
PositionFractionalAtDistanceBetweenPoints(double X1,double Y1,double X2,double Y2,double dist)236     std::pair<double, double> PositionFractionalAtDistanceBetweenPoints(double X1, double Y1, double X2, double Y2, double dist) {
237         double newX = X1 + (X2 - X1) * dist;
238         double newY = Y1 + (Y2 - Y1) * dist;
239         return {newX, newY};
240     }
241 
242     /* Returns apparent map X and Y position of an object at universe position
243      * \a X and \a Y for an object that is located on a starlane between
244      * systems with ids \a lane_start_sys_id and \a lane_end_sys_id
245      * The apparent position of an object depends on its actual position, the
246      * actual positions of the systems at the ends of the lane, and the
247      * apparent positions of the ends of the lanes.  The apparent position of
248      * objects on the lane is compressed into the space between the apparent
249      * ends of the lane, but is proportional to the distance of the actual
250      * position along the lane. */
ScreenPosOnStarlane(double X,double Y,int lane_start_sys_id,int lane_end_sys_id,const LaneEndpoints & screen_lane_endpoints)251     boost::optional<std::pair<double, double>> ScreenPosOnStarlane(double X, double Y, int lane_start_sys_id, int lane_end_sys_id, const LaneEndpoints& screen_lane_endpoints) {
252         // get endpoints of lane in universe.  may be different because on-
253         // screen lanes are drawn between system circles, not system centres
254         int empire_id = HumanClientApp::GetApp()->EmpireID();
255         auto prev = EmpireKnownObjects(empire_id).get(lane_start_sys_id);
256         auto next = EmpireKnownObjects(empire_id).get(lane_end_sys_id);
257         if (!next || !prev) {
258             ErrorLogger() << "ScreenPosOnStarlane couldn't find next system " << lane_start_sys_id << " or prev system " << lane_end_sys_id;
259             return boost::none;
260         }
261 
262         // get fractional distance along lane that fleet's universe position is
263         double dist_along_lane = FractionalDistanceBetweenPoints(prev->X(), prev->Y(), X, Y, next->X(), next->Y());
264 
265         return PositionFractionalAtDistanceBetweenPoints(screen_lane_endpoints.X1, screen_lane_endpoints.Y1,
266                                                          screen_lane_endpoints.X2, screen_lane_endpoints.Y2,
267                                                          dist_along_lane);
268     }
269 
WndLeft(const GG::Wnd * wnd)270     GG::X WndLeft(const GG::Wnd* wnd) { return wnd ? wnd->Left() : GG::X0; }
WndRight(const GG::Wnd * wnd)271     GG::X WndRight(const GG::Wnd* wnd) { return wnd ? wnd->Right() : GG::X0; }
WndTop(const GG::Wnd * wnd)272     GG::Y WndTop(const GG::Wnd* wnd) { return wnd ? wnd->Top() : GG::Y0; }
WndBottom(const GG::Wnd * wnd)273     GG::Y WndBottom(const GG::Wnd* wnd) { return wnd ? wnd->Bottom() : GG::Y0; }
InRect(GG::X left,GG::Y top,GG::X right,GG::Y bottom,const GG::Pt & pt)274     bool InRect(GG::X left, GG::Y top, GG::X right, GG::Y bottom, const GG::Pt& pt)
275     { return pt.x >= left && pt.y >= top && pt.x < right && pt.y < bottom; } //pt >= ul && pt < lr;
276 
AppWidth()277     GG::X AppWidth() {
278         if (HumanClientApp* app = HumanClientApp::GetApp())
279             return app->AppWidth();
280         return GG::X0;
281     }
282 
AppHeight()283     GG::Y AppHeight() {
284         if (HumanClientApp* app = HumanClientApp::GetApp())
285             return app->AppHeight();
286         return GG::Y0;
287     }
288 
ClientPlayerIsModerator()289     bool ClientPlayerIsModerator()
290     { return HumanClientApp::GetApp()->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR; }
291 
PlayTurnButtonClickSound()292     void PlayTurnButtonClickSound()
293     { Sound::GetSound().PlaySound(GetOptionsDB().Get<std::string>("ui.button.turn.press.sound.path"), true); }
294 
ToggleBoolOption(const std::string & option_name)295     bool ToggleBoolOption(const std::string& option_name) {
296         bool initially_enabled = GetOptionsDB().Get<bool>(option_name);
297         GetOptionsDB().Set(option_name, !initially_enabled);
298         return !initially_enabled;
299     }
300 
301     const std::string FLEET_DETAIL_SHIP_COUNT{UserStringNop("MAP_FLEET_SHIP_COUNT")};
302     const std::string FLEET_DETAIL_ARMED_COUNT{UserStringNop("MAP_FLEET_ARMED_COUNT")};
303     const std::string FLEET_DETAIL_SLOT_COUNT{UserStringNop("MAP_FLEET_SLOT_COUNT")};
304     const std::string FLEET_DETAIL_PART_COUNT{UserStringNop("MAP_FLEET_PART_COUNT")};
305     const std::string FLEET_DETAIL_UNARMED_COUNT{UserStringNop("MAP_FLEET_UNARMED_COUNT")};
306     const std::string FLEET_DETAIL_COLONY_COUNT{UserStringNop("MAP_FLEET_COLONY_COUNT")};
307     const std::string FLEET_DETAIL_CARRIER_COUNT{UserStringNop("MAP_FLEET_CARRIER_COUNT")};
308     const std::string FLEET_DETAIL_TROOP_COUNT{UserStringNop("MAP_FLEET_TROOP_COUNT")};
309 
310 
311     /** BrowseInfoWnd for the fleet icon tooltip */
312     class FleetDetailBrowseWnd : public GG::BrowseInfoWnd {
313     public:
FleetDetailBrowseWnd(int empire_id,GG::X width)314         FleetDetailBrowseWnd(int empire_id, GG::X width) :
315             GG::BrowseInfoWnd(GG::X0, GG::Y0, width, GG::Y(ClientUI::Pts())),
316             m_empire_id(empire_id),
317             m_margin(5)
318         {
319             GG::X value_col_width{(m_margin * 3) + (ClientUI::Pts() * 3)};
320             m_col_widths = {width - value_col_width, value_col_width};
321 
322             RequirePreRender();
323         }
324 
PreRender()325         void PreRender() override {
326             SetChildClippingMode(ClipToClient);
327 
328             NewLabelValue(FLEET_DETAIL_SHIP_COUNT, true);
329             NewLabelValue(FLEET_DETAIL_ARMED_COUNT);
330             NewLabelValue(FLEET_DETAIL_SLOT_COUNT);
331             NewLabelValue(FLEET_DETAIL_PART_COUNT);
332             NewLabelValue(FLEET_DETAIL_UNARMED_COUNT);
333             NewLabelValue(FLEET_DETAIL_COLONY_COUNT);
334             NewLabelValue(FLEET_DETAIL_CARRIER_COUNT);
335             NewLabelValue(FLEET_DETAIL_TROOP_COUNT);
336 
337             UpdateLabels();
338             ResetShipDesignLabels();
339             DoLayout();
340         }
341 
342         typedef std::pair<std::shared_ptr<CUILabel>,
343                           std::shared_ptr<CUILabel>> LabelValueType;
344 
WndHasBrowseInfo(const Wnd * wnd,std::size_t mode) const345         bool WndHasBrowseInfo(const Wnd* wnd, std::size_t mode) const override {
346             assert(mode <= wnd->BrowseModes().size());
347             return true;
348         }
349 
Render()350         void Render() override {
351             const GG::Y row_height{ClientUI::Pts() + (m_margin * 2)};
352             const GG::Y offset{32};
353             const GG::Clr& BG_CLR = ClientUI::WndColor();
354             const GG::Clr& BORDER_CLR = ClientUI::WndOuterBorderColor();
355             const GG::Pt& UL = GG::Pt(UpperLeft().x, UpperLeft().y + offset);
356             const GG::Pt& LR = LowerRight();
357 
358             // main background
359             GG::FlatRectangle(UL, LR, BG_CLR, BORDER_CLR);
360 
361             // summary text background
362             GG::FlatRectangle(UL, GG::Pt(LR.x, row_height + offset), BORDER_CLR, BORDER_CLR);
363 
364             // seperation line between armed/unarmed and utility ships
365             GG::Y line_ht(UL.y + (row_height * 2) + (row_height * 5 / 4));
366             GG::Pt line_ul(UL.x + (m_margin * 2), line_ht);
367             GG::Pt line_lr(LR.x - (m_margin * 2), line_ht);
368             GG::Line(line_ul, line_lr, BORDER_CLR);
369 
370             // seperation line between ships and parts
371             line_ht = {UL.y + (row_height * 5) + (row_height * 6 / 4)};
372             line_ul = {UL.x + (m_margin * 2), line_ht};
373             line_lr = {LR.x - (m_margin * 2), line_ht};
374             GG::Line(line_ul, line_lr, BORDER_CLR);
375 
376             // seperation line between parts and designs
377             line_ht = { UL.y + (row_height * 7) + (row_height * 7 / 4) };
378             line_ul = { UL.x + (m_margin * 2), line_ht };
379             line_lr = { LR.x - (m_margin * 2), line_ht };
380             GG::Line(line_ul, line_lr, BORDER_CLR);
381         }
382 
DoLayout()383         void DoLayout() {
384             const GG::Y row_height{ClientUI::Pts()};
385             const GG::Y offset{32};
386             const GG::X descr_width{m_col_widths.at(0) - (m_margin * 2)};
387             const GG::X value_width{m_col_widths.at(1) - (m_margin * 3)};
388 
389             GG::Pt descr_ul{GG::X{m_margin}, offset + m_margin};
390             GG::Pt descr_lr{descr_ul.x + descr_width, offset + row_height};
391             GG::Pt value_ul{descr_lr.x + m_margin, descr_ul.y};
392             GG::Pt value_lr{value_ul.x + value_width, descr_lr.y};
393 
394             const GG::Pt next_row{GG::X0, row_height + (m_margin * 2)};
395             const GG::Pt space_row{next_row * 5 / 4};
396 
397             LayoutRow(FLEET_DETAIL_SHIP_COUNT,
398                       descr_ul, descr_lr, value_ul, value_lr, space_row);
399             LayoutRow(FLEET_DETAIL_ARMED_COUNT,
400                       descr_ul, descr_lr, value_ul, value_lr, next_row);
401             LayoutRow(FLEET_DETAIL_UNARMED_COUNT,
402                       descr_ul, descr_lr, value_ul, value_lr, space_row);
403             LayoutRow(FLEET_DETAIL_CARRIER_COUNT,
404                       descr_ul, descr_lr, value_ul, value_lr, next_row);
405             LayoutRow(FLEET_DETAIL_TROOP_COUNT,
406                       descr_ul, descr_lr, value_ul, value_lr, next_row);
407             LayoutRow(FLEET_DETAIL_COLONY_COUNT,
408                       descr_ul, descr_lr, value_ul, value_lr, space_row);
409             LayoutRow(FLEET_DETAIL_PART_COUNT,
410                       descr_ul, descr_lr, value_ul, value_lr, next_row);
411             LayoutRow(FLEET_DETAIL_SLOT_COUNT,
412                       descr_ul, descr_lr, value_ul, value_lr,
413                       m_ship_design_labels.empty() ? GG::Pt(GG::X0, GG::Y0) : space_row);
414 
415             for (auto it = m_ship_design_labels.begin(); it != m_ship_design_labels.end(); ++it) {
416                 LayoutRow(*it, descr_ul, descr_lr, value_ul, value_lr,
417                           std::next(it) == m_ship_design_labels.end()? GG::Pt(GG::X0, GG::Y0) : next_row);
418             }
419 
420             Resize(GG::Pt(value_lr.x + (m_margin * 3), value_lr.y + (m_margin * 3)));
421         }
422 
423         /** Constructs and attaches new description and value labels
424          *  for the given description row @p descr. */
NewLabelValue(const std::string & descr,bool title=false)425         void NewLabelValue(const std::string& descr, bool title = false) {
426             if (m_labels.count(descr))
427                 return;
428 
429             GG::Y height{ClientUI::Pts()};
430             // center format for title label
431             m_labels.emplace(descr, std::make_pair(
432                 GG::Wnd::Create<CUILabel>(UserString(descr),
433                                           title ? GG::FORMAT_CENTER : GG::FORMAT_RIGHT,
434                                           GG::NO_WND_FLAGS, GG::X0, GG::Y0,
435                                           m_col_widths.at(0) - (m_margin * 2), height
436                 ),
437                 GG::Wnd::Create<CUILabel>("0", GG::FORMAT_RIGHT,
438                                           GG::NO_WND_FLAGS, GG::X0, GG::Y0,
439                                           m_col_widths.at(1) - (m_margin * 2), height
440                 )
441             ));
442 
443             if (title) {  // utilize bold font for title label and value
444                 m_labels.at(descr).first->SetFont(ClientUI::GetBoldFont());
445                 m_labels.at(descr).second->SetFont(ClientUI::GetBoldFont());
446             }
447 
448             AttachChild(m_labels.at(descr).first);
449             AttachChild(m_labels.at(descr).second);
450         }
451 
452         /** Updates the text displayed for the value of each label */
UpdateLabels()453         void UpdateLabels() {
454             UpdateValues();
455             for (const auto& value : m_values) {
456                 auto label_it = m_labels.find(value.first);
457                 if (label_it == m_labels.end())
458                     continue;
459                 label_it->second.second->ChangeTemplatedText(std::to_string(value.second), 0);
460             }
461         }
462 
463     protected:
464         /** Updates the value for display with each label */
UpdateValues()465         void UpdateValues() {
466             m_values.clear();
467             m_values[FLEET_DETAIL_SHIP_COUNT] = 0;
468             m_ship_design_counts.clear();
469             if (m_empire_id == ALL_EMPIRES)
470                 return;
471 
472             const auto& destroyed_objects = GetUniverse().EmpireKnownDestroyedObjectIDs(m_empire_id);
473             for (auto& ship : Objects().all<Ship>()) {
474                 if (!ship->OwnedBy(m_empire_id) || destroyed_objects.count(ship->ID()))
475                     continue;
476                 m_values[FLEET_DETAIL_SHIP_COUNT]++;
477 
478                 if (ship->IsArmed())
479                     m_values[FLEET_DETAIL_ARMED_COUNT]++;
480                 else
481                     m_values[FLEET_DETAIL_UNARMED_COUNT]++;
482 
483                 if (ship->CanColonize())
484                     m_values[FLEET_DETAIL_COLONY_COUNT]++;
485 
486                 if (ship->HasTroops())
487                     m_values[FLEET_DETAIL_TROOP_COUNT]++;
488 
489                 if (ship->HasFighters())
490                     m_values[FLEET_DETAIL_CARRIER_COUNT]++;
491 
492                 const ShipDesign* design = ship->Design();
493                 if (!design)
494                     continue;
495                 m_ship_design_counts[design->ID()]++;
496                 for (const std::string& part : design->Parts()) {
497                     m_values[FLEET_DETAIL_SLOT_COUNT] ++;
498                     if (!part.empty())
499                         m_values[FLEET_DETAIL_PART_COUNT] ++;
500                 }
501             }
502 
503         }
504 
505         /** Resize/Move controls for row @p descr
506          *  and then advance sizes by @p row_advance */
LayoutRow(const std::string & descr,GG::Pt & descr_ul,GG::Pt & descr_lr,GG::Pt & value_ul,GG::Pt & value_lr,const GG::Pt & row_advance)507         void LayoutRow(const std::string& descr,
508                        GG::Pt& descr_ul, GG::Pt& descr_lr,
509                        GG::Pt& value_ul, GG::Pt& value_lr,
510                        const GG::Pt& row_advance)
511         {
512             if (!m_labels.count(descr)) {
513                 ErrorLogger() << "Unable to find expected label key " << descr;
514                 return;
515             }
516 
517             LayoutRow(m_labels.at(descr), descr_ul, descr_lr, value_ul, value_lr, row_advance);
518         }
519 
LayoutRow(LabelValueType & row,GG::Pt & descr_ul,GG::Pt & descr_lr,GG::Pt & value_ul,GG::Pt & value_lr,const GG::Pt & row_advance)520         void LayoutRow(LabelValueType& row,
521             GG::Pt& descr_ul, GG::Pt& descr_lr,
522             GG::Pt& value_ul, GG::Pt& value_lr,
523             const GG::Pt& row_advance)
524         {
525             row.first->SizeMove(descr_ul, descr_lr);
526             if (row.second) {
527                 row.second->SizeMove(value_ul, value_lr);
528             }
529             descr_ul += row_advance;
530             descr_lr += row_advance;
531             value_ul += row_advance;
532             value_lr += row_advance;
533         }
534 
535         /** Remove the old labels for ship design counts, and add the new ones.
536             The stats themselves are updated in UpdateValues. */
ResetShipDesignLabels()537         void ResetShipDesignLabels() {
538             for (auto& labels : m_ship_design_labels) {
539                 DetachChild(labels.first);
540                 DetachChild(labels.second);
541             }
542             m_ship_design_labels.clear();
543             for (const auto& entry : m_ship_design_counts) {
544                 GG::Y height{ ClientUI::Pts() };
545                 // center format for title label
546                 m_ship_design_labels.emplace_back(
547                     GG::Wnd::Create<CUILabel>(GetShipDesign(entry.first)->Name(),
548                         GG::FORMAT_RIGHT,
549                         GG::NO_WND_FLAGS, GG::X0, GG::Y0,
550                         m_col_widths.at(0) - (m_margin * 2), height
551                         ),
552                     GG::Wnd::Create<CUILabel>(std::to_string(entry.second),
553                         GG::FORMAT_RIGHT,
554                         GG::NO_WND_FLAGS, GG::X0, GG::Y0,
555                         m_col_widths.at(1) - (m_margin * 2), height
556                         )
557                 );
558             }
559             std::sort(m_ship_design_labels.begin(), m_ship_design_labels.end(),
560                 [](LabelValueType a, LabelValueType b) {
561                     return a.first->Text() < b.first->Text();
562                 }
563             );
564             for (auto& labels : m_ship_design_labels) {
565                 AttachChild(labels.first);
566                 AttachChild(labels.second);
567             }
568         }
569 
570     private:
UpdateImpl(size_t mode,const Wnd * target)571         void UpdateImpl(size_t mode, const Wnd* target) override {
572             UpdateLabels();
573             ResetShipDesignLabels();
574             DoLayout();
575         }
576 
577         std::unordered_map<std::string, int>            m_values;             ///< Internal value for display with a description
578         std::unordered_map<std::string, LabelValueType> m_labels;             ///< Label controls mapped to the description key
579         std::unordered_map<int, int>                    m_ship_design_counts; ///< Map of ship design ids to the number of ships with that id
580         std::vector<LabelValueType>                     m_ship_design_labels; ///< Label controls for ship designs, sorted by disply name
581         int                                             m_empire_id;          ///< ID of the viewing empire
582         std::vector<GG::X>                              m_col_widths;         ///< widths of each column
583         int                                             m_margin;             ///< margin between controls
584     };
585 }
586 
587 
588 ////////////////////////////////////////////////////////////
589 // MapWnd::MapScaleLine
590 ////////////////////////////////////////////////////////////
591 /** Displays a notched line with number labels to indicate Universe distance on the map. */
592 class MapWnd::MapScaleLine : public GG::Control {
593 public:
MapScaleLine(GG::X x,GG::Y y,GG::X w,GG::Y h)594     MapScaleLine(GG::X x, GG::Y y, GG::X w, GG::Y h) :
595         GG::Control(x, y, w, h, GG::ONTOP)
596     {
597         m_label = GG::Wnd::Create<GG::TextControl>(GG::X0, GG::Y0, GG::X1, GG::Y1, "", ClientUI::GetFont(), ClientUI::TextColor());
598     }
599 
CompleteConstruction()600     void CompleteConstruction() override {
601         GG::Control::CompleteConstruction();
602         AttachChild(m_label);
603         std::set<int> dummy = std::set<int>();
604         Update(1.0, dummy, INVALID_OBJECT_ID);
605         GetOptionsDB().OptionChangedSignal("ui.map.scale.legend.shown").connect(
606             boost::bind(&MapScaleLine::UpdateEnabled, this));
607         UpdateEnabled();
608     }
609 
~MapScaleLine()610     virtual ~MapScaleLine()
611     {}
612 
Render()613     void Render() override {
614         if (!m_enabled)
615             return;
616 
617         // use GL to draw line and ticks and labels to indicte a length on the map
618         GG::Pt ul = UpperLeft();
619         GG::Pt lr = ul + GG::Pt(m_line_length, Height());
620 
621         GG::GL2DVertexBuffer verts;
622         verts.reserve(6);
623         // left border
624         verts.store(Value(ul.x), Value(ul.y));
625         verts.store(Value(ul.x), Value(lr.y));
626         // right border
627         verts.store(Value(lr.x), Value(ul.y));
628         verts.store(Value(lr.x), Value(lr.y));
629         // bottom line
630         verts.store(Value(ul.x), Value(lr.y));
631         verts.store(Value(lr.x), Value(lr.y));
632 
633         glColor(GG::CLR_WHITE);
634         glLineWidth(2.0);
635 
636         glPushAttrib(GL_ENABLE_BIT | GL_LINE_WIDTH);
637         glDisable(GL_TEXTURE_2D);
638 
639         glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
640         glEnableClientState(GL_VERTEX_ARRAY);
641         glDisableClientState(GL_COLOR_ARRAY);
642         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
643 
644         verts.activate();
645         glDrawArrays(GL_LINES, 0, verts.size());
646 
647         glPopClientAttrib();
648         glPopAttrib();
649     }
650 
GetLength() const651     GG::X GetLength() const
652     { return m_line_length; }
653 
GetScaleFactor() const654     double GetScaleFactor() const
655     { return m_scale_factor; }
656 
Update(double zoom_factor,std::set<int> & fleet_ids,int sel_system_id)657     void Update(double zoom_factor, std::set<int>& fleet_ids, int sel_system_id) {
658         // The uu length of the map scale line is generally adjusted in this routine up or down by factors of two or five as
659         // the zoom_factor changes, so that it's pixel length on the screen is kept to a reasonable distance.  We also add
660         // additional stopping points for the map scale to augment the usefulness of the linked map scale circle (whose
661         // radius is the same as the map scale length).  These additional stopping points include the speeds and detection
662         // ranges of any selected fleets, and the detection ranges of all planets in the currently selected system,
663         // provided such values are at least 20 uu.
664 
665         // get selected fleet speeds and detection ranges
666         std::set<double> fixed_distances;
667         for (const auto& fleet : Objects().find<Fleet>(fleet_ids)) {
668             if (!fleet)
669                 continue;
670             if (fleet->Speed() > 20)
671                 fixed_distances.insert(fleet->Speed());
672             for (const auto& ship : Objects().find<Ship>(fleet->ShipIDs())) {
673                 if (!ship)
674                     continue;
675                 const float ship_range = ship->GetMeter(METER_DETECTION)->Initial();
676                 if (ship_range > 20)
677                     fixed_distances.insert(ship_range);
678                 const float ship_speed = ship->Speed();
679                 if (ship_speed > 20)
680                     fixed_distances.insert(ship_speed);
681             }
682         }
683         // get detection ranges for planets in the selected system (if any)
684         if (const auto system = Objects().get<System>(sel_system_id)) {
685             for (const auto& planet : Objects().find<Planet>(system->PlanetIDs())) {
686                 if (!planet)
687                     continue;
688                 const float planet_range = planet->GetMeter(METER_DETECTION)->Initial();
689                 if (planet_range > 20)
690                     fixed_distances.insert(planet_range);
691             }
692         }
693 
694         zoom_factor = std::min(std::max(zoom_factor, ZOOM_MIN), ZOOM_MAX);  // sanity range limits to prevent divide by zero
695         m_scale_factor = zoom_factor;
696 
697         // determine length of line to draw and how long that is in universe units
698         double AVAILABLE_WIDTH = static_cast<double>(std::max(Value(Width()), 1));
699 
700         // length in universe units that could be shown if full AVAILABLE_WIDTH was used
701         double max_shown_length = AVAILABLE_WIDTH / m_scale_factor;
702 
703         // select an actual shown length in universe units by reducing max_shown_length to a nice round number,
704         // where nice round numbers are numbers beginning with 1, 2 or 5
705         // We start by getting the highest power of 10 that is less than the max_shown_length,
706         // and then we step that initial value up, either by a factor of 2 or 5 if that
707         // will still fit, or to one of the values taken from fleets and planets
708         // if they are at least as reat as the standard value but not larger than the max_shown_length
709 
710         // get appropriate power of 10
711         double pow10 = floor(log10(max_shown_length));
712         double init_length = std::pow(10.0, pow10);
713         double shown_length = init_length;
714 
715         // determin a preliminary shown length
716         // see if next higher multiples of 5 or 2 can be used
717         if (shown_length * 5.0 <= max_shown_length)
718             shown_length *= 5.0;
719         else if (shown_length * 2.0 <= max_shown_length)
720             shown_length *= 2.0;
721 
722         // DebugLogger()  << "MapScaleLine::Update maxLen: " << max_shown_length
723         //                        << "; init_length: " << init_length
724         //                        << "; shown_length: " << shown_length;
725 
726         // for (double distance : fixed_distances) {
727         //     DebugLogger()  << " MapScaleLine::Update fleet speed: " << distance;
728         // }
729 
730         // if there are additional scale steps to check (from fleets & planets)
731         if (!fixed_distances.empty()) {
732             // check if the set of fixed distances includes a value that is in between the max_shown_length
733             // and one zoom step smaller than the max shown length.  If so, use that for the shown_length;
734             // otherwise, check if the set of fixed distances includes a value that is greater than the
735             // shown_length determined above but still less than the max_shown_length, and if so then use that value.
736             // Note: if the ZOOM_STEP_SIZE is too large, then potential values in the set of fixed distances
737             // might get skipped over.
738             std::set<double>::iterator distance_ub = fixed_distances.upper_bound(max_shown_length/ZOOM_STEP_SIZE);
739             if (distance_ub != fixed_distances.end() && *distance_ub <= max_shown_length) {
740                 DebugLogger()  << " MapScaleLine::Update distance_ub: " << *distance_ub;
741                 shown_length = *distance_ub;
742             } else {
743                 distance_ub = fixed_distances.upper_bound(shown_length);
744                 if (distance_ub != fixed_distances.end() && *distance_ub <= max_shown_length) {
745                     DebugLogger()  << " MapScaleLine::Update distance_ub: " << *distance_ub;
746                     shown_length = *distance_ub;
747                 }
748             }
749         }
750 
751         // determine end of drawn scale line
752         m_line_length = GG::X(static_cast<int>(shown_length * m_scale_factor));
753 
754         // update text
755         std::string label_text = boost::io::str(FlexibleFormat(UserString("MAP_SCALE_INDICATOR")) %
756                                                 std::to_string(static_cast<int>(shown_length)));
757         m_label->SetText(label_text);
758         m_label->Resize(GG::Pt(GG::X(m_line_length), Height()));
759     }
760 
761 private:
UpdateEnabled()762     void UpdateEnabled() {
763         m_enabled = GetOptionsDB().Get<bool>("ui.map.scale.legend.shown");
764         if (m_enabled)
765             AttachChild(m_label);
766         else
767             DetachChild(m_label);
768     }
769 
770     double                              m_scale_factor = 1.0;
771     GG::X                               m_line_length = GG::X1;
772     std::shared_ptr<GG::TextControl>    m_label;
773     bool                                m_enabled = false;
774 };
775 
776 ////////////////////////////////////////////////////////////
777 // MapWndPopup
778 ////////////////////////////////////////////////////////////
MapWndPopup(const std::string & t,GG::X default_x,GG::Y default_y,GG::X default_w,GG::Y default_h,GG::Flags<GG::WndFlag> flags,const std::string & config_name)779 MapWndPopup::MapWndPopup(const std::string& t, GG::X default_x, GG::Y default_y, GG::X default_w, GG::Y default_h,
780                          GG::Flags<GG::WndFlag> flags, const std::string& config_name) :
781     CUIWnd(t, default_x, default_y, default_w, default_h, flags, config_name)
782 {}
783 
MapWndPopup(const std::string & t,GG::Flags<GG::WndFlag> flags,const std::string & config_name)784 MapWndPopup::MapWndPopup(const std::string& t, GG::Flags<GG::WndFlag> flags, const std::string& config_name) :
785     CUIWnd(t, flags, config_name)
786 {}
787 
CompleteConstruction()788 void MapWndPopup::CompleteConstruction() {
789     CUIWnd::CompleteConstruction();
790 
791     // MapWndPopupWnd is registered as a top level window, the same as ClientUI and MapWnd.
792     // Consequently, when the GUI shutsdown either could be destroyed before this Wnd
793     if (auto client = ClientUI::GetClientUI())
794         if (auto mapwnd = client->GetMapWnd())
795             mapwnd->RegisterPopup(std::static_pointer_cast<MapWndPopup>(shared_from_this()));
796 }
797 
~MapWndPopup()798 MapWndPopup::~MapWndPopup()
799 {
800     if (!Visible()) {
801         // Make sure it doesn't save visible = 0 to the config (doesn't
802         // make sense for windows that are created/destroyed repeatedly),
803         // try/catch because this is called from a dtor and OptionsDB
804         // functions can throw.
805         try {
806             Show();
807         } catch (const std::exception& e) {
808             ErrorLogger() << "~MapWndPopup() : caught exception cleaning up a popup: " << e.what();
809         }
810     }
811 
812     // MapWndPopupWnd is registered as a top level window, the same as ClientUI and MapWnd.
813     // Consequently, when the GUI shutsdown either could be destroyed before this Wnd
814     if (auto client = ClientUI::GetClientUI())
815         if (auto mapwnd = client->GetMapWnd())
816             mapwnd->RemovePopup(this);
817 }
818 
CloseClicked()819 void MapWndPopup::CloseClicked()
820 { CUIWnd::CloseClicked(); }
821 
Close()822 void MapWndPopup::Close()
823 { CloseClicked(); }
824 
825 
826 //////////////////////////////////
827 //LaneEndpoints
828 //////////////////////////////////
LaneEndpoints()829 LaneEndpoints::LaneEndpoints() :
830     X1(static_cast<float>(UniverseObject::INVALID_POSITION)),
831     Y1(static_cast<float>(UniverseObject::INVALID_POSITION)),
832     X2(static_cast<float>(UniverseObject::INVALID_POSITION)),
833     Y2(static_cast<float>(UniverseObject::INVALID_POSITION))
834 {}
835 
836 
837 ////////////////////////////////////////////////
838 // MapWnd::MovementLineData::Vertex
839 ////////////////////////////////////////////////
840 struct MapWnd::MovementLineData::Vertex {
VertexMapWnd::MovementLineData::Vertex841     Vertex(double x_, double y_, int eta_, bool show_eta_, bool flag_blockade_ = false, bool flag_supply_block_ = false) :
842     x(x_), y(y_), eta(eta_), show_eta(show_eta_), flag_blockade(flag_blockade_), flag_supply_block(flag_supply_block_)
843     {}
844     double  x, y;       // apparent in-universe position of a point on move line.  not actual universe positions, but rather where the move line vertices are drawn
845     int     eta;        // turns taken to reach point by object travelling along move line
846     bool    show_eta;   // should an ETA indicator / number be shown over this vertex?
847     bool    flag_blockade;
848     bool    flag_supply_block;
849 };
850 
851 ////////////////////////////////////////////////
852 // MapWnd::MovementLineData
853 ////////////////////////////////////////////////
MovementLineData()854 MapWnd::MovementLineData::MovementLineData() :
855     path(),
856     colour(GG::CLR_ZERO)
857 {}
858 
MovementLineData(const std::list<MovePathNode> & path_,const std::map<std::pair<int,int>,LaneEndpoints> & lane_end_points_map,GG::Clr colour_,int empireID)859 MapWnd::MovementLineData::MovementLineData(const std::list<MovePathNode>& path_,
860                                            const std::map<std::pair<int, int>, LaneEndpoints>& lane_end_points_map,
861                                            GG::Clr colour_/*= GG::CLR_WHITE*/, int empireID /*= ALL_EMPIRES*/) :
862     path(path_),
863     colour(colour_)
864 {
865     // generate screen position vertices
866 
867     if (path.empty() || path.size() == 1)
868         return; // nothing to draw.  need at least two nodes at different locations to draw a line
869 
870     //DebugLogger() << "move_line path: ";
871     //for (const MovePathNode& node : path)
872     //    DebugLogger() << " ... " << node.object_id << " (" << node.x << ", " << node.y << ") eta: " << node.eta << " turn_end: " << node.turn_end;
873 
874 
875     // draw lines connecting points of interest along path.  only draw a line if the previous and
876     // current positions of the ends of the line are known.
877     const   MovePathNode& first_node =  *(path.begin());
878     double  prev_node_x =               first_node.x;
879     double  prev_node_y =               first_node.y;
880     int     prev_sys_id =               first_node.object_id;
881     int     prev_eta =                  first_node.eta;
882     int     next_sys_id =               INVALID_OBJECT_ID;
883 
884     const Empire* empire = GetEmpire(empireID);
885     std::set<int> unobstructed;
886     bool s_flag = false;
887     bool calc_s_flag = false;
888     if (empire) {
889         unobstructed = empire->SupplyUnobstructedSystems();
890         calc_s_flag = true;
891         //s_flag = ((first_node.object_id != INVALID_OBJECT_ID) && !unobstructed.count(first_node.object_id));
892     }
893 
894     for (const MovePathNode& node : path) {
895         // stop rendering if end of path is indicated
896         if (node.eta == Fleet::ETA_UNKNOWN || node.eta == Fleet::ETA_NEVER || node.eta == Fleet::ETA_OUT_OF_RANGE)
897             break;
898 
899         // 1) Get systems at ends of lane on which current node is located.
900 
901         if (node.object_id == INVALID_OBJECT_ID) {
902             // node is in open space.
903             // node should have valid prev_sys_id and node.lane_end_id to get info about starlane this node is on
904             prev_sys_id = node.lane_start_id;
905             next_sys_id = node.lane_end_id;
906 
907         } else {
908             // node is at a system.
909             // node should / may not have a valid lane_end_id, but this system's id is the end of a lane approaching it
910             next_sys_id = node.object_id;
911             // if this is the first node of the path, prev_sys_id should be set to node.object_id from pre-loop initialization.
912             // if this node is later in the path, prev_sys_id should have been set in previous loop iteration
913         }
914 
915         // skip invalid line segments
916         if (prev_sys_id == next_sys_id || next_sys_id == INVALID_OBJECT_ID || prev_sys_id == INVALID_OBJECT_ID)
917             continue;
918 
919 
920         // 2) Get apparent universe positions of nodes, which depend on endpoints of lane and actual universe position of nodes
921 
922         // get lane end points
923         auto ends_it = lane_end_points_map.find({prev_sys_id, next_sys_id});
924         if (ends_it == lane_end_points_map.end()) {
925             ErrorLogger() << "couldn't get endpoints of lane for move line";
926             break;
927         }
928         const LaneEndpoints& lane_endpoints = ends_it->second;
929 
930         // get on-screen positions of nodes shifted to fit on starlane
931         auto start_xy = ScreenPosOnStarlane(prev_node_x, prev_node_y, prev_sys_id, next_sys_id, lane_endpoints);
932         auto end_xy =   ScreenPosOnStarlane(node.x,      node.y,      prev_sys_id, next_sys_id, lane_endpoints);
933 
934         if (!start_xy) {
935             ErrorLogger() << "System " << prev_sys_id << " has invalid screen coordinates.";
936             continue;
937         }
938         if (!end_xy) {
939             ErrorLogger() << "System " << next_sys_id << " has invalid screen coordinates.";
940             continue;
941         }
942 
943         // 3) Add points for line segment to list of Vertices
944         bool b_flag = node.post_blockade;
945         s_flag = s_flag || (calc_s_flag &&
946             ((node.object_id != INVALID_OBJECT_ID) && !unobstructed.count(node.object_id)));
947         vertices.push_back(Vertex(start_xy->first,   start_xy->second,    prev_eta,   false,          b_flag, s_flag));
948         vertices.push_back(Vertex(end_xy->first,     end_xy->second,      node.eta,   node.turn_end,  b_flag, s_flag));
949 
950 
951         // 4) prep for next iteration
952         prev_node_x = node.x;
953         prev_node_y = node.y;
954         prev_eta = node.eta;
955         if (node.object_id != INVALID_OBJECT_ID) {  // only need to update previous and next sys ids if current node is at a system
956             prev_sys_id = node.object_id;                       // to be used in next iteration
957             next_sys_id = INVALID_OBJECT_ID;    // to be set in next iteration
958         }
959     }
960 }
961 
962 
963 ///////////////////////////
964 // MapWnd
965 ///////////////////////////
MapWnd()966 MapWnd::MapWnd() :
967     GG::Wnd(-AppWidth(), -AppHeight(),
968             static_cast<GG::X>(GetUniverse().UniverseWidth() * ZOOM_MAX + AppWidth() * 1.5),
969             static_cast<GG::Y>(GetUniverse().UniverseWidth() * ZOOM_MAX + AppHeight() * 1.5),
970             GG::INTERACTIVE | GG::DRAGABLE)
971 {}
972 
CompleteConstruction()973 void MapWnd::CompleteConstruction() {
974     GG::Wnd::CompleteConstruction();
975 
976     SetName("MapWnd");
977 
978 #if BOOST_VERSION >= 106000
979     using boost::placeholders::_1;
980     using boost::placeholders::_2;
981 #endif
982 
983     GetUniverse().UniverseObjectDeleteSignal.connect(
984         boost::bind(&MapWnd::UniverseObjectDeleted, this, _1));
985 
986     // toolbar
987     m_toolbar = GG::Wnd::Create<CUIToolBar>();
988     m_toolbar->SetName("MapWnd Toolbar");
989     GG::GUI::GetGUI()->Register(m_toolbar);
990     m_toolbar->Hide();
991 
992     auto layout = GG::Wnd::Create<GG::Layout>(m_toolbar->ClientUpperLeft().x, m_toolbar->ClientUpperLeft().y,
993                                               m_toolbar->ClientWidth(),       m_toolbar->ClientHeight(),
994                                               1, 23);
995     layout->SetName("Toolbar Layout");
996     m_toolbar->SetLayout(layout);
997 
998     //////////////////////////////
999     // Toolbar buttons and icons
1000     //////////////////////////////
1001 
1002     // turn button
1003     // determine size from the text that will go into the button, using a test year string
1004     std::string turn_button_longest_reasonable_text =  boost::io::str(FlexibleFormat(UserString("MAP_BTN_TURN_UPDATE")) % "99999"); // it is unlikely a game will go over 100000 turns
1005     std::string unready_button_longest_reasonable_text =  boost::io::str(FlexibleFormat(UserString("MAP_BTN_TURN_UNREADY")) % "99999");
1006     m_btn_turn = Wnd::Create<CUIButton>(turn_button_longest_reasonable_text.size() > unready_button_longest_reasonable_text.size() ?
1007                                         turn_button_longest_reasonable_text :
1008                                         unready_button_longest_reasonable_text);
1009     m_btn_turn->Resize(m_btn_turn->MinUsableSize());
1010     m_btn_turn->LeftClickedSignal.connect(boost::bind(&MapWnd::EndTurn, this));
1011     m_btn_turn->LeftClickedSignal.connect(&PlayTurnButtonClickSound);
1012     RefreshTurnButtonTooltip();
1013 
1014     boost::filesystem::path button_texture_dir = ClientUI::ArtDir() / "icons" / "buttons";
1015 
1016     // auto turn button
1017     m_btn_auto_turn = Wnd::Create<CUIButton>(
1018         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "manual_turn.png")),
1019         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "auto_turn.png")),
1020         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "manual_turn_mouseover.png")));
1021 
1022     m_btn_auto_turn->LeftClickedSignal.connect(
1023         boost::bind(&MapWnd::ToggleAutoEndTurn, this));
1024     m_btn_auto_turn->Resize(GG::Pt(GG::X(24), GG::Y(24)));
1025     m_btn_auto_turn->SetMinSize(GG::Pt(GG::X(24), GG::Y(24)));
1026     ToggleAutoEndTurn();    // toggle twice to set textures without changing default setting state
1027     ToggleAutoEndTurn();
1028 
1029     // timeout remain label
1030     // determine size from the text that will go into label, using a test time string
1031     std::string timeout_seconds_longest_reasonable_text = boost::io::str(FlexibleFormat(UserString("MAP_TIMEOUT_SECONDS")) % 59); // seconds part never exceeds 59
1032     std::string timeout_mins_secs_longest_reasonable_text = boost::io::str(FlexibleFormat(UserString("MAP_TIMEOUT_MINS_SECS")) % 59 % 59); // seconds and minutes part never exceeds 59
1033     std::string timeout_hrs_mins_longest_reasonable_text = boost::io::str(FlexibleFormat(UserString("MAP_TIMEOUT_HRS_MINS")) % 999 % 59); // minutes part never exceeds 59, turn interval hopefully doesn't exceed month
1034     const auto& timeout_1_longest_reasonable_text = timeout_seconds_longest_reasonable_text.size() > timeout_mins_secs_longest_reasonable_text.size() ?
1035                                                     timeout_seconds_longest_reasonable_text :
1036                                                     timeout_mins_secs_longest_reasonable_text;
1037     m_timeout_remain = Wnd::Create<CUILabel>(timeout_hrs_mins_longest_reasonable_text.size() > timeout_1_longest_reasonable_text.size() ?
1038                                              timeout_hrs_mins_longest_reasonable_text :
1039                                              timeout_1_longest_reasonable_text);
1040     m_timeout_remain->Resize(m_timeout_remain->MinUsableSize());
1041 
1042     // FPS indicator
1043     m_FPS = GG::Wnd::Create<FPSIndicator>();
1044     m_FPS->Hide();
1045 
1046     // create custom InWindow function for Menu button that extends its
1047     // clickable area to the adjacent edges of the toolbar containing it
1048     std::function<bool (const SettableInWindowCUIButton*, const GG::Pt&)> in_window_func =
1049         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1050                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1051     // Menu button
1052     m_btn_menu = Wnd::Create<SettableInWindowCUIButton>(
1053         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "menu.png")),
1054         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "menu_clicked.png")),
1055         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "menu_mouseover.png")),
1056         in_window_func);
1057     m_btn_menu->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1058     m_btn_menu->LeftClickedSignal.connect(boost::bind(&MapWnd::ShowMenu, this));
1059     m_btn_menu->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1060     m_btn_menu->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1061         UserString("MAP_BTN_MENU"), UserString("MAP_BTN_MENU_DESC")));
1062 
1063     in_window_func =
1064         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1065                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1066     // Encyclo"pedia" button
1067     m_btn_pedia = Wnd::Create<SettableInWindowCUIButton>(
1068         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "pedia.png")),
1069         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "pedia_clicked.png")),
1070         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "pedia_mouseover.png")),
1071         in_window_func);
1072     m_btn_pedia->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1073     m_btn_pedia->LeftClickedSignal.connect(boost::bind(&MapWnd::TogglePedia, this));
1074     m_btn_pedia->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1075     m_btn_pedia->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1076         UserString("MAP_BTN_PEDIA"), UserString("MAP_BTN_PEDIA_DESC")));
1077 
1078     in_window_func =
1079         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1080                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1081     // Graphs button
1082     m_btn_graphs = Wnd::Create<SettableInWindowCUIButton>(
1083         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "charts.png")),
1084         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "charts_clicked.png")),
1085         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "charts_mouseover.png")),
1086         in_window_func);
1087     m_btn_graphs->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1088     m_btn_graphs->LeftClickedSignal.connect(boost::bind(&MapWnd::ShowGraphs, this));
1089     m_btn_graphs->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1090     m_btn_graphs->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1091         UserString("MAP_BTN_GRAPH"), UserString("MAP_BTN_GRAPH_DESC")));
1092 
1093     in_window_func =
1094         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1095                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1096     // Design button
1097     m_btn_design = Wnd::Create<SettableInWindowCUIButton>(
1098         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "design.png")),
1099         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "design_clicked.png")),
1100         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "design_mouseover.png")),
1101         in_window_func);
1102     m_btn_design->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1103     m_btn_design->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleDesign, this));
1104     m_btn_design->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1105     m_btn_design->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1106         UserString("MAP_BTN_DESIGN"), UserString("MAP_BTN_DESIGN_DESC")));
1107 
1108     in_window_func =
1109         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1110                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1111     // Production button
1112     m_btn_production = Wnd::Create<SettableInWindowCUIButton>(
1113         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "production.png")),
1114         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "production_clicked.png")),
1115         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "production_mouseover.png")),
1116         in_window_func);
1117     m_btn_production->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1118     m_btn_production->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleProduction, this));
1119     m_btn_production->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1120     m_btn_production->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1121         UserString("MAP_BTN_PRODUCTION"), UserString("MAP_BTN_PRODUCTION_DESC")));
1122 
1123     in_window_func =
1124         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1125                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1126     // Research button
1127     m_btn_research = Wnd::Create<SettableInWindowCUIButton>(
1128         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "research.png")),
1129         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "research_clicked.png")),
1130         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "research_mouseover.png")),
1131         in_window_func);
1132     m_btn_research->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1133     m_btn_research->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleResearch, this));
1134     m_btn_research->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1135     m_btn_research->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1136         UserString("MAP_BTN_RESEARCH"), UserString("MAP_BTN_RESEARCH_DESC")));
1137 
1138     in_window_func =
1139         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1140                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1141     // Objects button
1142     m_btn_objects = Wnd::Create<SettableInWindowCUIButton>(
1143         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "objects.png")),
1144         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "objects_clicked.png")),
1145         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "objects_mouseover.png")),
1146         in_window_func);
1147     m_btn_objects->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1148     m_btn_objects->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleObjects, this));
1149     m_btn_objects->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1150     m_btn_objects->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1151         UserString("MAP_BTN_OBJECTS"), UserString("MAP_BTN_OBJECTS_DESC")));
1152 
1153     in_window_func =
1154         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1155                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1156     // Empires button
1157     m_btn_empires = Wnd::Create<SettableInWindowCUIButton>(
1158         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "empires.png")),
1159         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "empires_clicked.png")),
1160         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "empires_mouseover.png")),
1161         in_window_func);
1162     m_btn_empires->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1163     m_btn_empires->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleEmpires, this));
1164     m_btn_empires->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1165     m_btn_empires->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1166         UserString("MAP_BTN_EMPIRES"), UserString("MAP_BTN_EMPIRES_DESC")));
1167 
1168     in_window_func =
1169         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1170                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1171     // SitRep button
1172     m_btn_siterep = Wnd::Create<SettableInWindowCUIButton>(
1173         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "sitrep.png")),
1174         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "sitrep_clicked.png")),
1175         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "sitrep_mouseover.png")),
1176         in_window_func);
1177     m_btn_siterep->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1178     m_btn_siterep->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleSitRep, this));
1179     m_btn_siterep->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1180     m_btn_siterep->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1181         UserString("MAP_BTN_SITREP"), UserString("MAP_BTN_SITREP_DESC")));
1182 
1183     in_window_func =
1184         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1185                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1186     // Messages button
1187     m_btn_messages = Wnd::Create<SettableInWindowCUIButton>(
1188         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "messages.png")),
1189         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "messages_clicked.png")),
1190         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "messages_mouseover.png")),
1191         in_window_func);
1192     m_btn_messages->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1193     m_btn_messages->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleMessages, this));
1194     m_btn_messages->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1195     m_btn_messages->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1196         UserString("MAP_BTN_MESSAGES"), UserString("MAP_BTN_MESSAGES_DESC")));
1197 
1198     in_window_func =
1199         boost::bind(&InRect, boost::bind(&WndLeft, _1), boost::bind(&WndTop, m_toolbar.get()),
1200                     boost::bind(&WndRight, _1), boost::bind(&WndBottom, _1), _2);
1201     // Moderator button
1202     m_btn_moderator = Wnd::Create<SettableInWindowCUIButton>(
1203         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "moderator.png")),
1204         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "moderator_clicked.png")),
1205         GG::SubTexture(ClientUI::GetTexture(button_texture_dir / "moderator_mouseover.png")),
1206         in_window_func);
1207     m_btn_moderator->SetMinSize(GG::Pt(GG::X(32), GG::Y(32)));
1208     m_btn_moderator->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleModeratorActions, this));
1209     m_btn_moderator->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1210     m_btn_moderator->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
1211         UserString("MAP_BTN_MODERATOR"), UserString("MAP_BTN_MODERATOR_DESC")));
1212 
1213 
1214     // resources
1215     const GG::X ICON_DUAL_WIDTH(100);
1216     const GG::X ICON_WIDTH(24);
1217     m_population = GG::Wnd::Create<StatisticIcon>(ClientUI::MeterIcon(METER_POPULATION), 0, 3, false,
1218                                                   ICON_DUAL_WIDTH, m_btn_turn->Height());
1219     m_population->SetName("Population StatisticIcon");
1220 
1221     m_industry = GG::Wnd::Create<StatisticIcon>(ClientUI::MeterIcon(METER_INDUSTRY), 0, 3, false,
1222                                                 ICON_DUAL_WIDTH, m_btn_turn->Height());
1223     m_industry->SetName("Industry StatisticIcon");
1224     m_industry->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleProduction, this));
1225 
1226     m_stockpile = GG::Wnd::Create<StatisticIcon>(ClientUI::MeterIcon(METER_STOCKPILE), 0, 3, false,
1227                                                  ICON_DUAL_WIDTH, m_btn_turn->Height());
1228     m_stockpile->SetName("Stockpile StatisticIcon");
1229 
1230     m_research = GG::Wnd::Create<StatisticIcon>(ClientUI::MeterIcon(METER_RESEARCH), 0, 3, false,
1231                                                 ICON_DUAL_WIDTH, m_btn_turn->Height());
1232     m_research->SetName("Research StatisticIcon");
1233     m_research->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleResearch, this));
1234 
1235     m_trade = GG::Wnd::Create<StatisticIcon>(ClientUI::MeterIcon(METER_TRADE), 0, 3, false,
1236                                              ICON_DUAL_WIDTH, m_btn_turn->Height());
1237     m_trade->SetName("Trade StatisticIcon");
1238 
1239     m_fleet = GG::Wnd::Create<StatisticIcon>(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "sitrep" / "fleet_arrived.png"),
1240                                              0, 3, false,
1241                                              ICON_DUAL_WIDTH, m_btn_turn->Height());
1242     m_fleet->SetName("Fleet StatisticIcon");
1243 
1244     m_detection = GG::Wnd::Create<StatisticIcon>(ClientUI::MeterIcon(METER_DETECTION), 0, 3, false,
1245                                                  ICON_DUAL_WIDTH, m_btn_turn->Height());
1246     m_detection->SetName("Detection StatisticIcon");
1247 
1248     GG::SubTexture wasted_ressource_subtexture = GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
1249                                                                 "wasted_resource.png", false));
1250     GG::SubTexture wasted_ressource_mouseover_subtexture = GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
1251                                                                 "wasted_resource_mouseover.png", false));
1252     GG::SubTexture wasted_ressource_clicked_subtexture = GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
1253                                                                 "wasted_resource_clicked.png", false));
1254 
1255     m_industry_wasted = Wnd::Create<CUIButton>(
1256         wasted_ressource_subtexture,
1257         wasted_ressource_clicked_subtexture,
1258         wasted_ressource_mouseover_subtexture);
1259 
1260     m_research_wasted = Wnd::Create<CUIButton>(
1261         wasted_ressource_subtexture,
1262         wasted_ressource_clicked_subtexture,
1263         wasted_ressource_mouseover_subtexture);
1264 
1265     m_industry_wasted->Resize(GG::Pt(ICON_WIDTH, GG::Y(Value(ICON_WIDTH))));
1266     m_industry_wasted->SetMinSize(GG::Pt(ICON_WIDTH, GG::Y(Value(ICON_WIDTH))));
1267     m_research_wasted->Resize(GG::Pt(ICON_WIDTH, GG::Y(Value(ICON_WIDTH))));
1268     m_research_wasted->SetMinSize(GG::Pt(ICON_WIDTH, GG::Y(Value(ICON_WIDTH))));
1269 
1270     m_industry_wasted->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1271     m_research_wasted->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1272 
1273     m_industry_wasted->LeftClickedSignal.connect(boost::bind(&MapWnd::ZoomToSystemWithWastedPP, this));
1274     m_research_wasted->LeftClickedSignal.connect(boost::bind(&MapWnd::ToggleResearch, this));
1275 
1276     //Set the correct tooltips
1277     RefreshIndustryResourceIndicator();
1278     RefreshResearchResourceIndicator();
1279 
1280 
1281 
1282     /////////////////////////////////////
1283     // place buttons / icons on toolbar
1284     /////////////////////////////////////
1285     int layout_column(0);
1286 
1287     layout->SetMinimumColumnWidth(layout_column, m_btn_turn->Width());
1288     layout->SetColumnStretch(layout_column, 0.0);
1289     layout->Add(m_btn_turn,         0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1290     ++layout_column;
1291 
1292     layout->SetMinimumColumnWidth(layout_column, ICON_WIDTH);
1293     layout->SetColumnStretch(layout_column, 0.0);
1294     layout->Add(m_btn_auto_turn,    0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1295     ++layout_column;
1296 
1297     layout->SetMinimumColumnWidth(layout_column, m_timeout_remain->Width());
1298     layout->SetColumnStretch(layout_column, 0.0);
1299     layout->Add(m_timeout_remain,   0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1300     ++layout_column;
1301 
1302     layout->SetMinimumColumnWidth(layout_column, GG::X(ClientUI::Pts()*4));
1303     layout->SetColumnStretch(layout_column, 0.0);
1304     layout->Add(m_FPS,              0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1305     ++layout_column;
1306 
1307     layout->SetMinimumColumnWidth(layout_column, ICON_WIDTH);
1308     layout->SetColumnStretch(layout_column, 0.0);
1309     layout->Add(m_industry_wasted,  0, layout_column, GG::ALIGN_RIGHT | GG::ALIGN_VCENTER);
1310     ++layout_column;
1311 
1312     layout->SetColumnStretch(layout_column, 1.0);
1313     layout->Add(m_industry, 0, layout_column, GG::ALIGN_LEFT | GG::ALIGN_VCENTER);
1314     ++layout_column;
1315 
1316     layout->SetColumnStretch(layout_column, 1.2);
1317     layout->Add(m_stockpile, 0, layout_column, GG::ALIGN_LEFT | GG::ALIGN_VCENTER);
1318     ++layout_column;
1319 
1320     layout->SetMinimumColumnWidth(layout_column, ICON_WIDTH);
1321     layout->SetColumnStretch(layout_column, 0.0);
1322     layout->Add(m_research_wasted,  0, layout_column, GG::ALIGN_RIGHT | GG::ALIGN_VCENTER);
1323     ++layout_column;
1324 
1325     layout->SetColumnStretch(layout_column, 1.0);
1326     layout->Add(m_research,         0, layout_column, GG::ALIGN_LEFT | GG::ALIGN_VCENTER);
1327     ++layout_column;
1328 
1329     layout->SetColumnStretch(layout_column, 1.0);
1330     layout->Add(m_fleet,            0, layout_column, GG::ALIGN_LEFT | GG::ALIGN_VCENTER);
1331     ++layout_column;
1332 
1333     layout->SetColumnStretch(layout_column, 1.0);
1334     layout->Add(m_population,       0, layout_column, GG::ALIGN_LEFT | GG::ALIGN_VCENTER);
1335     ++layout_column;
1336 
1337     layout->SetColumnStretch(layout_column, 1.0);
1338     layout->Add(m_detection,        0, layout_column, GG::ALIGN_LEFT | GG::ALIGN_VCENTER);
1339     ++layout_column;
1340 
1341     layout->SetMinimumColumnWidth(layout_column, m_btn_moderator->Width());
1342     layout->SetColumnStretch(layout_column, 0.0);
1343     layout->Add(m_btn_moderator,    0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1344     ++layout_column;
1345 
1346     layout->SetMinimumColumnWidth(layout_column, m_btn_messages->Width());
1347     layout->SetColumnStretch(layout_column, 0.0);
1348     layout->Add(m_btn_messages,    0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1349     ++layout_column;
1350 
1351     layout->SetMinimumColumnWidth(layout_column, m_btn_siterep->Width());
1352     layout->SetColumnStretch(layout_column, 0.0);
1353     layout->Add(m_btn_siterep,      0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1354     ++layout_column;
1355 
1356     layout->SetMinimumColumnWidth(layout_column, m_btn_empires->Width());
1357     layout->SetColumnStretch(layout_column, 0.0);
1358     layout->Add(m_btn_empires,      0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1359     ++layout_column;
1360 
1361     layout->SetMinimumColumnWidth(layout_column, m_btn_objects->Width());
1362     layout->SetColumnStretch(layout_column, 0.0);
1363     layout->Add(m_btn_objects,      0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1364     ++layout_column;
1365 
1366     layout->SetMinimumColumnWidth(layout_column, m_btn_research->Width());
1367     layout->SetColumnStretch(layout_column, 0.0);
1368     layout->Add(m_btn_research,     0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1369     ++layout_column;
1370 
1371     layout->SetMinimumColumnWidth(layout_column, m_btn_production->Width());
1372     layout->SetColumnStretch(layout_column, 0.0);
1373     layout->Add(m_btn_production,   0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1374     ++layout_column;
1375 
1376     layout->SetMinimumColumnWidth(layout_column, m_btn_design->Width());
1377     layout->SetColumnStretch(layout_column, 0.0);
1378     layout->Add(m_btn_design,       0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1379     ++layout_column;
1380 
1381     layout->SetMinimumColumnWidth(layout_column, m_btn_graphs->Width());
1382     layout->SetColumnStretch(layout_column, 0.0);
1383     layout->Add(m_btn_graphs,       0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1384     ++layout_column;
1385 
1386     layout->SetMinimumColumnWidth(layout_column, m_btn_pedia->Width());
1387     layout->SetColumnStretch(layout_column, 0.0);
1388     layout->Add(m_btn_pedia,        0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1389     ++layout_column;
1390 
1391     layout->SetMinimumColumnWidth(layout_column, m_btn_menu->Width());
1392     layout->SetColumnStretch(layout_column, 0.0);
1393     layout->Add(m_btn_menu,         0, layout_column, GG::ALIGN_CENTER | GG::ALIGN_VCENTER);
1394     ++layout_column;
1395 
1396     layout->SetCellMargin(5);
1397     layout->SetBorderMargin(5);
1398 
1399 
1400     ///////////////////
1401     // Misc widgets on map screen
1402     ///////////////////
1403 
1404     // scale line
1405     m_scale_line = GG::Wnd::Create<MapScaleLine>(GG::X(LAYOUT_MARGIN),   GG::Y(LAYOUT_MARGIN) + m_toolbar->Height(),
1406                                                  SCALE_LINE_MAX_WIDTH,   SCALE_LINE_HEIGHT);
1407     GG::GUI::GetGUI()->Register(m_scale_line);
1408     int sel_system_id = SidePanel::SystemID();
1409     m_scale_line->Update(ZoomFactor(), m_selected_fleet_ids, sel_system_id);
1410     m_scale_line->Hide();
1411 
1412     // Zoom slider
1413     const int ZOOM_SLIDER_MIN = static_cast<int>(ZOOM_IN_MIN_STEPS),
1414               ZOOM_SLIDER_MAX = static_cast<int>(ZOOM_IN_MAX_STEPS);
1415     m_zoom_slider = GG::Wnd::Create<CUISlider<double>>(ZOOM_SLIDER_MIN, ZOOM_SLIDER_MAX, GG::VERTICAL, GG::INTERACTIVE | GG::ONTOP);
1416     m_zoom_slider->MoveTo(GG::Pt(m_btn_turn->Left(), m_scale_line->Bottom() + GG::Y(LAYOUT_MARGIN)));
1417     m_zoom_slider->Resize(GG::Pt(GG::X(ClientUI::ScrollWidth()), ZOOM_SLIDER_HEIGHT));
1418     m_zoom_slider->SlideTo(m_zoom_steps_in);
1419     GG::GUI::GetGUI()->Register(m_zoom_slider);
1420     m_zoom_slider->Hide();
1421     m_zoom_slider->SlidSignal.connect(boost::bind(
1422         static_cast<void (MapWnd::*)(double, bool)>(&MapWnd::SetZoom), this, _1, false));
1423     GetOptionsDB().OptionChangedSignal("ui.map.zoom.slider.shown").connect(
1424         boost::bind(&MapWnd::RefreshSliders, this));
1425 
1426     ///////////////////
1427     // Map sub-windows
1428     ///////////////////
1429 
1430     // system-view side panel
1431     m_side_panel = GG::Wnd::Create<SidePanel>(MAP_SIDEPANEL_WND_NAME);
1432     GG::GUI::GetGUI()->Register(m_side_panel);
1433 
1434     SidePanel::SystemSelectedSignal.connect(        boost::bind(&MapWnd::SelectSystem, this, _1));
1435     SidePanel::PlanetSelectedSignal.connect(        boost::bind(&MapWnd::SelectPlanet, this, _1));
1436     SidePanel::PlanetDoubleClickedSignal.connect(   boost::bind(&MapWnd::PlanetDoubleClicked, this, _1));
1437     SidePanel::PlanetRightClickedSignal.connect(    boost::bind(&MapWnd::PlanetRightClicked, this, _1));
1438     SidePanel::BuildingRightClickedSignal.connect(  boost::bind(&MapWnd::BuildingRightClicked, this, _1));
1439 
1440     // not strictly necessary, as in principle whenever any ResourceCenter
1441     // changes, all meter estimates and resource pools should / could be
1442     // updated.  however, this is a convenience to limit the updates to
1443     // what is actually being shown in the sidepanel right now, which is
1444     // useful since most ResourceCenter changes will be due to focus
1445     // changes on the sidepanel, and most differences in meter estimates
1446     // and resource pools due to this will be in the same system
1447     SidePanel::ResourceCenterChangedSignal.connect(
1448         boost::bind(&MapWnd::UpdateSidePanelSystemObjectMetersAndResourcePools, this));
1449 
1450     // situation report window
1451     m_sitrep_panel = GG::Wnd::Create<SitRepPanel>(SITREP_WND_NAME);
1452     // Wnd is manually closed by user
1453     m_sitrep_panel->ClosingSignal.connect(boost::bind(&MapWnd::HideSitRep, this));
1454     if (m_sitrep_panel->Visible()) {
1455         PushWndStack(m_sitrep_panel);
1456         m_btn_siterep->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "sitrep_mouseover.png")));
1457         m_btn_siterep->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "sitrep.png")));
1458     }
1459 
1460     // encyclopedia panel
1461     m_pedia_panel = GG::Wnd::Create<EncyclopediaDetailPanel>(GG::ONTOP | GG::INTERACTIVE | GG::DRAGABLE | GG::RESIZABLE | CLOSABLE | PINABLE, MAP_PEDIA_WND_NAME);
1462     // Wnd is manually closed by user
1463     m_pedia_panel->ClosingSignal.connect(boost::bind(&MapWnd::HidePedia, this));
1464     if (m_pedia_panel->Visible()) {
1465         PushWndStack(m_pedia_panel);
1466         m_btn_pedia->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "pedia_mouseover.png")));
1467         m_btn_pedia->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "pedia.png")));
1468     }
1469 
1470     // objects list
1471     m_object_list_wnd = GG::Wnd::Create<ObjectListWnd>(OBJECT_WND_NAME);
1472     // Wnd is manually closed by user
1473     m_object_list_wnd->ClosingSignal.connect(boost::bind(&MapWnd::HideObjects, this));
1474     m_object_list_wnd->ObjectDumpSignal.connect(boost::bind(&ClientUI::DumpObject, ClientUI::GetClientUI(), _1));
1475     if (m_object_list_wnd->Visible()) {
1476         PushWndStack(m_object_list_wnd);
1477         m_btn_objects->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "objects_mouseover.png")));
1478         m_btn_objects->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "objects.png")));
1479     }
1480 
1481     // moderator actions
1482     m_moderator_wnd = GG::Wnd::Create<ModeratorActionsWnd>(MODERATOR_WND_NAME);
1483     m_moderator_wnd->ClosingSignal.connect(boost::bind(&MapWnd::HideModeratorActions, this));
1484     if (m_moderator_wnd->Visible()) {
1485         PushWndStack(m_moderator_wnd);
1486         m_btn_moderator->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "moderator_mouseover.png")));
1487         m_btn_moderator->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "moderator.png")));
1488     }
1489 
1490     // Combat report
1491     m_combat_report_wnd = GG::Wnd::Create<CombatReportWnd>(COMBAT_REPORT_WND_NAME);
1492 
1493     // position CUIWnds owned by the MapWnd
1494     InitializeWindows();
1495 
1496     // messages and empires windows
1497     if (ClientUI* cui = ClientUI::GetClientUI()) {
1498         if (const auto& msg_wnd = cui->GetMessageWnd()) {
1499             // Wnd is manually closed by user
1500             msg_wnd->ClosingSignal.connect(boost::bind(&MapWnd::HideMessages, this));
1501             if (msg_wnd->Visible()) {
1502                 PushWndStack(msg_wnd);
1503                 m_btn_messages->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "messages_mouseover.png")));
1504                 m_btn_messages->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "messages.png")));
1505             }
1506         }
1507         if (const auto& plr_wnd = cui->GetPlayerListWnd()) {
1508             // Wnd is manually closed by user
1509             plr_wnd->ClosingSignal.connect(boost::bind(&MapWnd::HideEmpires, this));
1510             if (plr_wnd->Visible()) {
1511                 PushWndStack(plr_wnd);
1512                 m_btn_empires->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "empires_mouseover.png")));
1513                 m_btn_empires->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "empires.png")));
1514             }
1515         }
1516     }
1517 
1518     HumanClientApp::GetApp()->RepositionWindowsSignal.connect(
1519         boost::bind(&MapWnd::InitializeWindows, this));
1520 
1521     // research window
1522     m_research_wnd = GG::Wnd::Create<ResearchWnd>(AppWidth(), AppHeight() - m_toolbar->Height());
1523     m_research_wnd->MoveTo(GG::Pt(GG::X0, m_toolbar->Height()));
1524     GG::GUI::GetGUI()->Register(m_research_wnd);
1525     m_research_wnd->Hide();
1526 
1527     // production window
1528     m_production_wnd = GG::Wnd::Create<ProductionWnd>(AppWidth(), AppHeight() - m_toolbar->Height());
1529     m_production_wnd->MoveTo(GG::Pt(GG::X0, m_toolbar->Height()));
1530     GG::GUI::GetGUI()->Register(m_production_wnd);
1531     m_production_wnd->Hide();
1532     m_production_wnd->SystemSelectedSignal.connect(boost::bind(&MapWnd::SelectSystem, this, _1));
1533     m_production_wnd->PlanetSelectedSignal.connect(boost::bind(&MapWnd::SelectPlanet, this, _1));
1534 
1535     // design window
1536     m_design_wnd = GG::Wnd::Create<DesignWnd>(AppWidth(), AppHeight() - m_toolbar->Height());
1537     m_design_wnd->MoveTo(GG::Pt(GG::X0, m_toolbar->Height()));
1538     GG::GUI::GetGUI()->Register(m_design_wnd);
1539     m_design_wnd->Hide();
1540 
1541 
1542 
1543     //////////////////
1544     // General Gamestate response signals
1545     //////////////////
1546     FleetUIManager& fm = FleetUIManager::GetFleetUIManager();
1547     fm.ActiveFleetWndChangedSignal.connect(boost::bind(&MapWnd::SelectedFleetsChanged, this));
1548     fm.ActiveFleetWndSelectedFleetsChangedSignal.connect(boost::bind(&MapWnd::SelectedFleetsChanged, this));
1549     fm.ActiveFleetWndSelectedShipsChangedSignal.connect(boost::bind(&MapWnd::SelectedShipsChanged, this));
1550     fm.FleetRightClickedSignal.connect(boost::bind(&MapWnd::FleetRightClicked, this, _1));
1551     fm.ShipRightClickedSignal.connect(boost::bind(&MapWnd::ShipRightClicked, this, _1));
1552 
1553     DoLayout();
1554 
1555     // Connect keyboard accelerators for map
1556     ConnectKeyboardAcceleratorSignals();
1557 
1558     m_timeout_clock.Stop();
1559     m_timeout_clock.Connect(this);
1560 }
1561 
~MapWnd()1562 MapWnd::~MapWnd()
1563 {}
1564 
DoLayout()1565 void MapWnd::DoLayout() {
1566     m_toolbar->Resize(GG::Pt(AppWidth(), TOOLBAR_HEIGHT));
1567     m_research_wnd->Resize(GG::Pt(AppWidth(), AppHeight() - m_toolbar->Height()));
1568     m_production_wnd->Resize(GG::Pt(AppWidth(), AppHeight() - m_toolbar->Height()));
1569     m_design_wnd->Resize(GG::Pt(AppWidth(), AppHeight() - m_toolbar->Height()));
1570     m_sitrep_panel->ValidatePosition();
1571     m_object_list_wnd->ValidatePosition();
1572     m_pedia_panel->ValidatePosition();
1573     m_side_panel->ValidatePosition();
1574     m_combat_report_wnd->ValidatePosition();
1575     m_moderator_wnd->ValidatePosition();
1576 
1577     if (ClientUI* cui = ClientUI::GetClientUI()) {
1578         if (const auto& msg_wnd = cui->GetMessageWnd())
1579             msg_wnd->ValidatePosition();
1580         if (const auto& plr_wnd = cui->GetPlayerListWnd())
1581             plr_wnd->ValidatePosition();
1582     }
1583 
1584     FleetUIManager::GetFleetUIManager().CullEmptyWnds();
1585     for (auto& fwnd : FleetUIManager::GetFleetUIManager()) {
1586         if (auto wnd = fwnd.lock()) {
1587             wnd->ValidatePosition();
1588         } else {
1589             ErrorLogger() << "MapWnd::DoLayout(): null FleetWnd* found in the FleetUIManager::iterator.";
1590         }
1591     }
1592 }
1593 
InitializeWindows()1594 void MapWnd::InitializeWindows() {
1595     const GG::X SIDEPANEL_WIDTH(GetOptionsDB().Get<int>("ui.map.sidepanel.width"));
1596 
1597     // system-view side panel
1598     const GG::Pt sidepanel_ul(AppWidth() - SIDEPANEL_WIDTH, m_toolbar->Bottom());
1599     const GG::Pt sidepanel_wh(SIDEPANEL_WIDTH, AppHeight() - m_toolbar->Height());
1600 
1601     // situation report window
1602     const GG::Pt sitrep_ul(SCALE_LINE_MAX_WIDTH + LAYOUT_MARGIN, m_toolbar->Bottom());
1603     const GG::Pt sitrep_wh(SITREP_PANEL_WIDTH, SITREP_PANEL_HEIGHT);
1604 
1605     // encyclopedia panel
1606     const GG::Pt pedia_ul(SCALE_LINE_MAX_WIDTH + LAYOUT_MARGIN, m_toolbar->Bottom() + SITREP_PANEL_HEIGHT);
1607     const GG::Pt pedia_wh(SITREP_PANEL_WIDTH, SITREP_PANEL_HEIGHT);
1608 
1609     // objects list
1610     const GG::Pt object_list_ul(GG::X0, m_scale_line->Bottom() + GG::Y(LAYOUT_MARGIN));
1611     const GG::Pt object_list_wh(SITREP_PANEL_WIDTH, SITREP_PANEL_HEIGHT);
1612 
1613     // moderator actions
1614     const GG::Pt moderator_ul(GG::X0, m_scale_line->Bottom() + GG::Y(LAYOUT_MARGIN));
1615     const GG::Pt moderator_wh(SITREP_PANEL_WIDTH, SITREP_PANEL_HEIGHT);
1616 
1617     // Combat report
1618     // These values were formerly in UI/CombatReport/CombatReportWnd.cpp
1619     const GG::Pt combat_log_ul(GG::X(150), GG::Y(50));
1620     const GG::Pt combat_log_wh(GG::X(400), GG::Y(300));
1621 
1622     m_side_panel->       InitSizeMove(sidepanel_ul,   sidepanel_ul + sidepanel_wh);
1623     m_sitrep_panel->     InitSizeMove(sitrep_ul,      sitrep_ul + sitrep_wh);
1624     m_pedia_panel->      InitSizeMove(pedia_ul,       pedia_ul + pedia_wh);
1625     m_object_list_wnd->  InitSizeMove(object_list_ul, object_list_ul + object_list_wh);
1626     m_moderator_wnd->    InitSizeMove(moderator_ul,   moderator_ul + moderator_wh);
1627     m_combat_report_wnd->InitSizeMove(combat_log_ul,  combat_log_ul + combat_log_wh);
1628 }
1629 
ClientUpperLeft() const1630 GG::Pt MapWnd::ClientUpperLeft() const
1631 { return UpperLeft() + GG::Pt(AppWidth(), AppHeight()); }
1632 
ZoomFactor() const1633 double MapWnd::ZoomFactor() const
1634 { return ZoomScaleFactor(m_zoom_steps_in); }
1635 
ScreenCoordsFromUniversePosition(double universe_x,double universe_y) const1636 GG::Pt MapWnd::ScreenCoordsFromUniversePosition(double universe_x, double universe_y) const {
1637     GG::Pt cl_ul = ClientUpperLeft();
1638     GG::X x((universe_x * ZoomFactor()) + cl_ul.x);
1639     GG::Y y((universe_y * ZoomFactor()) + cl_ul.y);
1640     return GG::Pt(x, y);
1641 }
1642 
UniversePositionFromScreenCoords(GG::Pt screen_coords) const1643 std::pair<double, double> MapWnd::UniversePositionFromScreenCoords(GG::Pt screen_coords) const {
1644     GG::Pt cl_ul = ClientUpperLeft();
1645     double x = Value((screen_coords - cl_ul).x / ZoomFactor());
1646     double y = Value((screen_coords - cl_ul).y / ZoomFactor());
1647     return std::pair<double, double>(x, y);
1648 }
1649 
SelectedPlanetID() const1650 int MapWnd::SelectedPlanetID() const
1651 { return m_production_wnd->SelectedPlanetID(); }
1652 
GetSaveGameUIData(SaveGameUIData & data) const1653 void MapWnd::GetSaveGameUIData(SaveGameUIData& data) const {
1654     data.map_left = Value(Left());
1655     data.map_top = Value(Top());
1656     data.map_zoom_steps_in = m_zoom_steps_in;
1657     data.fleets_exploring = m_fleets_exploring;
1658 }
1659 
InProductionViewMode() const1660 bool MapWnd::InProductionViewMode() const
1661 { return m_in_production_view_mode; }
1662 
InResearchViewMode() const1663 bool MapWnd::InResearchViewMode() const
1664 { return m_research_wnd->Visible(); }
1665 
InDesignViewMode() const1666 bool MapWnd::InDesignViewMode() const
1667 { return m_design_wnd->Visible(); }
1668 
GetModeratorActionSetting() const1669 ModeratorActionSetting MapWnd::GetModeratorActionSetting() const
1670 { return m_moderator_wnd->SelectedAction(); }
1671 
AutoEndTurnEnabled() const1672 bool MapWnd::AutoEndTurnEnabled() const
1673 { return m_auto_end_turn; }
1674 
PreRender()1675 void MapWnd::PreRender() {
1676     // Save CPU / GPU activity by skipping rendering when it's not needed
1677     // As of this writing, the design and research screens have fully opaque backgrounds.
1678     if (m_design_wnd->Visible())
1679         return;
1680     if (m_research_wnd->Visible())
1681         return;
1682 
1683     GG::Wnd::PreRender();
1684     DeferredRefreshFleetButtons();
1685 }
1686 
Render()1687 void MapWnd::Render() {
1688     // HACK! This is placed here so we can be sure it is executed frequently
1689     // (every time we render), and before we render any of the
1690     // FleetWnds.  It doesn't necessarily belong in MapWnd at all.
1691     FleetUIManager::GetFleetUIManager().CullEmptyWnds();
1692 
1693     // Save CPU / GPU activity by skipping rendering when it's not needed
1694     // As of this writing, the design and research screens have fully opaque backgrounds.
1695     if (m_design_wnd->Visible())
1696         return;
1697     if (m_research_wnd->Visible())
1698         return;
1699 
1700     glMatrixMode(GL_MODELVIEW);
1701     glPushMatrix();
1702     glLoadIdentity();
1703 
1704     GG::Pt origin_offset = UpperLeft() + GG::Pt(AppWidth(), AppHeight());
1705     glTranslatef(Value(origin_offset.x), Value(origin_offset.y), 0.0f);
1706     glScalef(ZoomFactor(), ZoomFactor(), 1.0f);
1707 
1708     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
1709 
1710     RenderStarfields();
1711     RenderGalaxyGas();
1712     RenderVisibilityRadii();
1713     RenderFields();
1714     RenderSystemOverlays();
1715     RenderStarlanes();
1716     RenderSystems();
1717     RenderFleetMovementLines();
1718 
1719     glPopClientAttrib();
1720     glPopMatrix();
1721 }
1722 
RenderStarfields()1723 void MapWnd::RenderStarfields() {
1724     if (!GetOptionsDB().Get<bool>("ui.map.background.starfields.shown"))
1725         return;
1726 
1727     double starfield_width = GetUniverse().UniverseWidth();
1728     if (m_starfield_verts.empty()) {
1729         ClearStarfieldRenderingBuffers();
1730         Seed(static_cast<int>(starfield_width));
1731         m_starfield_colours.clear();
1732         std::size_t NUM_STARS = std::pow(2.0, 12.0);
1733 
1734         m_starfield_verts.reserve(NUM_STARS);
1735         m_starfield_colours.reserve(NUM_STARS);
1736         for (std::size_t i = 0; i < NUM_STARS; ++i) {
1737             float x = RandGaussian(starfield_width/2, starfield_width/3);   // RandDouble(0, starfield_width);
1738             float y = RandGaussian(starfield_width/2, starfield_width/3);   // RandDouble(0, starfield_width);
1739             float r2 = (x - starfield_width/2)*(x - starfield_width/2) + (y-starfield_width/2)*(y-starfield_width/2);
1740             float z = RandDouble(-100, 100)*std::exp(-r2/(starfield_width*starfield_width/4));
1741             m_starfield_verts.store(x, y, z);
1742 
1743             float brightness = 1.0f - std::pow(RandZeroToOne(), 2);
1744             m_starfield_colours.store(GG::CLR_WHITE * brightness);
1745         }
1746         m_starfield_verts.createServerBuffer();
1747         m_starfield_colours.createServerBuffer();
1748     }
1749 
1750 
1751     GLfloat window_width = Value(AppWidth());
1752     GLfloat window_height = std::max(1, Value(AppHeight()));
1753     glViewport(0, 0, window_width, window_height);
1754 
1755     bool perspective_starfield = true;
1756     GLfloat zpos_1to1 = 0.0f;
1757     if (perspective_starfield) {
1758         glMatrixMode(GL_PROJECTION);
1759         glPushMatrix();
1760         glLoadIdentity();
1761 
1762         GLfloat aspect_ratio = window_width / window_height;
1763         GLfloat zclip_near = window_height/4;
1764         GLfloat zclip_far = 3*zclip_near;
1765         GLfloat fov_height = zclip_near;
1766         GLfloat fov_width = fov_height * aspect_ratio;
1767         zpos_1to1 = -2*zclip_near;  // stars rendered at -viewport_size units should be 1:1 with ortho projected stuff
1768 
1769         glFrustum(-fov_width,   fov_width,
1770                    fov_height, -fov_height,
1771                    zclip_near,  zclip_far);
1772         glTranslatef(-window_width/2, -window_height/2, 0.0f);
1773     }
1774 
1775 
1776     glMatrixMode(GL_MODELVIEW);
1777     glPushMatrix();
1778     glLoadIdentity();
1779 
1780     // last: standard map panning
1781     GG::Pt origin_offset = UpperLeft() + GG::Pt(AppWidth(), AppHeight());
1782     glTranslatef(Value(origin_offset.x), Value(origin_offset.y), 0.0f);
1783     glScalef(ZoomFactor(), ZoomFactor(), 1.0f);
1784     glTranslatef(0.0, 0.0, zpos_1to1);
1785     glScalef(1.0f, 1.0f, ZoomFactor());
1786 
1787     // first: starfield manipulations
1788     bool rotate_starfield = false;
1789     if (rotate_starfield) {
1790         float ticks = GG::GUI::GetGUI()->Ticks();
1791         glTranslatef(starfield_width/2, starfield_width/2, 0.0f);   // move back to original position
1792         glRotatef(ticks/10, 0.0f, 0.0f, 1.0f);                      // rotate about centre of starfield
1793         glTranslatef(-starfield_width/2, -starfield_width/2, 0.0f); // move centre of starfield to origin
1794     }
1795 
1796 
1797     float point_star_scale = GetOptionsDB().Get<double>("ui.map.background.starfields.scale");
1798     glPointSize(point_star_scale * std::max(1.0, std::sqrt(ZoomFactor())));
1799     glEnable(GL_POINT_SMOOTH);
1800     glDisable(GL_TEXTURE_2D);
1801 
1802     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
1803     glEnableClientState(GL_VERTEX_ARRAY);
1804     glEnableClientState(GL_COLOR_ARRAY);
1805     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
1806 
1807     m_starfield_verts.activate();
1808     m_starfield_colours.activate();
1809     glDrawArrays(GL_POINTS, 0, m_starfield_verts.size());
1810 
1811     glPopClientAttrib();
1812 
1813     glEnable(GL_TEXTURE_2D);
1814     glPointSize(1.0f);
1815 
1816 
1817     if (perspective_starfield) {
1818         glMatrixMode(GL_PROJECTION);
1819         glPopMatrix();
1820     }
1821 
1822 
1823     glMatrixMode(GL_MODELVIEW);
1824     glPopMatrix();
1825 
1826     glViewport(0, 0, window_width, window_height);
1827 }
1828 
RenderFields()1829 void MapWnd::RenderFields() {
1830     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1831 
1832     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
1833     glEnableClientState(GL_VERTEX_ARRAY);
1834     glDisableClientState(GL_COLOR_ARRAY);
1835     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
1836 
1837     // render not visible fields
1838     for (auto& field_buffer : m_field_vertices) {
1839         if (field_buffer.second.second.empty())
1840             continue;
1841 
1842         glBindTexture(GL_TEXTURE_2D, field_buffer.first->OpenGLId());
1843         field_buffer.second.second.activate();
1844         m_field_texture_coords.activate();
1845         glDrawArrays(GL_QUADS, 0, field_buffer.second.second.size());
1846     }
1847 
1848     // if any, render scanlines over not-visible fields
1849     if (!m_field_scanline_circles.empty()
1850         && HumanClientApp::GetApp()->EmpireID() != ALL_EMPIRES
1851         && GetOptionsDB().Get<bool>("ui.map.scanlines.shown"))
1852     {
1853         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
1854         m_field_scanline_circles.activate();
1855         glBindTexture(GL_TEXTURE_2D, 0);
1856         //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1857 
1858         m_scanline_shader.SetColor(GetOptionsDB().Get<GG::Clr>("ui.map.field.scanlines.color"));
1859         m_scanline_shader.StartUsing();
1860 
1861         glDrawArrays(GL_TRIANGLES, 0, m_field_scanline_circles.size());
1862 
1863         m_scanline_shader.StopUsing();
1864         /*glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);*/
1865         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
1866     }
1867 
1868 
1869     // render visible fields
1870     for (auto& field_buffer : m_field_vertices) {
1871         if (field_buffer.second.first.empty())
1872             continue;
1873 
1874         glBindTexture(GL_TEXTURE_2D, field_buffer.first->OpenGLId());
1875         field_buffer.second.first.activate();
1876         m_field_texture_coords.activate();
1877         glDrawArrays(GL_QUADS, 0, field_buffer.second.first.size());
1878     }
1879 
1880 
1881     glPopClientAttrib();
1882 }
1883 
1884 namespace {
GetGasTexture()1885     std::shared_ptr<GG::Texture> GetGasTexture() {
1886         static std::shared_ptr<GG::Texture> gas_texture;
1887         if (!gas_texture) {
1888             gas_texture = ClientUI::GetClientUI()->GetTexture(ClientUI::ArtDir() / "galaxy_decoration" / "gaseous_array.png");
1889             gas_texture->SetFilters(GL_NEAREST, GL_NEAREST);
1890             glBindTexture(GL_TEXTURE_2D, gas_texture->OpenGLId());
1891             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1892             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1893         }
1894         return gas_texture;
1895     }
1896 }
1897 
RenderGalaxyGas()1898 void MapWnd::RenderGalaxyGas() {
1899     if (!GetOptionsDB().Get<bool>("ui.map.background.gas.shown"))
1900         return;
1901     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1902 
1903     if (m_galaxy_gas_quad_vertices.empty())
1904         return;
1905 
1906     glEnable(GL_TEXTURE_2D);
1907     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
1908     glEnableClientState(GL_VERTEX_ARRAY);
1909     glDisableClientState(GL_COLOR_ARRAY);
1910     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
1911 
1912     m_galaxy_gas_quad_vertices.activate();
1913     m_galaxy_gas_texture_coords.activate();
1914 
1915     glBindTexture(GL_TEXTURE_2D, GetGasTexture()->OpenGLId());
1916     glDrawArrays(GL_QUADS, 0, m_galaxy_gas_quad_vertices.size());
1917 
1918     glPopClientAttrib();
1919 }
1920 
RenderSystemOverlays()1921 void MapWnd::RenderSystemOverlays() {
1922     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1923     glPushMatrix();
1924     glLoadIdentity();
1925     for (auto& system_icon : m_system_icons)
1926     { system_icon.second->RenderOverlay(ZoomFactor()); }
1927     glPopMatrix();
1928 }
1929 
RenderSystems()1930 void MapWnd::RenderSystems() {
1931     const float HALO_SCALE_FACTOR = static_cast<float>(SystemHaloScaleFactor());
1932     int empire_id = HumanClientApp::GetApp()->EmpireID();
1933 
1934     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
1935     glEnableClientState(GL_VERTEX_ARRAY);
1936     glDisableClientState(GL_COLOR_ARRAY);
1937     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
1938 
1939     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1940 
1941     if (0.5f < HALO_SCALE_FACTOR && m_star_texture_coords.size()) {
1942         glMatrixMode(GL_TEXTURE);
1943         glTranslatef(0.5f, 0.5f, 0.0f);
1944         glScalef(1.0f / HALO_SCALE_FACTOR, 1.0f / HALO_SCALE_FACTOR, 1.0f);
1945         glTranslatef(-0.5f, -0.5f, 0.0f);
1946         m_star_texture_coords.activate();
1947         for (auto& star_halo_buffer : m_star_halo_quad_vertices) {
1948             if (!star_halo_buffer.second.size())
1949                 continue;
1950             glBindTexture(GL_TEXTURE_2D, star_halo_buffer.first->OpenGLId());
1951             star_halo_buffer.second.activate();
1952             glDrawArrays(GL_QUADS, 0, star_halo_buffer.second.size());
1953         }
1954         glLoadIdentity();
1955         glMatrixMode(GL_MODELVIEW);
1956     }
1957 
1958     if (m_star_texture_coords.size() &&
1959         ClientUI::SystemTinyIconSizeThreshold() < ZoomFactor() * ClientUI::SystemIconSize())
1960     {
1961         m_star_texture_coords.activate();
1962         for (auto& star_core_buffer : m_star_core_quad_vertices) {
1963             if (!star_core_buffer.second.size())
1964                 continue;
1965             glBindTexture(GL_TEXTURE_2D, star_core_buffer.first->OpenGLId());
1966             star_core_buffer.second.activate();
1967             glDrawArrays(GL_QUADS, 0, star_core_buffer.second.size());
1968         }
1969     }
1970 
1971     // circles around system icons and fog over unexplored systems
1972     bool circles = GetOptionsDB().Get<bool>("ui.map.system.circle.shown");
1973     bool fog_scanlines = false;
1974     Universe& universe = GetUniverse();
1975 
1976     if (empire_id != ALL_EMPIRES && GetOptionsDB().Get<bool>("ui.map.scanlines.shown"))
1977         fog_scanlines = true;
1978 
1979     RenderScaleCircle();
1980 
1981     if (fog_scanlines || circles) {
1982         glPushMatrix();
1983         glLoadIdentity();
1984         glDisable(GL_TEXTURE_2D);
1985         glEnable(GL_LINE_SMOOTH);
1986 
1987         // distance between inner and outer system circle
1988         const double circle_distance = GetOptionsDB().Get<double>("ui.map.system.circle.distance");
1989         // width of outer...
1990         const double outer_circle_width = GetOptionsDB().Get<double>("ui.map.system.circle.outer.width");
1991         // ... and inner circle line at close zoom
1992         const double inner_circle_width = GetOptionsDB().Get<double>("ui.map.system.circle.inner.width");
1993         // width of inner circle line when map is zoomed out
1994         const double max_inner_circle_width = GetOptionsDB().Get<double>("ui.map.system.circle.inner.max.width");
1995 
1996         for (const auto& system_icon : m_system_icons) {
1997             const auto& icon = system_icon.second;
1998 
1999             GG::Pt icon_size = icon->LowerRight() - icon->UpperLeft();
2000             GG::Pt icon_middle = icon->UpperLeft() + (icon_size / 2);
2001 
2002             GG::Pt circle_size = GG::Pt(static_cast<GG::X>(icon->EnclosingCircleDiameter()),
2003                                         static_cast<GG::Y>(icon->EnclosingCircleDiameter()));
2004 
2005             GG::Pt circle_ul = icon_middle - (circle_size / 2);
2006             GG::Pt circle_lr = circle_ul + circle_size;
2007 
2008             GG::Pt circle_distance_pt = GG::Pt(GG::X1, GG::Y1) * circle_distance;
2009 
2010             GG::Pt inner_circle_ul = circle_ul + (circle_distance_pt * ZoomFactor());
2011             GG::Pt inner_circle_lr = circle_lr - (circle_distance_pt * ZoomFactor());
2012 
2013             if (fog_scanlines
2014                 && (universe.GetObjectVisibilityByEmpire(system_icon.first, empire_id) <= VIS_BASIC_VISIBILITY))
2015             {
2016                 m_scanline_shader.SetColor(GetOptionsDB().Get<GG::Clr>("ui.map.system.scanlines.color"));
2017                 m_scanline_shader.RenderCircle(circle_ul, circle_lr);
2018             }
2019 
2020             // render circles around systems that have at least one starlane, if they are enabled
2021             if (!circles) continue;
2022 
2023             if (auto system = Objects().get<System>(system_icon.first)) {
2024                 if (system->NumStarlanes() > 0) {
2025                     bool has_empire_planet = false;
2026                     bool has_neutrals = false;
2027                     std::map<int, int> colony_count_by_empire_id;
2028                     const std::set<int>& known_destroyed_object_ids = GetUniverse().EmpireKnownDestroyedObjectIDs(HumanClientApp::GetApp()->EmpireID());
2029 
2030                     for (auto& planet : Objects().find<const Planet>(system->PlanetIDs())) {
2031                         if (known_destroyed_object_ids.count(planet->ID()) > 0)
2032                             continue;
2033 
2034                         // remember if this system has a player-owned planet, count # of colonies for each empire
2035                         if (!planet->Unowned()) {
2036                             has_empire_planet = true;
2037 
2038                             std::map<int, int>::iterator it = colony_count_by_empire_id.find(planet->Owner()) ;
2039                             if (it != colony_count_by_empire_id.end())
2040                                 it->second++;
2041                             else
2042                                 colony_count_by_empire_id.insert({planet->Owner(), 1});
2043                         }
2044 
2045                         // remember if this system has neutrals
2046                         if (planet->Unowned() && !planet->SpeciesName().empty() &&
2047                             planet->GetMeter(METER_POPULATION)->Initial() > 0.0f)
2048                         {
2049                             has_neutrals = true;
2050 
2051                             std::map<int, int>::iterator it = colony_count_by_empire_id.find(ALL_EMPIRES);
2052                             if (it != colony_count_by_empire_id.end())
2053                                 it->second++;
2054                             else
2055                                 colony_count_by_empire_id.insert({ALL_EMPIRES, 1});
2056                         }
2057                     }
2058 
2059                     // draw outer circle in color of supplying empire
2060                     int supply_empire_id = GetSupplyManager().EmpireThatCanSupplyAt(system_icon.first);
2061                     if (supply_empire_id != ALL_EMPIRES) {
2062                         if (const Empire* empire = GetEmpire(supply_empire_id))
2063                             glColor(empire->Color());
2064                         else
2065                             ErrorLogger() << "MapWnd::RenderSystems(): could not load empire with id " << supply_empire_id;
2066                     } else
2067                         glColor(GetOptionsDB().Get<GG::Clr>("ui.map.starlane.color"));
2068 
2069                     glLineWidth(outer_circle_width);
2070                     CircleArc(circle_ul, circle_lr, 0.0, TWO_PI, false);
2071 
2072                     // systems with neutrals and no empire have a segmented inner circle
2073                     if (has_neutrals && !(has_empire_planet)) {
2074                         float line_width = std::max(std::min(2 / ZoomFactor(), max_inner_circle_width), inner_circle_width);
2075                         glLineWidth(line_width);
2076                         glColor(ClientUI::TextColor());
2077 
2078                         float segment = static_cast<float>(TWO_PI) / 24.0f;
2079                         for (int n = 0; n < 24; n = n + 2)
2080                             CircleArc(inner_circle_ul, inner_circle_lr, n * segment, (n+1) * segment, false);
2081                     }
2082 
2083                     // systems with empire planets have an unbroken inner circle; color segments for each empire present
2084                     if (!has_empire_planet) continue;
2085 
2086                     float line_width = std::max(std::min(2 / ZoomFactor(), max_inner_circle_width), inner_circle_width);
2087                     glLineWidth(line_width);
2088 
2089                     int colonised_planets = 0;
2090                     int position = 0;
2091 
2092                     for (std::pair<int, int> it : colony_count_by_empire_id)
2093                         colonised_planets += it.second;
2094 
2095                     float segment = static_cast<float>(TWO_PI) / colonised_planets;
2096 
2097                     for (std::pair<int, int> it : colony_count_by_empire_id) {
2098                         if (const Empire* empire = GetEmpire(it.first))
2099                             glColor(empire->Color());
2100                         else
2101                             glColor(ClientUI::TextColor());
2102 
2103                         CircleArc(inner_circle_ul, inner_circle_lr, position * segment, (it.second + position) * segment, false);
2104                         position += it.second;
2105                     }
2106                 }
2107             }
2108         }
2109 
2110         glDisable(GL_LINE_SMOOTH);
2111         glEnable(GL_TEXTURE_2D);
2112         glPopMatrix();
2113         glLineWidth(1.0f);
2114     }
2115 
2116     glPopClientAttrib();
2117 }
2118 
RenderStarlanes()2119 void MapWnd::RenderStarlanes() {
2120     // if lanes everywhere, don't render the lanes
2121     if (GetGameRules().Get<bool>("RULE_STARLANES_EVERYWHERE"))
2122         return;
2123 
2124     bool coloured = GetOptionsDB().Get<bool>("ui.map.starlane.empire.color.shown");
2125     float core_multiplier = static_cast<float>(GetOptionsDB().Get<double>("ui.map.starlane.thickness.factor"));
2126     RenderStarlanes(m_RC_starlane_vertices, m_RC_starlane_colors, core_multiplier * ZoomFactor(), coloured, false);
2127     RenderStarlanes(m_starlane_vertices, m_starlane_colors, 1.0, coloured, true);
2128 }
2129 
RenderStarlanes(GG::GL2DVertexBuffer & vertices,GG::GLRGBAColorBuffer & colours,double thickness,bool coloured,bool do_base_render)2130 void MapWnd::RenderStarlanes(GG::GL2DVertexBuffer& vertices, GG::GLRGBAColorBuffer& colours,
2131                              double thickness, bool coloured, bool do_base_render) {
2132     if (vertices.size() && (colours.size() || !coloured) && (coloured || do_base_render)) {
2133         // render starlanes with vertex buffer (and possibly colour buffer)
2134         const GG::Clr UNOWNED_LANE_COLOUR = GetOptionsDB().Get<GG::Clr>("ui.map.starlane.color");
2135 
2136         glDisable(GL_TEXTURE_2D);
2137         glEnable(GL_LINE_SMOOTH);
2138 
2139         glLineWidth(static_cast<GLfloat>(thickness * GetOptionsDB().Get<double>("ui.map.starlane.thickness")));
2140 
2141         glPushAttrib(GL_COLOR_BUFFER_BIT);
2142         glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
2143         glEnableClientState(GL_VERTEX_ARRAY);
2144         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
2145 
2146         if (coloured) {
2147             glEnableClientState(GL_COLOR_ARRAY);
2148             colours.activate();
2149         } else {
2150             glDisableClientState(GL_COLOR_ARRAY);
2151             glColor(UNOWNED_LANE_COLOUR);
2152         }
2153         vertices.activate();
2154 
2155         glDrawArrays(GL_LINES, 0, vertices.size());
2156 
2157         glLineWidth(1.0);
2158 
2159         glPopClientAttrib();
2160         glPopAttrib();
2161 
2162         glEnable(GL_TEXTURE_2D);
2163         glDisable(GL_LINE_SMOOTH);
2164     }
2165 
2166     glLineWidth(1.0);
2167 }
2168 
2169 namespace {
2170     GG::GL2DVertexBuffer dot_vertices_buffer;
2171     GG::GLTexCoordBuffer dot_star_texture_coords;
2172     const unsigned int BUFFER_CAPACITY(512);    // should be long enough for most plausible fleet move lines
2173 
MoveLineDotTexture()2174     std::shared_ptr<GG::Texture> MoveLineDotTexture() {
2175         auto retval = ClientUI::GetTexture(ClientUI::ArtDir() / "misc" / "move_line_dot.png");
2176         return retval;
2177     }
2178 }
2179 
RenderFleetMovementLines()2180 void MapWnd::RenderFleetMovementLines() {
2181     if (ZoomFactor() < ClientUI::TinyFleetButtonZoomThreshold())
2182         return;
2183 
2184     // determine animation shift for move lines
2185     int dot_spacing = GetOptionsDB().Get<int>("ui.map.fleet.supply.dot.spacing");
2186     float rate = static_cast<float>(GetOptionsDB().Get<double>("ui.map.fleet.supply.dot.rate"));
2187     int ticks = GG::GUI::GetGUI()->Ticks();
2188     /* Updated each frame to shift rendered posistion of dots that are drawn to
2189      * show fleet move lines. */
2190     float move_line_animation_shift = static_cast<int>(ticks * rate) % dot_spacing;
2191 
2192     // texture for dots
2193     auto move_line_dot_texture = MoveLineDotTexture();
2194     float dot_size = Value(move_line_dot_texture->DefaultWidth());
2195 
2196     // texture coords
2197     if (dot_star_texture_coords.empty()) {
2198         dot_star_texture_coords.reserve(BUFFER_CAPACITY*4);
2199         for (std::size_t i = 0; i < BUFFER_CAPACITY; ++i) {
2200             dot_star_texture_coords.store(0.0f, 0.0f);
2201             dot_star_texture_coords.store(0.0f, 1.0f);
2202             dot_star_texture_coords.store(1.0f, 1.0f);
2203             dot_star_texture_coords.store(1.0f, 0.0f);
2204         }
2205     }
2206 
2207 
2208     // dots rendered same size for all zoom levels, so do positioning in screen
2209     // space instead of universe space
2210     glPushMatrix();
2211     glLoadIdentity();
2212 
2213     // render movement lines for all fleets
2214     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
2215     glEnableClientState(GL_VERTEX_ARRAY);
2216     glDisableClientState(GL_COLOR_ARRAY);
2217     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
2218 
2219     glBindTexture(GL_TEXTURE_2D, move_line_dot_texture->OpenGLId());
2220     for (const auto& fleet_line : m_fleet_lines)
2221     { RenderMovementLine(fleet_line.second, dot_size, dot_spacing, move_line_animation_shift); }
2222 
2223     // re-render selected fleets' movement lines in white
2224     for (int fleet_id : m_selected_fleet_ids) {
2225         auto line_it = m_fleet_lines.find(fleet_id);
2226         if (line_it != m_fleet_lines.end())
2227             RenderMovementLine(line_it->second, dot_size, dot_spacing, move_line_animation_shift, GG::CLR_WHITE);
2228     }
2229 
2230     // render move line ETA indicators for selected fleets
2231     for (int fleet_id : m_selected_fleet_ids) {
2232         auto line_it = m_fleet_lines.find(fleet_id);
2233         if (line_it != m_fleet_lines.end())
2234             RenderMovementLineETAIndicators(line_it->second);
2235     }
2236 
2237     // render projected move lines
2238     glBindTexture(GL_TEXTURE_2D, move_line_dot_texture->OpenGLId());
2239     for (const auto& fleet_line : m_projected_fleet_lines)
2240     { RenderMovementLine(fleet_line.second, dot_size, dot_spacing, move_line_animation_shift, GG::CLR_WHITE); }
2241 
2242     // render projected move line ETA indicators
2243     for (const auto& eta_indicator : m_projected_fleet_lines)
2244     { RenderMovementLineETAIndicators(eta_indicator.second, GG::CLR_WHITE); }
2245 
2246     glPopClientAttrib();
2247     glPopMatrix();
2248 }
2249 
RenderMovementLine(const MapWnd::MovementLineData & move_line,float dot_size,float dot_spacing,float dot_shift,GG::Clr clr)2250 void MapWnd::RenderMovementLine(const MapWnd::MovementLineData& move_line, float dot_size,
2251                                 float dot_spacing, float dot_shift, GG::Clr clr)
2252 {
2253     // assumes:
2254     // - dot texture has already been bound
2255     // - identity matrix has been loaded
2256     // - vertex array and texture coord array client states ahve been enabled
2257 
2258     const auto& vertices = move_line.vertices;
2259     if (vertices.empty())
2260         return; // nothing to draw.  need at least two nodes at different locations to draw a line
2261     if (vertices.size() % 2 == 1) {
2262         //ErrorLogger() << "RenderMovementLine given an odd number of vertices (" << vertices.size() << ") to render?!";
2263         return;
2264     }
2265 
2266     // if no override colour specified, use line's own colour info
2267     if (clr == GG::CLR_ZERO)
2268         glColor(move_line.colour);
2269     else
2270         glColor(clr);
2271 
2272     float dot_half_sz = dot_size / 2.0f;
2273     float offset = dot_shift;  // step along line in by move_line_animation_shift to get position of first dot
2274 
2275 
2276     // movement line data changes every frame, so no use for a server buffer...
2277     // so fill a client buffer each frame with latest vertex data for this line.
2278     // however, want to avoid extra reallocations of buffer, so reserve space.
2279     dot_vertices_buffer.clear();
2280     dot_vertices_buffer.reserve(BUFFER_CAPACITY*4);
2281 
2282     unsigned int dots_added_to_buffer = 0;
2283 
2284     // set vertex positions to outline a quad for each move line vertex
2285     for (auto verts_it = vertices.begin(); verts_it != vertices.end(); ++verts_it) {
2286         if (dots_added_to_buffer >= BUFFER_CAPACITY)
2287             break; // can't fit any more!
2288 
2289         // get next two vertices
2290         const auto& vert1 = *verts_it;
2291         ++verts_it;
2292         const auto& vert2 = *verts_it;
2293 
2294         // find centres of dots on screen
2295         GG::Pt vert1Pt = ScreenCoordsFromUniversePosition(vert1.x, vert1.y);
2296         GG::Pt vert2Pt = ScreenCoordsFromUniversePosition(vert2.x, vert2.y);
2297 
2298         // get unit vector along line connecting vertices
2299         float deltaX = Value(vert2Pt.x - vert1Pt.x);
2300         float deltaY = Value(vert2Pt.y - vert1Pt.y);
2301         float length = std::sqrt(deltaX*deltaX + deltaY*deltaY);
2302         if (length == 0.0f) // safety check
2303             length = 1.0f;
2304         float uVecX = deltaX / length;
2305         float uVecY = deltaY / length;
2306 
2307         // increment along line, adding dots to buffers, until end of line segment is passed
2308         while (offset < length && dots_added_to_buffer < BUFFER_CAPACITY) {
2309             ++dots_added_to_buffer;
2310 
2311             // don't know why the dot needs to be shifted half a dot size down/right and
2312             // rendered 2 x dot size on each axis, but apparently it does...
2313 
2314             // find position of dot from initial vertex position, offset length and unit vectors
2315             std::pair<float, float> ul(Value(vert1Pt.x) + offset * uVecX + dot_half_sz,
2316                                        Value(vert1Pt.y) + offset * uVecY + dot_half_sz);
2317 
2318             dot_vertices_buffer.store(ul.first - dot_size,   ul.second - dot_size);
2319             dot_vertices_buffer.store(ul.first - dot_size,   ul.second + dot_size);
2320             dot_vertices_buffer.store(ul.first + dot_size,   ul.second + dot_size);
2321             dot_vertices_buffer.store(ul.first + dot_size,   ul.second - dot_size);
2322 
2323             // move offset to that for next dot
2324             offset += dot_spacing;
2325         }
2326 
2327         offset -= length;   // so next segment's dots meld smoothly into this segment's
2328     }
2329 
2330     // after adding all dots to buffer, render in one call
2331     dot_vertices_buffer.activate();
2332     dot_star_texture_coords.activate();
2333     glDrawArrays(GL_QUADS, 0, dot_vertices_buffer.size());
2334 }
2335 
RenderMovementLineETAIndicators(const MapWnd::MovementLineData & move_line,GG::Clr clr)2336 void MapWnd::RenderMovementLineETAIndicators(const MapWnd::MovementLineData& move_line,
2337                                              GG::Clr clr)
2338 {
2339     const auto& vertices = move_line.vertices;
2340     if (vertices.empty())
2341         return; // nothing to draw.
2342 
2343 
2344     const double MARKER_HALF_SIZE = 9;
2345     const int MARKER_PTS = ClientUI::Pts();
2346     auto font = ClientUI::GetBoldFont(MARKER_PTS);
2347     auto flags = GG::FORMAT_CENTER | GG::FORMAT_VCENTER;
2348 
2349     glPushMatrix();
2350     glLoadIdentity();
2351     int flag_border = 5;
2352 
2353     for (const auto& vert : vertices) {
2354         if (!vert.show_eta)
2355             continue;
2356 
2357         // draw background disc in empire colour, or passed-in colour
2358         GG::Pt marker_centre = ScreenCoordsFromUniversePosition(vert.x, vert.y);
2359         GG::Pt ul = marker_centre - GG::Pt(GG::X(static_cast<int>(MARKER_HALF_SIZE)),
2360                                            GG::Y(static_cast<int>(MARKER_HALF_SIZE)));
2361         GG::Pt lr = marker_centre + GG::Pt(GG::X(static_cast<int>(MARKER_HALF_SIZE)),
2362                                            GG::Y(static_cast<int>(MARKER_HALF_SIZE)));
2363 
2364         glDisable(GL_TEXTURE_2D);
2365 
2366         // segmented circle of wedges to indicate blockades
2367         if (vert.flag_blockade) {
2368             float wedge = static_cast<float>(TWO_PI)/12.0f;
2369             for (int n = 0; n < 12; n = n + 2) {
2370                 glColor(GG::CLR_BLACK);
2371                 CircleArc(ul + GG::Pt(-flag_border*GG::X1,      -flag_border*GG::Y1),   lr + GG::Pt(flag_border*GG::X1,     flag_border*GG::Y1),    n*wedge,        (n+1)*wedge, true);
2372                 glColor(GG::CLR_RED);
2373                 CircleArc(ul + GG::Pt(-(flag_border)*GG::X1,    -(flag_border)*GG::Y1), lr + GG::Pt((flag_border)*GG::X1,   (flag_border)*GG::Y1),  (n+1)*wedge,    (n+2)*wedge, true);
2374             }
2375         } else if (vert.flag_supply_block) {
2376             float wedge = static_cast<float>(TWO_PI)/12.0f;
2377             for (int n = 0; n < 12; n = n + 2) {
2378                 glColor(GG::CLR_BLACK);
2379                 CircleArc(ul + GG::Pt(-flag_border*GG::X1,      -flag_border*GG::Y1),   lr + GG::Pt(flag_border*GG::X1,     flag_border*GG::Y1),    n*wedge,        (n+1)*wedge, true);
2380                 glColor(GG::CLR_YELLOW);
2381                 CircleArc(ul + GG::Pt(-(flag_border)*GG::X1,    -(flag_border)*GG::Y1), lr + GG::Pt((flag_border)*GG::X1,   (flag_border)*GG::Y1),  (n+1)*wedge,    (n+2)*wedge, true);
2382             }
2383         }
2384 
2385 
2386         // empire-coloured central fill within wedged outer ring
2387         if (clr == GG::CLR_ZERO)
2388             glColor(move_line.colour);
2389         else
2390             glColor(clr);
2391 
2392         CircleArc(ul, lr, 0.0, TWO_PI, true);
2393         glEnable(GL_TEXTURE_2D);
2394 
2395 
2396         // render ETA number in white with black shadows
2397         std::string text = "<s>" + std::to_string(vert.eta) + "</s>";
2398         glColor(GG::CLR_WHITE);
2399         // TODO cache the text_elements
2400         auto text_elements = font->ExpensiveParseFromTextToTextElements(text, flags);
2401         auto lines = font->DetermineLines(text, flags, lr.x - ul.x, text_elements);
2402         font->RenderText(ul, lr, text, flags, lines);
2403     }
2404     glPopMatrix();
2405 }
2406 
RenderVisibilityRadii()2407 void MapWnd::RenderVisibilityRadii() {
2408     if (!GetOptionsDB().Get<bool>("ui.map.detection.range.shown"))
2409         return;
2410 
2411     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
2412     glEnableClientState(GL_VERTEX_ARRAY);
2413     glEnableClientState(GL_COLOR_ARRAY);
2414     glPushAttrib(GL_ENABLE_BIT | GL_STENCIL_BUFFER_BIT);
2415     glEnable(GL_STENCIL_TEST);
2416 
2417     glEnable(GL_LINE_SMOOTH);
2418     glDisable(GL_TEXTURE_2D);
2419     glLineWidth(1.5f);
2420 
2421     // render each colour's radii separately, so they can consistently blend
2422     // when overlapping other colours, but be stenciled to avoid blending
2423     // when overlapping within a colour
2424     for (unsigned int i = 0; i < m_radii_radii_vertices_indices_runs.size(); ++i) {
2425         const auto& radii_start_run = m_radii_radii_vertices_indices_runs[i].first;
2426         const auto& border_start_run = m_radii_radii_vertices_indices_runs[i].second;
2427 
2428         glClear(GL_STENCIL_BUFFER_BIT);
2429         glStencilOp(GL_INCR, GL_INCR, GL_INCR);
2430         glStencilFunc(GL_EQUAL, 0x0, 0xff);
2431 
2432         m_visibility_radii_vertices.activate();
2433         m_visibility_radii_colors.activate();
2434         glDrawArrays(GL_TRIANGLES, radii_start_run.first, radii_start_run.second);
2435 
2436         glStencilFunc(GL_GREATER, 0x2, 0xff);
2437         glStencilOp(GL_DECR, GL_KEEP, GL_KEEP);
2438 
2439         m_visibility_radii_border_vertices.activate();
2440         m_visibility_radii_border_colors.activate();
2441 
2442         glDrawArrays(GL_LINES, border_start_run.first, border_start_run.second);
2443     }
2444 
2445     glEnable(GL_TEXTURE_2D);
2446     glLineWidth(1.0f);
2447     glPopAttrib();
2448     glPopClientAttrib();
2449 }
2450 
RenderScaleCircle()2451 void MapWnd::RenderScaleCircle() {
2452     if (SidePanel::SystemID() == INVALID_OBJECT_ID)
2453         return;
2454     if (!GetOptionsDB().Get<bool>("ui.map.scale.legend.shown") || !GetOptionsDB().Get<bool>("ui.map.scale.circle.shown"))
2455         return;
2456     if (m_scale_circle_vertices.empty())
2457         InitScaleCircleRenderingBuffer();
2458 
2459     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
2460     glEnableClientState(GL_VERTEX_ARRAY);
2461     glDisableClientState(GL_COLOR_ARRAY);
2462     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
2463 
2464     glEnable(GL_LINE_SMOOTH);
2465     glDisable(GL_TEXTURE_2D);
2466     glLineWidth(2.0f);
2467 
2468     GG::Clr circle_colour = GG::CLR_WHITE;
2469     circle_colour.a = 128;
2470     glColor(circle_colour);
2471 
2472     m_scale_circle_vertices.activate();
2473     glDrawArrays(GL_LINE_STRIP, 0, m_scale_circle_vertices.size());
2474 
2475     glEnable(GL_TEXTURE_2D);
2476     glDisable(GL_LINE_SMOOTH);
2477     glLineWidth(1.0f);
2478 
2479     glPopClientAttrib();
2480 }
2481 
RegisterWindows()2482 void MapWnd::RegisterWindows() {
2483     // TODO: move these wnds into a GG::Wnd and call parent_wnd->Show(false) to
2484     //       hide all windows instead of unregistering them all.
2485     // Actually register these CUIWnds so that the Visible() ones are rendered.
2486     if (HumanClientApp* app = HumanClientApp::GetApp()) {
2487         app->Register(m_sitrep_panel);
2488         app->Register(m_object_list_wnd);
2489         app->Register(m_pedia_panel);
2490         app->Register(m_side_panel);
2491         app->Register(m_combat_report_wnd);
2492         app->Register(m_moderator_wnd);
2493         // message and player list wnds are managed by the HumanClientFSM
2494     }
2495 }
2496 
RemoveWindows()2497 void MapWnd::RemoveWindows() {
2498     // Hide windows by unregistering them which works regardless of their
2499     // m_visible attribute.
2500     if (HumanClientApp* app = HumanClientApp::GetApp()) {
2501         app->Remove(m_sitrep_panel);
2502         app->Remove(m_object_list_wnd);
2503         app->Remove(m_pedia_panel);
2504         app->Remove(m_side_panel);
2505         app->Remove(m_combat_report_wnd);
2506         app->Remove(m_moderator_wnd);
2507         // message and player list wnds are managed by the HumanClientFSM
2508     }
2509 }
2510 
Pan(const GG::Pt & delta)2511 void MapWnd::Pan(const GG::Pt& delta) {
2512     GG::Pt move_to_pt = ClientUpperLeft() + delta;
2513     CorrectMapPosition(move_to_pt);
2514     MoveTo(move_to_pt - GG::Pt(AppWidth(), AppHeight()));
2515 }
2516 
PanX(GG::X x)2517 bool MapWnd::PanX(GG::X x) {
2518     Pan(GG::Pt(x, GG::Y0));
2519     return true;
2520 }
2521 
PanY(GG::Y y)2522 bool MapWnd::PanY(GG::Y y) {
2523     Pan(GG::Pt(GG::X0, y));
2524     return true;
2525 }
2526 
LButtonDown(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)2527 void MapWnd::LButtonDown(const GG::Pt &pt, GG::Flags<GG::ModKey> mod_keys)
2528 { m_drag_offset = pt - ClientUpperLeft(); }
2529 
LDrag(const GG::Pt & pt,const GG::Pt & move,GG::Flags<GG::ModKey> mod_keys)2530 void MapWnd::LDrag(const GG::Pt &pt, const GG::Pt &move, GG::Flags<GG::ModKey> mod_keys) {
2531     GG::Pt move_to_pt = pt - m_drag_offset;
2532     CorrectMapPosition(move_to_pt);
2533 
2534     MoveTo(move_to_pt - GG::Pt(AppWidth(), AppHeight()));
2535     m_dragged = true;
2536 }
2537 
LButtonUp(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)2538 void MapWnd::LButtonUp(const GG::Pt &pt, GG::Flags<GG::ModKey> mod_keys) {
2539     m_drag_offset = GG::Pt(-GG::X1, -GG::Y1);
2540     m_dragged = false;
2541 }
2542 
LClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)2543 void MapWnd::LClick(const GG::Pt &pt, GG::Flags<GG::ModKey> mod_keys) {
2544     m_drag_offset = GG::Pt(-GG::X1, -GG::Y1);
2545     FleetUIManager& manager = FleetUIManager::GetFleetUIManager();
2546     const auto fleet_wnd = manager.ActiveFleetWnd();
2547     bool quick_close_wnds = GetOptionsDB().Get<bool>("ui.quickclose.enabled");
2548 
2549     // if a fleet window is visible, hide it and deselect fleet; if not, hide sidepanel
2550     if (!m_dragged && !m_in_production_view_mode && fleet_wnd && quick_close_wnds) {
2551         manager.CloseAll();
2552     } else if (!m_dragged && !m_in_production_view_mode) {
2553         SelectSystem(INVALID_OBJECT_ID);
2554         m_side_panel->Hide();
2555     }
2556     m_dragged = false;
2557 }
2558 
RClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)2559 void MapWnd::RClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) {
2560     // if in moderator mode, treat as moderator action click
2561     if (ClientPlayerIsModerator()) {
2562         // only supported action on empty map location at present is creating a system
2563         if (m_moderator_wnd->SelectedAction() == MAS_CreateSystem) {
2564             ClientNetworking& net = HumanClientApp::GetApp()->Networking();
2565             auto u_pos = this->UniversePositionFromScreenCoords(pt);
2566             StarType star_type = m_moderator_wnd->SelectedStarType();
2567             net.SendMessage(ModeratorActionMessage(
2568                 Moderator::CreateSystem(u_pos.first, u_pos.second, star_type)));
2569             return;
2570         }
2571     }
2572 
2573     if (GetOptionsDB().Get<bool>("ui.map.menu.enabled")) {
2574         // create popup menu with map options in it.
2575         bool fps            = GetOptionsDB().Get<bool>("video.fps.shown");
2576         bool showPlanets    = GetOptionsDB().Get<bool>("ui.map.sidepanel.planet.shown");
2577         bool systemCircles  = GetOptionsDB().Get<bool>("ui.map.system.circle.shown");
2578         bool resourceColor  = GetOptionsDB().Get<bool>("ui.map.starlane.empire.color.shown");
2579         bool fleetSupply    = GetOptionsDB().Get<bool>("ui.map.fleet.supply.shown");
2580         bool gas            = GetOptionsDB().Get<bool>("ui.map.background.gas.shown");
2581         bool starfields     = GetOptionsDB().Get<bool>("ui.map.background.starfields.shown");
2582         bool scale          = GetOptionsDB().Get<bool>("ui.map.scale.legend.shown");
2583         bool scaleCircle    = GetOptionsDB().Get<bool>("ui.map.scale.circle.shown");
2584         bool zoomSlider     = GetOptionsDB().Get<bool>("ui.map.zoom.slider.shown");
2585         bool detectionRange = GetOptionsDB().Get<bool>("ui.map.detection.range.shown");
2586 
2587         auto show_fps_action        = [&fps]()            { GetOptionsDB().Set<bool>("video.fps.shown",                !fps);         };
2588         auto show_planets_action    = [&showPlanets]()    { GetOptionsDB().Set<bool>("ui.map.sidepanel.planet.shown",       !showPlanets);      };
2589         auto system_circles_action  = [&systemCircles]()  { GetOptionsDB().Set<bool>("ui.map.system.circle.shown",          !systemCircles);    };
2590         auto resource_color_action  = [&resourceColor]()  { GetOptionsDB().Set<bool>("ui.map.starlane.empire.color.shown",  !resourceColor);    };
2591         auto fleet_supply_action    = [&fleetSupply]()    { GetOptionsDB().Set<bool>("ui.map.fleet.supply.shown",      !fleetSupply);      };
2592         auto gas_action             = [&gas]()            { GetOptionsDB().Set<bool>("ui.map.background.gas.shown",         !gas);              };
2593         auto starfield_action       = [&starfields]()     { GetOptionsDB().Set<bool>("ui.map.background.starfields.shown",  !starfields); };
2594         auto map_scale_action       = [&scale]()          { GetOptionsDB().Set<bool>("ui.map.scale.legend.shown",           !scale);            };
2595         auto scale_circle_action    = [&scaleCircle]()    { GetOptionsDB().Set<bool>("ui.map.scale.circle.shown",           !scaleCircle);      };
2596         auto zoom_slider_action     = [&zoomSlider]()     { GetOptionsDB().Set<bool>("ui.map.zoom.slider.shown",            !zoomSlider);       };
2597         auto detection_range_action = [&detectionRange]() { GetOptionsDB().Set<bool>("ui.map.detection.range.shown",        !detectionRange);   };
2598 
2599         auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
2600         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_SHOW_FPS"),                     false, fps,            show_fps_action));
2601         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_SHOW_SIDEPANEL_PLANETS"),       false, showPlanets,    show_planets_action));
2602         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_UI_SYSTEM_CIRCLES"),            false, systemCircles,  system_circles_action));
2603         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_RESOURCE_STARLANE_COLOURING"),  false, resourceColor,  resource_color_action));
2604         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_FLEET_SUPPLY_LINES"),           false, fleetSupply,    fleet_supply_action));
2605         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_GALAXY_MAP_GAS"),               false, gas,            gas_action));
2606         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_GALAXY_MAP_STARFIELDS"),        false, starfields,     starfield_action));
2607         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_GALAXY_MAP_SCALE_LINE"),        false, scale,          map_scale_action));
2608         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_GALAXY_MAP_SCALE_CIRCLE"),      false, scaleCircle,    scale_circle_action));
2609         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_GALAXY_MAP_ZOOM_SLIDER"),       false, zoomSlider,     zoom_slider_action));
2610         popup->AddMenuItem(GG::MenuItem(UserString("OPTIONS_GALAXY_MAP_DETECTION_RANGE"),   false, detectionRange, detection_range_action));
2611         // display popup menu
2612         popup->Run();
2613 
2614     }
2615 }
2616 
MouseWheel(const GG::Pt & pt,int move,GG::Flags<GG::ModKey> mod_keys)2617 void MapWnd::MouseWheel(const GG::Pt& pt, int move, GG::Flags<GG::ModKey> mod_keys) {
2618     if (move)
2619         Zoom(move, pt);
2620 }
2621 
KeyPress(GG::Key key,std::uint32_t key_code_point,GG::Flags<GG::ModKey> mod_keys)2622 void MapWnd::KeyPress(GG::Key key, std::uint32_t key_code_point, GG::Flags<GG::ModKey> mod_keys) {
2623     if (key == GG::GGK_LSHIFT || key == GG::GGK_RSHIFT) {
2624         ReplotProjectedFleetMovement(mod_keys & GG::MOD_KEY_SHIFT);
2625     }
2626 }
2627 
KeyRelease(GG::Key key,std::uint32_t key_code_point,GG::Flags<GG::ModKey> mod_keys)2628 void MapWnd::KeyRelease(GG::Key key, std::uint32_t key_code_point, GG::Flags<GG::ModKey> mod_keys) {
2629     if (key == GG::GGK_LSHIFT || key == GG::GGK_RSHIFT) {
2630         ReplotProjectedFleetMovement(mod_keys & GG::MOD_KEY_SHIFT);
2631     }
2632 }
2633 
EnableOrderIssuing(bool enable)2634 void MapWnd::EnableOrderIssuing(bool enable/* = true*/) {
2635     // disallow order enabling if this client does not have an empire
2636     // and is not a moderator
2637     HumanClientApp* app = HumanClientApp::GetApp();
2638     bool moderator = false;
2639     bool observer = false;
2640     m_btn_turn->Disable(HumanClientApp::GetApp()->SinglePlayerGame() && !enable);
2641     if (!app) {
2642         enable = false;
2643         m_btn_turn->Disable(true);
2644     } else {
2645         bool have_empire = (app->EmpireID() != ALL_EMPIRES);
2646         moderator = (app->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR);
2647         if (!have_empire && !moderator) {
2648             enable = false;
2649             m_btn_turn->Disable(true);
2650         }
2651         observer = (app->GetClientType() == Networking::CLIENT_TYPE_HUMAN_OBSERVER);
2652     }
2653 
2654     m_moderator_wnd->EnableActions(enable && moderator);
2655     m_ready_turn = !enable;
2656 
2657     std::string button_label;
2658     if (!moderator && !observer && m_ready_turn && !HumanClientApp::GetApp()->SinglePlayerGame()) {
2659         // multiplayer game with a participating player who has sent orders
2660         button_label = UserString("MAP_BTN_TURN_UNREADY");
2661     } else {
2662         button_label = UserString("MAP_BTN_TURN_UPDATE");
2663     }
2664 
2665     m_btn_turn->SetText(boost::io::str(FlexibleFormat(button_label) % std::to_string(CurrentTurn())));
2666     RefreshTurnButtonTooltip();
2667     m_side_panel->EnableOrderIssuing(enable);
2668     m_production_wnd->EnableOrderIssuing(enable);
2669     m_research_wnd->EnableOrderIssuing(enable);
2670     m_design_wnd->EnableOrderIssuing(enable);
2671     FleetUIManager::GetFleetUIManager().EnableOrderIssuing(enable);
2672 }
2673 
InitTurn()2674 void MapWnd::InitTurn() {
2675     int turn_number = CurrentTurn();
2676     DebugLogger() << "Initializing turn " << turn_number;
2677     SectionedScopedTimer timer("MapWnd::InitTurn");
2678     timer.EnterSection("init");
2679 
2680     //DebugLogger() << GetSupplyManager().Dump();
2681 
2682     Universe& universe = GetUniverse();
2683     ObjectMap& objects = Objects();
2684 
2685     TraceLogger(effects) << "MapWnd::InitTurn initial:";
2686     for (auto obj : objects.all())
2687         TraceLogger(effects) << obj->Dump();
2688 
2689     timer.EnterSection("system graph");
2690     // FIXME: this is actually only needed when there was no mid-turn update
2691     universe.InitializeSystemGraph(HumanClientApp::GetApp()->EmpireID());
2692 
2693     timer.EnterSection("meter estimates");
2694     // update effect accounting and meter estimates
2695     universe.InitMeterEstimatesAndDiscrepancies();
2696 
2697     // if we've just loaded the game there may be some unexecuted orders, we
2698     // should reapply them now, so they are reflected in the UI, but do not
2699     // influence current meters or their discrepancies for this turn
2700     HumanClientApp::GetApp()->Orders().ApplyOrders();
2701 
2702     // redo meter estimates with unowned planets marked as owned by player, so accurate predictions of planet
2703     // population is available for currently uncolonized planets
2704     GetUniverse().UpdateMeterEstimates();
2705 
2706     GetUniverse().ApplyAppearanceEffects();
2707 
2708     timer.EnterSection("rendering");
2709     // set up system icons, starlanes, galaxy gas rendering
2710     InitTurnRendering();
2711 
2712     timer.EnterSection("fleet signals");
2713     // connect system fleet add and remove signals
2714     for (auto& system : objects.all<System>()) {
2715         m_system_fleet_insert_remove_signals[system->ID()].push_back(system->FleetsInsertedSignal.connect(
2716             [this](const std::vector<std::shared_ptr<Fleet>>& fleets) {
2717                 RefreshFleetButtons();
2718                 for (auto& fleet : fleets) {
2719                     if (!m_fleet_state_change_signals.count(fleet->ID()))
2720                         m_fleet_state_change_signals[fleet->ID()] = fleet->StateChangedSignal.connect(
2721                             boost::bind(&MapWnd::RefreshFleetButtons, this));
2722                 }
2723             }));
2724         m_system_fleet_insert_remove_signals[system->ID()].push_back(system->FleetsRemovedSignal.connect(
2725             [this](const std::vector<std::shared_ptr<Fleet>>& fleets) {
2726                 RefreshFleetButtons();
2727                 for (auto& fleet : fleets) {
2728                     auto found_signal = m_fleet_state_change_signals.find(fleet->ID());
2729                     if (found_signal != m_fleet_state_change_signals.end()) {
2730                         found_signal->second.disconnect();
2731                         m_fleet_state_change_signals.erase(found_signal);
2732                     }
2733                 }
2734             }));
2735     }
2736 
2737     for (auto& con : m_fleet_state_change_signals)
2738         con.second.disconnect();
2739     m_fleet_state_change_signals.clear();
2740 
2741     // connect fleet change signals to update fleet movement lines, so that ordering
2742     // fleets to move updates their displayed path and rearranges fleet buttons (if necessary)
2743     for (auto& fleet : Objects().all<Fleet>()) {
2744         m_fleet_state_change_signals[fleet->ID()] = fleet->StateChangedSignal.connect(
2745             boost::bind(&MapWnd::RefreshFleetButtons, this));
2746     }
2747 
2748     // set turn button to current turn
2749     m_btn_turn->SetText(boost::io::str(FlexibleFormat(UserString("MAP_BTN_TURN_UPDATE")) %
2750                                        std::to_string(turn_number)));
2751     RefreshTurnButtonTooltip();
2752 
2753     m_ready_turn = false;
2754     MoveChildUp(m_btn_turn);
2755 
2756 
2757     timer.EnterSection("sitreps");
2758     // are there any sitreps to show?
2759     bool show_intro_sitreps = CurrentTurn() == 1 &&
2760         GetOptionsDB().Get<Aggression>("setup.ai.aggression") <= TYPICAL;
2761     DebugLogger() << "showing intro sitreps : " << show_intro_sitreps;
2762     if (show_intro_sitreps || m_sitrep_panel->NumVisibleSitrepsThisTurn() > 0) {
2763         m_sitrep_panel->ShowSitRepsForTurn(CurrentTurn());
2764         if (!m_design_wnd->Visible() && !m_research_wnd->Visible() && !m_production_wnd->Visible())
2765             ShowSitRep();
2766     }
2767 
2768     if (m_sitrep_panel->Visible()) {
2769         // Ensure that the panel is at least updated if it's visible because it
2770         // can now set itself to be visible (from the config) before a game is
2771         // loaded, and it can be visible while the production/research/design
2772         // windows are open.
2773         m_sitrep_panel->Update();
2774     }
2775 
2776     m_combat_report_wnd->Hide();
2777 
2778     if (m_object_list_wnd->Visible())
2779         m_object_list_wnd->Refresh();
2780 
2781     m_moderator_wnd->Refresh();
2782     m_pedia_panel->Refresh();
2783 
2784 
2785     // show or hide system names, depending on zoom.  replicates code in MapWnd::Zoom
2786     if (ZoomFactor() * ClientUI::Pts() < MIN_SYSTEM_NAME_SIZE)
2787         HideSystemNames();
2788     else
2789         ShowSystemNames();
2790 
2791 
2792     // empire is recreated each turn based on turn update from server, so
2793     // connections of signals emitted from the empire must be remade each turn
2794     // (unlike connections to signals from the sidepanel)
2795     Empire* this_client_empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
2796     if (this_client_empire) {
2797         this_client_empire->GetResourcePool(RE_TRADE)->ChangedSignal.connect(
2798             boost::bind(&MapWnd::RefreshTradeResourceIndicator, this));
2799         this_client_empire->GetResourcePool(RE_RESEARCH)->ChangedSignal.connect(
2800             boost::bind(&MapWnd::RefreshResearchResourceIndicator, this));
2801         this_client_empire->GetResourcePool(RE_INDUSTRY)->ChangedSignal.connect(
2802             boost::bind(&MapWnd::RefreshIndustryResourceIndicator, this));
2803         this_client_empire->GetPopulationPool().ChangedSignal.connect(
2804             boost::bind(&MapWnd::RefreshPopulationIndicator, this));
2805         this_client_empire->GetProductionQueue().ProductionQueueChangedSignal.connect(
2806             boost::bind(&MapWnd::RefreshIndustryResourceIndicator, this));
2807         // so lane colouring to indicate wasted PP is updated
2808         this_client_empire->GetProductionQueue().ProductionQueueChangedSignal.connect(
2809             boost::bind(&MapWnd::InitStarlaneRenderingBuffers, this));
2810         this_client_empire->GetResearchQueue().ResearchQueueChangedSignal.connect(
2811             boost::bind(&MapWnd::RefreshResearchResourceIndicator, this));
2812     }
2813 
2814     m_toolbar->Show();
2815     m_FPS->Show();
2816     m_scale_line->Show();
2817     RefreshSliders();
2818 
2819 
2820     timer.EnterSection("update resource pools");
2821     for (auto& entry : Empires())
2822         entry.second->UpdateResourcePools();
2823 
2824 
2825     timer.EnterSection("refresh research");
2826     m_research_wnd->Refresh();
2827 
2828 
2829     timer.EnterSection("refresh sidepanel");
2830     SidePanel::Refresh();       // recreate contents of all SidePanels.  ensures previous turn's objects and signals are disposed of
2831 
2832 
2833     timer.EnterSection("refresh production wnd");
2834     m_production_wnd->Refresh();
2835 
2836 
2837     if (turn_number == 1 && this_client_empire) {
2838         // start first turn with player's system selected
2839         if (auto obj = objects.get(this_client_empire->CapitalID())) {
2840             SelectSystem(obj->SystemID());
2841             CenterOnMapCoord(obj->X(), obj->Y());
2842         }
2843 
2844         // default the tech tree to be centred on something interesting
2845         m_research_wnd->Reset();
2846     } else if (turn_number == 1 && !this_client_empire) {
2847         CenterOnMapCoord(0.0, 0.0);
2848     }
2849 
2850     timer.EnterSection("refresh indicators");
2851     RefreshIndustryResourceIndicator();
2852     RefreshResearchResourceIndicator();
2853     RefreshTradeResourceIndicator();
2854     RefreshFleetResourceIndicator();
2855     RefreshPopulationIndicator();
2856     RefreshDetectionIndicator();
2857 
2858     timer.EnterSection("dispatch exploring");
2859     FleetUIManager::GetFleetUIManager().RefreshAll();
2860     DispatchFleetsExploring();
2861 
2862     timer.EnterSection("enable observers");
2863     HumanClientApp* app = HumanClientApp::GetApp();
2864     if (app->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR) {
2865         // this client is a moderator
2866         m_btn_moderator->Disable(false);
2867         m_btn_moderator->Show();
2868     } else {
2869         HideModeratorActions();
2870         m_btn_moderator->Disable();
2871         m_btn_moderator->Hide();
2872     }
2873     if (app->GetClientType() == Networking::CLIENT_TYPE_HUMAN_OBSERVER) {
2874         m_btn_auto_turn->Disable();
2875         m_btn_auto_turn->Hide();
2876     } else {
2877         m_btn_auto_turn->Disable(false);
2878         m_btn_auto_turn->Show();
2879     }
2880 
2881     if (GetOptionsDB().Get<bool>("ui.turn.start.sound.enabled"))
2882         Sound::GetSound().PlaySound(GetOptionsDB().Get<std::string>("ui.turn.start.sound.path"), true);
2883 }
2884 
MidTurnUpdate()2885 void MapWnd::MidTurnUpdate() {
2886     DebugLogger() << "MapWnd::MidTurnUpdate";
2887     ScopedTimer timer("MapWnd::MidTurnUpdate", true);
2888 
2889     GetUniverse().InitializeSystemGraph(HumanClientApp::GetApp()->EmpireID());
2890 
2891     // set up system icons, starlanes, galaxy gas rendering
2892     InitTurnRendering();
2893 
2894     FleetUIManager::GetFleetUIManager().RefreshAll();
2895     SidePanel::Refresh();
2896 
2897     // show or hide system names, depending on zoom.  replicates code in MapWnd::Zoom
2898     if (ZoomFactor() * ClientUI::Pts() < MIN_SYSTEM_NAME_SIZE)
2899         HideSystemNames();
2900     else
2901         ShowSystemNames();
2902 }
2903 
InitTurnRendering()2904 void MapWnd::InitTurnRendering() {
2905     DebugLogger() << "MapWnd::InitTurnRendering";
2906     ScopedTimer timer("MapWnd::InitTurnRendering", true);
2907 
2908 #if BOOST_VERSION >= 106000
2909     using boost::placeholders::_1;
2910     using boost::placeholders::_2;
2911 #endif
2912 
2913     // adjust size of map window for universe and application size
2914     Resize(GG::Pt(static_cast<GG::X>(GetUniverse().UniverseWidth() * ZOOM_MAX + AppWidth() * 1.5),
2915                   static_cast<GG::Y>(GetUniverse().UniverseWidth() * ZOOM_MAX + AppHeight() * 1.5)));
2916 
2917 
2918     // remove any existing fleet movement lines or projected movement lines.  this gets cleared
2919     // here instead of with the movement line stuff because that would clear some movement lines
2920     // that come from the SystemIcons
2921     m_fleet_lines.clear();
2922     ClearProjectedFleetMovementLines();
2923 
2924     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
2925     const auto& this_client_known_destroyed_objects = GetUniverse().EmpireKnownDestroyedObjectIDs(client_empire_id);
2926     const auto& this_client_stale_object_info = GetUniverse().EmpireStaleKnowledgeObjectIDs(client_empire_id);
2927     const ObjectMap& objects = Objects();
2928 
2929     // remove old system icons
2930     for (const auto& system_icon : m_system_icons)
2931         DetachChild(system_icon.second);
2932     m_system_icons.clear();
2933 
2934     // create system icons
2935     for (auto& sys : objects.all<System>()) {
2936         int sys_id = sys->ID();
2937 
2938         // skip known destroyed objects
2939         if (this_client_known_destroyed_objects.count(sys_id))
2940             continue;
2941 
2942         // create new system icon
2943         auto icon = GG::Wnd::Create<SystemIcon>(GG::X0, GG::Y0, GG::X(10), sys_id);
2944         m_system_icons[sys_id] = icon;
2945         icon->InstallEventFilter(shared_from_this());
2946         if (SidePanel::SystemID() == sys_id)
2947             icon->SetSelected(true);
2948         AttachChild(icon);
2949 
2950         // connect UI response signals.  TODO: Make these configurable in GUI?
2951         icon->LeftClickedSignal.connect(boost::bind(&MapWnd::SystemLeftClicked, this, _1));
2952         icon->RightClickedSignal.connect(boost::bind(
2953             static_cast<void (MapWnd::*)(int, GG::Flags<GG::ModKey>)>(&MapWnd::SystemRightClicked), this, _1, _2));
2954         icon->LeftDoubleClickedSignal.connect(boost::bind(&MapWnd::SystemDoubleClicked, this, _1));
2955         icon->MouseEnteringSignal.connect(boost::bind(&MapWnd::MouseEnteringSystem, this, _1, _2));
2956         icon->MouseLeavingSignal.connect(boost::bind(&MapWnd::MouseLeavingSystem, this, _1));
2957     }
2958 
2959     // temp: reset starfield each turn
2960     m_starfield_verts.clear();
2961     m_starfield_colours.clear();
2962     // end temp
2963 
2964     // create buffers for system icon and galaxy gas rendering, and starlane rendering
2965     InitSystemRenderingBuffers();
2966     InitStarlaneRenderingBuffers();
2967 
2968     // position system icons
2969     DoSystemIconsLayout();
2970 
2971 
2972     // remove old field icons
2973     for (const auto& field_icon : m_field_icons)
2974         DetachChild(field_icon.second);
2975     m_field_icons.clear();
2976 
2977     // create field icons
2978     for (auto& field : objects.all<Field>()) {
2979         int fld_id = field->ID();
2980 
2981         // skip known destroyed and stale fields
2982         if (this_client_known_destroyed_objects.count(fld_id))
2983             continue;
2984         if (this_client_stale_object_info.count(fld_id))
2985             continue;
2986         // don't skip not visible but not stale fields; still expect these to be where last seen, or near there
2987         //if (field->GetVisibility(client_empire_id) <= VIS_NO_VISIBILITY)
2988         //    continue;
2989 
2990         // create new system icon
2991         auto icon = GG::Wnd::Create<FieldIcon>(fld_id);
2992         m_field_icons[fld_id] = icon;
2993         icon->InstallEventFilter(shared_from_this());
2994 
2995         AttachChild(icon);
2996 
2997         icon->RightClickedSignal.connect(boost::bind(
2998             static_cast<void (MapWnd::*)(int)>(&MapWnd::FieldRightClicked), this, _1));
2999     }
3000 
3001     // position field icons
3002     DoFieldIconsLayout();
3003     InitFieldRenderingBuffers();
3004 
3005     InitVisibilityRadiiRenderingBuffers();
3006 
3007     // create fleet buttons and move lines.  needs to be after InitStarlaneRenderingBuffers so that m_starlane_endpoints is populated
3008     RefreshFleetButtons();
3009 
3010 
3011     // move field icons to bottom of child stack so that other icons can be moused over with a field
3012     for (const auto& field_icon : m_field_icons)
3013         MoveChildDown(field_icon.second);
3014 }
3015 
InitSystemRenderingBuffers()3016 void MapWnd::InitSystemRenderingBuffers() {
3017     DebugLogger() << "MapWnd::InitSystemRenderingBuffers";
3018     ScopedTimer timer("MapWnd::InitSystemRenderingBuffers", true);
3019 
3020     // clear out all the old buffers
3021     ClearSystemRenderingBuffers();
3022 
3023     // Generate texture coordinates to be used for subsequent vertex buffer creation.
3024     // Note these coordinates assume the texture is twice as large as it should
3025     // be.  This allows us to use one set of texture coords for everything, even
3026     // though the star-halo textures must be rendered at sizes as much as twice
3027     // as large as the star-disc textures.
3028     for (std::size_t i = 0; i < m_system_icons.size(); ++i) {
3029         m_star_texture_coords.store( 1.5,-0.5);
3030         m_star_texture_coords.store(-0.5,-0.5);
3031         m_star_texture_coords.store(-0.5, 1.5);
3032         m_star_texture_coords.store( 1.5, 1.5);
3033     }
3034 
3035 
3036     for (const auto& system_icon : m_system_icons) {
3037         const auto& icon = system_icon.second;
3038         int system_id = system_icon.first;
3039         auto system = Objects().get<System>(system_id);
3040         if (!system) {
3041             ErrorLogger() << "MapWnd::InitSystemRenderingBuffers couldn't get system with id " << system_id;
3042             continue;
3043         }
3044 
3045         // Add disc and halo textures for system icon
3046         // See note above texture coords for why we're making coordinate sets that are 2x too big.
3047         double icon_size = ClientUI::SystemIconSize();
3048         float icon_ul_x = static_cast<float>(system->X() - icon_size);
3049         float icon_ul_y = static_cast<float>(system->Y() - icon_size);
3050         float icon_lr_x = static_cast<float>(system->X() + icon_size);
3051         float icon_lr_y = static_cast<float>(system->Y() + icon_size);
3052 
3053         if (icon->DiscTexture()) {
3054             auto& core_vertices = m_star_core_quad_vertices[icon->DiscTexture()];
3055             core_vertices.store(icon_lr_x,icon_ul_y);
3056             core_vertices.store(icon_ul_x,icon_ul_y);
3057             core_vertices.store(icon_ul_x,icon_lr_y);
3058             core_vertices.store(icon_lr_x,icon_lr_y);
3059         }
3060 
3061         if (icon->HaloTexture()) {
3062             auto& halo_vertices = m_star_halo_quad_vertices[icon->HaloTexture()];
3063             halo_vertices.store(icon_lr_x,icon_ul_y);
3064             halo_vertices.store(icon_ul_x,icon_ul_y);
3065             halo_vertices.store(icon_ul_x,icon_lr_y);
3066             halo_vertices.store(icon_lr_x,icon_lr_y);
3067         }
3068 
3069 
3070         // add (rotated) gaseous substance around system
3071         if (auto gas_texture = GetGasTexture()) {
3072             const float GAS_SIZE = ClientUI::SystemIconSize() * 6.0;
3073             const float ROTATION = system_id * 27.0; // arbitrary rotation in radians ("27.0" is just a number that produces pleasing results)
3074             const float COS_THETA = std::cos(ROTATION);
3075             const float SIN_THETA = std::sin(ROTATION);
3076 
3077             // Components of corner points of a quad
3078             const float X1 =  GAS_SIZE, Y1 =  GAS_SIZE;  // upper right corner (X1, Y1)
3079             const float X2 = -GAS_SIZE, Y2 =  GAS_SIZE;  // upper left corner  (X2, Y2)
3080             const float X3 = -GAS_SIZE, Y3 = -GAS_SIZE;  // lower left corner  (X3, Y3)
3081             const float X4 =  GAS_SIZE, Y4 = -GAS_SIZE;  // lower right corner (X4, Y4)
3082 
3083             // Calculate rotated corner point components after CCW ROTATION radians around origin.
3084             const float X1r =  COS_THETA*X1 + SIN_THETA*Y1;
3085             const float Y1r = -SIN_THETA*X1 + COS_THETA*Y1;
3086             const float X2r =  COS_THETA*X2 + SIN_THETA*Y2;
3087             const float Y2r = -SIN_THETA*X2 + COS_THETA*Y2;
3088             const float X3r =  COS_THETA*X3 + SIN_THETA*Y3;
3089             const float Y3r = -SIN_THETA*X3 + COS_THETA*Y3;
3090             const float X4r =  COS_THETA*X4 + SIN_THETA*Y4;
3091             const float Y4r = -SIN_THETA*X4 + COS_THETA*Y4;
3092 
3093             // See note above texture coords for why we're making coordinate sets that are 2x too big.
3094 
3095             // add to system position to get translated scaled rotated quad corner
3096             const float GAS_X1 = system->X() + X1r;
3097             const float GAS_Y1 = system->Y() + Y1r;
3098             const float GAS_X2 = system->X() + X2r;
3099             const float GAS_Y2 = system->Y() + Y2r;
3100             const float GAS_X3 = system->X() + X3r;
3101             const float GAS_Y3 = system->Y() + Y3r;
3102             const float GAS_X4 = system->X() + X4r;
3103             const float GAS_Y4 = system->Y() + Y4r;
3104 
3105             m_galaxy_gas_quad_vertices.store(GAS_X1,GAS_Y1); // rotated upper right
3106             m_galaxy_gas_quad_vertices.store(GAS_X2,GAS_Y2); // rotated upper left
3107             m_galaxy_gas_quad_vertices.store(GAS_X3,GAS_Y3); // rotated lower left
3108             m_galaxy_gas_quad_vertices.store(GAS_X4,GAS_Y4); // rotated lower right
3109 
3110             unsigned int subtexture_index = system_id % 12;                             //  0  1  2  3  4  5  6  7  8  9 10 11
3111             unsigned int subtexture_x_index = subtexture_index / 3;                     //  0  0  0  0  1  1  1  1  2  2  2  2
3112             unsigned int subtexture_y_index = subtexture_index - 4*subtexture_x_index;  //  0  1  2  3  0  1  2  3  0  1  2  3
3113 
3114             const GLfloat* default_tex_coords = gas_texture->DefaultTexCoords();
3115             const GLfloat tex_coord_min_x = default_tex_coords[0];
3116             const GLfloat tex_coord_min_y = default_tex_coords[1];
3117             const GLfloat tex_coord_max_x = default_tex_coords[2];
3118             const GLfloat tex_coord_max_y = default_tex_coords[3];
3119 
3120             // gas texture is expected to be a 4 wide by 3 high grid
3121             // also add a bit of padding to hopefully avoid artifacts of texture edges
3122             const GLfloat tx_low_x = tex_coord_min_x  + (subtexture_x_index + 0)*(tex_coord_max_x - tex_coord_min_x)/4;
3123             const GLfloat tx_high_x = tex_coord_min_x + (subtexture_x_index + 1)*(tex_coord_max_x - tex_coord_min_x)/4;
3124             const GLfloat tx_low_y = tex_coord_min_y  + (subtexture_y_index + 0)*(tex_coord_max_y - tex_coord_min_y)/3;
3125             const GLfloat tx_high_y = tex_coord_min_y + (subtexture_y_index + 1)*(tex_coord_max_y - tex_coord_min_y)/3;
3126 
3127             m_galaxy_gas_texture_coords.store(tx_high_x, tx_low_y);
3128             m_galaxy_gas_texture_coords.store(tx_low_x,  tx_low_y);
3129             m_galaxy_gas_texture_coords.store(tx_low_x,  tx_high_y);
3130             m_galaxy_gas_texture_coords.store(tx_high_x, tx_high_y);
3131         }
3132     }
3133 
3134     // create new buffers
3135 
3136     // star cores
3137     for (auto& star_core_buffer : m_star_core_quad_vertices) {
3138         glBindTexture(GL_TEXTURE_2D, star_core_buffer.first->OpenGLId());
3139         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
3140         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
3141 
3142         star_core_buffer.second.createServerBuffer();
3143     }
3144 
3145     // star halos
3146     for (auto& star_halo_buffer : m_star_halo_quad_vertices) {
3147         glBindTexture(GL_TEXTURE_2D, star_halo_buffer.first->OpenGLId());
3148         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
3149         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
3150 
3151         star_halo_buffer.second.createServerBuffer();
3152     }
3153 
3154     m_star_texture_coords.createServerBuffer();
3155     m_galaxy_gas_quad_vertices.createServerBuffer();
3156     m_galaxy_gas_texture_coords.createServerBuffer();
3157 }
3158 
ClearSystemRenderingBuffers()3159 void MapWnd::ClearSystemRenderingBuffers() {
3160     m_star_core_quad_vertices.clear();
3161     m_star_halo_quad_vertices.clear();
3162     m_galaxy_gas_quad_vertices.clear();
3163     m_galaxy_gas_texture_coords.clear();
3164     m_star_texture_coords.clear();
3165     m_star_circle_vertices.clear();
3166 }
3167 
3168 namespace GetPathsThroughSupplyLanes {
3169     // SupplyLaneMap map keyed by system containing all systems
3170     // corresponding to valid supply lane destinations
3171     typedef std::unordered_multimap<int,int> SupplyLaneMMap;
3172 
3173 
3174     /**
3175        GetPathsThroughSupplyLanes starts with:
3176 
3177        \p terminal_points are system ids of systems that contain either
3178        a resource source or a resource sink.
3179 
3180        \p supply_lanes are pairs of system ids at the end of supply
3181        lanes.
3182 
3183        GetPathsThroughSupplyLanes returns a \p good_path.
3184 
3185        The \p good_path is all system ids of systems connecting any \p
3186        terminal_point to any other reachable \p terminal_point along the
3187        \p supply_lanes. The \p good_path is all systems on a path that
3188        could transport a resource from a source to a sink along a
3189        starlane that is part of the starlanes through which supply can
3190        flow (See Empire/Supply.h for details.). The \p good_path
3191        includes the terminal point system ids that are part of the
3192        path.  The \p good_path will exclude terminal_points not
3193        connected to a supply lane, islands of supply lane not connected
3194        to at least two terminal points, and dead-end lengths of supply
3195        lane that don't connect between two terminal points.
3196 
3197 
3198        Algorithm Descrition:
3199 
3200        The algorithm starts with terminal points and supply lanes.  It
3201        finds all paths from any terminal point to any other terminal
3202        point connected only by supply lanes.
3203 
3204        The algorithm finds and returns all system ids on the \p
3205        good_path in two steps:
3206        1) find mid points on paths along supply lanes between terminal points,
3207        2) return the system ids collected by tracing the paths from
3208           mid points to terminal points of the found paths.
3209 
3210 
3211        In the first part, it starts a breadth first search from every
3212        terminal point at once.  It tracks which terminal point each path
3213        started from.
3214 
3215        When two paths from different terminal points meet it records
3216        both points that met as mid points on a good path between
3217        terminal points.
3218 
3219        When two paths meet from the same terminal points it merges them
3220        into one path.
3221 
3222 
3223        In the second part, it starts from the mid points and works its
3224        way back to the terminal points, recording every system along the
3225        path as part of the good path.  It stops when it reaches a system
3226        already in the good path.
3227 
3228 
3229        The algorithm is fast because neither the first nor the second
3230        part visits any system more than once.
3231 
3232        The first part uses visited to track already visited systems.
3233 
3234        The second part stops back tracking along paths when it reaches
3235        systems already on the good path.
3236 
3237      */
3238     void GetPathsThroughSupplyLanes(
3239         std::unordered_set<int>& good_path,
3240         const std::unordered_set<int>& terminal_points,
3241         const SupplyLaneMMap& supply_lanes);
3242 
3243 
3244     // PathInfo stores the \p ids of systems one hop back on a path
3245     // toward an \p o originating terminal system.
3246     struct PathInfo {
PathInfoGetPathsThroughSupplyLanes::PathInfo3247         PathInfo(int a, int o) : one_hop_back(1, a), single_origin(o) {}
PathInfoGetPathsThroughSupplyLanes::PathInfo3248         PathInfo(int o) : one_hop_back(), single_origin(o) {}
3249         // system(s) one hop back on the path.
3250         // The terminal point has no preceding system.
3251         // Merged paths are indicated with multiple preceding systems.
3252         std::vector<int> one_hop_back;
3253         // The originating terminal point.
3254         // If single origin is boost::none then two paths with at least
3255         // two different terminal points merged.
3256         boost::optional<int> single_origin;
3257     };
3258 
3259     struct PrevCurrInfo {
PrevCurrInfoGetPathsThroughSupplyLanes::PrevCurrInfo3260         PrevCurrInfo(int p, int n, int o) : prev(p), curr(n), origin(o) {}
3261         int prev, curr, origin;
3262     };
3263 
GetPathsThroughSupplyLanes(std::unordered_set<int> & good_path,const std::unordered_set<int> & terminal_points,const SupplyLaneMMap & supply_lanes)3264     void GetPathsThroughSupplyLanes(
3265         std::unordered_set<int> & good_path,
3266         const std::unordered_set<int> & terminal_points,
3267         const SupplyLaneMMap& supply_lanes)
3268     {
3269         good_path.clear();
3270 
3271         // No terminal points, so all paths lead nowhere.
3272         if (terminal_points.empty())
3273             return;
3274 
3275         // Part One:  Find all reachable mid points between two different
3276         // terminal points.
3277 
3278         // try_next holds systems reached in the breadth first search
3279         // that have not had the supply lanes leaving them explored.
3280         std::deque<PrevCurrInfo> try_next;
3281 
3282         // visited holds systems already reached by the breadth first search.
3283         std::unordered_map<int, PathInfo> visited;
3284 
3285         // reachable_midpoints holds all systems reachable from at least
3286         // two different terminal points.
3287         std::vector<int> reachable_midpoints;
3288 
3289         // Initialize with all the terminal points, by adding all
3290         // terminal points to the queue, and to visited.
3291         for (int terminal_point : terminal_points) {
3292             try_next.push_back(PrevCurrInfo(terminal_point, terminal_point, terminal_point));
3293             visited.insert({terminal_point, PathInfo(terminal_point)});
3294         }
3295 
3296         // Find all reachable midpoints where paths from two different
3297         // terminal points meet.
3298         while (!try_next.empty() ) {
3299             // Try the next system from the queue.
3300             const PrevCurrInfo& curr = try_next.front();
3301 
3302             // Check each supply lane that exits this sytem.
3303             auto supplylane_endpoints = supply_lanes.equal_range(curr.curr);
3304             for (auto sup_it = supplylane_endpoints.first;
3305                  sup_it != supplylane_endpoints.second; ++sup_it)
3306             {
3307                 int next = sup_it->second;
3308 
3309                 // Skip the system if it back tracks.
3310                 if (next == curr.prev)
3311                     continue;
3312 
3313                 auto previous = visited.find(next);
3314 
3315                 // next has no previous so it is an unvisited
3316                 // system. Create a new previous from curr->next with
3317                 // the same originating terminal point as the current
3318                 // system.
3319                 if (previous == visited.end()) {
3320                     visited.insert({next, PathInfo(curr.curr, curr.origin)});
3321                     try_next.push_back(PrevCurrInfo(curr.curr, next, curr.origin));
3322 
3323                 // next has an ancester so it was visited. Modify the
3324                 // older previous to merge paths/create mid points.
3325                 } else {
3326                     // curr and the previous have the same origin so add
3327                     // curr to the systems one hop back along the path
3328                     // to previous.
3329                     if (previous->second.single_origin
3330                         && previous->second.single_origin == curr.origin)
3331                     {
3332                         previous->second.one_hop_back.push_back(curr.curr);
3333 
3334                     // curr and the previous have different origins so
3335                     // mark both as reachable midpoints along a good path.
3336                     } else if (previous->second.single_origin
3337                         && previous->second.single_origin != curr.origin)
3338                     {
3339                         // Single origin becomes multi-origin and these
3340                         // points are both marked as midpoints.
3341                         previous->second.single_origin = boost::none;
3342                         reachable_midpoints.push_back(curr.curr);
3343                         reachable_midpoints.push_back(next);
3344 
3345                     // previous is multi-origin so it is already a mid
3346                     // point on a good path to multiple terminal
3347                     // points.  Add curr to the systems one hop back
3348                     // along the path to previous.
3349                     } else
3350                         previous->second.one_hop_back.push_back(curr.curr);
3351                 }
3352             }
3353             try_next.pop_front();
3354         }
3355 
3356         // Queue is exhausted.
3357 
3358         // Part Two: Starting from the mid points find all systems on
3359         // good paths between terminal points.
3360 
3361         // No terminal point has a path to any other terminal point.
3362         if (reachable_midpoints.empty())
3363             return;
3364 
3365         // Return all systems on any path back to a terminal point.
3366         // Start from every mid point and back track along all paths
3367         // from that mid point adding each system to the good path.
3368         // Stop back tracking when you hit a system already on the good
3369         // path.
3370 
3371         // All visited systems on the path(s) from this midpoint not yet processed.
3372         std::unordered_set<int> unprocessed;
3373 
3374         for (int reachable_midpoint : reachable_midpoints) {
3375             std::unordered_map<int, PathInfo>::const_iterator previous_ii_sys;
3376             int ii_sys;
3377 
3378             // Add the mid point to unprocessed, and while there
3379             // are more unprocessed keep checking if the next system is
3380             // in the good_path.
3381             unprocessed.insert(reachable_midpoint);
3382             while (!unprocessed.empty()) {
3383                 ii_sys = *unprocessed.begin();
3384                 unprocessed.erase(unprocessed.begin());
3385 
3386                 // If ii_sys is not in the good_path, then add it to the
3387                 // good_path and add all of its visited to the unprocessed.
3388                 if ((previous_ii_sys = visited.find(ii_sys)) != visited.end()
3389                     && (good_path.count(ii_sys) == 0))
3390                 {
3391                     good_path.insert(ii_sys);
3392                     unprocessed.insert(previous_ii_sys->second.one_hop_back.begin(),
3393                                        previous_ii_sys->second.one_hop_back.end());
3394                 }
3395             }
3396         }
3397         return;
3398     }
3399 }
3400 
3401 namespace {
3402     // Reimplementation of the boost::hash_range function, embedding
3403     // boost::hash_combine and using std::hash instead of boost::hash
3404     struct hash_set {
operator ()__anon5bb68ec91211::hash_set3405         std::size_t operator()(const std::set<int>& set) const
3406         {
3407             std::size_t seed(0);
3408             std::hash<int> hasher;
3409 
3410             for(auto element : set)
3411                 seed ^= hasher(element) + 0x9e3779b9 + (seed<<6) + (seed>>2);
3412 
3413             return seed;
3414         }
3415     };
3416 
3417     /** Look a \p kkey in \p mmap and if not found allocate a new
3418         shared_ptr with the default constructor.*/
3419     template <typename Map>
lookup_or_make_shared(Map & mmap,typename Map::key_type const & kkey)3420     typename Map::mapped_type& lookup_or_make_shared(Map& mmap, typename Map::key_type const& kkey) {
3421         auto map_it = mmap.find(kkey);
3422         if (map_it == mmap.end()) {
3423             map_it = mmap.insert(map_it, {kkey, std::make_shared<typename Map::mapped_type::element_type>()});
3424             if (map_it == mmap.end())
3425                 ErrorLogger() << "Unable to insert new empty set into map.";
3426         }
3427         return map_it->second;
3428     }
3429 
3430 
3431     /* Takes X and Y coordinates of a pair of systems and moves these points inwards along the vector
3432      * between them by the radius of a system on screen (at zoom 1.0) and return result */
StarlaneEndPointsFromSystemPositions(double X1,double Y1,double X2,double Y2)3433     LaneEndpoints StarlaneEndPointsFromSystemPositions(double X1, double Y1, double X2, double Y2) {
3434         // get unit vector
3435         double deltaX = X2 - X1, deltaY = Y2 - Y1;
3436         double mag = std::sqrt(deltaX*deltaX + deltaY*deltaY);
3437 
3438         double ring_radius = ClientUI::SystemCircleSize() / 2.0 + 0.5;
3439 
3440         // safety check.  don't modify original coordinates if they're too close togther
3441         if (mag > 2*ring_radius) {
3442             // rescale vector to length of ring radius
3443             double offsetX = deltaX / mag * ring_radius;
3444             double offsetY = deltaY / mag * ring_radius;
3445 
3446             // move start and end points inwards by rescaled vector
3447             X1 += offsetX;
3448             Y1 += offsetY;
3449             X2 -= offsetX;
3450             Y2 -= offsetY;
3451         }
3452 
3453         LaneEndpoints retval(static_cast<float>(X1), static_cast<float>(Y1), static_cast<float>(X2), static_cast<float>(Y2));
3454         return retval;
3455     }
3456 
3457 
GetResPoolLaneInfo(int empire_id,std::unordered_map<std::set<int>,std::shared_ptr<std::set<int>>,hash_set> & res_pool_systems,std::unordered_map<std::set<int>,std::shared_ptr<std::set<int>>,hash_set> & res_group_cores,std::unordered_set<int> & res_group_core_members,std::unordered_map<int,std::shared_ptr<std::set<int>>> & member_to_core,std::shared_ptr<std::unordered_set<int>> & under_alloc_res_grp_core_members)3458     void GetResPoolLaneInfo(int empire_id,
3459                             std::unordered_map<std::set<int>, std::shared_ptr<std::set<int>>, hash_set>& res_pool_systems,
3460                             std::unordered_map<std::set<int>, std::shared_ptr<std::set<int>>, hash_set>& res_group_cores,
3461                             std::unordered_set<int>& res_group_core_members,
3462                             std::unordered_map<int, std::shared_ptr<std::set<int>>>& member_to_core,
3463                             std::shared_ptr<std::unordered_set<int>>& under_alloc_res_grp_core_members)
3464     {
3465         res_pool_systems.clear();
3466         res_group_cores.clear();
3467         res_group_core_members.clear();
3468         member_to_core.clear();
3469         under_alloc_res_grp_core_members.reset();
3470         if (empire_id == ALL_EMPIRES)
3471             return;
3472         const Empire* empire = GetEmpire(empire_id);
3473         if (!empire)
3474             return;
3475 
3476         const ProductionQueue& queue = empire->GetProductionQueue();
3477         const auto& allocated_pp(queue.AllocatedPP());
3478         const auto available_pp(empire->GetResourcePool(RE_INDUSTRY)->Output());
3479         // For each industry set,
3480         // add all planet's systems to res_pool_systems[industry set]
3481         for (const auto& available_pp_group : available_pp) {
3482             float group_pp = available_pp_group.second;
3483             if (group_pp < 1e-4f)
3484                 continue;
3485 
3486             // std::string this_pool = "( ";
3487             for (int object_id : available_pp_group.first) {
3488                 // this_pool += std::to_string(object_id) +", ";
3489 
3490                 auto planet = Objects().get<Planet>(object_id);
3491                 if (!planet)
3492                     continue;
3493 
3494                 //DebugLogger() << "Empire " << empire_id << "; Planet (" << object_id << ") is named " << planet->Name();
3495 
3496                 int system_id = planet->SystemID();
3497                 auto system = Objects().get<System>(system_id);
3498                 if (!system)
3499                     continue;
3500 
3501                 lookup_or_make_shared(res_pool_systems, available_pp_group.first)->insert(system_id);
3502             }
3503             // this_pool += ")";
3504             //DebugLogger() << "Empire " << empire_id << "; ResourcePool[RE_INDUSTRY] resourceGroup (" << this_pool << ") has (" << available_pp_group.second << " PP available";
3505             //DebugLogger() << "Empire " << empire_id << "; ResourcePool[RE_INDUSTRY] resourceGroup (" << this_pool << ") has (" << allocated_pp[available_pp_group.first] << " PP allocated";
3506         }
3507 
3508 
3509         // Convert supply starlanes to non-directional.  This saves half
3510         // of the lookups.
3511         GetPathsThroughSupplyLanes::SupplyLaneMMap resource_supply_lanes_undirected;
3512         const auto resource_supply_lanes_directed =
3513             GetSupplyManager().SupplyStarlaneTraversals(empire_id);
3514 
3515         for (const auto& supply_lane : resource_supply_lanes_directed) {
3516             resource_supply_lanes_undirected.insert({supply_lane.first, supply_lane.second});
3517             resource_supply_lanes_undirected.insert({supply_lane.second, supply_lane.first});
3518         }
3519 
3520         // For each pool of resources find all paths available through
3521         // the supply network.
3522 
3523         for (auto& res_pool_system : res_pool_systems) {
3524             auto& group_core = lookup_or_make_shared(res_group_cores, res_pool_system.first);
3525 
3526             // All individual resource system are included in the
3527             // network on their own.
3528             for (int system_id : *(res_pool_system.second)) {
3529                 group_core->insert(system_id);
3530                 res_group_core_members.insert(system_id);
3531             }
3532 
3533             // Convert res_pool_system.second from set<int> to
3534             // unordered_set<int> to improve lookup speed.
3535             std::unordered_set<int> terminal_points;
3536             for (int system_id : *(res_pool_system.second)) {
3537                 terminal_points.insert(system_id);
3538             }
3539 
3540             std::unordered_set<int> paths;
3541             GetPathsThroughSupplyLanes::GetPathsThroughSupplyLanes(
3542                 paths, terminal_points, resource_supply_lanes_undirected);
3543 
3544             // All systems on the paths are valid end points so they are
3545             // added to the core group of systems that will be rendered
3546             // with thick lines.
3547             for (int waypoint : paths) {
3548                 group_core->insert(waypoint);
3549                 res_group_core_members.insert(waypoint);
3550                 member_to_core[waypoint] = group_core;
3551             }
3552         }
3553 
3554         // Take note of all systems of under allocated resource groups.
3555         for (const auto& available_pp_group : available_pp) {
3556             float group_pp = available_pp_group.second;
3557             if (group_pp < 1e-4f)
3558                 continue;
3559 
3560             auto allocated_it = allocated_pp.find(available_pp_group.first);
3561             if (allocated_it == allocated_pp.end() || (group_pp > allocated_it->second + 0.05)) {
3562                 auto group_core_it = res_group_cores.find(available_pp_group.first);
3563                 if (group_core_it != res_group_cores.end()) {
3564                     if (!under_alloc_res_grp_core_members)
3565                         under_alloc_res_grp_core_members = std::make_shared<std::unordered_set<int>>();
3566                     under_alloc_res_grp_core_members->insert(group_core_it->second->begin(), group_core_it->second->end());
3567                 }
3568             }
3569         }
3570     }
3571 
3572 
PrepFullLanesToRender(const std::unordered_map<int,std::shared_ptr<SystemIcon>> & sys_icons,GG::GL2DVertexBuffer & starlane_vertices,GG::GLRGBAColorBuffer & starlane_colors)3573     void PrepFullLanesToRender(const std::unordered_map<int, std::shared_ptr<SystemIcon>>& sys_icons,
3574                                GG::GL2DVertexBuffer& starlane_vertices,
3575                                GG::GLRGBAColorBuffer& starlane_colors)
3576     {
3577         const auto& this_client_known_destroyed_objects =
3578             GetUniverse().EmpireKnownDestroyedObjectIDs(HumanClientApp::GetApp()->EmpireID());
3579         const GG::Clr UNOWNED_LANE_COLOUR = GetOptionsDB().Get<GG::Clr>("ui.map.starlane.color");
3580 
3581         std::set<std::pair<int, int>> already_rendered_full_lanes;
3582 
3583         for (const auto& id_icon : sys_icons) {
3584             int system_id = id_icon.first;
3585 
3586             // skip systems that don't actually exist
3587             if (this_client_known_destroyed_objects.count(system_id))
3588                 continue;
3589 
3590             auto start_system = Objects().get<System>(system_id);
3591             if (!start_system) {
3592                 ErrorLogger() << "GetFullLanesToRender couldn't get system with id " << system_id;
3593                 continue;
3594             }
3595 
3596             // add system's starlanes
3597             for (const auto& render_lane : start_system->StarlanesWormholes()) {
3598                 bool lane_is_wormhole = render_lane.second;
3599                 if (lane_is_wormhole) continue; // at present, not rendering wormholes
3600 
3601                 int lane_end_sys_id = render_lane.first;
3602 
3603                 // skip lanes to systems that don't actually exist
3604                 if (this_client_known_destroyed_objects.count(lane_end_sys_id))
3605                     continue;
3606 
3607                 auto dest_system = Objects().get<System>(render_lane.first);
3608                 if (!dest_system)
3609                     continue;
3610 
3611 
3612                 // check that this lane isn't already in map / being rendered.
3613                 if (already_rendered_full_lanes.count({start_system->ID(), dest_system->ID()}))
3614                     continue;
3615                 already_rendered_full_lanes.insert({start_system->ID(), dest_system->ID()});
3616                 already_rendered_full_lanes.insert({dest_system->ID(), start_system->ID()});
3617 
3618 
3619                 // add vertices for this full-length starlane
3620                 LaneEndpoints lane_endpoints = StarlaneEndPointsFromSystemPositions(start_system->X(), start_system->Y(), dest_system->X(), dest_system->Y());
3621                 starlane_vertices.store(lane_endpoints.X1, lane_endpoints.Y1);
3622                 starlane_vertices.store(lane_endpoints.X2, lane_endpoints.Y2);
3623 
3624 
3625                 // determine colour(s) for lane based on which empire(s) can transfer resources along the lane.
3626                 // todo: multiple rendered lanes (one for each empire) when multiple empires use the same lane.
3627                 GG::Clr lane_colour = UNOWNED_LANE_COLOUR;    // default colour if no empires transfer resources along starlane
3628                 for (auto& entry : Empires()) {
3629                     Empire* empire = entry.second;
3630                     const auto& resource_supply_lanes = GetSupplyManager().SupplyStarlaneTraversals(entry.first);
3631 
3632                     std::pair<int, int> lane_forward{start_system->ID(), dest_system->ID()};
3633                     std::pair<int, int> lane_backward{dest_system->ID(), start_system->ID()};
3634 
3635                     // see if this lane exists in this empire's supply propagation lanes set.  either direction accepted.
3636                     if (resource_supply_lanes.count(lane_forward) || resource_supply_lanes.count(lane_backward)) {
3637                         lane_colour = empire->Color();
3638                         break;
3639                     }
3640                 }
3641 
3642                 // vertex colours for starlane
3643                 starlane_colors.store(lane_colour);
3644                 starlane_colors.store(lane_colour);
3645             }
3646         }
3647     }
3648 
PrepResourceConnectionLanesToRender(const std::unordered_map<int,std::shared_ptr<SystemIcon>> & sys_icons,int empire_id,std::set<std::pair<int,int>> & rendered_half_starlanes,GG::GL2DVertexBuffer & rc_starlane_vertices,GG::GLRGBAColorBuffer & rc_starlane_colors)3649     void PrepResourceConnectionLanesToRender(const std::unordered_map<int, std::shared_ptr<SystemIcon>>& sys_icons,
3650                                              int empire_id,
3651                                              std::set<std::pair<int, int>>& rendered_half_starlanes,
3652                                              GG::GL2DVertexBuffer& rc_starlane_vertices,
3653                                              GG::GLRGBAColorBuffer& rc_starlane_colors)
3654     {
3655         rendered_half_starlanes.clear();
3656 
3657         const Empire* empire = GetEmpire(empire_id);
3658         if (!empire)
3659             return;
3660         GG::Clr lane_colour = empire->Color();
3661 
3662         // map keyed by ResourcePool (set of objects) to the corresponding set of system ids
3663         std::unordered_map<std::set<int>, std::shared_ptr<std::set<int>>, hash_set> res_pool_systems;
3664         // map keyed by ResourcePool to the set of systems considered the core of the corresponding ResGroup
3665         std::unordered_map<std::set<int>, std::shared_ptr<std::set<int>>, hash_set> res_group_cores;
3666         std::unordered_set<int> res_group_core_members;
3667         std::unordered_map<int, std::shared_ptr<std::set<int>>> member_to_core;
3668         std::shared_ptr<std::unordered_set<int>> under_alloc_res_grp_core_members;
3669         GetResPoolLaneInfo(empire_id, res_pool_systems,
3670                            res_group_cores, res_group_core_members,
3671                            member_to_core, under_alloc_res_grp_core_members);
3672 
3673         const std::set<int>& this_client_known_destroyed_objects =
3674             GetUniverse().EmpireKnownDestroyedObjectIDs(HumanClientApp::GetApp()->EmpireID());
3675         //unused variable const GG::Clr UNOWNED_LANE_COLOUR = GetOptionsDB().Get<GG::Clr>("ui.map.starlane.color");
3676 
3677 
3678         for (const auto& id_icon : sys_icons) {
3679             int system_id = id_icon.first;
3680 
3681             // skip systems that don't actually exist
3682             if (this_client_known_destroyed_objects.count(system_id))
3683                 continue;
3684 
3685             auto start_system = Objects().get<System>(system_id);
3686             if (!start_system) {
3687                 ErrorLogger() << "GetFullLanesToRender couldn't get system with id " << system_id;
3688                 continue;
3689             }
3690 
3691             // add system's starlanes
3692             for (const auto& render_lane : start_system->StarlanesWormholes()) {
3693                 bool lane_is_wormhole = render_lane.second;
3694                 if (lane_is_wormhole) continue; // at present, not rendering wormholes
3695 
3696                 int lane_end_sys_id = render_lane.first;
3697 
3698                 // skip lanes to systems that don't actually exist
3699                 if (this_client_known_destroyed_objects.count(lane_end_sys_id))
3700                     continue;
3701 
3702                 auto dest_system = Objects().get<System>(render_lane.first);
3703                 if (!dest_system)
3704                     continue;
3705                 //std::cout << "colouring lanes between " << start_system->Name() << " and " << dest_system->Name() << std::endl;
3706 
3707 
3708                 // check that this lane isn't already going to be rendered.  skip it if it is.
3709                 if (rendered_half_starlanes.count({start_system->ID(), dest_system->ID()}))
3710                     continue;
3711 
3712 
3713                 // add resource connection highlight lanes
3714                 //std::pair<int, int> lane_forward{start_system->ID(), dest_system->ID()};
3715                 //std::pair<int, int> lane_backward{dest_system->ID(), start_system->ID()};
3716                 LaneEndpoints lane_endpoints = StarlaneEndPointsFromSystemPositions(start_system->X(), start_system->Y(), dest_system->X(), dest_system->Y());
3717 
3718                 if (!res_group_core_members.count(start_system->ID()))
3719                     continue;
3720 
3721                 //start system is a res Grp core member for empire -- highlight
3722                 float indicator_extent = 0.5f;
3723                 GG::Clr lane_colour_to_use = lane_colour;
3724                 if (under_alloc_res_grp_core_members
3725                     && under_alloc_res_grp_core_members->count(start_system->ID()))
3726                 {
3727                     lane_colour_to_use = GG::DarkenClr(GG::InvertClr(lane_colour));
3728                 }
3729 
3730                 auto start_core = member_to_core.find(start_system->ID());
3731                 auto dest_core = member_to_core.find(dest_system->ID());
3732                 if (start_core != member_to_core.end() && dest_core != member_to_core.end()
3733                     && (start_core->second != dest_core->second)
3734                     && (*(start_core->second) != *(dest_core->second)))
3735                 {
3736                     indicator_extent = 0.2f;
3737                 }
3738                 rc_starlane_vertices.store(lane_endpoints.X1, lane_endpoints.Y1);
3739                 rc_starlane_vertices.store((lane_endpoints.X2 - lane_endpoints.X1) * indicator_extent + lane_endpoints.X1,  // part way along starlane
3740                                            (lane_endpoints.Y2 - lane_endpoints.Y1) * indicator_extent + lane_endpoints.Y1);
3741 
3742                 rc_starlane_colors.store(lane_colour_to_use);
3743                 rc_starlane_colors.store(lane_colour_to_use);
3744             }
3745         }
3746     }
3747 
PrepObstructedLaneTraversalsToRender(const std::unordered_map<int,std::shared_ptr<SystemIcon>> & sys_icons,int empire_id,std::set<std::pair<int,int>> & rendered_half_starlanes,GG::GL2DVertexBuffer & starlane_vertices,GG::GLRGBAColorBuffer & starlane_colors)3748     void PrepObstructedLaneTraversalsToRender(const std::unordered_map<int, std::shared_ptr<SystemIcon>>& sys_icons,
3749                                               int empire_id,
3750                                               std::set<std::pair<int, int>>& rendered_half_starlanes,
3751                                               GG::GL2DVertexBuffer& starlane_vertices,
3752                                               GG::GLRGBAColorBuffer& starlane_colors)
3753     {
3754         auto this_empire = GetEmpire(empire_id);
3755         if (!this_empire)
3756             return;
3757 
3758         const auto& this_client_known_destroyed_objects =
3759             GetUniverse().EmpireKnownDestroyedObjectIDs(HumanClientApp::GetApp()->EmpireID());
3760 
3761 
3762         for (const auto& id_icon : sys_icons) {
3763             int system_id = id_icon.first;
3764 
3765             // skip systems that don't actually exist
3766             if (this_client_known_destroyed_objects.count(system_id))
3767                 continue;
3768 
3769             // skip systems that don't actually exist
3770             if (this_client_known_destroyed_objects.count(system_id))
3771                 continue;
3772 
3773             auto start_system = Objects().get<System>(system_id);
3774             if (!start_system) {
3775                 ErrorLogger() << "MapWnd::InitStarlaneRenderingBuffers couldn't get system with id " << system_id;
3776                 continue;
3777             }
3778 
3779             // add system's starlanes
3780             for (const auto& render_lane : start_system->StarlanesWormholes()) {
3781                 bool lane_is_wormhole = render_lane.second;
3782                 if (lane_is_wormhole) continue; // at present, not rendering wormholes
3783 
3784                 int lane_end_sys_id = render_lane.first;
3785 
3786                 // skip lanes to systems that don't actually exist
3787                 if (this_client_known_destroyed_objects.count(lane_end_sys_id))
3788                     continue;
3789 
3790                 auto dest_system = Objects().get<System>(render_lane.first);
3791                 if (!dest_system)
3792                     continue;
3793                 //std::cout << "colouring lanes between " << start_system->Name() << " and " << dest_system->Name() << std::endl;
3794 
3795 
3796                 // check that this lane isn't already going to be rendered.  skip it if it is.
3797                 if (rendered_half_starlanes.count({start_system->ID(), dest_system->ID()}))
3798                     continue;
3799 
3800 
3801                 // add obstructed lane traversals as half lanes
3802                 for (auto& entry : Empires()) {
3803                     Empire* empire = entry.second;
3804                     const auto& resource_obstructed_supply_lanes =
3805                         GetSupplyManager().SupplyObstructedStarlaneTraversals(entry.first);
3806 
3807                     // see if this lane exists in this empire's obstructed supply propagation lanes set.  either direction accepted.
3808                     if (!resource_obstructed_supply_lanes.count({start_system->ID(), dest_system->ID()}))
3809                         continue;
3810 
3811                     // found an empire that has a half lane here, so add it.
3812                     rendered_half_starlanes.insert({start_system->ID(), dest_system->ID()});  // inserted as ordered pair, so both directions can have different half-lanes
3813 
3814                     LaneEndpoints lane_endpoints = StarlaneEndPointsFromSystemPositions(start_system->X(), start_system->Y(), dest_system->X(), dest_system->Y());
3815                     starlane_vertices.store(lane_endpoints.X1, lane_endpoints.Y1);
3816                     starlane_vertices.store((lane_endpoints.X1 + lane_endpoints.X2) * 0.5f,   // half way along starlane
3817                                             (lane_endpoints.Y1 + lane_endpoints.Y2) * 0.5f);
3818 
3819                     GG::Clr lane_colour = empire->Color();
3820                     starlane_colors.store(lane_colour);
3821                     starlane_colors.store(lane_colour);
3822 
3823                     //std::cout << "Adding half lane between " << start_system->Name() << " to " << dest_system->Name() << " with colour of empire " << empire->Name() << std::endl;
3824 
3825                     break;
3826                 }
3827             }
3828         }
3829     }
3830 
CalculateStarlaneEndpoints(const std::unordered_map<int,std::shared_ptr<SystemIcon>> & sys_icons)3831     std::map<std::pair<int, int>, LaneEndpoints> CalculateStarlaneEndpoints(
3832         const std::unordered_map<int, std::shared_ptr<SystemIcon>>& sys_icons)
3833     {
3834 
3835         std::map<std::pair<int, int>, LaneEndpoints> retval;
3836 
3837         const std::set<int>& this_client_known_destroyed_objects =
3838             GetUniverse().EmpireKnownDestroyedObjectIDs(HumanClientApp::GetApp()->EmpireID());
3839 
3840         for (auto const& id_icon : sys_icons) {
3841             int system_id = id_icon.first;
3842 
3843             // skip systems that don't actually exist
3844             if (this_client_known_destroyed_objects.count(system_id))
3845                 continue;
3846 
3847             auto start_system = Objects().get<System>(system_id);
3848             if (!start_system) {
3849                 ErrorLogger() << "GetFullLanesToRender couldn't get system with id " << system_id;
3850                 continue;
3851             }
3852 
3853             // add system's starlanes
3854             for (const auto& render_lane : start_system->StarlanesWormholes()) {
3855                 bool lane_is_wormhole = render_lane.second;
3856                 if (lane_is_wormhole) continue; // at present, not rendering wormholes
3857 
3858                 int lane_end_sys_id = render_lane.first;
3859 
3860                 // skip lanes to systems that don't actually exist
3861                 if (this_client_known_destroyed_objects.count(lane_end_sys_id))
3862                     continue;
3863 
3864                 auto dest_system = Objects().get<System>(render_lane.first);
3865                 if (!dest_system)
3866                     continue;
3867 
3868                 retval[{system_id, lane_end_sys_id}] =
3869                     StarlaneEndPointsFromSystemPositions(start_system->X(), start_system->Y(),
3870                                                          dest_system->X(), dest_system->Y());
3871                 retval[{lane_end_sys_id, system_id}] =
3872                     StarlaneEndPointsFromSystemPositions(dest_system->X(), dest_system->Y(),
3873                                                          start_system->X(), start_system->Y());
3874             }
3875         }
3876 
3877         return retval;
3878     }
3879 }
3880 
InitStarlaneRenderingBuffers()3881 void MapWnd::InitStarlaneRenderingBuffers() {
3882     DebugLogger() << "MapWnd::InitStarlaneRenderingBuffers";
3883     ScopedTimer timer("MapWnd::InitStarlaneRenderingBuffers", true);
3884 
3885     ClearStarlaneRenderingBuffers();
3886 
3887     // todo: move this somewhere better... fill in starlane endpoint cache
3888     m_starlane_endpoints = CalculateStarlaneEndpoints(m_system_icons);
3889 
3890 
3891     // temp storage
3892     std::set<std::pair<int, int>> rendered_half_starlanes;  // stored as unaltered pairs, so that a each direction of traversal can be shown separately
3893 
3894 
3895     // add vertices and colours to lane rendering buffers
3896     PrepFullLanesToRender(m_system_icons, m_starlane_vertices, m_starlane_colors);
3897     PrepResourceConnectionLanesToRender(m_system_icons, HumanClientApp::GetApp()->EmpireID(),
3898                                         rendered_half_starlanes,
3899                                         m_RC_starlane_vertices, m_RC_starlane_colors);
3900     PrepObstructedLaneTraversalsToRender(m_system_icons, HumanClientApp::GetApp()->EmpireID(),
3901                                          rendered_half_starlanes,
3902                                          m_starlane_vertices, m_starlane_colors);
3903 
3904 
3905     // fill new buffers
3906     m_starlane_vertices.createServerBuffer();
3907     m_starlane_colors.createServerBuffer();
3908     m_starlane_vertices.harmonizeBufferType(m_starlane_colors);
3909     m_RC_starlane_vertices.createServerBuffer();
3910     m_RC_starlane_colors.createServerBuffer();
3911     m_RC_starlane_vertices.harmonizeBufferType(m_RC_starlane_colors);
3912 }
3913 
ClearStarlaneRenderingBuffers()3914 void MapWnd::ClearStarlaneRenderingBuffers() {
3915     m_starlane_vertices.clear();
3916     m_starlane_colors.clear();
3917     m_RC_starlane_vertices.clear();
3918     m_RC_starlane_colors.clear();
3919 }
3920 
InitFieldRenderingBuffers()3921 void MapWnd::InitFieldRenderingBuffers() {
3922     DebugLogger() << "MapWnd::InitFieldRenderingBuffers";
3923     ScopedTimer timer("MapWnd::InitFieldRenderingBuffers", true);
3924 
3925     ClearFieldRenderingBuffers();
3926 
3927     const Universe& universe = GetUniverse();
3928     int empire_id = HumanClientApp::GetApp()->EmpireID();
3929 
3930 
3931     for (auto& field_icon : m_field_icons) {
3932         bool current_field_visible = universe.GetObjectVisibilityByEmpire(field_icon.first, empire_id) > VIS_BASIC_VISIBILITY;
3933         auto field = Objects().get<Field>(field_icon.first);
3934         if (!field)
3935             continue;
3936         const float FIELD_SIZE = field->GetMeter(METER_SIZE)->Initial();  // field size is its radius
3937         if (FIELD_SIZE <= 0)
3938             continue;
3939         auto field_texture = field_icon.second->FieldTexture();
3940         if (!field_texture)
3941             continue;
3942 
3943         auto& field_both_vertex_buffers = m_field_vertices[field_texture];
3944         GG::GL2DVertexBuffer& current_field_vertex_buffer =
3945             current_field_visible ? field_both_vertex_buffers.first : field_both_vertex_buffers.second;
3946 
3947         // determine field rotation angle...
3948         float rotation_angle = field->ID() * 27.0f; // arbitrary rotation in radians ("27.0" is just a number that produces pleasing results)
3949         // per-turn rotation of texture. TODO: make depend on something scriptable
3950         float rotation_speed = 0.03f;               // arbitrary rotation rate in radians
3951         if (rotation_speed != 0.0f)
3952             rotation_angle += CurrentTurn() * rotation_speed;
3953 
3954         const float COS_THETA = std::cos(rotation_angle);
3955         const float SIN_THETA = std::sin(rotation_angle);
3956 
3957         // Components of corner points of a quad
3958         const float X1 =  FIELD_SIZE, Y1 =  FIELD_SIZE; // upper right corner (X1, Y1)
3959         const float X2 = -FIELD_SIZE, Y2 =  FIELD_SIZE; // upper left corner  (X2, Y2)
3960         const float X3 = -FIELD_SIZE, Y3 = -FIELD_SIZE; // lower left corner  (X3, Y3)
3961         const float X4 =  FIELD_SIZE, Y4 = -FIELD_SIZE; // lower right corner (X4, Y4)
3962 
3963         // Calculate rotated corner point components after CCW ROTATION radians around origin.
3964         const float X1r =  COS_THETA*X1 + SIN_THETA*Y1;
3965         const float Y1r = -SIN_THETA*X1 + COS_THETA*Y1;
3966         const float X2r =  COS_THETA*X2 + SIN_THETA*Y2;
3967         const float Y2r = -SIN_THETA*X2 + COS_THETA*Y2;
3968         const float X3r =  COS_THETA*X3 + SIN_THETA*Y3;
3969         const float Y3r = -SIN_THETA*X3 + COS_THETA*Y3;
3970         const float X4r =  COS_THETA*X4 + SIN_THETA*Y4;
3971         const float Y4r = -SIN_THETA*X4 + COS_THETA*Y4;
3972 
3973         // add to system position to get translated scaled rotated quad corners
3974         const float FIELD_X1 = field->X() + X1r;
3975         const float FIELD_Y1 = field->Y() + Y1r;
3976         const float FIELD_X2 = field->X() + X2r;
3977         const float FIELD_Y2 = field->Y() + Y2r;
3978         const float FIELD_X3 = field->X() + X3r;
3979         const float FIELD_Y3 = field->Y() + Y3r;
3980         const float FIELD_X4 = field->X() + X4r;
3981         const float FIELD_Y4 = field->Y() + Y4r;
3982 
3983         current_field_vertex_buffer.store(FIELD_X1, FIELD_Y1);  // rotated upper right
3984         current_field_vertex_buffer.store(FIELD_X2, FIELD_Y2);  // rotated upper left
3985         current_field_vertex_buffer.store(FIELD_X3, FIELD_Y3);  // rotated lower left
3986         current_field_vertex_buffer.store(FIELD_X4, FIELD_Y4);  // rotated lower right
3987 
3988         // also add circles to render scanlines for not-visible fields
3989         if (!current_field_visible) {
3990             GG::Pt circle_ul = GG::Pt(GG::X(field->X() - FIELD_SIZE), GG::Y(field->Y() - FIELD_SIZE));
3991             GG::Pt circle_lr = GG::Pt(GG::X(field->X() + FIELD_SIZE), GG::Y(field->Y() + FIELD_SIZE));
3992             BufferStoreCircleArcVertices(m_field_scanline_circles, circle_ul, circle_lr, 0, TWO_PI, true, 0, false);
3993         }
3994     }
3995     m_field_scanline_circles.createServerBuffer();
3996 
3997     for (auto& field_buffer : m_field_vertices) {
3998         auto field_texture = field_buffer.first;
3999         if (!field_texture)
4000             continue;
4001 
4002         // todo: why the binding here?
4003         glBindTexture(GL_TEXTURE_2D, field_texture->OpenGLId());
4004         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
4005         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
4006 
4007         field_buffer.second.first.createServerBuffer();
4008         field_buffer.second.second.createServerBuffer();
4009     }
4010     glBindTexture(GL_TEXTURE_2D, 0);
4011 
4012     // this buffer should only need to be as big as the largest number of
4013     // visible or not visisble fields for any single texture, but
4014     // this is simpler to prepare and should be more than big enough
4015     for (std::size_t i = 0; i < m_field_icons.size(); ++i) {
4016         m_field_texture_coords.store(1.0f, 0.0f);
4017         m_field_texture_coords.store(0.0f, 0.0f);
4018         m_field_texture_coords.store(0.0f, 1.0f);
4019         m_field_texture_coords.store(1.0f, 1.0f);
4020     }
4021     m_field_texture_coords.createServerBuffer();
4022 }
4023 
ClearFieldRenderingBuffers()4024 void MapWnd::ClearFieldRenderingBuffers() {
4025     m_field_vertices.clear();
4026     m_field_texture_coords.clear();
4027     m_field_scanline_circles.clear();
4028 }
4029 
InitVisibilityRadiiRenderingBuffers()4030 void MapWnd::InitVisibilityRadiiRenderingBuffers() {
4031     DebugLogger() << "MapWnd::InitVisibilityRadiiRenderingBuffers";
4032     //std::cout << "InitVisibilityRadiiRenderingBuffers" << std::endl;
4033     ScopedTimer timer("MapWnd::InitVisibilityRadiiRenderingBuffers", true);
4034 
4035     ClearVisibilityRadiiRenderingBuffers();
4036 
4037     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
4038     const auto& destroyed_object_ids = GetUniverse().DestroyedObjectIds();
4039     const auto& stale_object_ids = GetUniverse().EmpireStaleKnowledgeObjectIDs(client_empire_id);
4040 
4041     // for each map position and empire, find max value of detection range at that position
4042     std::map<std::pair<int, std::pair<float, float>>, float> empire_position_max_detection_ranges;
4043 
4044     for (auto& obj : Objects().all<UniverseObject>()) {
4045         int object_id = obj->ID();
4046         // skip destroyed objects
4047         if (destroyed_object_ids.count(object_id))
4048             continue;
4049         // skip stale objects
4050         if (stale_object_ids.count(object_id))
4051             continue;
4052 
4053         // skip unowned objects
4054         if (obj->Unowned())
4055             continue;
4056 
4057         // skip objects not at least partially visible this turn
4058         if (obj->GetVisibility(client_empire_id) <= VIS_BASIC_VISIBILITY)
4059             continue;
4060 
4061         // don't show radii for fleets or moving ships
4062         if (obj->ObjectType() == OBJ_FLEET) {
4063             continue;
4064         } else if (obj->ObjectType() == OBJ_SHIP) {
4065             auto ship = std::dynamic_pointer_cast<const Ship>(obj);
4066             if (!ship)
4067                 continue;
4068             auto fleet = Objects().get<Fleet>(ship->FleetID());
4069             if (!fleet || INVALID_OBJECT_ID == fleet->SystemID())
4070                 continue;
4071         }
4072 
4073         const Meter* detection_meter = obj->GetMeter(METER_DETECTION);
4074         if (!detection_meter)
4075             continue;
4076 
4077         // if this object has the largest yet checked visibility range at this location, update the location's range
4078         float X = static_cast<float>(obj->X());
4079         float Y = static_cast<float>(obj->Y());
4080         float D = detection_meter->Current();
4081         // skip objects that don't contribute detection
4082         if (D <= 0.0f)
4083             continue;
4084 
4085         // find this empires entry for this location, if any
4086         std::pair<int, std::pair<float, float>> key{obj->Owner(), {X, Y}};
4087         auto range_it = empire_position_max_detection_ranges.find(key);
4088         if (range_it != empire_position_max_detection_ranges.end()) {
4089             if (range_it->second < D) range_it->second = D; // update existing entry
4090         } else {
4091             empire_position_max_detection_ranges[key] = D;  // add new entry to map
4092         }
4093     }
4094 
4095     std::map<GG::Clr, std::vector<std::pair<GG::Pt, GG::Pt>>> circles;
4096     for (const auto& detection_circle : empire_position_max_detection_ranges) {
4097         const Empire* empire = GetEmpire(detection_circle.first.first);
4098         if (!empire) {
4099             ErrorLogger() << "InitVisibilityRadiiRenderingBuffers couldn't find empire with id: " << detection_circle.first.first;
4100             continue;
4101         }
4102 
4103         float radius = detection_circle.second;
4104         if (radius < 5.0f || radius > 2048.0f)  // hide uselessly small and ridiculously large circles. the latter so super-testers don't have an empire-coloured haze over the whole map.
4105             continue;
4106 
4107         GG::Clr circle_colour = empire->Color();
4108         circle_colour.a = 8*GetOptionsDB().Get<int>("ui.map.detection.range.opacity");
4109 
4110         GG::Pt circle_centre = GG::Pt(GG::X(detection_circle.first.second.first), GG::Y(detection_circle.first.second.second));
4111         GG::Pt ul = circle_centre - GG::Pt(GG::X(static_cast<int>(radius)), GG::Y(static_cast<int>(radius)));
4112         GG::Pt lr = circle_centre + GG::Pt(GG::X(static_cast<int>(radius)), GG::Y(static_cast<int>(radius)));
4113 
4114         circles[circle_colour].push_back({ul, lr});
4115 
4116         //std::cout << "adding radii circle at: " << circle_centre << " for empire: " << it->first.first << std::endl;
4117     }
4118 
4119 
4120     const GG::Pt BORDER_INSET(GG::X(1.0f), GG::Y(1.0f));
4121 
4122     // loop over colours / empires, adding a batch of triangles to buffers for
4123     // each's visibilty circles and outlines
4124     for (const auto& circle_group : circles) {
4125         // get empire colour and calculate brighter radii outline colour
4126         GG::Clr circle_colour = circle_group.first;
4127         GG::Clr border_colour = circle_colour;
4128         border_colour.a = std::min(255, border_colour.a + 80);
4129         AdjustBrightness(border_colour, 2.0, true);
4130 
4131         std::size_t radii_start_index = m_visibility_radii_vertices.size();
4132         std::size_t border_start_index = m_visibility_radii_border_vertices.size();
4133 
4134         for (const auto& circle : circle_group.second) {
4135             const GG::Pt& ul = circle.first;
4136             const GG::Pt& lr = circle.second;
4137 
4138             unsigned int initial_size = m_visibility_radii_vertices.size();
4139             // store triangles for filled / transparent part of radii
4140             BufferStoreCircleArcVertices(m_visibility_radii_vertices, ul, lr, 0.0, TWO_PI, true, 0, false);
4141 
4142             // store colours for triangles
4143             unsigned int size_increment = m_visibility_radii_vertices.size() - initial_size;
4144             for (unsigned int count = 0; count < size_increment; ++count)
4145                  m_visibility_radii_colors.store(circle_colour);
4146 
4147             // store line segments for border lines of radii
4148             initial_size = m_visibility_radii_border_vertices.size();
4149             BufferStoreCircleArcVertices(m_visibility_radii_border_vertices, ul + BORDER_INSET, lr - BORDER_INSET,
4150                                          0.0, TWO_PI, false, 0, false);
4151 
4152             // store colours for line segments
4153             size_increment = m_visibility_radii_border_vertices.size() - initial_size;
4154             for (unsigned int count = 0; count < size_increment; ++count)
4155                  m_visibility_radii_border_colors.store(border_colour);
4156         }
4157 
4158         // store how many vertices to render for this colour
4159         std::size_t radii_end_index = m_visibility_radii_vertices.size();
4160         std::size_t border_end_index = m_visibility_radii_border_vertices.size();
4161 
4162         m_radii_radii_vertices_indices_runs.push_back({
4163             {radii_start_index, radii_end_index - radii_start_index},
4164             {border_start_index, border_end_index - border_start_index}});
4165     }
4166 
4167     m_visibility_radii_border_vertices.createServerBuffer();
4168     m_visibility_radii_border_colors.createServerBuffer();
4169     m_visibility_radii_border_vertices.harmonizeBufferType(m_visibility_radii_border_colors);
4170     m_visibility_radii_vertices.createServerBuffer();
4171     m_visibility_radii_colors.createServerBuffer();
4172     m_visibility_radii_vertices.harmonizeBufferType(m_visibility_radii_colors);
4173 }
4174 
ClearVisibilityRadiiRenderingBuffers()4175 void MapWnd::ClearVisibilityRadiiRenderingBuffers() {
4176     m_visibility_radii_vertices.clear();
4177     m_visibility_radii_colors.clear();
4178     m_visibility_radii_border_vertices.clear();
4179     m_visibility_radii_border_colors.clear();
4180     m_radii_radii_vertices_indices_runs.clear();
4181 }
4182 
InitScaleCircleRenderingBuffer()4183 void MapWnd::InitScaleCircleRenderingBuffer() {
4184     ClearScaleCircleRenderingBuffer();
4185 
4186     if (!m_scale_line)
4187         return;
4188 
4189     int radius = static_cast<int>(Value(m_scale_line->GetLength()) / std::max(0.001, m_scale_line->GetScaleFactor()));
4190     if (radius < 5)
4191         return;
4192 
4193     auto selected_system = Objects().get<System>(SidePanel::SystemID());
4194     if (!selected_system)
4195         return;
4196 
4197     GG::Pt circle_centre = GG::Pt(GG::X(selected_system->X()), GG::Y(selected_system->Y()));
4198     GG::Pt ul = circle_centre - GG::Pt(GG::X(radius), GG::Y(radius));
4199     GG::Pt lr = circle_centre + GG::Pt(GG::X(radius), GG::Y(radius));
4200 
4201     BufferStoreCircleArcVertices(m_scale_circle_vertices, ul, lr, 0, TWO_PI, false, 0, true);
4202 
4203     m_scale_circle_vertices.createServerBuffer();
4204 }
4205 
ClearScaleCircleRenderingBuffer()4206 void MapWnd::ClearScaleCircleRenderingBuffer()
4207 { m_scale_circle_vertices.clear(); }
4208 
ClearStarfieldRenderingBuffers()4209 void MapWnd::ClearStarfieldRenderingBuffers() {
4210     m_starfield_verts.clear();
4211     m_starfield_colours.clear();
4212 }
4213 
RestoreFromSaveData(const SaveGameUIData & data)4214 void MapWnd::RestoreFromSaveData(const SaveGameUIData& data) {
4215     m_zoom_steps_in = data.map_zoom_steps_in;
4216 
4217     GG::Pt ul = UpperLeft();
4218     GG::Pt map_ul = GG::Pt(GG::X(data.map_left), GG::Y(data.map_top));
4219     GG::Pt map_move = map_ul - ul;
4220     OffsetMove(map_move);
4221 
4222     // this correction ensures that zooming in doesn't leave too large a margin to the side
4223     GG::Pt move_to_pt = ul = ClientUpperLeft();
4224     CorrectMapPosition(move_to_pt);
4225     //GG::Pt final_move = move_to_pt - ul;
4226 
4227     MoveTo(move_to_pt - GG::Pt(AppWidth(), AppHeight()));
4228 
4229     m_fleets_exploring = data.fleets_exploring;
4230 }
4231 
ShowSystemNames()4232 void MapWnd::ShowSystemNames() {
4233     for (auto& system_icon : m_system_icons) {
4234         system_icon.second->ShowName();
4235     }
4236 }
4237 
HideSystemNames()4238 void MapWnd::HideSystemNames() {
4239     for (auto& system_icon : m_system_icons) {
4240         system_icon.second->HideName();
4241     }
4242 }
4243 
CenterOnMapCoord(double x,double y)4244 void MapWnd::CenterOnMapCoord(double x, double y) {
4245     GG::Pt ul = ClientUpperLeft();
4246     GG::X_d current_x = (AppWidth() / 2 - ul.x) / ZoomFactor();
4247     GG::Y_d current_y = (AppHeight() / 2 - ul.y) / ZoomFactor();
4248     GG::Pt map_move = GG::Pt(static_cast<GG::X>((current_x - x) * ZoomFactor()),
4249                              static_cast<GG::Y>((current_y - y) * ZoomFactor()));
4250     OffsetMove(map_move);
4251 
4252     // this correction ensures that the centering doesn't leave too large a margin to the side
4253     GG::Pt move_to_pt = ul = ClientUpperLeft();
4254     CorrectMapPosition(move_to_pt);
4255 
4256     MoveTo(move_to_pt - GG::Pt(AppWidth(), AppHeight()));
4257 }
4258 
ShowPlanet(int planet_id)4259 void MapWnd::ShowPlanet(int planet_id) {
4260     if (!m_in_production_view_mode) {
4261         ShowPedia();
4262         m_pedia_panel->SetPlanet(planet_id);
4263     }
4264     if (m_in_production_view_mode) {
4265         m_production_wnd->ShowPedia();
4266         m_production_wnd->ShowPlanetInEncyclopedia(planet_id);
4267     }
4268 }
4269 
ShowCombatLog(int log_id)4270 void MapWnd::ShowCombatLog(int log_id) {
4271     m_combat_report_wnd->SetLog( log_id );
4272     m_combat_report_wnd->Show();
4273     GG::GUI::GetGUI()->MoveUp(m_combat_report_wnd);
4274     PushWndStack(m_combat_report_wnd);
4275 }
4276 
ShowTech(const std::string & tech_name)4277 void MapWnd::ShowTech(const std::string& tech_name) {
4278     if (m_research_wnd->Visible())
4279         m_research_wnd->ShowTech(tech_name);
4280     if (m_in_production_view_mode) {
4281         m_production_wnd->ShowPedia();
4282         m_production_wnd->ShowTechInEncyclopedia(tech_name);
4283     } else {
4284         if (!m_pedia_panel->Visible())
4285             TogglePedia();
4286         m_pedia_panel->SetTech(tech_name);
4287     }
4288 }
4289 
ShowBuildingType(const std::string & building_type_name)4290 void MapWnd::ShowBuildingType(const std::string& building_type_name) {
4291     if (m_production_wnd->Visible()) {
4292         m_production_wnd->ShowPedia();
4293         m_production_wnd->ShowBuildingTypeInEncyclopedia(building_type_name);
4294     } else {
4295         if (!m_pedia_panel->Visible())
4296             TogglePedia();
4297         m_pedia_panel->SetBuildingType(building_type_name);
4298     }
4299 }
4300 
ShowShipPart(const std::string & ship_part_name)4301 void MapWnd::ShowShipPart(const std::string& ship_part_name) {
4302     if (m_design_wnd->Visible())
4303         m_design_wnd->ShowShipPartInEncyclopedia(ship_part_name);
4304     if (m_in_production_view_mode) {
4305         m_production_wnd->ShowPedia();
4306         m_production_wnd->ShowShipPartInEncyclopedia(ship_part_name);
4307     } else {
4308         if (!m_pedia_panel->Visible())
4309             TogglePedia();
4310         m_pedia_panel->SetShipPart(ship_part_name);
4311     }
4312 }
4313 
ShowShipHull(const std::string & ship_hull_name)4314 void MapWnd::ShowShipHull(const std::string& ship_hull_name) {
4315     if (m_design_wnd->Visible()) {
4316         m_design_wnd->ShowShipHullInEncyclopedia(ship_hull_name);
4317     } else {
4318         if (!m_pedia_panel->Visible())
4319             TogglePedia();
4320         m_pedia_panel->SetShipHull(ship_hull_name);
4321     }
4322 }
4323 
ShowShipDesign(int design_id)4324 void MapWnd::ShowShipDesign(int design_id) {
4325     if (m_production_wnd->Visible()) {
4326         m_production_wnd->ShowPedia();
4327         m_production_wnd->ShowShipDesignInEncyclopedia(design_id);
4328     } else {
4329         if (!m_pedia_panel->Visible())
4330             TogglePedia();
4331         m_pedia_panel->SetDesign(design_id);
4332     }
4333 }
4334 
ShowSpecial(const std::string & special_name)4335 void MapWnd::ShowSpecial(const std::string& special_name) {
4336     if (m_production_wnd->Visible()) {
4337         m_production_wnd->ShowPedia();
4338         m_production_wnd->ShowSpecialInEncyclopedia(special_name);
4339     } else {
4340         if (!m_pedia_panel->Visible())
4341             TogglePedia();
4342         m_pedia_panel->SetSpecial(special_name);
4343     }
4344 }
4345 
ShowSpecies(const std::string & species_name)4346 void MapWnd::ShowSpecies(const std::string& species_name) {
4347     if (m_production_wnd->Visible()) {
4348         m_production_wnd->ShowPedia();
4349         m_production_wnd->ShowSpeciesInEncyclopedia(species_name);
4350     } else {
4351         if (!m_pedia_panel->Visible())
4352             TogglePedia();
4353         m_pedia_panel->SetSpecies(species_name);
4354     }
4355 }
4356 
ShowFieldType(const std::string & field_type_name)4357 void MapWnd::ShowFieldType(const std::string& field_type_name) {
4358     if (m_production_wnd->Visible()) {
4359         m_production_wnd->ShowPedia();
4360         m_production_wnd->ShowFieldTypeInEncyclopedia(field_type_name);
4361     } else {
4362         if (!m_pedia_panel->Visible())
4363             TogglePedia();
4364         m_pedia_panel->SetFieldType(field_type_name);
4365     }
4366 }
4367 
ShowEmpire(int empire_id)4368 void MapWnd::ShowEmpire(int empire_id) {
4369     if (m_in_production_view_mode) {
4370         m_production_wnd->ShowPedia();
4371         m_production_wnd->ShowEmpireInEncyclopedia(empire_id);
4372     }
4373     else {
4374         if (!m_pedia_panel->Visible())
4375             TogglePedia();
4376         m_pedia_panel->SetEmpire(empire_id);
4377     }
4378 }
4379 
ShowMeterTypeArticle(const std::string & meter_string)4380 void MapWnd::ShowMeterTypeArticle(const std::string& meter_string) {
4381     ShowPedia();
4382     m_pedia_panel->SetMeterType(meter_string);
4383 }
4384 
ShowEncyclopediaEntry(const std::string & str)4385 void MapWnd::ShowEncyclopediaEntry(const std::string& str) {
4386     if (!m_pedia_panel->Visible())
4387         TogglePedia();
4388     m_pedia_panel->SetEncyclopediaArticle(str);
4389 }
4390 
CenterOnObject(int id)4391 void MapWnd::CenterOnObject(int id) {
4392     if (auto obj = Objects().get(id))
4393         CenterOnMapCoord(obj->X(), obj->Y());
4394 }
4395 
CenterOnObject(std::shared_ptr<const UniverseObject> obj)4396 void MapWnd::CenterOnObject(std::shared_ptr<const UniverseObject> obj) {
4397     if (!obj) return;
4398     CenterOnMapCoord(obj->X(), obj->Y());
4399 }
4400 
ReselectLastSystem()4401 void MapWnd::ReselectLastSystem() {
4402     if (SidePanel::SystemID() != INVALID_OBJECT_ID)
4403         SelectSystem(SidePanel::SystemID());
4404 }
4405 
SelectSystem(int system_id)4406 void MapWnd::SelectSystem(int system_id) {
4407     //std::cout << "MapWnd::SelectSystem(" << system_id << ")" << std::endl;
4408     auto system = Objects().get<System>(system_id);
4409     if (!system && system_id != INVALID_OBJECT_ID) {
4410         ErrorLogger() << "MapWnd::SelectSystem couldn't find system with id " << system_id << " so is selected no system instead";
4411         system_id = INVALID_OBJECT_ID;
4412     }
4413 
4414 
4415     if (system) {
4416         // ensure meter estimates are up to date, particularly for which ship is selected
4417         GetUniverse().UpdateMeterEstimates(system_id, true);
4418     }
4419 
4420 
4421     if (SidePanel::SystemID() != system_id) {
4422         // remove map selection indicator from previously selected system
4423         if (SidePanel::SystemID() != INVALID_OBJECT_ID) {
4424             const auto& it = m_system_icons.find(SidePanel::SystemID());
4425             if (it != m_system_icons.end())
4426                 it->second->SetSelected(false);
4427         }
4428 
4429         // set selected system on sidepanel and production screen, as appropriate
4430         if (m_in_production_view_mode)
4431             m_production_wnd->SelectSystem(system_id);  // calls SidePanel::SetSystem
4432         else
4433             SidePanel::SetSystem(system_id);
4434 
4435         // place map selection indicator on newly selected system
4436         if (SidePanel::SystemID() != INVALID_OBJECT_ID) {
4437             const auto& it = m_system_icons.find(SidePanel::SystemID());
4438             if (it != m_system_icons.end())
4439                 it->second->SetSelected(true);
4440         }
4441     }
4442 
4443     InitScaleCircleRenderingBuffer();
4444 
4445     if (m_in_production_view_mode) {
4446         // don't need to do anything to ensure this->m_side_panel is visible,
4447         // since it should be hidden if in production view mode.
4448         return;
4449     }
4450 
4451     // even if selected system hasn't changed, it may be nessary to show or
4452     // hide this mapwnd's sidepanel, in case it was hidden at some point and
4453     // should be visible, or is visible and should be hidden.
4454 
4455     if (SidePanel::SystemID() == INVALID_OBJECT_ID) {
4456         // no selected system.  hide sidepanel.
4457         m_side_panel->Hide();
4458         RemoveFromWndStack(m_side_panel);
4459     } else {
4460         // selected a valid system, show sidepanel
4461         m_side_panel->Show();
4462         GG::GUI::GetGUI()->MoveUp(m_side_panel);
4463         PushWndStack(m_side_panel);
4464     }
4465 }
4466 
ReselectLastFleet()4467 void MapWnd::ReselectLastFleet() {
4468     //// DEBUG
4469     //std::cout << "MapWnd::ReselectLastFleet m_selected_fleet_ids: " << std::endl;
4470     //for (int fleet_id : m_selected_fleet_ids) {
4471     //    auto obj = GetUniverse().Object(fleet_id);
4472     //    if (obj)
4473     //        std::cout << "    " << obj->Name() << "(" << fleet_id << ")" << std::endl;
4474     //    else
4475     //        std::cout << "    [missing object] (" << fleet_id << ")" << std::endl;
4476     //}
4477 
4478     const ObjectMap& objects = GetUniverse().Objects();
4479 
4480     // search through stored selected fleets' ids and remove ids of missing fleets
4481     std::set<int> missing_fleets;
4482     for (const auto& fleet : objects.find<Fleet>(m_selected_fleet_ids)) {
4483         if (!fleet)
4484             missing_fleets.insert(fleet->ID());
4485     }
4486     for (int fleet_id : missing_fleets)
4487         m_selected_fleet_ids.erase(fleet_id);
4488 
4489 
4490     // select a not-missing fleet, if any
4491     for (int fleet_id : m_selected_fleet_ids) {
4492         SelectFleet(fleet_id);
4493         break;              // abort after first fleet selected... don't need to do more
4494     }
4495 }
4496 
SelectPlanet(int planetID)4497 void MapWnd::SelectPlanet(int planetID)
4498 { m_production_wnd->SelectPlanet(planetID); }   // calls SidePanel::SelectPlanet()
4499 
SelectFleet(int fleet_id)4500 void MapWnd::SelectFleet(int fleet_id)
4501 { SelectFleet(Objects().get<Fleet>(fleet_id)); }
4502 
SelectFleet(std::shared_ptr<Fleet> fleet)4503 void MapWnd::SelectFleet(std::shared_ptr<Fleet> fleet) {
4504     FleetUIManager& manager = FleetUIManager::GetFleetUIManager();
4505 
4506     if (!fleet) {
4507         //std::cout << "MapWnd::SelectFleet selecting no fleet: deselecting all selected fleets." << std::endl;
4508 
4509         // first deselect any selected fleets in non-active fleet wnd.  this should
4510         // not emit any signals about the active fleet wnd's fleets changing
4511         const auto& active_fleet_wnd = manager.ActiveFleetWnd();
4512 
4513         for (const auto& fwnd : manager) {
4514             auto wnd = fwnd.lock();
4515             if (wnd && wnd.get() != active_fleet_wnd)
4516                 wnd->DeselectAllFleets();
4517         }
4518 
4519         // and finally deselect active fleet wnd fleets.  this might emit a signal
4520         // which will update this->m_selected_Fleets
4521         if (active_fleet_wnd)
4522             active_fleet_wnd->DeselectAllFleets();
4523 
4524         return;
4525     }
4526 
4527     //std::cout << "MapWnd::SelectFleet " << fleet->ID() << std::endl;
4528 
4529     // find if there is a FleetWnd for this fleet already open.
4530     auto fleet_wnd = manager.WndForFleetID(fleet->ID());
4531 
4532     // if there isn't a FleetWnd for this fleet open, need to open one
4533     if (!fleet_wnd) {
4534         // Add any overlapping fleet buttons for moving or offroad fleets.
4535         const auto wnd_fleet_ids = FleetIDsOfFleetButtonsOverlapping(fleet->ID());
4536 
4537         // A leeway, scaled to the button size, around a group of moving fleets so the fleetwnd
4538         // tracks moving fleets together
4539         const auto& any_fleet_button = m_fleet_buttons.empty() ? nullptr : m_fleet_buttons.begin()->second;
4540         double leeway_around_moving_fleets = any_fleet_button ?
4541             2.0 * (Value(any_fleet_button->Width()) + Value(any_fleet_button->Height())) / ZoomFactor() : 0.0;
4542 
4543         // create new fleetwnd in which to show selected fleet
4544         fleet_wnd = manager.NewFleetWnd(wnd_fleet_ids, leeway_around_moving_fleets);
4545 
4546         // opened a new FleetWnd, so play sound
4547         FleetButton::PlayFleetButtonOpenSound();
4548     }
4549 
4550     // make sure selected fleet's FleetWnd is active
4551     manager.SetActiveFleetWnd(fleet_wnd);
4552     GG::GUI::GetGUI()->MoveUp(fleet_wnd);
4553     PushWndStack(fleet_wnd);
4554 
4555     // if indicated fleet is already the only selected fleet in the active FleetWnd, nothing to do.
4556     if (m_selected_fleet_ids.size() == 1 && m_selected_fleet_ids.count(fleet->ID()))
4557         return;
4558 
4559     // select fleet in FleetWnd.  this deselects all other fleets in the FleetWnd.
4560     // this->m_selected_fleet_ids will be updated by ActiveFleetWndSelectedFleetsChanged or ActiveFleetWndChanged
4561     // signals being emitted and connected to MapWnd::SelectedFleetsChanged
4562     fleet_wnd->SelectFleet(fleet->ID());
4563 }
4564 
RemoveFleet(int fleet_id)4565 void MapWnd::RemoveFleet(int fleet_id) {
4566     m_fleet_lines.erase(fleet_id);
4567     m_projected_fleet_lines.erase(fleet_id);
4568     m_selected_fleet_ids.erase(fleet_id);
4569     RefreshFleetButtons();
4570 }
4571 
SetFleetMovementLine(int fleet_id)4572 void MapWnd::SetFleetMovementLine(int fleet_id) {
4573     if (fleet_id == INVALID_OBJECT_ID)
4574         return;
4575 
4576     auto fleet = Objects().get<Fleet>(fleet_id);
4577     if (!fleet) {
4578         ErrorLogger() << "MapWnd::SetFleetMovementLine was passed invalid fleet id " << fleet_id;
4579         return;
4580     }
4581     //std::cout << "creating fleet movement line for fleet at (" << fleet->X() << ", " << fleet->Y() << ")" << std::endl;
4582 
4583     // get colour: empire colour, or white if no single empire applicable
4584     GG::Clr line_colour = GG::CLR_WHITE;
4585     const Empire* empire = GetEmpire(fleet->Owner());
4586     if (empire)
4587         line_colour = empire->Color();
4588     else if (fleet->Unowned() && fleet->HasMonsters())
4589         line_colour = GG::CLR_RED;
4590 
4591     // create and store line
4592     auto route(fleet->TravelRoute());
4593     auto path = fleet->MovePath(route, true);
4594     auto route_it = route.begin();
4595     if (!route.empty() && (++route_it) != route.end()) {
4596         //DebugLogger() << "MapWnd::SetFleetMovementLine fleet id " << fleet_id<<" checking for blockade at system "<< route.front() <<
4597         //    " with m_arrival_lane "<< fleet->ArrivalStarlane()<<" and next destination "<<*route_it;
4598         if (fleet->SystemID() == route.front() && fleet->BlockadedAtSystem(route.front(), *route_it)) { //adjust ETAs if necessary
4599             //if (!route.empty() && fleet->SystemID()==route.front() && (++(path.begin()))->post_blockade) {
4600             //DebugLogger() << "MapWnd::SetFleetMovementLine fleet id " << fleet_id<<" blockaded at system "<< route.front() <<
4601             //    " with m_arrival_lane "<< fleet->ArrivalStarlane()<<" and next destination "<<*route_it;
4602             if (route_it != route.end() && !( (*route_it == fleet->ArrivalStarlane())  ||
4603                 (empire && empire->PreservedLaneTravel(fleet->SystemID(), *route_it)) ) )
4604             {
4605                 for (MovePathNode& node : path) {
4606                     //DebugLogger() <<   "MapWnd::SetFleetMovementLine fleet id " << fleet_id<<" node obj " << node.object_id <<
4607                     //                            ", node lane end " << node.lane_end_id << ", is post-blockade (" << node.post_blockade << ")";
4608                     node.eta++;
4609                 }
4610             } else {
4611                 //DebugLogger() << "MapWnd::SetFleetMovementLine fleet id " << fleet_id<<" slips through second block check";
4612             }
4613         }
4614     }
4615     m_fleet_lines[fleet_id] = MovementLineData(path, m_starlane_endpoints, line_colour, fleet->Owner());
4616 }
4617 
SetProjectedFleetMovementLine(int fleet_id,const std::list<int> & travel_route)4618 void MapWnd::SetProjectedFleetMovementLine(int fleet_id, const std::list<int>& travel_route) {
4619     if (fleet_id == INVALID_OBJECT_ID)
4620         return;
4621 
4622     // ensure passed fleet exists
4623     auto fleet = Objects().get<Fleet>(fleet_id);
4624     if (!fleet) {
4625         ErrorLogger() << "MapWnd::SetProjectedFleetMovementLine was passed invalid fleet id " << fleet_id;
4626         return;
4627     }
4628 
4629     //std::cout << "creating projected fleet movement line for fleet at (" << fleet->X() << ", " << fleet->Y() << ")" << std::endl;
4630     const Empire* empire = GetEmpire(fleet->Owner());
4631 
4632     // get move path to show.  if there isn't one, show nothing
4633     auto path = fleet->MovePath(travel_route, true);
4634 
4635 
4636 
4637     // We need the route to contain the current system
4638     // even when it is empty to switch between non appending
4639     // and appending projections on shift changes
4640     if (path.empty()) {
4641         path.push_back(MovePathNode(fleet->X(), fleet->Y(), true, 0, fleet->SystemID(), INVALID_OBJECT_ID, INVALID_OBJECT_ID));
4642     }
4643 
4644     auto route_it = travel_route.begin();
4645     if (!travel_route.empty() && (++route_it) != travel_route.end()) {
4646         if (fleet->SystemID() == travel_route.front() && fleet->BlockadedAtSystem(travel_route.front(), *route_it)) { //adjust ETAs if necessary
4647             //if (!route.empty() && fleet->SystemID()==route.front() && (++(path.begin()))->post_blockade) {
4648             //DebugLogger() << "MapWnd::SetFleetMovementLine fleet id " << fleet_id<<" blockaded at system "<< route.front() <<
4649             //" with m_arrival_lane "<< fleet->ArrivalStarlane()<<" and next destination "<<*route_it;
4650             if (route_it != travel_route.end() && !((*route_it == fleet->ArrivalStarlane()) ||
4651                 (empire && empire->PreservedLaneTravel(fleet->SystemID(), *route_it))))
4652             {
4653                 for (MovePathNode& node : path) {
4654                     //DebugLogger() <<   "MapWnd::SetFleetMovementLine fleet id " << fleet_id << " node obj " << node.object_id <<
4655                     //                            ", node lane end " << node.lane_end_id << ", is post-blockade (" << node.post_blockade << ")";
4656                     node.eta++;
4657                 }
4658             }
4659         }
4660     }
4661 
4662     // get colour: empire colour, or white if no single empire applicable
4663     GG::Clr line_colour = GG::CLR_WHITE;
4664     if (empire)
4665         line_colour = empire->Color();
4666 
4667     // create and store line
4668     m_projected_fleet_lines[fleet_id] = MovementLineData(path, m_starlane_endpoints, line_colour, fleet->Owner());
4669 }
4670 
SetProjectedFleetMovementLines(const std::vector<int> & fleet_ids,const std::list<int> & travel_route)4671 void MapWnd::SetProjectedFleetMovementLines(const std::vector<int>& fleet_ids,
4672                                             const std::list<int>& travel_route)
4673 {
4674     for (int fleet_id : fleet_ids)
4675         SetProjectedFleetMovementLine(fleet_id, travel_route);
4676 }
4677 
ClearProjectedFleetMovementLines()4678 void MapWnd::ClearProjectedFleetMovementLines()
4679 { m_projected_fleet_lines.clear(); }
4680 
ForgetObject(int id)4681 void MapWnd::ForgetObject(int id) {
4682     // Remove visibility information for this object from
4683     // the empire's visibility table.
4684     // The object is usually a ghost ship or fleet.
4685     // Server changes cause a permanent effect
4686     // Tell the server to change what the empire wants to know
4687     // in future so that the server doesn't keep resending this
4688     // object information.
4689     auto obj = Objects().get(id);
4690     if (!obj)
4691         return;
4692 
4693     // If there is only 1 ship in a fleet, forget the fleet
4694     auto ship = std::dynamic_pointer_cast<const Ship>(obj);
4695     if (ship) {
4696         if (auto ship_s_fleet = GetUniverse().Objects().get<const Fleet>(ship->FleetID())) {
4697             bool only_ship_in_fleet = ship_s_fleet->NumShips() == 1;
4698             if (only_ship_in_fleet)
4699                 return ForgetObject(ship->FleetID());
4700         }
4701     }
4702 
4703     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
4704 
4705     HumanClientApp::GetApp()->Orders().IssueOrder(
4706         std::make_shared<ForgetOrder>(client_empire_id, obj->ID()));
4707 
4708     // Client changes for immediate effect
4709     // Force the client to change immediately.
4710     GetUniverse().ForgetKnownObject(ALL_EMPIRES, obj->ID());
4711 
4712     // Force a refresh
4713     RequirePreRender();
4714 
4715     // Update fleet wnd if needed
4716     if (auto fleet = std::dynamic_pointer_cast<const Fleet>(obj)) {
4717         RemoveFleet(fleet->ID());
4718         fleet->StateChangedSignal();
4719     }
4720 
4721     if (ship)
4722         ship->StateChangedSignal();
4723 }
4724 
DoSystemIconsLayout()4725 void MapWnd::DoSystemIconsLayout() {
4726     // position and resize system icons and gaseous substance
4727     const int SYSTEM_ICON_SIZE = SystemIconSize();
4728     for (auto& system_icon : m_system_icons) {
4729         auto system = Objects().get<System>(system_icon.first);
4730         if (!system) {
4731             ErrorLogger() << "MapWnd::DoSystemIconsLayout couldn't get system with id " << system_icon.first;
4732             continue;
4733         }
4734 
4735         GG::Pt icon_ul(GG::X(static_cast<int>(system->X()*ZoomFactor() - SYSTEM_ICON_SIZE / 2.0)),
4736                        GG::Y(static_cast<int>(system->Y()*ZoomFactor() - SYSTEM_ICON_SIZE / 2.0)));
4737         system_icon.second->SizeMove(icon_ul, icon_ul + GG::Pt(GG::X(SYSTEM_ICON_SIZE), GG::Y(SYSTEM_ICON_SIZE)));
4738     }
4739 }
4740 
DoFieldIconsLayout()4741 void MapWnd::DoFieldIconsLayout() {
4742     // position and resize field icons
4743     for (auto& field_icon : m_field_icons) {
4744         auto field = Objects().get<Field>(field_icon.first);
4745         if (!field) {
4746             ErrorLogger() << "MapWnd::DoFieldIconsLayout couldn't get field with id " << field_icon.first;
4747             continue;
4748         }
4749 
4750         double RADIUS = ZoomFactor() * field->GetMeter(METER_SIZE)->Initial();    // Field's METER_SIZE gives the radius of the field
4751 
4752         GG::Pt icon_ul(GG::X(static_cast<int>(field->X()*ZoomFactor() - RADIUS)),
4753                        GG::Y(static_cast<int>(field->Y()*ZoomFactor() - RADIUS)));
4754         field_icon.second->SizeMove(icon_ul, icon_ul + GG::Pt(GG::X(2*RADIUS), GG::Y(2*RADIUS)));
4755     }
4756 }
4757 
DoFleetButtonsLayout()4758 void MapWnd::DoFleetButtonsLayout() {
4759     const ObjectMap& objects = GetUniverse().Objects();
4760 
4761     auto place_system_fleet_btn = [this](const std::unordered_map<int, std::unordered_set<std::shared_ptr<FleetButton>>>::value_type& system_and_btns, bool is_departing) {
4762         // calculate system icon position
4763         auto system = Objects().get<System>(system_and_btns.first);
4764         if (!system) {
4765             ErrorLogger() << "MapWnd::DoFleetButtonsLayout couldn't find system with id " << system_and_btns.first;
4766             return;
4767         }
4768 
4769         const int SYSTEM_ICON_SIZE = SystemIconSize();
4770         GG::Pt icon_ul(GG::X(static_cast<int>(system->X()*ZoomFactor() - SYSTEM_ICON_SIZE / 2.0)),
4771                        GG::Y(static_cast<int>(system->Y()*ZoomFactor() - SYSTEM_ICON_SIZE / 2.0)));
4772 
4773         // get system icon itself.  can't use the system icon's UpperLeft to position fleet button due to weirdness that results that I don't want to figure out
4774         const auto& sys_it = m_system_icons.find(system->ID());
4775         if (sys_it == m_system_icons.end()) {
4776             ErrorLogger() << "couldn't find system icon for fleet button in DoFleetButtonsLayout";
4777             return;
4778         }
4779         const auto& system_icon = sys_it->second;
4780 
4781         // place all buttons
4782         int n = 1;
4783         for (auto& button : system_and_btns.second) {
4784             GG::Pt ul = system_icon->NthFleetButtonUpperLeft(n, is_departing);
4785             ++n;
4786             button->MoveTo(ul + icon_ul);
4787         }
4788     };
4789 
4790     // position departing fleet buttons
4791     for (const auto& system_and_btns : m_departing_fleet_buttons)
4792         place_system_fleet_btn(system_and_btns, true);
4793 
4794     // position stationary fleet buttons
4795     for (const auto& system_and_btns : m_stationary_fleet_buttons)
4796         place_system_fleet_btn(system_and_btns, false);
4797 
4798     // position moving fleet buttons
4799     for (auto& lane_and_fbs : m_moving_fleet_buttons) {
4800         for (auto& fb : lane_and_fbs.second) {
4801             const GG::Pt FLEET_BUTTON_SIZE = fb->Size();
4802             std::shared_ptr<const Fleet> fleet;
4803 
4804             // skip button if it has no fleets (somehow...?) or if the first fleet in the button is 0
4805             if (fb->Fleets().empty() || !(fleet = objects.get<Fleet>(*fb->Fleets().begin()))) {
4806                 ErrorLogger() << "DoFleetButtonsLayout couldn't get first fleet for button";
4807                 continue;
4808             }
4809 
4810             auto button_pos = MovingFleetMapPositionOnLane(fleet);
4811             if (!button_pos)
4812                 continue;
4813 
4814             // position button
4815             GG::Pt button_ul(button_pos->first  * ZoomFactor() - FLEET_BUTTON_SIZE.x / 2.0,
4816                              button_pos->second * ZoomFactor() - FLEET_BUTTON_SIZE.y / 2.0);
4817 
4818             fb->MoveTo(button_ul);
4819         }
4820     }
4821 
4822     // position offroad fleet buttons
4823     for (auto& pos_and_fbs : m_offroad_fleet_buttons) {
4824         for (auto& fb : pos_and_fbs.second) {
4825             const GG::Pt FLEET_BUTTON_SIZE = fb->Size();
4826             std::shared_ptr<const Fleet> fleet;
4827 
4828             // skip button if it has no fleets (somehow...?) or if the first fleet in the button is 0
4829             if (fb->Fleets().empty() || !(fleet = objects.get<Fleet>(*fb->Fleets().begin()))) {
4830                 ErrorLogger() << "DoFleetButtonsLayout couldn't get first fleet for button";
4831                 continue;
4832             }
4833 
4834             // position button
4835             auto& button_pos = pos_and_fbs.first;
4836             GG::Pt button_ul(button_pos.first  * ZoomFactor() - FLEET_BUTTON_SIZE.x / 2.0,
4837                              button_pos.second * ZoomFactor() - FLEET_BUTTON_SIZE.y / 2.0);
4838 
4839             fb->MoveTo(button_ul);
4840         }
4841     }
4842 
4843 }
4844 
MovingFleetMapPositionOnLane(std::shared_ptr<const Fleet> fleet) const4845 boost::optional<std::pair<double, double>> MapWnd::MovingFleetMapPositionOnLane(
4846     std::shared_ptr<const Fleet> fleet) const
4847 {
4848     if (!fleet)
4849         return boost::none;
4850 
4851     // get endpoints of lane on screen
4852     int sys1_id = fleet->PreviousSystemID();
4853     int sys2_id = fleet->NextSystemID();
4854 
4855     // get apparent positions of endpoints for this lane that have been pre-calculated
4856     auto endpoints_it = m_starlane_endpoints.find({sys1_id, sys2_id});
4857     if (endpoints_it == m_starlane_endpoints.end()) {
4858         // couldn't find an entry for the lane this fleet is one, so just
4859         // return actual position of fleet on starlane - ignore the distance
4860         // away from the star centre at which starlane endpoints should appear
4861         return std::make_pair(fleet->X(), fleet->Y());
4862     }
4863 
4864     // return apparent position of fleet on starlane
4865     const LaneEndpoints& screen_lane_endpoints = endpoints_it->second;
4866     return ScreenPosOnStarlane(fleet->X(), fleet->Y(), sys1_id, sys2_id,
4867                               screen_lane_endpoints);
4868 }
4869 
4870 namespace {
4871     template <typename Key>
4872     using KeyToFleetsMap = std::unordered_map<Key, std::vector<int>, boost::hash<Key>>;
4873     using SystemXEmpireToFleetsMap = KeyToFleetsMap<std::pair<int, int>>;
4874     using LocationXEmpireToFleetsMap = KeyToFleetsMap<std::pair<std::pair<double, double>, int>>;
4875     using StarlaneToFleetsMap = KeyToFleetsMap<std::pair<int, int>>;
4876 
4877     /** Return fleet if \p obj is not destroyed, not stale, a fleet and not empty.*/
IsQualifiedFleet(const std::shared_ptr<const UniverseObject> & obj,int empire_id,const std::set<int> & known_destroyed_objects,const std::set<int> & stale_object_info)4878     std::shared_ptr<const Fleet> IsQualifiedFleet(const std::shared_ptr<const UniverseObject>& obj,
4879                                                   int empire_id,
4880                                                   const std::set<int>& known_destroyed_objects,
4881                                                   const std::set<int>& stale_object_info)
4882     {
4883         int object_id = obj->ID();
4884         auto fleet = std::dynamic_pointer_cast<const Fleet>(obj);
4885 
4886         if (fleet
4887             && !fleet->Empty()
4888             && (known_destroyed_objects.count(object_id) == 0)
4889             && (stale_object_info.count(object_id) == 0))
4890         {
4891             return fleet;
4892         }
4893         return nullptr;
4894     }
4895 
4896     /** If the \p fleet has orders and is departing from a valid system, return the system*/
IsDepartingFromSystem(const std::shared_ptr<const Fleet> & fleet)4897     std::shared_ptr<const System> IsDepartingFromSystem(
4898         const std::shared_ptr<const Fleet>& fleet)
4899     {
4900         if (fleet->FinalDestinationID() != INVALID_OBJECT_ID
4901             && !fleet->TravelRoute().empty()
4902             && fleet->SystemID() != INVALID_OBJECT_ID)
4903         {
4904             auto system = Objects().get<System>(fleet->SystemID());
4905             if (system)
4906                 return system;
4907             ErrorLogger() << "Couldn't get system with id " << fleet->SystemID()
4908                           << " of a departing fleet named " << fleet->Name();
4909         }
4910         return nullptr;
4911     }
4912 
4913     /** If the \p fleet is stationary in a valid system, return the system*/
IsStationaryInSystem(const std::shared_ptr<const Fleet> & fleet)4914     std::shared_ptr<const System> IsStationaryInSystem(
4915         const std::shared_ptr<const Fleet>& fleet)
4916     {
4917         if ((fleet->FinalDestinationID() == INVALID_OBJECT_ID
4918              || fleet->TravelRoute().empty())
4919             && fleet->SystemID() != INVALID_OBJECT_ID)
4920         {
4921             auto system = Objects().get<System>(fleet->SystemID());
4922             if (system)
4923                 return system;
4924             ErrorLogger() << "Couldn't get system with id " << fleet->SystemID()
4925                           << " of a stationary fleet named " << fleet->Name();
4926         }
4927         return nullptr;
4928     }
4929 
4930     /** If the \p fleet has a valid destination and it not at a system, return true*/
IsMoving(const std::shared_ptr<const Fleet> & fleet)4931     bool IsMoving(const std::shared_ptr<const Fleet>& fleet) {
4932         return (fleet->FinalDestinationID() != INVALID_OBJECT_ID
4933                 && fleet->SystemID() == INVALID_OBJECT_ID);
4934     }
4935 
4936     /** Return the starlane's endpoints if the \p fleet is on a starlane. */
IsOnStarlane(const std::shared_ptr<const Fleet> & fleet)4937     boost::optional<std::pair<int, int>> IsOnStarlane(const std::shared_ptr<const Fleet>& fleet) {
4938         // get endpoints of lane on screen
4939         int sys1_id = fleet->PreviousSystemID();
4940         int sys2_id = fleet->NextSystemID();
4941 
4942         auto sys1 = Objects().get<System>(sys1_id);
4943         if (!sys1)
4944             return boost::none;
4945         if (sys1->HasStarlaneTo(sys2_id))
4946             return std::make_pair(std::min(sys1_id, sys2_id), std::max(sys1_id, sys2_id));
4947 
4948         return boost::none;
4949     }
4950 
4951     /** If the \p fleet has a valid destination, is not at a system and is
4952         on a starlane, return the starlane's endpoint system ids */
IsMovingOnStarlane(const std::shared_ptr<const Fleet> & fleet)4953     boost::optional<std::pair<int, int>> IsMovingOnStarlane(const std::shared_ptr<const Fleet>& fleet) {
4954         if (!IsMoving(fleet))
4955             return boost::none;
4956 
4957         return IsOnStarlane(fleet);
4958     }
4959 
4960     /** If the \p fleet has a valid destination and it not on a starlane, return true*/
IsOffRoad(const std::shared_ptr<const Fleet> & fleet)4961     bool IsOffRoad(const std::shared_ptr<const Fleet>& fleet)
4962     { return (IsMoving(fleet) && !IsOnStarlane(fleet)); }
4963 }
4964 
RefreshFleetButtons()4965 void MapWnd::RefreshFleetButtons() {
4966     RequirePreRender();
4967     m_deferred_refresh_fleet_buttons = true;
4968 }
4969 
DeferredRefreshFleetButtons()4970 void MapWnd::DeferredRefreshFleetButtons() {
4971     if (!m_deferred_refresh_fleet_buttons)
4972         return;
4973     m_deferred_refresh_fleet_buttons = false;
4974 
4975     ScopedTimer timer("RefreshFleetButtons()");
4976 
4977     // determine fleets that need buttons so that fleets at the same location can
4978     // be grouped by empire owner and buttons created
4979 
4980     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
4981     const auto& this_client_known_destroyed_objects =
4982         GetUniverse().EmpireKnownDestroyedObjectIDs(client_empire_id);
4983     const auto& this_client_stale_object_info =
4984         GetUniverse().EmpireStaleKnowledgeObjectIDs(client_empire_id);
4985 
4986     SystemXEmpireToFleetsMap   departing_fleets;
4987     SystemXEmpireToFleetsMap   stationary_fleets;
4988     m_moving_fleets.clear();
4989     LocationXEmpireToFleetsMap moving_fleets;
4990     LocationXEmpireToFleetsMap offroad_fleets;
4991 
4992     for (const auto& entry : Objects().ExistingFleets()) {
4993         auto fleet = IsQualifiedFleet(entry.second, client_empire_id,
4994                                       this_client_known_destroyed_objects,
4995                                       this_client_stale_object_info);
4996 
4997         if (!fleet)
4998             continue;
4999 
5000         // Collect fleets with a travel route just departing.
5001         if (auto departure_system = IsDepartingFromSystem(fleet)) {
5002             departing_fleets[{departure_system->ID(), fleet->Owner()}].push_back(fleet->ID());
5003 
5004             // Collect stationary fleets by system.
5005         } else if (auto stationary_system = IsStationaryInSystem(fleet)) {
5006             // DebugLogger() << fleet->Name() << " is Stationary." ;
5007             stationary_fleets[{stationary_system->ID(), fleet->Owner()}].push_back(fleet->ID());
5008 
5009             // Collect traveling fleets between systems by which starlane they
5010             // are on (ignoring location on lane and direction of travel)
5011         } else if (auto starlane_end_systems = IsMovingOnStarlane(fleet)) {
5012             moving_fleets[{*starlane_end_systems, fleet->Owner()}].push_back(fleet->ID());
5013             m_moving_fleets[*starlane_end_systems].push_back(fleet->ID());
5014 
5015         } else if (IsOffRoad(fleet)) {
5016             offroad_fleets[{{fleet->X(), fleet->Y()}, fleet->Owner()}].push_back(fleet->ID());
5017 
5018         } else {
5019             ErrorLogger() << "Fleet "<< fleet->Name() <<"(" << fleet->ID()
5020                           << ") is not stationary, departing from a system or in transit."
5021                           << " final dest id is " << fleet->FinalDestinationID()
5022                           << " travel routes is of length = " << fleet->TravelRoute().size()
5023                           << " system id is " << fleet->SystemID()
5024                           << " location is (" << fleet->X() << "," <<fleet->Y() << ")";
5025         }
5026     }
5027 
5028     DeleteFleetButtons();
5029 
5030     // create new fleet buttons for fleets...
5031     const auto FLEETBUTTON_SIZE = FleetButtonSizeType();
5032     CreateFleetButtonsOfType(m_departing_fleet_buttons,  departing_fleets,  FLEETBUTTON_SIZE);
5033     CreateFleetButtonsOfType(m_stationary_fleet_buttons, stationary_fleets, FLEETBUTTON_SIZE);
5034     CreateFleetButtonsOfType(m_moving_fleet_buttons,     moving_fleets,     FLEETBUTTON_SIZE);
5035     CreateFleetButtonsOfType(m_offroad_fleet_buttons,    offroad_fleets,    FLEETBUTTON_SIZE);
5036 
5037     // position fleetbuttons
5038     DoFleetButtonsLayout();
5039 
5040     // add selection indicators to fleetbuttons
5041     RefreshFleetButtonSelectionIndicators();
5042 
5043     // create movement lines (after positioning buttons, so lines will originate from button location)
5044     for (const auto& fleet_button : m_fleet_buttons)
5045         SetFleetMovementLine(fleet_button.first);
5046 }
5047 
5048 template <typename FleetButtonMap, typename FleetsMap>
CreateFleetButtonsOfType(FleetButtonMap & type_fleet_buttons,const FleetsMap & fleets_map,const FleetButton::SizeType & fleet_button_size)5049 void MapWnd::CreateFleetButtonsOfType(FleetButtonMap& type_fleet_buttons,
5050                                       const FleetsMap& fleets_map,
5051                                       const FleetButton::SizeType& fleet_button_size)
5052 {
5053     for (const auto& fleets : fleets_map) {
5054         const auto& key = fleets.first.first;
5055 
5056         // buttons need fleet IDs
5057         const auto& fleet_IDs = fleets.second;
5058         if (fleet_IDs.empty())
5059             continue;
5060 
5061         // sort fleets by position
5062         std::map<std::pair<double, double>, std::vector<int>> fleet_positions_ids;
5063         for (const auto& fleet : Objects().find<Fleet>(fleet_IDs)) {
5064             if (!fleet)
5065                 continue;
5066             fleet_positions_ids[{fleet->X(), fleet->Y()}].push_back(fleet->ID());
5067         }
5068 
5069         // create separate FleetButton for each cluster of fleets
5070         for (const auto& cluster : fleet_positions_ids) {
5071             const auto& ids_in_cluster = cluster.second;
5072 
5073             // create new fleetbutton for this cluster of fleets
5074             auto fb = GG::Wnd::Create<FleetButton>(ids_in_cluster, fleet_button_size);
5075 
5076             // store per type of fleet button.
5077             type_fleet_buttons[key].insert(fb);
5078 
5079             // store FleetButton for fleets in current cluster
5080             for (int fleet_id : ids_in_cluster)
5081                 m_fleet_buttons[fleet_id] = fb;
5082 
5083             fb->LeftClickedSignal.connect(boost::bind(&MapWnd::FleetButtonLeftClicked, this, fb.get()));
5084             fb->RightClickedSignal.connect(boost::bind(&MapWnd::FleetButtonRightClicked, this, fb.get()));
5085             AttachChild(std::move(fb));
5086         }
5087     }
5088 }
5089 
DeleteFleetButtons()5090 void MapWnd::DeleteFleetButtons() {
5091     for (auto& id_and_fb : m_fleet_buttons)
5092         DetachChild(id_and_fb.second);
5093     m_fleet_buttons.clear();
5094 
5095     // The pointers in the following containers duplicate those in
5096     // m_fleet_buttons and therefore don't need to be detached.
5097     m_stationary_fleet_buttons.clear();
5098     m_departing_fleet_buttons.clear();
5099     m_moving_fleet_buttons.clear();
5100     m_offroad_fleet_buttons.clear();
5101 }
5102 
RefreshSliders()5103 void MapWnd::RefreshSliders() {
5104     if (m_zoom_slider) {
5105         if (GetOptionsDB().Get<bool>("ui.map.zoom.slider.shown") && Visible())
5106             m_zoom_slider->Show();
5107         else
5108             m_zoom_slider->Hide();
5109     }
5110 }
5111 
SystemIconSize() const5112 int MapWnd::SystemIconSize() const
5113 { return static_cast<int>(ClientUI::SystemIconSize() * ZoomFactor()); }
5114 
SystemNamePts() const5115 int MapWnd::SystemNamePts() const {
5116     const int       SYSTEM_NAME_MINIMUM_PTS = 6;    // limit to absolute minimum point size
5117     const double    MAX_NAME_ZOOM_FACTOR = 1.5;     // limit to relative max above standard UI font size
5118     const double    NAME_ZOOM_FACTOR = std::min(MAX_NAME_ZOOM_FACTOR, ZoomFactor());
5119     const int       ZOOMED_PTS = static_cast<int>(ClientUI::Pts() * NAME_ZOOM_FACTOR);
5120     return std::max(ZOOMED_PTS, SYSTEM_NAME_MINIMUM_PTS);
5121 }
5122 
SystemHaloScaleFactor() const5123 double MapWnd::SystemHaloScaleFactor() const
5124 { return 1.0 + log10(ZoomFactor()); }
5125 
FleetButtonSizeType() const5126 FleetButton::SizeType MapWnd::FleetButtonSizeType() const {
5127     // no SizeType::LARGE as these icons are too big for the map.  (they can be used in the FleetWnd, however)
5128     if      (ZoomFactor() > ClientUI::MediumFleetButtonZoomThreshold())
5129         return FleetButton::SizeType::MEDIUM;
5130 
5131     else if (ZoomFactor() > ClientUI::SmallFleetButtonZoomThreshold())
5132         return FleetButton::SizeType::SMALL;
5133 
5134     else if (ZoomFactor() > ClientUI::TinyFleetButtonZoomThreshold())
5135         return FleetButton::SizeType::TINY;
5136 
5137     else
5138         return FleetButton::SizeType::NONE;
5139 }
5140 
Zoom(int delta)5141 void MapWnd::Zoom(int delta) {
5142     GG::Pt center = GG::Pt(AppWidth() / 2.0, AppHeight() / 2.0);
5143     Zoom(delta, center);
5144 }
5145 
Zoom(int delta,const GG::Pt & position)5146 void MapWnd::Zoom(int delta, const GG::Pt& position) {
5147     if (delta == 0)
5148         return;
5149 
5150     // increment zoom steps in by delta steps
5151     double new_zoom_steps_in = m_zoom_steps_in + static_cast<double>(delta);
5152     SetZoom(new_zoom_steps_in, true, position);
5153 }
5154 
SetZoom(double steps_in,bool update_slide)5155 void MapWnd::SetZoom(double steps_in, bool update_slide) {
5156     GG::Pt center = GG::Pt(AppWidth() / 2.0, AppHeight() / 2.0);
5157     SetZoom(steps_in, update_slide, center);
5158 }
5159 
SetZoom(double steps_in,bool update_slide,const GG::Pt & position)5160 void MapWnd::SetZoom(double steps_in, bool update_slide, const GG::Pt& position) {
5161     // impose range limits on zoom steps
5162     double new_steps_in = std::max(std::min(steps_in, ZOOM_IN_MAX_STEPS), ZOOM_IN_MIN_STEPS);
5163 
5164     // abort if no change
5165     if (new_steps_in == m_zoom_steps_in)
5166         return;
5167 
5168 
5169     // save position offsets and old zoom factors
5170     GG::Pt                      ul =                    ClientUpperLeft();
5171     const GG::X_d               center_x =              AppWidth() / 2.0;
5172     const GG::Y_d               center_y =              AppHeight() / 2.0;
5173     GG::X_d                     ul_offset_x =           ul.x - center_x;
5174     GG::Y_d                     ul_offset_y =           ul.y - center_y;
5175     const double                OLD_ZOOM =              ZoomFactor();
5176     const FleetButton::SizeType OLD_FLEETBUTTON_SIZE =  FleetButtonSizeType();
5177 
5178 
5179     // set new zoom level
5180     m_zoom_steps_in = new_steps_in;
5181 
5182 
5183     // keeps position the same after zooming
5184     // used to keep the mouse at the same position when doing mouse wheel zoom
5185     const GG::Pt position_center_delta = GG::Pt(position.x - center_x, position.y - center_y);
5186     ul_offset_x -= position_center_delta.x;
5187     ul_offset_y -= position_center_delta.y;
5188 
5189     // correct map offsets for zoom changes
5190     ul_offset_x *= (ZoomFactor() / OLD_ZOOM);
5191     ul_offset_y *= (ZoomFactor() / OLD_ZOOM);
5192 
5193     // now add the zoom position offset at the new zoom level
5194     ul_offset_x += position_center_delta.x;
5195     ul_offset_y += position_center_delta.y;
5196 
5197     // show or hide system names, depending on zoom.  replicates code in MapWnd::Zoom
5198     if (ZoomFactor() * ClientUI::Pts() < MIN_SYSTEM_NAME_SIZE)
5199         HideSystemNames();
5200     else
5201         ShowSystemNames();
5202 
5203 
5204     DoSystemIconsLayout();
5205     DoFieldIconsLayout();
5206 
5207 
5208     // if fleet buttons need to change size, need to fully refresh them (clear
5209     // and recreate).  If they are the same size as before the zoom, then can
5210     // just reposition them without recreating
5211     const FleetButton::SizeType NEW_FLEETBUTTON_SIZE = FleetButtonSizeType();
5212     if (OLD_FLEETBUTTON_SIZE != NEW_FLEETBUTTON_SIZE)
5213         RefreshFleetButtons();
5214     else
5215         DoFleetButtonsLayout();
5216 
5217 
5218     // move field icons to bottom of child stack so that other icons can be moused over with a field
5219     for (const auto& field_icon : m_field_icons)
5220         MoveChildDown(field_icon.second);
5221 
5222 
5223     // translate map and UI widgets to account for the change in upper left due to zooming
5224     GG::Pt map_move(static_cast<GG::X>((center_x + ul_offset_x) - ul.x),
5225                     static_cast<GG::Y>((center_y + ul_offset_y) - ul.y));
5226     OffsetMove(map_move);
5227 
5228     // this correction ensures that zooming in doesn't leave too large a margin to the side
5229     GG::Pt move_to_pt = ul = ClientUpperLeft();
5230     CorrectMapPosition(move_to_pt);
5231 
5232     MoveTo(move_to_pt - GG::Pt(AppWidth(), AppHeight()));
5233 
5234     int sel_system_id = SidePanel::SystemID();
5235     if (m_scale_line)
5236         m_scale_line->Update(ZoomFactor(), m_selected_fleet_ids, sel_system_id);
5237     if (update_slide && m_zoom_slider)
5238         m_zoom_slider->SlideTo(m_zoom_steps_in);
5239 
5240     InitScaleCircleRenderingBuffer();
5241 
5242     ZoomedSignal(ZoomFactor());
5243 }
5244 
CorrectMapPosition(GG::Pt & move_to_pt)5245 void MapWnd::CorrectMapPosition(GG::Pt& move_to_pt) {
5246     GG::X contents_width(static_cast<int>(ZoomFactor() * GetUniverse().UniverseWidth()));
5247     GG::X app_width =  AppWidth();
5248     GG::Y app_height = AppHeight();
5249     GG::X map_margin_width(app_width);
5250 
5251     //std::cout << "MapWnd::CorrectMapPosition appwidth: " << Value(app_width) << " appheight: " << Value(app_height)
5252     //          << " to_x: " << Value(move_to_pt.x) << " to_y: " << Value(move_to_pt.y) << std::endl;;
5253 
5254     // restrict map positions to prevent map from being dragged too far off screen.
5255     // add extra padding to restrictions when universe to be shown is larger than
5256     // the screen area in which to show it.
5257     if (app_width - map_margin_width < contents_width || Value(app_height) - map_margin_width < contents_width) {
5258         if (map_margin_width < move_to_pt.x)
5259             move_to_pt.x = map_margin_width;
5260         if (move_to_pt.x + contents_width < app_width - map_margin_width)
5261             move_to_pt.x = app_width - map_margin_width - contents_width;
5262         if (map_margin_width < Value(move_to_pt.y))
5263             move_to_pt.y = GG::Y(Value(map_margin_width));
5264         if (Value(move_to_pt.y) + contents_width < Value(app_height) - map_margin_width)
5265             move_to_pt.y = app_height - Value(map_margin_width) - Value(contents_width);
5266     } else {
5267         if (move_to_pt.x < 0)
5268             move_to_pt.x = GG::X0;
5269         if (app_width < move_to_pt.x + contents_width)
5270             move_to_pt.x = app_width - contents_width;
5271         if (move_to_pt.y < GG::Y0)
5272             move_to_pt.y = GG::Y0;
5273         if (app_height < move_to_pt.y + Value(contents_width))
5274             move_to_pt.y = app_height - Value(contents_width);
5275     }
5276 }
5277 
FieldRightClicked(int field_id)5278 void MapWnd::FieldRightClicked(int field_id) {
5279     if (ClientPlayerIsModerator()) {
5280         ModeratorActionSetting mas = m_moderator_wnd->SelectedAction();
5281         ClientNetworking& net = HumanClientApp::GetApp()->Networking();
5282 
5283         if (mas == MAS_Destroy) {
5284             net.SendMessage(ModeratorActionMessage(Moderator::DestroyUniverseObject(field_id)));
5285         }
5286         return;
5287     }
5288 }
5289 
SystemDoubleClicked(int system_id)5290 void MapWnd::SystemDoubleClicked(int system_id) {
5291     if (!m_in_production_view_mode) {
5292         if (!m_production_wnd->Visible())
5293             ToggleProduction();
5294         CenterOnObject(system_id);
5295         m_production_wnd->SelectSystem(system_id);
5296     }
5297 }
5298 
SystemLeftClicked(int system_id)5299 void MapWnd::SystemLeftClicked(int system_id) {
5300     SelectSystem(system_id);
5301     SystemLeftClickedSignal(system_id);
5302 }
5303 
SystemRightClicked(int system_id,GG::Flags<GG::ModKey> mod_keys)5304 void MapWnd::SystemRightClicked(int system_id, GG::Flags<GG::ModKey> mod_keys) {
5305     if (ClientPlayerIsModerator()) {
5306         ModeratorActionSetting mas = m_moderator_wnd->SelectedAction();
5307         ClientNetworking& net = HumanClientApp::GetApp()->Networking();
5308 
5309         if (mas == MAS_Destroy) {
5310             net.SendMessage(ModeratorActionMessage(
5311                 Moderator::DestroyUniverseObject(system_id)));
5312 
5313         } else if (mas == MAS_CreatePlanet) {
5314             net.SendMessage(ModeratorActionMessage(
5315                 Moderator::CreatePlanet(system_id, m_moderator_wnd->SelectedPlanetType(),
5316                                         m_moderator_wnd->SelectedPlanetSize())));
5317 
5318         } else if (mas == MAS_AddStarlane) {
5319             int selected_system_id = SidePanel::SystemID();
5320             if (Objects().get<System>(selected_system_id)) {
5321                 net.SendMessage(ModeratorActionMessage(
5322                     Moderator::AddStarlane(system_id, selected_system_id)));
5323             }
5324 
5325         } else if (mas == MAS_RemoveStarlane) {
5326             int selected_system_id = SidePanel::SystemID();
5327             if (Objects().get<System>(selected_system_id)) {
5328                 net.SendMessage(ModeratorActionMessage(
5329                     Moderator::RemoveStarlane(system_id, selected_system_id)));
5330             }
5331         } else if (mas == MAS_SetOwner) {
5332             int empire_id = m_moderator_wnd->SelectedEmpire();
5333             auto system = Objects().get<System>(system_id);
5334             if (!system)
5335                 return;
5336 
5337             for (auto& obj : Objects().find<const UniverseObject>(system->ContainedObjectIDs())) {
5338                 UniverseObjectType obj_type = obj->ObjectType();
5339                 if (obj_type >= OBJ_BUILDING && obj_type < OBJ_SYSTEM) {
5340                     net.SendMessage(ModeratorActionMessage(
5341                     Moderator::SetOwner(obj->ID(), empire_id)));
5342                 }
5343             }
5344         }
5345     }
5346 
5347     if (!m_in_production_view_mode && FleetUIManager::GetFleetUIManager().ActiveFleetWnd()) {
5348         if (system_id == INVALID_OBJECT_ID)
5349             ClearProjectedFleetMovementLines();
5350         else
5351             PlotFleetMovement(system_id, !m_ready_turn, mod_keys &  GG::MOD_KEY_SHIFT);
5352         SystemRightClickedSignal(system_id);
5353     }
5354 }
5355 
MouseEnteringSystem(int system_id,GG::Flags<GG::ModKey> mod_keys)5356 void MapWnd::MouseEnteringSystem(int system_id, GG::Flags<GG::ModKey> mod_keys) {
5357     if (ClientPlayerIsModerator()) {
5358         //
5359     } else {
5360         if (!m_in_production_view_mode)
5361             PlotFleetMovement(system_id, false, mod_keys & GG::MOD_KEY_SHIFT);
5362     }
5363     SystemBrowsedSignal(system_id);
5364 }
5365 
MouseLeavingSystem(int system_id)5366 void MapWnd::MouseLeavingSystem(int system_id)
5367 { MouseEnteringSystem(INVALID_OBJECT_ID, GG::Flags<GG::ModKey>()); }
5368 
PlanetDoubleClicked(int planet_id)5369 void MapWnd::PlanetDoubleClicked(int planet_id) {
5370     if (planet_id == INVALID_OBJECT_ID)
5371         return;
5372 
5373     // retrieve system_id from planet_id
5374     auto planet = Objects().get<Planet>(planet_id);
5375     if (!planet)
5376         return;
5377 
5378     // open production screen
5379     if (!m_in_production_view_mode) {
5380         if (!m_production_wnd->Visible())
5381             ToggleProduction();
5382         CenterOnObject(planet->SystemID());
5383         m_production_wnd->SelectSystem(planet->SystemID());
5384         m_production_wnd->SelectPlanet(planet_id);
5385     }
5386 }
5387 
PlanetRightClicked(int planet_id)5388 void MapWnd::PlanetRightClicked(int planet_id) {
5389     if (planet_id == INVALID_OBJECT_ID)
5390         return;
5391     if (!ClientPlayerIsModerator())
5392         return;
5393 
5394     ModeratorActionSetting mas = m_moderator_wnd->SelectedAction();
5395     ClientNetworking& net = HumanClientApp::GetApp()->Networking();
5396 
5397     if (mas == MAS_Destroy) {
5398         net.SendMessage(ModeratorActionMessage(
5399             Moderator::DestroyUniverseObject(planet_id)));
5400     } else if (mas == MAS_SetOwner) {
5401         int empire_id = m_moderator_wnd->SelectedEmpire();
5402         net.SendMessage(ModeratorActionMessage(
5403             Moderator::SetOwner(planet_id, empire_id)));
5404     }
5405 }
5406 
BuildingRightClicked(int building_id)5407 void MapWnd::BuildingRightClicked(int building_id) {
5408     if (building_id == INVALID_OBJECT_ID)
5409         return;
5410     if (!ClientPlayerIsModerator())
5411         return;
5412 
5413     ModeratorActionSetting mas = m_moderator_wnd->SelectedAction();
5414     ClientNetworking& net = HumanClientApp::GetApp()->Networking();
5415 
5416     if (mas == MAS_Destroy) {
5417         net.SendMessage(ModeratorActionMessage(
5418             Moderator::DestroyUniverseObject(building_id)));
5419     } else if (mas == MAS_SetOwner) {
5420         int empire_id = m_moderator_wnd->SelectedEmpire();
5421         net.SendMessage(ModeratorActionMessage(
5422             Moderator::SetOwner(building_id, empire_id)));
5423     }
5424 }
5425 
ReplotProjectedFleetMovement(bool append)5426 void MapWnd::ReplotProjectedFleetMovement(bool append) {
5427     TraceLogger() << "MapWnd::ReplotProjectedFleetMovement" << (append?" append":"");
5428     for (const auto& fleet_line : m_projected_fleet_lines) {
5429         const MovementLineData& data = fleet_line.second;
5430         if (!data.path.empty()) {
5431             int target = data.path.back().object_id;
5432             if (target != INVALID_OBJECT_ID) {
5433                 PlotFleetMovement(target, false, append);
5434             }
5435         }
5436     }
5437 }
5438 
PlotFleetMovement(int system_id,bool execute_move,bool append)5439 void MapWnd::PlotFleetMovement(int system_id, bool execute_move, bool append) {
5440     if (!FleetUIManager::GetFleetUIManager().ActiveFleetWnd())
5441         return;
5442 
5443     if (execute_move || append)
5444         DebugLogger() << "PlotFleetMovement " << (execute_move?" execute":"") << (append?" append":"");
5445     else
5446         TraceLogger() << "PlotfleetMovement";
5447 
5448     int empire_id = HumanClientApp::GetApp()->EmpireID();
5449     auto fleet_ids = FleetUIManager::GetFleetUIManager().ActiveFleetWnd()->SelectedFleetIDs();
5450 
5451     // apply to all selected this-player-owned fleets in currently-active FleetWnd
5452     for (const auto& fleet : Objects().find<Fleet>(fleet_ids)) {
5453         if (!fleet)
5454             continue;
5455 
5456         // only give orders / plot prospective move paths of fleets owned by player
5457         if (!(fleet->OwnedBy(empire_id)) || !(fleet->NumShips()))
5458             continue;
5459 
5460         // plot empty move pathes if destination is not a known system
5461         if (system_id == INVALID_OBJECT_ID) {
5462             m_projected_fleet_lines.erase(fleet->ID());
5463             continue;
5464         }
5465 
5466         int fleet_sys_id = fleet->SystemID();
5467         if (append && !fleet->TravelRoute().empty()) {
5468             fleet_sys_id = fleet->TravelRoute().back();
5469         }
5470 
5471         int start_system = fleet_sys_id;
5472         if (fleet_sys_id == INVALID_OBJECT_ID)
5473             start_system = fleet->NextSystemID();
5474 
5475         // get path to destination...
5476         std::list<int> route = GetPathfinder()->ShortestPath(start_system, system_id, empire_id).first;
5477         // Prepend a non-empty old_route to the beginning of route.
5478         if (append && !fleet->TravelRoute().empty()) {
5479             std::list<int> old_route(fleet->TravelRoute());
5480             old_route.erase(--old_route.end()); //end of old is begin of new
5481             route.splice(route.begin(), old_route);
5482         }
5483 
5484         // disallow "offroad" (direct non-starlane non-wormhole) travel
5485         if (route.size() == 2 && *route.begin() != *route.rbegin()) {
5486             int begin_id = *route.begin();
5487             auto begin_sys = Objects().get<System>(begin_id);
5488             int end_id = *route.rbegin();
5489             auto end_sys = Objects().get<System>(end_id);
5490 
5491             if (!begin_sys->HasStarlaneTo(end_id) && !begin_sys->HasWormholeTo(end_id) &&
5492                 !end_sys->HasStarlaneTo(begin_id) && !end_sys->HasWormholeTo(begin_id))
5493             {
5494                 continue;
5495             }
5496         }
5497 
5498         // if actually ordering fleet movement, not just prospectively previewing, ... do so
5499         if (execute_move && !route.empty()){
5500             HumanClientApp::GetApp()->Orders().IssueOrder(
5501                 std::make_shared<FleetMoveOrder>(empire_id, fleet->ID(), system_id, append));
5502             StopFleetExploring(fleet->ID());
5503         }
5504 
5505         // show route on map
5506         SetProjectedFleetMovementLine(fleet->ID(), route);
5507     }
5508 }
5509 
FleetIDsOfFleetButtonsOverlapping(int fleet_id) const5510 std::vector<int> MapWnd::FleetIDsOfFleetButtonsOverlapping(int fleet_id) const {
5511     std::vector<int> fleet_ids;
5512 
5513     auto fleet = Objects().get<Fleet>(fleet_id);
5514     if (!fleet) {
5515         ErrorLogger() << "MapWnd::FleetIDsOfFleetButtonsOverlapping: Fleet id "
5516                       << fleet_id << " does not exist.";
5517         return fleet_ids;
5518     }
5519 
5520     const auto& it = m_fleet_buttons.find(fleet_id);
5521     if (it == m_fleet_buttons.end()) {
5522         // Log that a FleetButton could not be found for the requested fleet, and include when the fleet was last seen
5523         int empire_id = HumanClientApp::GetApp()->EmpireID();
5524         auto vis_turn_map = GetUniverse().GetObjectVisibilityTurnMapByEmpire(fleet_id, empire_id);
5525         int vis_turn = -1;
5526         if (vis_turn_map.find(VIS_BASIC_VISIBILITY) != vis_turn_map.end())
5527             vis_turn = vis_turn_map[VIS_BASIC_VISIBILITY];
5528         ErrorLogger() << "Couldn't find a FleetButton for fleet " << fleet_id
5529                       << " with last basic vis turn " << vis_turn;
5530         return fleet_ids;
5531     }
5532     const auto& fleet_btn = it->second;
5533 
5534     // A check if a button overlaps the fleet button
5535     auto overlaps_fleet_btn = [&fleet_btn](const FleetButton& test_fb) {
5536         GG::Pt center = GG::Pt((fleet_btn->Left() + fleet_btn->Right()) / 2,
5537                                (fleet_btn->Top() + fleet_btn->Bottom()) /2);
5538 
5539         bool retval = test_fb.InWindow(center)
5540                    || test_fb.InWindow(fleet_btn->UpperLeft())
5541                    || test_fb.InWindow(GG::Pt(fleet_btn->Right(), fleet_btn->Top()))
5542                    || test_fb.InWindow(GG::Pt(fleet_btn->Left(), fleet_btn->Bottom()))
5543                    || test_fb.InWindow(fleet_btn->LowerRight());
5544 
5545         return retval;
5546     };
5547 
5548     // There are 4 types of fleet buttons: moving on a starlane, offroad,
5549     // and stationary or departing from a system.
5550 
5551     // Moving fleet buttons only overlap with fleet buttons on the same starlane
5552     if (const auto starlane_end_systems = IsMovingOnStarlane(fleet)) {
5553         const auto& lane_btns_it = m_moving_fleet_buttons.find(*starlane_end_systems);
5554         if (lane_btns_it == m_moving_fleet_buttons.end())
5555             return fleet_ids;
5556 
5557         // Add all fleets for each overlapping button on the starlane
5558         for (const auto& test_fb : lane_btns_it->second)
5559             if (overlaps_fleet_btn(*test_fb))
5560                 std::copy(test_fb->Fleets().begin(), test_fb->Fleets().end(), std::back_inserter(fleet_ids));
5561 
5562         return fleet_ids;
5563     }
5564 
5565     // Offroad fleet buttons only overlap other offroad fleet buttons.
5566     if (IsOffRoad(fleet)) {
5567         // This scales poorly (linearly) with increasing universe size if
5568         // offroading is common.
5569         for (const auto& pos_and_fbs: m_offroad_fleet_buttons) {
5570             const auto& fbs = pos_and_fbs.second;
5571             if (fbs.empty())
5572                 continue;
5573 
5574             // Since all buttons are at the same position, only check if the first
5575             // button overlaps fleet_btn
5576             if (!overlaps_fleet_btn(**fbs.begin()))
5577                 continue;
5578 
5579             // Add all fleets for all fleet buttons to btn_fleet
5580             for (const auto& overlapped_fb: fbs) {
5581                 std::copy(overlapped_fb->Fleets().begin(), overlapped_fb->Fleets().end(),
5582                           std::back_inserter(fleet_ids));
5583             }
5584         }
5585 
5586         return fleet_ids;
5587     }
5588 
5589     // Stationary and departing fleet buttons should not overlap with each
5590     // other because of their offset placement for each empire.
5591     return fleet_btn->Fleets();
5592 }
5593 
FleetIDsOfFleetButtonsOverlapping(const FleetButton & fleet_btn) const5594 std::vector<int> MapWnd::FleetIDsOfFleetButtonsOverlapping(const FleetButton& fleet_btn) const {
5595     // get possible fleets to select from, and a pointer to one of those fleets
5596     if (fleet_btn.Fleets().empty()) {
5597         ErrorLogger() << "Clicked FleetButton contained no fleets!";
5598         return std::vector<int>();
5599     }
5600 
5601     // Add any overlapping fleet buttons for moving or offroad fleets.
5602     const auto overlapped_fleets = FleetIDsOfFleetButtonsOverlapping(fleet_btn.Fleets()[0]);
5603 
5604     if (overlapped_fleets.empty())
5605         ErrorLogger() << "Clicked FleetButton and overlapping buttons contained no fleets!";
5606     return overlapped_fleets;
5607 }
5608 
FleetButtonLeftClicked(const FleetButton * fleet_btn)5609 void MapWnd::FleetButtonLeftClicked(const FleetButton* fleet_btn) {
5610     if (!fleet_btn)
5611         return;
5612 
5613     // allow switching to fleetView even when in production mode
5614     if (m_in_production_view_mode)
5615         HideProduction();
5616 
5617     // Add any overlapping fleet buttons for moving or offroad fleets.
5618     const auto fleet_ids_to_include_in_fleet_wnd = FleetIDsOfFleetButtonsOverlapping(*fleet_btn);
5619     if (fleet_ids_to_include_in_fleet_wnd.empty())
5620         return;
5621 
5622     int already_selected_fleet_id = INVALID_OBJECT_ID;
5623 
5624     // Find if a FleetWnd for these fleet(s) is already open, and if so, if there
5625     // is a single selected fleet in the window, and if so, what fleet that is
5626 
5627     // Note: The shared_ptr<FleetWnd> scope is confined to this if block, so that
5628     // SelectFleet below can delete the FleetWnd and re-use the CUIWnd config from
5629     // OptionsDB if needed.
5630     if (const auto& wnd_for_button = FleetUIManager::GetFleetUIManager().WndForFleetIDs(fleet_ids_to_include_in_fleet_wnd)) {
5631         // check which fleet(s) is/are selected in the button's FleetWnd
5632         auto selected_fleet_ids = wnd_for_button->SelectedFleetIDs();
5633 
5634         // record selected fleet if just one fleet is selected.  otherwise, keep default
5635         // INVALID_OBJECT_ID to indicate that no single fleet is selected
5636         if (selected_fleet_ids.size() == 1)
5637             already_selected_fleet_id = *(selected_fleet_ids.begin());
5638     }
5639 
5640 
5641     // pick fleet to select from fleets represented by the clicked FleetButton.
5642     int fleet_to_select_id = INVALID_OBJECT_ID;
5643 
5644 
5645     // fleet_ids are the ids of the clicked and nearby buttons, but when
5646     // clicking a button, only the fleets in that button should be cycled
5647     // through.
5648     const auto& selectable_fleet_ids = fleet_btn->Fleets();
5649 
5650     if (already_selected_fleet_id == INVALID_OBJECT_ID || selectable_fleet_ids.size() == 1) {
5651         // no (single) fleet is already selected, or there is only one selectable fleet,
5652         // so select first fleet in button
5653         fleet_to_select_id = *selectable_fleet_ids.begin();
5654 
5655     } else {
5656         // select next fleet after already-selected fleet, or first fleet if already-selected
5657         // fleet is the last fleet in the button.
5658 
5659         // to do this, scan through button's fleets to find already_selected_fleet
5660         bool found_already_selected_fleet = false;
5661         for (auto it = selectable_fleet_ids.begin(); it != selectable_fleet_ids.end(); ++it) {
5662             if (*it == already_selected_fleet_id) {
5663                 // found already selected fleet.  get NEXT fleet.  don't need to worry about
5664                 // there not being enough fleets to do this because if above checks for case
5665                 // of there being only one fleet in this button
5666                 ++it;
5667                 // if next fleet iterator is past end of fleets, loop around to first fleet
5668                 if (it == selectable_fleet_ids.end())
5669                     it = selectable_fleet_ids.begin();
5670                 // get fleet to select out of iterator
5671                 fleet_to_select_id = *it;
5672                 found_already_selected_fleet = true;
5673                 break;
5674             }
5675         }
5676 
5677         if (!found_already_selected_fleet) {
5678             // didn't find already-selected fleet.  the selected fleet might have been moving when the
5679             // click button was for stationary fleets, or vice versa.  regardless, just default back
5680             // to selecting the first fleet for this button
5681             fleet_to_select_id = *selectable_fleet_ids.begin();
5682         }
5683     }
5684 
5685 
5686     // select chosen fleet
5687     if (fleet_to_select_id != INVALID_OBJECT_ID)
5688         SelectFleet(fleet_to_select_id);
5689 }
5690 
FleetButtonRightClicked(const FleetButton * fleet_btn)5691 void MapWnd::FleetButtonRightClicked(const FleetButton* fleet_btn) {
5692     if (!fleet_btn)
5693         return;
5694 
5695     // Add any overlapping fleet buttons for moving or offroad fleets.
5696     const auto fleet_ids = FleetIDsOfFleetButtonsOverlapping(*fleet_btn);
5697     if (fleet_ids.empty())
5698         return;
5699 
5700     // if fleetbutton holds currently not visible fleets, offer to dismiss them
5701     int empire_id = HumanClientApp::GetApp()->EmpireID();
5702     std::vector<int> sensor_ghosts;
5703 
5704     // find sensor ghosts
5705     for (const auto& fleet : Objects().find<Fleet>(fleet_ids)) {
5706         if (!fleet)
5707             continue;
5708         if (fleet->OwnedBy(empire_id))
5709             continue;
5710         if (GetUniverse().GetObjectVisibilityByEmpire(fleet->ID(), empire_id) >= VIS_BASIC_VISIBILITY)
5711             continue;
5712         sensor_ghosts.push_back(fleet->ID());
5713     }
5714 
5715     // should there be sensor ghosts, offer to dismiss them
5716     if (sensor_ghosts.size() > 0) {
5717         auto popup = GG::Wnd::Create<CUIPopupMenu>(fleet_btn->LowerRight().x, fleet_btn->LowerRight().y);
5718 
5719         auto forget_fleet_actions = [this, empire_id, sensor_ghosts]() {
5720             for (auto fleet_id : sensor_ghosts) {
5721                 ForgetObject(fleet_id);
5722             }
5723         };
5724 
5725         popup->AddMenuItem(GG::MenuItem(UserString("FW_ORDER_DISMISS_SENSOR_GHOST_ALL"), false, false, forget_fleet_actions));
5726         popup->Run();
5727 
5728         // Force a redraw
5729         RequirePreRender();
5730         auto fleet_wnd = FleetUIManager::GetFleetUIManager().ActiveFleetWnd();
5731         if (fleet_wnd)
5732             fleet_wnd->RequirePreRender();
5733     }
5734 
5735     FleetsRightClicked(fleet_ids);
5736 }
5737 
FleetRightClicked(int fleet_id)5738 void MapWnd::FleetRightClicked(int fleet_id) {
5739     if (fleet_id == INVALID_OBJECT_ID)
5740         return;
5741     std::vector<int> fleet_ids;
5742     fleet_ids.push_back(fleet_id);
5743     FleetsRightClicked(fleet_ids);
5744 }
5745 
FleetsRightClicked(const std::vector<int> & fleet_ids)5746 void MapWnd::FleetsRightClicked(const std::vector<int>& fleet_ids) {
5747     if (fleet_ids.empty())
5748         return;
5749     if (!ClientPlayerIsModerator())
5750         return;
5751 
5752     ModeratorActionSetting mas = m_moderator_wnd->SelectedAction();
5753     ClientNetworking& net = HumanClientApp::GetApp()->Networking();
5754 
5755     if (mas == MAS_Destroy) {
5756         for (int fleet_id : fleet_ids) {
5757             net.SendMessage(ModeratorActionMessage(
5758                 Moderator::DestroyUniverseObject(fleet_id)));
5759         }
5760     } else if (mas == MAS_SetOwner) {
5761         int empire_id = m_moderator_wnd->SelectedEmpire();
5762         for (int fleet_id : fleet_ids) {
5763             net.SendMessage(ModeratorActionMessage(
5764                 Moderator::SetOwner(fleet_id, empire_id)));
5765         }
5766     }
5767 }
5768 
ShipRightClicked(int ship_id)5769 void MapWnd::ShipRightClicked(int ship_id) {
5770     if (ship_id == INVALID_OBJECT_ID)
5771         return;
5772     std::vector<int> ship_ids;
5773     ship_ids.push_back(ship_id);
5774     ShipsRightClicked(ship_ids);
5775 }
5776 
ShipsRightClicked(const std::vector<int> & ship_ids)5777 void MapWnd::ShipsRightClicked(const std::vector<int>& ship_ids) {
5778     if (ship_ids.empty())
5779         return;
5780     if (!ClientPlayerIsModerator())
5781         return;
5782 
5783     ModeratorActionSetting mas = m_moderator_wnd->SelectedAction();
5784     ClientNetworking& net = HumanClientApp::GetApp()->Networking();
5785 
5786     if (mas == MAS_Destroy) {
5787         for (int ship_id : ship_ids) {
5788             net.SendMessage(ModeratorActionMessage(
5789                 Moderator::DestroyUniverseObject(ship_id)));
5790         }
5791     } else if (mas == MAS_SetOwner) {
5792         int empire_id = m_moderator_wnd->SelectedEmpire();
5793         for (int ship_id : ship_ids) {
5794             net.SendMessage(ModeratorActionMessage(
5795                 Moderator::SetOwner(ship_id, empire_id)));
5796         }
5797     }
5798 }
5799 
SelectedFleetsChanged()5800 void MapWnd::SelectedFleetsChanged() {
5801     // get selected fleets
5802     std::set<int> selected_fleet_ids;
5803     if (const FleetWnd* fleet_wnd = FleetUIManager::GetFleetUIManager().ActiveFleetWnd())
5804         selected_fleet_ids = fleet_wnd->SelectedFleetIDs();
5805 
5806     // if old and new sets of selected fleets are the same, don't need to change anything
5807     if (selected_fleet_ids == m_selected_fleet_ids)
5808         return;
5809 
5810     // set new selected fleets
5811     m_selected_fleet_ids = selected_fleet_ids;
5812 
5813     // update fleetbutton selection indicators
5814     RefreshFleetButtonSelectionIndicators();
5815 }
5816 
SelectedShipsChanged()5817 void MapWnd::SelectedShipsChanged() {
5818     ScopedTimer timer("MapWnd::SelectedShipsChanged", true);
5819 
5820     // get selected ships
5821     std::set<int> selected_ship_ids;
5822     if (const FleetWnd* fleet_wnd = FleetUIManager::GetFleetUIManager().ActiveFleetWnd())
5823         selected_ship_ids = fleet_wnd->SelectedShipIDs();
5824 
5825     // if old and new sets of selected fleets are the same, don't need to change anything
5826     if (selected_ship_ids == m_selected_ship_ids)
5827         return;
5828 
5829     // set new selected fleets
5830     m_selected_ship_ids = selected_ship_ids;
5831 
5832 
5833     // refresh meters of planets in currently selected system, as changing selected fleets
5834     // may have changed which species a planet should have population estimates shown for
5835 
5836     SidePanel::Update();
5837 }
5838 
RefreshFleetButtonSelectionIndicators()5839 void MapWnd::RefreshFleetButtonSelectionIndicators() {
5840     // clear old selection indicators
5841     for (auto& stationary_fleet_button : m_stationary_fleet_buttons) {
5842         for (auto& button : stationary_fleet_button.second)
5843             button->SetSelected(false);
5844     }
5845 
5846     for (auto& departing_fleet_button : m_departing_fleet_buttons) {
5847         for (auto& button : departing_fleet_button.second)
5848             button->SetSelected(false);
5849     }
5850 
5851     for (auto& moving_fleet_button : m_moving_fleet_buttons) {
5852         for (auto& button : moving_fleet_button.second)
5853             button->SetSelected(false);
5854     }
5855 
5856     // add new selection indicators
5857     for (int fleet_id : m_selected_fleet_ids) {
5858         const auto& button_it = m_fleet_buttons.find(fleet_id);
5859         if (button_it != m_fleet_buttons.end())
5860             button_it->second->SetSelected(true);
5861     }
5862 }
5863 
UniverseObjectDeleted(std::shared_ptr<const UniverseObject> obj)5864 void MapWnd::UniverseObjectDeleted(std::shared_ptr<const UniverseObject> obj) {
5865     if (obj)
5866         DebugLogger() << "MapWnd::UniverseObjectDeleted: " << obj->ID();
5867     else
5868         DebugLogger() << "MapWnd::UniverseObjectDeleted: NO OBJECT";
5869     if (auto fleet = std::dynamic_pointer_cast<const Fleet>(obj))
5870         RemoveFleet(fleet->ID());
5871 }
5872 
RegisterPopup(const std::shared_ptr<MapWndPopup> & popup)5873 void MapWnd::RegisterPopup(const std::shared_ptr<MapWndPopup>& popup) {
5874     if (popup)
5875         m_popups.push_back(std::weak_ptr<MapWndPopup>(popup));
5876 }
5877 
RemovePopup(MapWndPopup * popup)5878 void MapWnd::RemovePopup(MapWndPopup* popup) {
5879     if (!popup)
5880         return;
5881 
5882     const auto& it = std::find_if(m_popups.begin(), m_popups.end(),
5883                                   [&popup](const std::weak_ptr<Wnd>& xx){ return xx.lock().get() == popup;});
5884     if (it != m_popups.end())
5885         m_popups.erase(it);
5886 }
5887 
ResetEmpireShown()5888 void MapWnd::ResetEmpireShown() {
5889     m_production_wnd->SetEmpireShown(HumanClientApp::GetApp()->EmpireID());
5890     m_research_wnd->SetEmpireShown(HumanClientApp::GetApp()->EmpireID());
5891     // TODO: Design?
5892 }
5893 
Sanitize()5894 void MapWnd::Sanitize() {
5895     ShowAllPopups(); // make sure popups don't save visible = 0 to the config
5896     CloseAllPopups();
5897     HideResearch();
5898     HideProduction();
5899     HideDesign();
5900     RemoveWindows();
5901     m_pedia_panel->ClearItems();    // deletes all pedia items in the memory
5902     m_toolbar->Hide();
5903     m_FPS->Hide();
5904     m_scale_line->Hide();
5905     m_zoom_slider->Hide();
5906     m_combat_report_wnd->Hide();
5907     m_sitrep_panel->ShowSitRepsForTurn(INVALID_GAME_TURN);
5908     if (m_auto_end_turn)
5909         ToggleAutoEndTurn();
5910 
5911     ResetEmpireShown();
5912 
5913     SelectSystem(INVALID_OBJECT_ID);
5914 
5915     // temp
5916     m_starfield_verts.clear();
5917     m_starfield_colours.clear();
5918     // end temp
5919 
5920     ClearSystemRenderingBuffers();
5921     ClearStarlaneRenderingBuffers();
5922     ClearFieldRenderingBuffers();
5923     ClearVisibilityRadiiRenderingBuffers();
5924     ClearScaleCircleRenderingBuffer();
5925     ClearStarfieldRenderingBuffers();
5926 
5927 
5928     if (ClientUI* cui = ClientUI::GetClientUI()) {
5929         // clearing of message window commented out because scrollbar has quirks
5930         // after doing so until enough messages are added
5931         //if (MessageWnd* msg_wnd = cui->GetMessageWnd())
5932         //    msg_wnd->Clear();
5933         if (const auto& plr_wnd = cui->GetPlayerListWnd())
5934             plr_wnd->Clear();
5935     }
5936 
5937     MoveTo(GG::Pt(-AppWidth(), -AppHeight()));
5938     m_zoom_steps_in = 1.0;
5939 
5940     m_research_wnd->Sanitize();
5941     m_production_wnd->Sanitize();
5942     m_design_wnd->Sanitize();
5943 
5944     m_selected_fleet_ids.clear();
5945     m_selected_ship_ids.clear();
5946 
5947     m_starlane_endpoints.clear();
5948 
5949     DeleteFleetButtons();
5950 
5951     for (auto& signal : m_fleet_state_change_signals)
5952         signal.second.disconnect();
5953     m_fleet_state_change_signals.clear();
5954 
5955     for (auto& entry : m_system_fleet_insert_remove_signals) {
5956         for (auto& connection : entry.second)
5957             connection.disconnect();
5958         entry.second.clear();
5959     }
5960     m_system_fleet_insert_remove_signals.clear();
5961     m_fleet_lines.clear();
5962     m_projected_fleet_lines.clear();
5963     m_system_icons.clear();
5964     m_fleets_exploring.clear();
5965     m_line_between_systems = {INVALID_OBJECT_ID, INVALID_OBJECT_ID};
5966 
5967     DetachChildren();
5968 }
5969 
ResetTimeoutClock(int timeout)5970 void MapWnd::ResetTimeoutClock(int timeout) {
5971     m_timeout_time = timeout <= 0 ?
5972                      std::chrono::time_point<std::chrono::high_resolution_clock>() :
5973                      std::chrono::high_resolution_clock::now() + std::chrono::high_resolution_clock::duration(std::chrono::seconds(timeout));
5974 
5975     TimerFiring(0, &m_timeout_clock);
5976 }
5977 
TimerFiring(unsigned int ticks,GG::Timer * timer)5978 void MapWnd::TimerFiring(unsigned int ticks, GG::Timer* timer) {
5979     std::chrono::high_resolution_clock::duration remaining = m_timeout_time - std::chrono::high_resolution_clock::now();
5980     auto remaining_sec = std::chrono::duration_cast<std::chrono::seconds>(remaining);
5981     if (remaining_sec.count() <= 0) {
5982         m_timeout_clock.Stop();
5983         m_timeout_remain->SetText("");
5984         return;
5985     }
5986 
5987     int sec_part = remaining_sec.count() % 60;
5988     int min_part = remaining_sec.count() / 60 % 60;
5989     int hour_part = remaining_sec.count() / 3600;
5990 
5991     if (hour_part == 0) {
5992         if (min_part == 0) {
5993             m_timeout_remain->SetText(boost::io::str(FlexibleFormat(UserString("MAP_TIMEOUT_SECONDS")) % sec_part));
5994         } else {
5995             m_timeout_remain->SetText(boost::io::str(FlexibleFormat(UserString("MAP_TIMEOUT_MINS_SECS")) % min_part % sec_part));
5996         }
5997     } else {
5998          m_timeout_remain->SetText(boost::io::str(FlexibleFormat(UserString("MAP_TIMEOUT_HRS_MINS")) % hour_part % min_part));
5999     }
6000 
6001     if (!m_timeout_clock.Running()) {
6002         m_timeout_clock.Reset(GG::GUI::GetGUI()->Ticks());
6003         m_timeout_clock.Start();
6004     }
6005 }
6006 
PushWndStack(std::shared_ptr<GG::Wnd> wnd)6007 void MapWnd::PushWndStack(std::shared_ptr<GG::Wnd> wnd) {
6008     if (!wnd)
6009         return;
6010     // First remove it from its current location in the stack (if any), to prevent it from being
6011     // present in two locations at once.
6012     RemoveFromWndStack(wnd);
6013     m_wnd_stack.push_back(wnd);
6014 }
6015 
RemoveFromWndStack(std::shared_ptr<GG::Wnd> wnd)6016 void MapWnd::RemoveFromWndStack(std::shared_ptr<GG::Wnd> wnd) {
6017     // Recreate the stack, but without the Wnd to be removed or any null/expired weak_ptrs
6018     std::vector<std::weak_ptr<GG::Wnd>> new_stack;
6019     for (auto& weak_wnd : m_wnd_stack) {
6020         // skip adding to the new stack if it's null/expired
6021         if (auto shared_wnd = weak_wnd.lock()) {
6022             // skip adding to the new stack if it's the one to be removed
6023             if (shared_wnd != wnd) {
6024                 // Swap them to avoid another reference count check
6025                 new_stack.emplace_back();
6026                 new_stack.back().swap(weak_wnd);
6027             }
6028         }
6029     }
6030     m_wnd_stack.swap(new_stack);
6031 }
6032 
ReturnToMap()6033 bool MapWnd::ReturnToMap() {
6034     std::shared_ptr<GG::Wnd> wnd;
6035     // Pop the top Wnd from the stack, and repeat until we find a non-null and visible one (or
6036     // until the stack runs out).
6037     // Need to check that it's visible, in case it was closed without being removed from the stack;
6038     // if we didn't reject such a Wnd, we might close no window, or even open a window.
6039     // Either way, the Wnd is removed from the stack, since it is no longer of any use.
6040     while (!m_wnd_stack.empty() && !(wnd && wnd->Visible())) {
6041         wnd = m_wnd_stack.back().lock();
6042         m_wnd_stack.pop_back();
6043     }
6044     // If no non-null and visible Wnd was found, then there's nothing to do.
6045     if (!(wnd && wnd->Visible())) {
6046         return true;
6047     }
6048 
6049     auto cui = ClientUI::GetClientUI();
6050 
6051     if (wnd == m_sitrep_panel) {
6052         ToggleSitRep();
6053     } else if (wnd == m_research_wnd) {
6054         ToggleResearch();
6055     } else if (wnd == m_design_wnd) {
6056         ToggleDesign();
6057     } else if (wnd == m_production_wnd) {
6058         ToggleProduction();
6059     } else if (wnd == m_pedia_panel) {
6060         TogglePedia();
6061     } else if (wnd == m_object_list_wnd) {
6062         ToggleObjects();
6063     } else if (wnd == m_moderator_wnd) {
6064         ToggleModeratorActions();
6065     } else if (wnd == m_combat_report_wnd) {
6066         m_combat_report_wnd->Hide();
6067     } else if (wnd == m_side_panel) {
6068         SelectSystem(INVALID_OBJECT_ID);
6069     } else if (dynamic_cast<FleetWnd*>(wnd.get())) {
6070         // if it is any fleet window at all, go ahead and close all fleet windows.
6071         FleetUIManager::GetFleetUIManager().CloseAll();
6072     } else if (cui && wnd == cui->GetPlayerListWnd()) {
6073         HideEmpires();
6074     } else if (cui && wnd == cui->GetMessageWnd()) {
6075         HideMessages();
6076     } else {
6077         ErrorLogger() << "Unknown GG::Wnd " << wnd->Name() << " found in MapWnd::m_wnd_stack";
6078     }
6079 
6080     return true;
6081 }
6082 
EndTurn()6083 bool MapWnd::EndTurn() {
6084     if (m_ready_turn) {
6085         HumanClientApp::GetApp()->UnreadyTurn();
6086     } else {
6087         ClientUI* cui = ClientUI::GetClientUI();
6088         if (!cui) {
6089             ErrorLogger() << "MapWnd::EndTurn: No client UI available";
6090             return false;
6091         }
6092         SaveGameUIData ui_data;
6093         cui->GetSaveGameUIData(ui_data);
6094         HumanClientApp::GetApp()->StartTurn(ui_data);
6095     }
6096     return true;
6097 }
6098 
ToggleAutoEndTurn()6099 void MapWnd::ToggleAutoEndTurn() {
6100     if (!m_btn_auto_turn)
6101         return;
6102 
6103      if (m_auto_end_turn) {
6104         m_auto_end_turn = false;
6105         m_btn_auto_turn->SetUnpressedGraphic(   GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "manual_turn.png")));
6106         m_btn_auto_turn->SetPressedGraphic(     GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "auto_turn.png")));
6107         m_btn_auto_turn->SetRolloverGraphic(    GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "manual_turn_mouseover.png")));
6108 
6109         m_btn_auto_turn->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6110         m_btn_auto_turn->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
6111             UserString("MAP_BTN_MANUAL_TURN_ADVANCE"),
6112             UserString("MAP_BTN_MANUAL_TURN_ADVANCE_DESC")
6113         ));
6114     } else {
6115         m_auto_end_turn = true;
6116         m_btn_auto_turn->SetUnpressedGraphic(   GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "auto_turn.png")));
6117         m_btn_auto_turn->SetPressedGraphic(     GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "manual_turn.png")));
6118         m_btn_auto_turn->SetRolloverGraphic(    GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "auto_turn_mouseover.png")));
6119 
6120         m_btn_auto_turn->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6121         m_btn_auto_turn->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
6122             UserString("MAP_BTN_AUTO_ADVANCE_ENABLED"),
6123             UserString("MAP_BTN_AUTO_ADVANCE_ENABLED_DESC")
6124         ));
6125     }
6126 }
6127 
ShowModeratorActions()6128 void MapWnd::ShowModeratorActions() {
6129     // hide other "competing" windows
6130     HideResearch();
6131     HideProduction();
6132     HideDesign();
6133 
6134     // update moderator window
6135     m_moderator_wnd->Refresh();
6136 
6137     // show the moderator window
6138     m_moderator_wnd->Show();
6139     GG::GUI::GetGUI()->MoveUp(m_moderator_wnd);
6140     PushWndStack(m_moderator_wnd);
6141 
6142     m_btn_moderator->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "moderator_mouseover.png")));
6143     m_btn_moderator->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "moderator.png")));
6144 }
6145 
HideModeratorActions()6146 void MapWnd::HideModeratorActions() {
6147     m_moderator_wnd->Hide();
6148     RemoveFromWndStack(m_moderator_wnd);
6149     m_btn_moderator->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "moderator.png")));
6150     m_btn_moderator->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "moderator_mouseover.png")));
6151 }
6152 
ToggleModeratorActions()6153 bool MapWnd::ToggleModeratorActions() {
6154     if (!m_moderator_wnd->Visible() || m_production_wnd->Visible() || m_research_wnd->Visible() || m_design_wnd->Visible()) {
6155         ShowModeratorActions();
6156     } else {
6157         HideModeratorActions();
6158     }
6159     return true;
6160 }
6161 
ShowObjects()6162 void MapWnd::ShowObjects() {
6163     ClearProjectedFleetMovementLines();
6164 
6165     // hide other "competing" windows
6166     HideResearch();
6167     HideProduction();
6168     HideDesign();
6169 
6170     // update objects window
6171     m_object_list_wnd->Refresh();
6172 
6173     // show the objects window
6174     m_object_list_wnd->Show();
6175     GG::GUI::GetGUI()->MoveUp(m_object_list_wnd);
6176     PushWndStack(m_object_list_wnd);
6177 
6178     // indicate selection on button
6179     m_btn_objects->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "objects_mouseover.png")));
6180     m_btn_objects->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "objects.png")));
6181 }
6182 
HideObjects()6183 void MapWnd::HideObjects() {
6184     m_object_list_wnd->Hide(); // necessary so it won't be visible when next toggled
6185     RemoveFromWndStack(m_object_list_wnd);
6186     m_btn_objects->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "objects.png")));
6187     m_btn_objects->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "objects_mouseover.png")));
6188 }
6189 
ToggleObjects()6190 bool MapWnd::ToggleObjects() {
6191     if (!m_object_list_wnd->Visible() || m_production_wnd->Visible() || m_research_wnd->Visible() || m_design_wnd->Visible()) {
6192         ShowObjects();
6193     } else {
6194         HideObjects();
6195     }
6196     return true;
6197 }
6198 
ShowSitRep()6199 void MapWnd::ShowSitRep() {
6200     ClearProjectedFleetMovementLines();
6201 
6202     // hide other "competing" windows
6203     HideResearch();
6204     HideProduction();
6205     HideDesign();
6206 
6207     // show the sitrep window
6208     m_sitrep_panel->Show();
6209     GG::GUI::GetGUI()->MoveUp(m_sitrep_panel);
6210     PushWndStack(m_sitrep_panel);
6211 
6212     // indicate selection on button
6213     m_btn_siterep->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "sitrep_mouseover.png")));
6214     m_btn_siterep->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "sitrep.png")));
6215 }
6216 
HideSitRep()6217 void MapWnd::HideSitRep() {
6218     m_sitrep_panel->Hide(); // necessary so it won't be visible when next toggled
6219     RemoveFromWndStack(m_sitrep_panel);
6220     m_btn_siterep->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "sitrep.png")));
6221     m_btn_siterep->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "sitrep_mouseover.png")));
6222 }
6223 
ToggleSitRep()6224 bool MapWnd::ToggleSitRep() {
6225     if (!m_sitrep_panel->Visible() || m_production_wnd->Visible() || m_research_wnd->Visible() || m_design_wnd->Visible()) {
6226         ShowSitRep();
6227     } else {
6228         HideSitRep();
6229     }
6230     return true;
6231 }
6232 
ShowMessages()6233 void MapWnd::ShowMessages() {
6234     // hide other "competing" windows
6235     HideResearch();
6236     HideProduction();
6237     HideDesign();
6238 
6239     ClientUI* cui = ClientUI::GetClientUI();
6240     if (!cui)
6241         return;
6242     const auto& msg_wnd = cui->GetMessageWnd();
6243     if (!msg_wnd)
6244         return;
6245     GG::GUI* gui = GG::GUI::GetGUI();
6246     if (!gui)
6247         return;
6248     msg_wnd->Show();
6249     msg_wnd->OpenForInput();
6250     gui->MoveUp(msg_wnd);
6251     PushWndStack(msg_wnd);
6252 
6253     // indicate selection on button
6254     m_btn_messages->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "messages_mouseover.png")));
6255     m_btn_messages->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "messages.png")));
6256 }
6257 
HideMessages()6258 void MapWnd::HideMessages() {
6259     if (ClientUI* cui = ClientUI::GetClientUI()) {
6260         cui->GetMessageWnd()->Hide();
6261         RemoveFromWndStack(cui->GetMessageWnd());
6262     }
6263     m_btn_messages->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "messages.png")));
6264     m_btn_messages->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "messages_mouseover.png")));
6265 }
6266 
ToggleMessages()6267 bool MapWnd::ToggleMessages() {
6268     ClientUI* cui = ClientUI::GetClientUI();
6269     if (!cui)
6270         return false;
6271     const auto& msg_wnd = cui->GetMessageWnd();
6272     if (!msg_wnd)
6273         return false;
6274     if (!msg_wnd->Visible() || m_production_wnd->Visible() || m_research_wnd->Visible() || m_design_wnd->Visible()) {
6275         ShowMessages();
6276     } else {
6277         HideMessages();
6278     }
6279     return true;
6280 }
6281 
ShowEmpires()6282 void MapWnd::ShowEmpires() {
6283     // hide other "competing" windows
6284     HideResearch();
6285     HideProduction();
6286     HideDesign();
6287 
6288     ClientUI* cui = ClientUI::GetClientUI();
6289     if (!cui)
6290         return;
6291     const auto& plr_wnd = cui->GetPlayerListWnd();
6292     if (!plr_wnd)
6293         return;
6294     GG::GUI* gui = GG::GUI::GetGUI();
6295     if (!gui)
6296         return;
6297     plr_wnd->Show();
6298     gui->MoveUp(plr_wnd);
6299     PushWndStack(plr_wnd);
6300 
6301     // indicate selection on button
6302     m_btn_empires->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "empires_mouseover.png")));
6303     m_btn_empires->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "empires.png")));
6304 }
6305 
HideEmpires()6306 void MapWnd::HideEmpires() {
6307     if (ClientUI* cui = ClientUI::GetClientUI()) {
6308         cui->GetPlayerListWnd()->Hide();
6309         RemoveFromWndStack(cui->GetPlayerListWnd());
6310     }
6311     m_btn_empires->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "empires.png")));
6312     m_btn_empires->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "empires_mouseover.png")));
6313 }
6314 
ToggleEmpires()6315 bool MapWnd::ToggleEmpires() {
6316     ClientUI* cui = ClientUI::GetClientUI();
6317     if (!cui)
6318         return false;
6319     const auto& plr_wnd = cui->GetPlayerListWnd();
6320     if (!plr_wnd)
6321         return false;
6322     if (!plr_wnd->Visible() || m_production_wnd->Visible() || m_research_wnd->Visible() || m_design_wnd->Visible()) {
6323         ShowEmpires();
6324     } else {
6325         HideEmpires();
6326     }
6327     return true;
6328 }
6329 
ShowPedia()6330 void MapWnd::ShowPedia() {
6331     // if production screen is visible, toggle the production screen's pedia, not the one of the map screen
6332     if (m_in_production_view_mode) {
6333         m_production_wnd->TogglePedia();
6334         return;
6335     }
6336 
6337     if (m_research_wnd->Visible()) {
6338         m_research_wnd->TogglePedia();
6339         return;
6340     }
6341 
6342     ClearProjectedFleetMovementLines();
6343 
6344     // hide other "competing" windows
6345     HideResearch();
6346     HideProduction();
6347     HideDesign();
6348 
6349     if (m_pedia_panel->GetItemsSize() == 0)
6350         m_pedia_panel->SetIndex();
6351 
6352     // update pedia window
6353     m_pedia_panel->Refresh();
6354 
6355     // show the pedia window
6356     m_pedia_panel->Show();
6357     GG::GUI::GetGUI()->MoveUp(m_pedia_panel);
6358     PushWndStack(m_pedia_panel);
6359 
6360     // indicate selection on button
6361     m_btn_pedia->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "pedia_mouseover.png")));
6362     m_btn_pedia->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "pedia.png")));
6363 }
6364 
HidePedia()6365 void MapWnd::HidePedia() {
6366     m_pedia_panel->Hide();
6367     RemoveFromWndStack(m_pedia_panel);
6368     m_btn_pedia->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "pedia.png")));
6369     m_btn_pedia->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "pedia_mouseover.png")));
6370 }
6371 
TogglePedia()6372 bool MapWnd::TogglePedia() {
6373     if (!m_pedia_panel->Visible() || m_production_wnd->Visible() || m_research_wnd->Visible() || m_design_wnd->Visible()) {
6374         ShowPedia();
6375     } else {
6376         HidePedia();
6377     }
6378     return true;
6379 }
6380 
ShowGraphs()6381 bool MapWnd::ShowGraphs() {
6382     ShowPedia();
6383     m_pedia_panel->AddItem(TextLinker::ENCYCLOPEDIA_TAG, "ENC_GRAPH");
6384     return true;
6385 }
6386 
HideSidePanel()6387 void MapWnd::HideSidePanel() {
6388     m_sidepanel_open_before_showing_other = m_side_panel->Visible();   // a kludge, so the sidepanel will reappear after opening and closing a full screen wnd
6389     m_side_panel->Hide();
6390 }
6391 
RestoreSidePanel()6392 void MapWnd::RestoreSidePanel() {
6393     if (m_sidepanel_open_before_showing_other)
6394         ReselectLastSystem();
6395     // send order changes could be made in research, production or other windows
6396     HumanClientApp::GetApp()->SendPartialOrders();
6397 }
6398 
ShowResearch()6399 void MapWnd::ShowResearch() {
6400     ClearProjectedFleetMovementLines();
6401 
6402     // hide other "competing" windows
6403     HideProduction();
6404     HideDesign();
6405     HideSidePanel();
6406 
6407     // show the research window
6408     m_research_wnd->Show();
6409     GG::GUI::GetGUI()->MoveUp(m_research_wnd);
6410     PushWndStack(m_research_wnd);
6411 
6412     // hide pedia again if it is supposed to be hidden persistently
6413     if (GetOptionsDB().Get<bool>("ui.research.pedia.hidden.enabled"))
6414         m_research_wnd->HidePedia();
6415 
6416     // indicate selection on button
6417     m_btn_research->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "research_mouseover.png")));
6418     m_btn_research->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "research.png")));
6419 }
6420 
HideResearch()6421 void MapWnd::HideResearch() {
6422     m_research_wnd->Hide();
6423     RemoveFromWndStack(m_research_wnd);
6424     m_btn_research->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "research.png")));
6425     m_btn_research->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "research_mouseover.png")));
6426 
6427     RestoreSidePanel();
6428 }
6429 
ToggleResearch()6430 bool MapWnd::ToggleResearch() {
6431     if (m_research_wnd->Visible())
6432         HideResearch();
6433     else
6434         ShowResearch();
6435     return true;
6436 }
6437 
ShowProduction()6438 void MapWnd::ShowProduction() {
6439     ClearProjectedFleetMovementLines();
6440 
6441     // hide other "competing" windows
6442     HideResearch();
6443     HideDesign();
6444     HideSidePanel();
6445     HidePedia();
6446     if (GetOptionsDB().Get<bool>("ui.production.mappanels.removed")) {
6447         RemoveWindows();
6448         GG::GUI::GetGUI()->Remove(ClientUI::GetClientUI()->GetMessageWnd());
6449         GG::GUI::GetGUI()->Remove(ClientUI::GetClientUI()->GetPlayerListWnd());
6450     }
6451 
6452     m_in_production_view_mode = true;
6453     HideAllPopups();
6454     GG::GUI::GetGUI()->MoveUp(m_production_wnd);
6455     PushWndStack(m_production_wnd);
6456 
6457     // indicate selection on button
6458     m_btn_production->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "production_mouseover.png")));
6459     m_btn_production->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "production.png")));
6460 
6461     // if no system is currently shown in sidepanel, default to this empire's
6462     // home system (ie. where the capital is)
6463     if (SidePanel::SystemID() == INVALID_OBJECT_ID) {
6464         if (const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID()))
6465             if (auto obj = Objects().get(empire->CapitalID()))
6466                 SelectSystem(obj->SystemID());
6467     } else {
6468         // if a system is already shown, make sure a planet gets selected by
6469         // default when the production screen opens up
6470         m_production_wnd->SelectDefaultPlanet();
6471     }
6472     m_production_wnd->Update();
6473     m_production_wnd->Show();
6474 
6475     // hide pedia again if it is supposed to be hidden persistently
6476     if (GetOptionsDB().Get<bool>("ui.production.pedia.hidden.enabled"))
6477         m_production_wnd->TogglePedia();
6478 }
6479 
HideProduction()6480 void MapWnd::HideProduction() {
6481     m_production_wnd->Hide();
6482     RemoveFromWndStack(m_production_wnd);
6483     m_in_production_view_mode = false;
6484     m_btn_production->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "production.png")));
6485     m_btn_production->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "production_mouseover.png")));
6486 
6487     // Don't check ui.production.mappanels.removed to avoid a
6488     // situation where the option is changed and the panels aren't re-registered.
6489     RegisterWindows();
6490     GG::GUI::GetGUI()->Register(ClientUI::GetClientUI()->GetMessageWnd());
6491     GG::GUI::GetGUI()->Register(ClientUI::GetClientUI()->GetPlayerListWnd());
6492 
6493     ShowAllPopups();
6494     RestoreSidePanel();
6495 }
6496 
ToggleProduction()6497 bool MapWnd::ToggleProduction() {
6498     if (m_in_production_view_mode)
6499         HideProduction();
6500     else
6501         ShowProduction();
6502 
6503     // make info panels in production/map window's side panel update their expand-collapse state
6504     m_side_panel->Update();
6505 
6506     return true;
6507 }
6508 
ShowDesign()6509 void MapWnd::ShowDesign() {
6510     ClearProjectedFleetMovementLines();
6511 
6512     // hide other "competing" windows
6513     HideResearch();
6514     HideProduction();
6515     HideSidePanel();
6516 
6517     // show the design window
6518     m_design_wnd->Show();
6519     GG::GUI::GetGUI()->MoveUp(m_design_wnd);
6520     PushWndStack(m_design_wnd);
6521     m_design_wnd->Reset();
6522 
6523     // indicate selection on button
6524     m_btn_design->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "design_mouseover.png")));
6525     m_btn_design->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "design.png")));
6526 }
6527 
HideDesign()6528 void MapWnd::HideDesign() {
6529     m_design_wnd->Hide();
6530     RemoveFromWndStack(m_design_wnd);
6531     m_btn_design->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "design.png")));
6532     m_btn_design->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "design_mouseover.png")));
6533 
6534     RestoreSidePanel();
6535 }
6536 
ToggleDesign()6537 bool MapWnd::ToggleDesign() {
6538     if (m_design_wnd->Visible())
6539         HideDesign();
6540     else
6541         ShowDesign();
6542     return true;
6543 }
6544 
ShowMenu()6545 bool MapWnd::ShowMenu() {
6546     if (m_menu_showing)
6547         return true;
6548 
6549     ClearProjectedFleetMovementLines();
6550     m_menu_showing = true;
6551 
6552     m_btn_menu->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "menu_mouseover.png")));
6553     m_btn_menu->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "menu.png")));
6554 
6555     auto menu = GG::Wnd::Create<InGameMenu>();
6556     menu->Run();
6557     m_menu_showing = false;
6558 
6559     m_btn_menu->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "menu.png")));
6560     m_btn_menu->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "buttons" / "menu_mouseover.png")));
6561 
6562     return true;
6563 }
6564 
CloseSystemView()6565 bool MapWnd::CloseSystemView() {
6566     SelectSystem(INVALID_OBJECT_ID);
6567     m_side_panel->Hide();   // redundant, but safer to keep in case the behavior of SelectSystem changes
6568     return true;
6569 }
6570 
KeyboardZoomIn()6571 bool MapWnd::KeyboardZoomIn() {
6572     Zoom(1);
6573     return true;
6574 }
6575 
KeyboardZoomOut()6576 bool MapWnd::KeyboardZoomOut() {
6577     Zoom(-1);
6578     return true;
6579 }
6580 
RefreshTurnButtonTooltip()6581 void MapWnd::RefreshTurnButtonTooltip() {
6582     auto app = HumanClientApp::GetApp();
6583     std::string btn_turn_tooltip;
6584 
6585     if (!m_ready_turn) {
6586         if (app->SinglePlayerGame())
6587             btn_turn_tooltip = UserString("MAP_BTN_TURN_TOOLTIP_DESC_SP");
6588         else
6589             btn_turn_tooltip = UserString("MAP_BTN_TURN_TOOLTIP_DESC_MP");
6590         if (app->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR)
6591             btn_turn_tooltip = UserString("MAP_BTN_TURN_TOOLTIP_DESC_MOD");
6592     }
6593     if (m_ready_turn && !app->SinglePlayerGame())
6594         btn_turn_tooltip = UserString("MAP_BTN_TURN_TOOLTIP_DESC_WAIT");
6595     if (app->GetClientType() == Networking::CLIENT_TYPE_HUMAN_OBSERVER)
6596         btn_turn_tooltip = UserString("MAP_BTN_TURN_TOOLTIP_DESC_OBS");
6597 
6598     m_btn_turn->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6599     m_btn_turn->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
6600         UserString("MAP_BTN_TURN_TOOLTIP"), btn_turn_tooltip));
6601 }
6602 
RefreshTradeResourceIndicator()6603 void MapWnd::RefreshTradeResourceIndicator() {
6604     Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
6605     if (!empire) {
6606         m_trade->SetValue(0.0);
6607         return;
6608     }
6609     m_trade->SetValue(empire->ResourceStockpile(RE_TRADE));
6610     m_trade->ClearBrowseInfoWnd();
6611     m_trade->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6612     m_trade->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
6613         UserString("MAP_TRADE_TITLE"), UserString("MAP_TRADE_TEXT")));
6614 }
6615 
RefreshFleetResourceIndicator()6616 void MapWnd::RefreshFleetResourceIndicator() {
6617     int empire_id = HumanClientApp::GetApp()->EmpireID();
6618     Empire* empire = GetEmpire(empire_id);
6619     if (!empire) {
6620         m_fleet->SetValue(0.0);
6621         return;
6622     }
6623 
6624     const auto& this_client_known_destroyed_objects = GetUniverse().EmpireKnownDestroyedObjectIDs(empire_id);
6625 
6626     int total_fleet_count = 0;
6627     for (auto& ship : Objects().all<Ship>()) {
6628         if (ship->OwnedBy(empire_id) && !this_client_known_destroyed_objects.count(ship->ID()))
6629             total_fleet_count++;
6630     }
6631 
6632     m_fleet->SetValue(total_fleet_count);
6633     m_fleet->ClearBrowseInfoWnd();
6634     m_fleet->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6635     m_fleet->SetBrowseInfoWnd(GG::Wnd::Create<FleetDetailBrowseWnd>(
6636         empire_id, GG::X(FontBasedUpscale(250))));
6637 }
6638 
RefreshResearchResourceIndicator()6639 void MapWnd::RefreshResearchResourceIndicator() {
6640     const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
6641     if (!empire) {
6642         m_research->SetValue(0.0);
6643         m_research_wasted->Hide();
6644         return;
6645     }
6646     m_research->SetValue(empire->ResourceOutput(RE_RESEARCH));
6647     m_research->ClearBrowseInfoWnd();
6648     m_research->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6649 
6650     float total_RP_spent = empire->GetResearchQueue().TotalRPsSpent();
6651     float total_RP_output = empire->GetResourcePool(RE_RESEARCH)->TotalOutput();
6652     float total_RP_wasted = total_RP_output - total_RP_spent;
6653     float total_RP_target_output = empire->GetResourcePool(RE_RESEARCH)->TargetOutput();
6654 
6655     m_research->SetBrowseInfoWnd(GG::Wnd::Create<ResourceBrowseWnd>(
6656         UserString("MAP_RESEARCH_TITLE"), UserString("RESEARCH_INFO_RP"),
6657         total_RP_spent, total_RP_output, total_RP_target_output
6658     ));
6659 
6660     if (total_RP_wasted > 0.05) {
6661         DebugLogger()  << "MapWnd::RefreshResearchResourceIndicator: Showing Research Wasted Icon with RP spent: "
6662                        << total_RP_spent << " and RP Production: " << total_RP_output << ", wasting " << total_RP_wasted;
6663         m_research_wasted->Show();
6664         m_research_wasted->ClearBrowseInfoWnd();
6665         m_research_wasted->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6666 
6667         m_research_wasted->SetBrowseInfoWnd(GG::Wnd::Create<WastedStockpiledResourceBrowseWnd>(
6668             UserString("MAP_RESEARCH_WASTED_TITLE"), UserString("RESEARCH_INFO_RP"),
6669             total_RP_output, total_RP_wasted, false, 0.0f, 0.0f, total_RP_wasted,
6670             UserString("MAP_RES_CLICK_TO_OPEN")));
6671 
6672     } else {
6673         m_research_wasted->Hide();
6674     }
6675 }
6676 
RefreshDetectionIndicator()6677 void MapWnd::RefreshDetectionIndicator() {
6678     const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
6679     if (!empire)
6680         return;
6681     m_detection->SetValue(empire->GetMeter("METER_DETECTION_STRENGTH")->Current());
6682     m_detection->ClearBrowseInfoWnd();
6683     m_detection->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6684     m_detection->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
6685         UserString("MAP_DETECTION_TITLE"), UserString("MAP_DETECTION_TEXT")));
6686 }
6687 
RefreshIndustryResourceIndicator()6688 void MapWnd::RefreshIndustryResourceIndicator() {
6689     const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
6690     if (!empire) {
6691         m_industry->SetValue(0.0);
6692         m_industry_wasted->Hide();
6693         m_stockpile->SetValue(0.0);
6694         return;
6695     }
6696     m_industry->SetValue(empire->ResourceOutput(RE_INDUSTRY));
6697     m_industry->ClearBrowseInfoWnd();
6698     m_industry->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6699 
6700     double total_PP_spent = empire->GetProductionQueue().TotalPPsSpent();
6701     double total_PP_output = empire->GetResourcePool(RE_INDUSTRY)->TotalOutput();
6702     double total_PP_target_output = empire->GetResourcePool(RE_INDUSTRY)->TargetOutput();
6703     float  stockpile = empire->GetResourcePool(RE_INDUSTRY)->Stockpile();
6704     float  stockpile_used = boost::accumulate(empire->GetProductionQueue().AllocatedStockpilePP() | boost::adaptors::map_values, 0.0f);
6705     float  stockpile_use_capacity = empire->GetProductionQueue().StockpileCapacity();
6706     float  expected_stockpile = empire->GetProductionQueue().ExpectedNewStockpileAmount();
6707 
6708     float  stockpile_plusminus_next_turn = expected_stockpile - stockpile;
6709     double total_PP_for_stockpile_projects = empire->GetProductionQueue().ExpectedProjectTransferToStockpile();
6710     double total_PP_to_stockpile = expected_stockpile - stockpile + stockpile_used;
6711     double total_PP_excess = total_PP_output - total_PP_spent;
6712     double total_PP_wasted = total_PP_output - total_PP_spent - total_PP_to_stockpile + total_PP_for_stockpile_projects;
6713 
6714     m_industry->SetBrowseInfoWnd(GG::Wnd::Create<ResourceBrowseWnd>(
6715         UserString("MAP_PRODUCTION_TITLE"), UserString("PRODUCTION_INFO_PP"),
6716         total_PP_spent, total_PP_output, total_PP_target_output,
6717         true, stockpile_used, stockpile, expected_stockpile));
6718 
6719     m_stockpile->SetValue(stockpile);
6720     m_stockpile->SetValue(stockpile_plusminus_next_turn, 1);
6721     m_stockpile->ClearBrowseInfoWnd();
6722     m_stockpile->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6723     m_stockpile->SetBrowseInfoWnd(GG::Wnd::Create<ResourceBrowseWnd>(
6724         UserString("MAP_STOCKPILE_TITLE"), UserString("PRODUCTION_INFO_PP"),
6725         -1.0f, -1.0f, -1.0f,
6726         true, stockpile_used, stockpile, expected_stockpile,
6727         true, stockpile_use_capacity));
6728 
6729     // red "waste" icon if the non-project transfer to IS is more than either 3x per-turn use or 80% total output
6730     // else yellow icon if the non-project transfer to IS is more than 20% total output, or if there is any transfer
6731     // to IS and the IS is expected to be above 10x per-turn use.
6732     if (total_PP_wasted > 0.05 || (total_PP_excess > std::min(3.0 * stockpile_use_capacity, 0.8 * total_PP_output))) {
6733         DebugLogger()  << "MapWnd::RefreshIndustryResourceIndicator: Showing Industry Wasted Icon with Industry spent: "
6734                        << total_PP_spent << " and Industry Production: " << total_PP_output << ", wasting " << total_PP_wasted;
6735         boost::filesystem::path button_texture_dir = ClientUI::ArtDir() / "icons" / "buttons";
6736         m_industry_wasted->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
6737                                                                 "wasted_resource.png", false)));
6738         m_industry_wasted->SetPressedGraphic(GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
6739                                                                 "wasted_resource_clicked.png", false)));
6740         m_industry_wasted->SetRolloverGraphic(GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
6741                                                                 "wasted_resource_mouseover.png", false)));
6742         m_industry_wasted->Show();
6743         m_industry_wasted->ClearBrowseInfoWnd();
6744         m_industry_wasted->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6745         m_industry_wasted->SetBrowseInfoWnd(GG::Wnd::Create<WastedStockpiledResourceBrowseWnd>(
6746             UserString("MAP_PRODUCTION_WASTED_TITLE"), UserString("PRODUCTION_INFO_PP"),
6747             total_PP_output, total_PP_excess,
6748             true, stockpile_use_capacity, total_PP_to_stockpile, total_PP_wasted,
6749             UserString("MAP_PROD_CLICK_TO_OPEN")));
6750     } else if (total_PP_to_stockpile > 0.05 && (expected_stockpile > (10 * stockpile_use_capacity) ||
6751                                                 total_PP_excess > 0.2 * total_PP_output)) {
6752         boost::filesystem::path button_texture_dir = ClientUI::ArtDir() / "icons" / "buttons";
6753         m_industry_wasted->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
6754                                                                 "warned_resource.png", false)));
6755         m_industry_wasted->SetPressedGraphic(GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
6756                                                                 "warned_resource_clicked.png", false)));
6757         m_industry_wasted->SetRolloverGraphic(GG::SubTexture(ClientUI::GetTexture(button_texture_dir /
6758                                                                 "warned_resource_mouseover.png", false)));
6759         m_industry_wasted->Show();
6760         m_industry_wasted->ClearBrowseInfoWnd();
6761         m_industry_wasted->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6762         m_industry_wasted->SetBrowseInfoWnd(GG::Wnd::Create<WastedStockpiledResourceBrowseWnd>(
6763             UserString("MAP_PRODUCTION_WASTED_TITLE"), UserString("PRODUCTION_INFO_PP"),
6764             total_PP_output, total_PP_excess,
6765             true, stockpile_use_capacity, total_PP_to_stockpile, total_PP_wasted,
6766             UserString("MAP_PROD_CLICK_TO_OPEN")));
6767     } else {
6768         m_industry_wasted->Hide();
6769     }
6770 }
6771 
RefreshPopulationIndicator()6772 void MapWnd::RefreshPopulationIndicator() {
6773     Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
6774     if (!empire) {
6775         m_population->SetValue(0.0);
6776         return;
6777     }
6778     m_population->SetValue(empire->GetPopulationPool().Population());
6779     m_population->ClearBrowseInfoWnd();
6780 
6781     const auto pop_center_ids = empire->GetPopulationPool().PopCenterIDs();
6782     std::map<std::string, float> population_counts;
6783     std::map<std::string, float> tag_counts;
6784     const ObjectMap& objects = Objects();
6785 
6786     //tally up all species population counts
6787     for (const auto& pc : objects.find<PopCenter>(pop_center_ids)) {
6788         if (!pc)
6789             continue;
6790 
6791         const std::string& species_name = pc->SpeciesName();
6792         if (species_name.empty())
6793             continue;
6794         float this_pop = pc->GetMeter(METER_POPULATION)->Initial();
6795         population_counts[species_name] += this_pop;
6796         if (const Species* species = GetSpecies(species_name) ) {
6797             for (const std::string& tag : species->Tags()) {
6798                 tag_counts[tag] += this_pop;
6799             }
6800         }
6801     }
6802 
6803     m_population->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
6804     m_population->SetBrowseInfoWnd(GG::Wnd::Create<CensusBrowseWnd>(
6805         UserString("MAP_POPULATION_DISTRIBUTION"), population_counts, tag_counts, GetSpeciesManager().census_order()));
6806 }
6807 
UpdateSidePanelSystemObjectMetersAndResourcePools()6808 void MapWnd::UpdateSidePanelSystemObjectMetersAndResourcePools() {
6809     GetUniverse().UpdateMeterEstimates(SidePanel::SystemID(), true);
6810     UpdateEmpireResourcePools();
6811 }
6812 
UpdateEmpireResourcePools()6813 void MapWnd::UpdateEmpireResourcePools() {
6814     //std::cout << "MapWnd::UpdateEmpireResourcePools" << std::endl;
6815     Empire *empire = GetEmpire( HumanClientApp::GetApp()->EmpireID() );
6816     /* Recalculate stockpile, available, production, predicted change of
6817      * resources.  When resource pools update, they emit ChangeSignal, which is
6818      * connected to MapWnd::Refresh???ResourceIndicator, which updates the
6819      * empire resource pool indicators of the MapWnd. */
6820     empire->UpdateResourcePools();
6821 
6822     // Update indicators on sidepanel, which are not directly connected to from the ResourcePool ChangedSignal
6823     SidePanel::Update();
6824 }
6825 
ZoomToHomeSystem()6826 bool MapWnd::ZoomToHomeSystem() {
6827     const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
6828     if (!empire)
6829         return false;
6830     int home_id = empire->CapitalID();
6831 
6832     if (home_id != INVALID_OBJECT_ID) {
6833         auto object = Objects().get(home_id);
6834         if (!object)
6835             return false;
6836         CenterOnObject(object->SystemID());
6837         SelectSystem(object->SystemID());
6838     }
6839 
6840     return true;
6841 }
6842 
6843 namespace {
6844     struct CustomRowCmp {
operator ()__anon5bb68ec91811::CustomRowCmp6845         bool operator()(const std::pair<std::string, int>& lhs, const std::pair<std::string, int>& rhs) const {
6846             return GetLocale("en_US.UTF-8").operator()(lhs.first, rhs.first);    // todo: use .second values to break ties
6847         }
6848     };
6849 
GetSystemNamesIDs()6850     std::set<std::pair<std::string, int>, CustomRowCmp> GetSystemNamesIDs() {
6851         // get systems, store alphabetized
6852         std::set<std::pair<std::string, int>, CustomRowCmp> system_names_ids;
6853         for (auto& system : Objects().all<System>()) {
6854             system_names_ids.insert({system->Name(), system->ID()});
6855         }
6856         return system_names_ids;
6857     }
6858 
GetOwnedSystemNamesIDs(int empire_id)6859     std::set<std::pair<std::string, int>, CustomRowCmp> GetOwnedSystemNamesIDs(int empire_id) {
6860         auto owned_planets = Objects().find<Planet>(OwnedVisitor(empire_id));
6861 
6862         // get IDs of systems that contain any owned planets
6863         std::set<int> system_ids;
6864         for (auto& obj : owned_planets)
6865         { system_ids.insert(obj->SystemID()); }
6866 
6867         // store systems, sorted alphabetically
6868         std::set<std::pair<std::string, int>, CustomRowCmp> system_names_ids;
6869         for (const auto& sys : Objects().find<System>(system_ids)) {
6870             if (!sys)
6871                 continue;
6872             system_names_ids.insert({sys->Name(), sys->ID()});
6873         }
6874 
6875         return system_names_ids;
6876     }
6877 }
6878 
ZoomToPrevOwnedSystem()6879 bool MapWnd::ZoomToPrevOwnedSystem() {
6880     // get planets owned by client's player, sorted alphabetically
6881     auto system_names_ids = GetOwnedSystemNamesIDs(HumanClientApp::GetApp()->EmpireID());
6882     if (system_names_ids.empty())
6883         return false;
6884 
6885     // find currently selected system in list
6886     auto it = system_names_ids.rend();
6887     auto sel_sys = Objects().get<System>(SidePanel::SystemID());
6888     if (sel_sys) {
6889         it = std::find(system_names_ids.rbegin(), system_names_ids.rend(),  std::make_pair(sel_sys->Name(), sel_sys->ID()));
6890         if (it != system_names_ids.rend())
6891             ++it;
6892     }
6893     if (it == system_names_ids.rend())
6894         it = system_names_ids.rbegin();
6895 
6896     if (it != system_names_ids.rend()) {
6897         CenterOnObject(it->second);
6898         SelectSystem(it->second);
6899     }
6900 
6901     return true;
6902 }
6903 
ZoomToNextOwnedSystem()6904 bool MapWnd::ZoomToNextOwnedSystem() {
6905     // get planets owned by client's player, sorted alphabetically
6906     auto system_names_ids = GetOwnedSystemNamesIDs(HumanClientApp::GetApp()->EmpireID());
6907     if (system_names_ids.empty())
6908         return false;
6909 
6910     auto it = system_names_ids.end();
6911 
6912     // find currently selected system in list
6913     auto sel_sys = Objects().get<System>(SidePanel::SystemID());
6914     if (sel_sys) {
6915         it = std::find(system_names_ids.begin(), system_names_ids.end(), std::make_pair(sel_sys->Name(), sel_sys->ID()));
6916         if (it != system_names_ids.end())
6917             ++it;
6918     }
6919     if (it == system_names_ids.end())
6920         it = system_names_ids.begin();
6921 
6922     if (it != system_names_ids.end()) {
6923         CenterOnObject(it->second);
6924         SelectSystem(it->second);
6925     }
6926 
6927     return true;
6928 }
6929 
ZoomToPrevSystem()6930 bool MapWnd::ZoomToPrevSystem() {
6931     auto system_names_ids = GetSystemNamesIDs();
6932     if (system_names_ids.empty())
6933         return false;
6934 
6935     // find currently selected system in list
6936     auto it = system_names_ids.rend();
6937     auto sel_sys = Objects().get<System>(SidePanel::SystemID());
6938     if (sel_sys) {
6939         it = std::find(system_names_ids.rbegin(), system_names_ids.rend(),  std::make_pair(sel_sys->Name(), sel_sys->ID()));
6940         if (it != system_names_ids.rend())
6941             ++it;
6942     }
6943     if (it == system_names_ids.rend())
6944         it = system_names_ids.rbegin();
6945 
6946     if (it != system_names_ids.rend()) {
6947         CenterOnObject(it->second);
6948         SelectSystem(it->second);
6949     }
6950 
6951     return true;
6952 }
6953 
ZoomToNextSystem()6954 bool MapWnd::ZoomToNextSystem() {
6955     auto system_names_ids = GetSystemNamesIDs();
6956     if (system_names_ids.empty())
6957         return false;
6958 
6959     auto it = system_names_ids.end();
6960 
6961     // find currently selected system in list
6962     auto sel_sys = Objects().get<System>(SidePanel::SystemID());
6963     if (sel_sys) {
6964         it = std::find(system_names_ids.begin(), system_names_ids.end(), std::make_pair(sel_sys->Name(), sel_sys->ID()));
6965         if (it != system_names_ids.end())
6966             ++it;
6967     }
6968     if (it == system_names_ids.end())
6969         it = system_names_ids.begin();
6970 
6971     if (it != system_names_ids.end()) {
6972         CenterOnObject(it->second);
6973         SelectSystem(it->second);
6974     }
6975 
6976     return true;
6977 }
6978 
ZoomToPrevIdleFleet()6979 bool MapWnd::ZoomToPrevIdleFleet() {
6980     auto vec = GetUniverse().Objects().find<Fleet>(StationaryFleetVisitor(HumanClientApp::GetApp()->EmpireID()));
6981     auto it = std::find_if(vec.begin(), vec.end(),
6982         [this](const std::shared_ptr<UniverseObject>& o){ return o->ID() == this->m_current_fleet_id; });
6983     const auto& destroyed_object_ids = GetUniverse().DestroyedObjectIds();
6984     if (it != vec.begin())
6985         --it;
6986     else
6987         it = vec.end();
6988     while (it != vec.begin() && (it == vec.end() || destroyed_object_ids.count((*it)->ID())))
6989         --it;
6990     m_current_fleet_id = it != vec.end() ? (*it)->ID() : vec.empty() ? INVALID_OBJECT_ID : vec.back()->ID();
6991 
6992     if (m_current_fleet_id != INVALID_OBJECT_ID) {
6993         CenterOnObject(m_current_fleet_id);
6994         SelectFleet(m_current_fleet_id);
6995     }
6996 
6997     return true;
6998 }
6999 
ZoomToNextIdleFleet()7000 bool MapWnd::ZoomToNextIdleFleet() {
7001     auto vec = GetUniverse().Objects().find<Fleet>(StationaryFleetVisitor(HumanClientApp::GetApp()->EmpireID()));
7002     auto it = std::find_if(vec.begin(), vec.end(),
7003         [this](const std::shared_ptr<UniverseObject>& o){ return o->ID() == this->m_current_fleet_id; });
7004     const auto& destroyed_object_ids = GetUniverse().DestroyedObjectIds();
7005     if (it != vec.end())
7006         ++it;
7007     while (it != vec.end() && destroyed_object_ids.count((*it)->ID()))
7008         ++it;
7009     m_current_fleet_id = it != vec.end() ? (*it)->ID() : vec.empty() ? INVALID_OBJECT_ID : vec.front()->ID();
7010 
7011     if (m_current_fleet_id != INVALID_OBJECT_ID) {
7012         CenterOnObject(m_current_fleet_id);
7013         SelectFleet(m_current_fleet_id);
7014     }
7015 
7016     return true;
7017 }
7018 
ZoomToPrevFleet()7019 bool MapWnd::ZoomToPrevFleet() {
7020     auto vec = GetUniverse().Objects().find<Fleet>(OwnedVisitor(HumanClientApp::GetApp()->EmpireID()));
7021     auto it = std::find_if(vec.begin(), vec.end(),
7022         [this](const std::shared_ptr<UniverseObject>& o){ return o->ID() == this->m_current_fleet_id; });
7023     const auto& destroyed_object_ids = GetUniverse().DestroyedObjectIds();
7024     if (it != vec.begin())
7025         --it;
7026     else
7027         it = vec.end();
7028     while (it != vec.begin() && (it == vec.end() || destroyed_object_ids.count((*it)->ID())))
7029         --it;
7030     m_current_fleet_id = it != vec.end() ? (*it)->ID() : vec.empty() ? INVALID_OBJECT_ID : vec.back()->ID();
7031 
7032     if (m_current_fleet_id != INVALID_OBJECT_ID) {
7033         CenterOnObject(m_current_fleet_id);
7034         SelectFleet(m_current_fleet_id);
7035     }
7036 
7037     return true;
7038 }
7039 
ZoomToNextFleet()7040 bool MapWnd::ZoomToNextFleet() {
7041     auto vec = GetUniverse().Objects().find<Fleet>(OwnedVisitor(HumanClientApp::GetApp()->EmpireID()));
7042     auto it = std::find_if(vec.begin(), vec.end(),
7043         [this](const std::shared_ptr<UniverseObject>& o){ return o->ID() == this->m_current_fleet_id; });
7044     auto& destroyed_object_ids = GetUniverse().DestroyedObjectIds();
7045     if (it != vec.end())
7046         ++it;
7047     while (it != vec.end() && destroyed_object_ids.count((*it)->ID()))
7048         ++it;
7049     m_current_fleet_id = it != vec.end() ? (*it)->ID() : vec.empty() ? INVALID_OBJECT_ID : vec.front()->ID();
7050 
7051     if (m_current_fleet_id != INVALID_OBJECT_ID) {
7052         CenterOnObject(m_current_fleet_id);
7053         SelectFleet(m_current_fleet_id);
7054     }
7055 
7056     return true;
7057 }
7058 
ZoomToSystemWithWastedPP()7059 bool MapWnd::ZoomToSystemWithWastedPP() {
7060     int empire_id = HumanClientApp::GetApp()->EmpireID();
7061     const Empire* empire = GetEmpire(empire_id);
7062     if (!empire)
7063         return false;
7064 
7065     const ProductionQueue& queue = empire->GetProductionQueue();
7066     const auto pool = empire->GetResourcePool(RE_INDUSTRY);
7067     if (!pool)
7068         return false;
7069     auto wasted_PP_objects(queue.ObjectsWithWastedPP(pool));
7070     if (wasted_PP_objects.empty())
7071         return false;
7072 
7073     // pick first object in first group
7074     auto& obj_group = *wasted_PP_objects.begin();
7075     if (obj_group.empty())
7076         return false; // shouldn't happen?
7077     for (const auto& obj_ids : wasted_PP_objects) {
7078         for (const auto& obj : Objects().find<UniverseObject>(obj_ids)) {
7079             if (obj && obj->SystemID() != INVALID_OBJECT_ID) {
7080                 // found object with wasted PP that is in a system.  zoom there.
7081                 CenterOnObject(obj->SystemID());
7082                 SelectSystem(obj->SystemID());
7083                 ShowProduction();
7084                 return true;
7085             }
7086         }
7087     }
7088     return false;
7089 }
7090 
7091 namespace {
7092     /// On when the MapWnd window is visible and not covered
7093     //  by one of the full screen covering windows
7094     class NotCoveredMapWndCondition {
7095     protected:
7096         const MapWnd& target;
7097 
7098     public:
NotCoveredMapWndCondition(const MapWnd & tg)7099         NotCoveredMapWndCondition(const MapWnd& tg) : target(tg) {}
operator ()() const7100         bool operator()() const {
7101             return target.Visible() && !target.InResearchViewMode() && !target.InDesignViewMode();
7102         };
7103     };
7104 }
7105 
ConnectKeyboardAcceleratorSignals()7106 void MapWnd::ConnectKeyboardAcceleratorSignals() {
7107     HotkeyManager* hkm = HotkeyManager::GetManager();
7108 
7109     hkm->Connect(boost::bind(&MapWnd::ReturnToMap, this), "ui.map.open",
7110                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7111     hkm->Connect(boost::bind(&MapWnd::EndTurn, this), "ui.turn.end",
7112                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7113     hkm->Connect(boost::bind(&MapWnd::ToggleSitRep, this), "ui.map.sitrep",
7114                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7115     hkm->Connect(boost::bind(&MapWnd::ToggleResearch, this), "ui.research",
7116                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7117     hkm->Connect(boost::bind(&MapWnd::ToggleProduction, this), "ui.production",
7118                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7119     hkm->Connect(boost::bind(&MapWnd::ToggleDesign, this), "ui.design",
7120                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7121     hkm->Connect(boost::bind(&MapWnd::ToggleObjects, this), "ui.map.objects",
7122                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7123     hkm->Connect(boost::bind(&MapWnd::ToggleMessages, this), "ui.map.messages",
7124                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7125     hkm->Connect(boost::bind(&MapWnd::ToggleEmpires, this), "ui.map.empires",
7126                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7127     hkm->Connect(boost::bind(&MapWnd::TogglePedia, this), "ui.pedia",
7128                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7129     hkm->Connect(boost::bind(&MapWnd::ShowGraphs, this), "ui.map.graphs",
7130                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7131     hkm->Connect(boost::bind(&MapWnd::ShowMenu, this), "ui.gamemenu",
7132                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
7133     hkm->Connect(boost::bind(&MapWnd::KeyboardZoomIn, this), "ui.zoom.in",
7134                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7135     hkm->Connect(boost::bind(&MapWnd::KeyboardZoomIn, this), "ui.zoom.in.alt",
7136                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7137     hkm->Connect(boost::bind(&MapWnd::KeyboardZoomOut, this), "ui.zoom.out",
7138                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7139     hkm->Connect(boost::bind(&MapWnd::KeyboardZoomOut, this), "ui.zoom.out.alt",
7140                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7141     hkm->Connect(boost::bind(&MapWnd::ZoomToHomeSystem, this), "ui.map.system.zoom.home",
7142                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7143     hkm->Connect(boost::bind(&MapWnd::ZoomToPrevSystem, this), "ui.map.system.zoom.prev",
7144                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7145     hkm->Connect(boost::bind(&MapWnd::ZoomToNextSystem, this), "ui.map.system.zoom.next",
7146                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7147     hkm->Connect(boost::bind(&MapWnd::ZoomToPrevOwnedSystem, this), "ui.map.system.owned.zoom.prev",
7148                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7149     hkm->Connect(boost::bind(&MapWnd::ZoomToNextOwnedSystem, this), "ui.map.system.owned.zoom.next",
7150                  AndCondition({NotCoveredMapWndCondition(*this), NoModalWndsOpenCondition}));
7151 
7152     // the list of windows for which the fleet shortcuts are blacklisted.
7153     std::initializer_list<const GG::Wnd*> bl = {m_research_wnd.get(), m_production_wnd.get(), m_design_wnd.get()};
7154 
7155     hkm->Connect(boost::bind(&MapWnd::ZoomToPrevFleet, this), "ui.map.fleet.zoom.prev",
7156                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7157     hkm->Connect(boost::bind(&MapWnd::ZoomToNextFleet, this), "ui.map.fleet.zoom.next",
7158                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7159     hkm->Connect(boost::bind(&MapWnd::ZoomToPrevIdleFleet, this), "ui.map.fleet.idle.zoom.prev",
7160                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7161     hkm->Connect(boost::bind(&MapWnd::ZoomToNextIdleFleet, this), "ui.map.fleet.idle.zoom.next",
7162                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7163 
7164     hkm->Connect(boost::bind(&MapWnd::PanX, this, GG::X(50)),   "ui.pan.right",
7165                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7166     hkm->Connect(boost::bind(&MapWnd::PanX, this, GG::X(-50)),  "ui.pan.left",
7167                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7168     hkm->Connect(boost::bind(&MapWnd::PanY, this, GG::Y(50)),   "ui.pan.down",
7169                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7170     hkm->Connect(boost::bind(&MapWnd::PanY, this, GG::Y(-50)),  "ui.pan.up",
7171                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7172 
7173     hkm->Connect(boost::bind(&ToggleBoolOption, "ui.map.scale.legend.shown"), "ui.map.scale.legend",
7174                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7175     hkm->Connect(boost::bind(&ToggleBoolOption, "ui.map.scale.circle.shown"), "ui.map.scale.circle",
7176                  AndCondition({OrCondition({InvisibleWindowCondition(bl), VisibleWindowCondition(this)}), NoModalWndsOpenCondition}));
7177 
7178 
7179     // these are general-use hotkeys, only connected here as a convenient location to do so once.
7180     hkm->Connect(boost::bind(&GG::GUI::CutFocusWndText, GG::GUI::GetGUI()), "ui.cut");
7181     hkm->Connect(boost::bind(&GG::GUI::CopyFocusWndText, GG::GUI::GetGUI()), "ui.copy");
7182     hkm->Connect(boost::bind(&GG::GUI::PasteFocusWndClipboardText, GG::GUI::GetGUI()), "ui.paste");
7183 
7184     hkm->Connect(boost::bind(&GG::GUI::FocusWndSelectAll, GG::GUI::GetGUI()), "ui.select.all");
7185     hkm->Connect(boost::bind(&GG::GUI::FocusWndDeselect, GG::GUI::GetGUI()), "ui.select.none");
7186 
7187     //hkm->Connect(boost::bind(&GG::GUI::SetPrevFocusWndInCycle, GG::GUI::GetGUI()), "ui.focus.prev",
7188     //             NoModalWndsOpenCondition);
7189     //hkm->Connect(boost::bind(&GG::GUI::SetNextFocusWndInCycle, GG::GUI::GetGUI()), "ui.focus.next",
7190     //             NoModalWndsOpenCondition);
7191 
7192     hkm->RebuildShortcuts();
7193 }
7194 
CloseAllPopups()7195 void MapWnd::CloseAllPopups() {
7196     GG::ProcessThenRemoveExpiredPtrs(m_popups,
7197                                      [](std::shared_ptr<MapWndPopup>& wnd)
7198                                      { wnd->Close(); });
7199 }
7200 
HideAllPopups()7201 void MapWnd::HideAllPopups() {
7202     GG::ProcessThenRemoveExpiredPtrs(m_popups,
7203                                      [](std::shared_ptr<MapWndPopup>& wnd)
7204                                      { wnd->Hide(); });
7205 }
7206 
SetFleetExploring(const int fleet_id)7207 void MapWnd::SetFleetExploring(const int fleet_id) {
7208     if (!std::count(m_fleets_exploring.begin(), m_fleets_exploring.end(), fleet_id)) {
7209         m_fleets_exploring.insert(fleet_id);
7210         DispatchFleetsExploring();
7211     }
7212 }
7213 
StopFleetExploring(const int fleet_id)7214 void MapWnd::StopFleetExploring(const int fleet_id) {
7215     auto it = m_fleets_exploring.find(fleet_id);
7216     if (it == m_fleets_exploring.end())
7217         return;
7218 
7219     m_fleets_exploring.erase(it);
7220 
7221     DispatchFleetsExploring();
7222     // force UI update. Removing a fleet from the UI's list of exploring fleets
7223     // doesn't actually change the Fleet object's state in any way, so the UI
7224     // would otherwise still show the fleet as "exploring"
7225     if (auto fleet = Objects().get<Fleet>(fleet_id))
7226         fleet->StateChangedSignal();
7227 }
7228 
IsFleetExploring(const int fleet_id)7229 bool MapWnd::IsFleetExploring(const int fleet_id){
7230     return std::count(m_fleets_exploring.begin(), m_fleets_exploring.end(), fleet_id);
7231 }
7232 
7233 namespace {
7234     typedef std::set<int> SystemIDListType;
7235     typedef std::unordered_set<int> FleetIDListType;
7236     typedef std::vector<int> RouteListType;
7237     typedef std::pair<double, RouteListType> OrderedRouteType;
7238     typedef std::pair<int, RouteListType> FleetRouteType;
7239     typedef std::pair<double, FleetRouteType> OrderedFleetRouteType;
7240     typedef std::unordered_map<int, int> SystemFleetMap;
7241 
7242     /** Number of jumps in a given route */
JumpsForRoute(const RouteListType & route)7243     int JumpsForRoute(const RouteListType& route) {
7244         int count = static_cast<int>(route.size());
7245         if (count > 0) // dont count source system
7246             -- count;
7247         return count;
7248     }
7249 
7250     /** If @p fleet can determine an eta for @p route */
FleetRouteInRange(const std::shared_ptr<Fleet> & fleet,const RouteListType & route)7251     bool FleetRouteInRange(const std::shared_ptr<Fleet>& fleet, const RouteListType& route) {
7252         std::list<int> route_list;
7253         std::copy(route.begin(), route.end(), std::back_inserter(route_list));
7254 
7255         auto eta = fleet->ETA(fleet->MovePath(route_list));
7256         if (eta.first == Fleet::ETA_NEVER || eta.first == Fleet::ETA_UNKNOWN || eta.first == Fleet::ETA_OUT_OF_RANGE)
7257             return false;
7258 
7259         return true;
7260     }
7261 
7262     //helper function for DispatchFleetsExploring
7263     //return the set of all systems ID with a starlane connecting them to a system in set
AddNeighboorsToSet(const Empire * empire,const SystemIDListType & system_ids)7264     SystemIDListType AddNeighboorsToSet(const Empire *empire, const SystemIDListType& system_ids){
7265         SystemIDListType retval;
7266         auto starlanes = empire->KnownStarlanes();
7267         for (auto system_id : system_ids) {
7268             auto new_neighboors_it = starlanes.find(system_id);
7269             if (new_neighboors_it != starlanes.end()){
7270                 for (auto neighbor_id : new_neighboors_it->second) {
7271                     retval.insert(neighbor_id);
7272                 }
7273             }
7274         }
7275 
7276         return retval;
7277     }
7278 
7279     /** Get the shortest suitable route from @p start_id to @p destination_id as known to @p empire_id */
GetShortestRoute(int empire_id,int start_id,int destination_id)7280     OrderedRouteType GetShortestRoute(int empire_id, int start_id, int destination_id) {
7281         auto start_system = Objects().get<System>(start_id);
7282         auto dest_system = Objects().get<System>(destination_id);
7283         if (!start_system || !dest_system) {
7284             WarnLogger() << "Invalid start or destination system";
7285             return OrderedRouteType();
7286         }
7287 
7288         auto ignore_hostile = GetOptionsDB().Get<bool>("ui.fleet.explore.hostile.ignored");
7289         auto fleet_pred = std::make_shared<HostileVisitor>(empire_id);
7290         std::pair<std::list<int>, double> route_distance;
7291 
7292         if (ignore_hostile)
7293             route_distance = GetPathfinder()->ShortestPath(start_id, destination_id, empire_id);
7294         else
7295             route_distance = GetPathfinder()->ShortestPath(start_id, destination_id, empire_id, fleet_pred);
7296 
7297         if (!route_distance.first.empty() && route_distance.second > 0.0) {
7298             RouteListType route(route_distance.first.begin(), route_distance.first.end());
7299             return std::make_pair(route_distance.second, route);
7300         }
7301 
7302         return OrderedRouteType();
7303     }
7304 
7305     /** Route from @p fleet current location to @p destination */
GetOrderedFleetRoute(const std::shared_ptr<Fleet> & fleet,const std::shared_ptr<System> & destination)7306     OrderedFleetRouteType GetOrderedFleetRoute(const std::shared_ptr<Fleet>& fleet,
7307                                                const std::shared_ptr<System>& destination)
7308     {
7309         if (!fleet || !destination) {
7310             WarnLogger() << "Invalid fleet or system";
7311             return OrderedFleetRouteType();
7312         }
7313         if ((fleet->Fuel() < 1.0f) || !fleet->MovePath().empty()) {
7314             WarnLogger() << "Fleet has no fuel or non-empty move path";
7315             return OrderedFleetRouteType();
7316         }
7317 
7318         auto order_route = GetShortestRoute(fleet->Owner(), fleet->SystemID(), destination->ID());
7319 
7320         if (order_route.first <= 0.0) {
7321             TraceLogger() << "No suitable route from system " << fleet->SystemID() << " to " << destination->ID()
7322                           << " (" << order_route.second.size() << ">" << order_route.first << ")";
7323             return OrderedFleetRouteType();
7324         }
7325 
7326         if (!FleetRouteInRange(fleet, order_route.second)) {
7327             TraceLogger() << "Fleet " << std::to_string(fleet->ID())
7328                           << " has no eta for route to " << std::to_string(*order_route.second.rbegin());
7329             return OrderedFleetRouteType();
7330         }
7331 
7332         // decrease priority of system if previously viewed but not yet explored
7333         if (!destination->Name().empty()) {
7334             order_route.first *= GetOptionsDB().Get<float>("ui.fleet.explore.system.known.multiplier");
7335             TraceLogger() << "Deferred priority for system " << destination->Name() << " (" << destination->ID() << ")";
7336         }
7337 
7338         auto fleet_route = std::make_pair(fleet->ID(), order_route.second);
7339         return std::make_pair(order_route.first, fleet_route);
7340     }
7341 
7342     /** Shortest route not exceeding @p max_jumps from @p dest_id to a system with supply as known to @p empire */
GetNearestSupplyRoute(const Empire * empire,int dest_id,int max_jumps=-1)7343     OrderedRouteType GetNearestSupplyRoute(const Empire* empire, int dest_id, int max_jumps = -1) {
7344         OrderedRouteType retval;
7345 
7346         if (!empire) {
7347             WarnLogger() << "Invalid empire";
7348             return retval;
7349         }
7350 
7351         auto supplyable_systems = GetSupplyManager().FleetSupplyableSystemIDs(empire->EmpireID(), true);
7352         if (!supplyable_systems.empty()) {
7353             TraceLogger() << [supplyable_systems]() {
7354                     std::string msg = "Supplyable systems:";
7355                     for (auto sys : supplyable_systems)
7356                         msg.append(" " + std::to_string(sys));
7357                     return msg;
7358                 }();
7359         }
7360 
7361             OrderedRouteType shortest_route;
7362 
7363         for (auto supply_system_id : supplyable_systems) {
7364             shortest_route = GetShortestRoute(empire->EmpireID(), dest_id, supply_system_id);
7365             TraceLogger() << [shortest_route, dest_id]() {
7366                     std::string msg = "Checking supply route from " + std::to_string(dest_id) +
7367                                       " dist:" + std::to_string(shortest_route.first) + " systems:";
7368                     for (auto node : shortest_route.second)
7369                         msg.append(" " + std::to_string(node));
7370                     return msg;
7371                 }();
7372 
7373             auto route_jumps = JumpsForRoute(shortest_route.second);
7374             if (max_jumps > -1 && route_jumps > max_jumps) {
7375                 TraceLogger() << "Rejecting route to " << std::to_string(*shortest_route.second.rbegin())
7376                               << " jumps " << std::to_string(route_jumps) << " exceed max " << std::to_string(max_jumps);
7377                 continue;
7378             }
7379 
7380             if (shortest_route.first <= 0.0 || shortest_route.second.empty()) {
7381                 TraceLogger() << "Invalid route";
7382                 continue;
7383             }
7384 
7385             if (retval.first <= 0.0 || shortest_route.first < retval.first) {
7386                 TraceLogger() << "Setting " << std::to_string(*shortest_route.second.rbegin()) << " as shortest route";
7387                 retval = shortest_route;
7388             }
7389         }
7390 
7391         return retval;
7392     }
7393 
7394     /** If @p fleet would be able to reach a system with supply after completing @p route */
CanResupplyAfterDestination(const std::shared_ptr<Fleet> & fleet,const RouteListType & route)7395     bool CanResupplyAfterDestination(const std::shared_ptr<Fleet>& fleet, const RouteListType& route) {
7396         if (!fleet || route.empty()) {
7397             WarnLogger() << "Invalid fleet or empty route";
7398             return false;
7399         }
7400         auto empire = GetEmpire(fleet->Owner());
7401         if (!empire) {
7402             WarnLogger() << "Invalid empire";
7403             return false;
7404         }
7405 
7406         int max_jumps = std::trunc(fleet->Fuel());
7407         if (max_jumps < 1) {
7408             TraceLogger() << "Not enough fuel " << std::to_string(max_jumps)
7409                           << " to move fleet " << std::to_string(fleet->ID());
7410             return false;
7411         }
7412 
7413         auto dest_nearest_supply = GetNearestSupplyRoute(empire, *route.rbegin(), max_jumps);
7414         auto dest_nearest_supply_jumps = JumpsForRoute(dest_nearest_supply.second);
7415         auto dest_jumps = JumpsForRoute(route);
7416         int total_jumps = dest_jumps + dest_nearest_supply_jumps;
7417 
7418         if (total_jumps > max_jumps) {
7419             TraceLogger() << "Not enough fuel " << std::to_string(max_jumps)
7420                           << " for fleet " << std::to_string(fleet->ID())
7421                           << " to resupply after destination " << std::to_string(total_jumps);
7422             return false;
7423         }
7424 
7425         return true;
7426     }
7427 
7428     /** Route from current system of @p fleet to nearest system with supply as determined by owning empire of @p fleet  */
ExploringFleetResupplyRoute(const std::shared_ptr<Fleet> & fleet)7429     OrderedRouteType ExploringFleetResupplyRoute(const std::shared_ptr<Fleet>& fleet) {
7430         auto empire = GetEmpire(fleet->Owner());
7431         if (!empire) {
7432             WarnLogger() << "Invalid empire for id " << fleet->Owner();
7433             return OrderedRouteType();
7434         }
7435 
7436         auto nearest_supply = GetNearestSupplyRoute(empire, fleet->SystemID(), std::trunc(fleet->Fuel()));
7437         if (nearest_supply.first > 0.0 && FleetRouteInRange(fleet, nearest_supply.second)) {
7438             return nearest_supply;
7439         }
7440 
7441         return OrderedRouteType();
7442     }
7443 
7444     /** Issue an order for @p fleet to move to nearest system with supply */
IssueFleetResupplyOrder(const std::shared_ptr<Fleet> & fleet)7445     bool IssueFleetResupplyOrder(const std::shared_ptr<Fleet>& fleet) {
7446         if (!fleet) {
7447             WarnLogger() << "Invalid fleet";
7448             return false;
7449         }
7450 
7451         auto route = ExploringFleetResupplyRoute(fleet);
7452         // Attempt move order if route is not empty and fleet has enough fuel to reach it
7453         if (route.second.empty()) {
7454             TraceLogger() << "Empty route for resupply of exploring fleet " << fleet->ID();
7455             return false;
7456         }
7457 
7458         auto num_jumps_resupply = JumpsForRoute(route.second);
7459         int max_fleet_jumps = std::trunc(fleet->Fuel());
7460         if (num_jumps_resupply <= max_fleet_jumps) {
7461             HumanClientApp::GetApp()->Orders().IssueOrder(
7462                     std::make_shared<FleetMoveOrder>(fleet->Owner(), fleet->ID(), *route.second.rbegin()));
7463         } else {
7464             TraceLogger() << "Not enough fuel for fleet " << fleet->ID()
7465                           << " to resupply at system " << *route.second.rbegin();
7466             return false;
7467         }
7468 
7469         if (fleet->FinalDestinationID() == *route.second.rbegin()) {
7470             TraceLogger() << "Sending fleet " << fleet->ID()
7471                           << " to refuel at system " << *route.second.rbegin();
7472             return true;
7473         } else {
7474             TraceLogger() << "Fleet move order failed fleet:" << fleet->ID() << " route:"
7475                           << [route]() {
7476                                  std::string retval = "";
7477                                  for (auto node : route.second)
7478                                      retval.append(" " + std::to_string(node));
7479                                  return retval;
7480                              }();
7481         }
7482 
7483         return false;
7484     }
7485 
7486     /** Issue order for @p fleet to move using @p route */
IssueFleetExploreOrder(const std::shared_ptr<Fleet> & fleet,const RouteListType & route)7487     bool IssueFleetExploreOrder(const std::shared_ptr<Fleet>& fleet, const RouteListType& route) {
7488         if (!fleet || route.empty()) {
7489             WarnLogger() << "Invalid fleet or empty route";
7490             return false;
7491         }
7492         if (!FleetRouteInRange(fleet, route)) {
7493             TraceLogger() << "Fleet " << std::to_string(fleet->ID())
7494                           << " has no eta for route to " << std::to_string(*route.rbegin());
7495             return false;
7496         }
7497 
7498         HumanClientApp::GetApp()->Orders().IssueOrder(
7499             std::make_shared<FleetMoveOrder>(fleet->Owner(), fleet->ID(), *route.rbegin()));
7500         if (fleet->FinalDestinationID() == *route.rbegin()) {
7501             TraceLogger() << "Sending fleet " << fleet->ID() << " to explore system " << *route.rbegin();
7502             return true;
7503         }
7504 
7505         TraceLogger() << "Fleet move order failed fleet:" << fleet->ID() << " dest:" << *route.rbegin();
7506         return false;
7507     }
7508 
7509     /** Determine and issue move order for fleet and route @p fleet_route */
IssueExploringFleetOrders(FleetIDListType & idle_fleets,SystemFleetMap & systems_being_explored,const FleetRouteType & fleet_route)7510     void IssueExploringFleetOrders(FleetIDListType& idle_fleets,
7511                                    SystemFleetMap& systems_being_explored,
7512                                    const FleetRouteType& fleet_route)
7513     {
7514         auto route = fleet_route.second;
7515         if (route.empty()) { // no route
7516             WarnLogger() << "Attempted to issue move order with empty route";
7517             return;
7518         }
7519 
7520         if (idle_fleets.empty()) { // no more fleets to issue orders to
7521             TraceLogger() << "No idle fleets";
7522             return;
7523         }
7524 
7525         if (systems_being_explored.count(*route.rbegin())) {
7526             TraceLogger() << "System " << std::to_string(*route.rbegin()) << " already being explored";
7527             return;
7528         }
7529 
7530         auto fleet_id = fleet_route.first;
7531         auto idle_fleet_it = idle_fleets.find(fleet_id);
7532         if (idle_fleet_it == idle_fleets.end()) { // fleet no longer idle
7533             TraceLogger() << "Fleet " << std::to_string(fleet_id) << " not idle";
7534             return;
7535         }
7536         auto fleet = Objects().get<Fleet>(fleet_id);
7537         if (!fleet) {
7538             ErrorLogger() << "No valid fleet with id " << fleet_id;
7539             idle_fleets.erase(idle_fleet_it);
7540             return;
7541         }
7542 
7543         if (std::trunc(fleet->Fuel()) < 1) {  // wait for fuel
7544             TraceLogger() << "Not enough fuel to move fleet " << std::to_string(fleet->ID());
7545             return;
7546         }
7547 
7548         // Determine if fleet should refuel
7549         if (fleet->Fuel() < fleet->MaxFuel() &&
7550             !CanResupplyAfterDestination(fleet, route))
7551         {
7552             if (IssueFleetResupplyOrder(fleet)) {
7553                 idle_fleets.erase(idle_fleet_it);
7554                 return;
7555             }
7556             TraceLogger() << "Fleet " << std::to_string(fleet->ID()) << " can not reach resupply";
7557         }
7558 
7559         if (IssueFleetExploreOrder(fleet, route)) {
7560             idle_fleets.erase(idle_fleet_it);
7561             systems_being_explored.emplace(*route.rbegin(), fleet->ID());
7562         }
7563     }
7564 
7565 };
7566 
DispatchFleetsExploring()7567 void MapWnd::DispatchFleetsExploring() {
7568     int empire_id = HumanClientApp::GetApp()->EmpireID();
7569     const Empire *empire = GetEmpire(empire_id);
7570     if (!empire) {
7571         WarnLogger() << "Invalid empire";
7572         return;
7573     }
7574     int max_routes_per_system = GetOptionsDB().Get<int>("ui.fleet.explore.system.route.limit");
7575     auto destroyed_objects = GetUniverse().EmpireKnownDestroyedObjectIDs(empire_id);
7576 
7577     FleetIDListType idle_fleets;
7578     /** all systems ID for which an exploring fleet is in route and the fleet assigned */
7579     SystemFleetMap systems_being_explored;
7580 
7581     // clean the fleet list by removing non-existing fleet, and extract the
7582     // fleets waiting for orders
7583     for (const auto& fleet : Objects().find<Fleet>(m_fleets_exploring)) {
7584         if (!fleet)
7585             continue;
7586         if (destroyed_objects.count(fleet->ID())) {
7587             m_fleets_exploring.erase(fleet->ID()); //this fleet can't explore anymore
7588         } else {
7589              if (fleet->MovePath().empty())
7590                 idle_fleets.insert(fleet->ID());
7591             else
7592                 systems_being_explored.emplace(fleet->FinalDestinationID(), fleet->ID());
7593         }
7594     }
7595 
7596     if (idle_fleets.empty())
7597         return;
7598 
7599     TraceLogger() << [idle_fleets]() {
7600             std::string retval = "MapWnd::DispatchFleetsExploring Idle Exploring Fleet IDs:";
7601             for (auto fleet : idle_fleets)
7602                 retval.append(" " + std::to_string(fleet));
7603             return retval;
7604         }();
7605 
7606     //list all unexplored systems by taking the neighboors of explored systems because ObjectMap does not list them all.
7607     SystemIDListType candidates_unknown_systems;
7608     const auto& empire_explored_systems = empire->ExploredSystems();
7609     SystemIDListType explored_systems(empire_explored_systems.begin(), empire_explored_systems.end());
7610     candidates_unknown_systems = AddNeighboorsToSet(empire, explored_systems);
7611     auto neighboors = AddNeighboorsToSet(empire, candidates_unknown_systems);
7612     candidates_unknown_systems.insert(neighboors.begin(), neighboors.end());
7613 
7614     // Populate list of unexplored systems
7615     SystemIDListType unexplored_systems;
7616     for (const auto& system : Objects().find<System>(candidates_unknown_systems)) {
7617         if (!system)
7618             continue;
7619         if (!empire->HasExploredSystem(system->ID()) &&
7620             !systems_being_explored.count(system->ID()))
7621         { unexplored_systems.insert(system->ID()); }
7622     }
7623 
7624     if (unexplored_systems.empty()) {
7625         TraceLogger() << "No unknown systems to explore";
7626         return;
7627     }
7628 
7629     TraceLogger() << [unexplored_systems]() {
7630             std::string retval = "MapWnd::DispatchFleetsExploring Unknown System IDs:";
7631             for (auto system : unexplored_systems)
7632                 retval.append(" " + std::to_string(system));
7633             return retval;
7634         }();
7635 
7636     std::multimap<double, FleetRouteType> fleet_routes;  // priority, (fleet, route)
7637 
7638     // Determine fleet routes for each unexplored system
7639     std::unordered_map<int, int> fleet_route_count;
7640     for (const auto& unexplored_system : Objects().find<System>(unexplored_systems)) {
7641         if (!unexplored_system)
7642             continue;
7643 
7644         for (const auto& fleet_id : idle_fleets) {
7645             if (max_routes_per_system > 0 &&
7646                 fleet_route_count[unexplored_system->ID()] > max_routes_per_system)
7647             { break; }
7648 
7649             auto fleet = Objects().get<Fleet>(fleet_id);
7650             if (!fleet) {
7651                 WarnLogger() << "Invalid fleet " << fleet_id;
7652                 continue;
7653             }
7654             if (fleet->Fuel() < 1.0f)
7655                 continue;
7656 
7657             auto route = GetOrderedFleetRoute(fleet, unexplored_system);
7658             if (route.first > 0.0) {
7659                 ++fleet_route_count[unexplored_system->ID()];
7660                 fleet_routes.emplace(route);
7661             }
7662         }
7663     }
7664 
7665     if (!fleet_routes.empty()) {
7666         TraceLogger() << [fleet_routes]() {
7667                 std::string retval = "MapWnd::DispatchFleetsExploring Explorable Systems:\n\t Priority\tFleet\tDestination";
7668                 for (auto route : fleet_routes) {
7669                     retval.append("\n\t" + std::to_string(route.first) + "\t" + std::to_string(route.second.first) +
7670                                   "\t " + std::to_string(route.second.second.empty() ? -1 : *route.second.second.rbegin()));
7671                 }
7672                 return retval;
7673             }();
7674     }
7675 
7676     // Issue fleet orders
7677     for (auto fleet_route : fleet_routes) {
7678         IssueExploringFleetOrders(idle_fleets, systems_being_explored, fleet_route.second);
7679     }
7680 
7681     // verify fleets have expected destination
7682     for (SystemFleetMap::iterator system_fleet_it = systems_being_explored.begin();
7683          system_fleet_it != systems_being_explored.end(); ++system_fleet_it)
7684     {
7685         auto fleet = Objects().get<Fleet>(system_fleet_it->second);
7686         if (!fleet)
7687             continue;
7688 
7689         auto dest_id = fleet->FinalDestinationID();
7690         if (dest_id == system_fleet_it->first)
7691             continue;
7692 
7693         WarnLogger() << "Non idle exploring fleet "<< system_fleet_it->second << " has differing destination:"
7694                      << fleet->FinalDestinationID() << " expected:" << system_fleet_it->first;
7695 
7696         idle_fleets.insert(system_fleet_it->second);
7697         // systems_being_explored.erase(system_fleet_it);
7698     }
7699 
7700     if (!idle_fleets.empty()) {
7701         DebugLogger() << [idle_fleets]() {
7702                 std::string retval = "MapWnd::DispatchFleetsExploring Idle exploring fleets after orders:";
7703                 for (auto fleet_id : idle_fleets)
7704                     retval.append(" " + std::to_string(fleet_id));
7705                 return retval;
7706             }();
7707     }
7708 }
7709 
ShowAllPopups()7710 void MapWnd::ShowAllPopups() {
7711     GG::ProcessThenRemoveExpiredPtrs(m_popups,
7712                                      [](std::shared_ptr<MapWndPopup>& wnd)
7713                                      { wnd->Show(); });
7714 }
7715