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