1 /*
2  * Copyright (C) 2002-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "wui/interactive_player.h"
21 
22 #include "base/i18n.h"
23 #include "base/macros.h"
24 #include "economy/flag.h"
25 #include "game_io/game_loader.h"
26 #include "graphic/game_renderer.h"
27 #include "graphic/mouse_cursor.h"
28 #include "logic/cmd_queue.h"
29 #include "logic/map_objects/checkstep.h"
30 #include "logic/map_objects/immovable.h"
31 #include "logic/map_objects/tribes/building.h"
32 #include "logic/map_objects/tribes/constructionsite.h"
33 #include "logic/map_objects/tribes/productionsite.h"
34 #include "logic/map_objects/tribes/soldier.h"
35 #include "logic/map_objects/tribes/tribe_descr.h"
36 #include "logic/message_queue.h"
37 #include "logic/player.h"
38 #include "ui_basic/unique_window.h"
39 #include "wui/building_statistics_menu.h"
40 #include "wui/debugconsole.h"
41 #include "wui/fieldaction.h"
42 #include "wui/game_message_menu.h"
43 #include "wui/game_objectives_menu.h"
44 #include "wui/general_statistics_menu.h"
45 #include "wui/seafaring_statistics_menu.h"
46 #include "wui/stock_menu.h"
47 #include "wui/tribal_encyclopedia.h"
48 #include "wui/ware_statistics_menu.h"
49 
50 using Widelands::Building;
51 using Widelands::Map;
52 
53 namespace {
54 
55 // Returns the brightness value in [0, 1.] for 'fcoords' at 'gametime' for
56 // 'pf'. See 'field_brightness' in fields_to_draw.cc for scale of values.
adjusted_field_brightness(const Widelands::FCoords & fcoords,const uint32_t gametime,const Widelands::Player::Field & pf)57 float adjusted_field_brightness(const Widelands::FCoords& fcoords,
58                                 const uint32_t gametime,
59                                 const Widelands::Player::Field& pf) {
60 	if (pf.vision == 0) {
61 		return 0.;
62 	}
63 
64 	uint32_t brightness = 144 + fcoords.field->get_brightness();
65 	brightness = std::min<uint32_t>(255, (brightness * 255) / 160);
66 
67 	if (pf.vision == 1) {
68 		static const uint32_t kDecayTimeInMs = 20000;
69 		const Widelands::Duration time_ago = gametime - pf.time_node_last_unseen;
70 		if (time_ago < kDecayTimeInMs) {
71 			brightness = (brightness * (2 * kDecayTimeInMs - time_ago)) / (2 * kDecayTimeInMs);
72 		} else {
73 			brightness = brightness / 2;
74 		}
75 	}
76 	return brightness / 255.;
77 }
78 // Remove statistics from the text to draw if the player does not match the map object's owner
filter_info_to_draw(InfoToDraw info_to_draw,const Widelands::MapObject * object,const Widelands::Player & player)79 InfoToDraw filter_info_to_draw(InfoToDraw info_to_draw,
80                                const Widelands::MapObject* object,
81                                const Widelands::Player& player) {
82 	InfoToDraw result = info_to_draw;
83 	const Widelands::Player* owner = object->get_owner();
84 	if (owner != nullptr && !player.see_all() && player.is_hostile(*owner)) {
85 		result = static_cast<InfoToDraw>(result & ~InfoToDraw::kStatistics);
86 	}
87 	return result;
88 }
89 
draw_immovables_for_visible_field(const Widelands::EditorGameBase & egbase,const FieldsToDraw::Field & field,const float scale,const InfoToDraw info_to_draw,const Widelands::Player & player,RenderTarget * dst)90 void draw_immovables_for_visible_field(const Widelands::EditorGameBase& egbase,
91                                        const FieldsToDraw::Field& field,
92                                        const float scale,
93                                        const InfoToDraw info_to_draw,
94                                        const Widelands::Player& player,
95                                        RenderTarget* dst) {
96 	Widelands::BaseImmovable* const imm = field.fcoords.field->get_immovable();
97 	if (imm != nullptr && imm->get_positions(egbase).front() == field.fcoords) {
98 		imm->draw(egbase.get_gametime(), filter_info_to_draw(info_to_draw, imm, player),
99 		          field.rendertarget_pixel, field.fcoords, scale, dst);
100 	}
101 }
102 
draw_bobs_for_visible_field(const Widelands::EditorGameBase & egbase,const FieldsToDraw::Field & field,const float scale,const InfoToDraw info_to_draw,const Widelands::Player & player,RenderTarget * dst)103 void draw_bobs_for_visible_field(const Widelands::EditorGameBase& egbase,
104                                  const FieldsToDraw::Field& field,
105                                  const float scale,
106                                  const InfoToDraw info_to_draw,
107                                  const Widelands::Player& player,
108                                  RenderTarget* dst) {
109 	for (Widelands::Bob* bob = field.fcoords.field->get_first_bob(); bob;
110 	     bob = bob->get_next_bob()) {
111 		bob->draw(egbase, filter_info_to_draw(info_to_draw, bob, player), field.rendertarget_pixel,
112 		          field.fcoords, scale, dst);
113 	}
114 }
115 
draw_immovable_for_formerly_visible_field(const FieldsToDraw::Field & field,const Widelands::Player::Field & player_field,const float scale,RenderTarget * dst)116 void draw_immovable_for_formerly_visible_field(const FieldsToDraw::Field& field,
117                                                const Widelands::Player::Field& player_field,
118                                                const float scale,
119                                                RenderTarget* dst) {
120 	if (player_field.map_object_descr == nullptr) {
121 		return;
122 	}
123 
124 	if (player_field.constructionsite.becomes) {
125 		assert(field.owner != nullptr);
126 		player_field.constructionsite.draw(
127 		   field.rendertarget_pixel, field.fcoords, scale, field.owner->get_playercolor(), dst);
128 
129 	} else if (upcast(const Widelands::BuildingDescr, building, player_field.map_object_descr)) {
130 		assert(field.owner != nullptr);
131 		// this is a building therefore we either draw unoccupied or idle animation
132 		dst->blit_animation(field.rendertarget_pixel, field.fcoords, scale,
133 		                    building->get_unoccupied_animation(), 0, &field.owner->get_playercolor());
134 	} else if (player_field.map_object_descr->type() == Widelands::MapObjectType::FLAG) {
135 		assert(field.owner != nullptr);
136 		dst->blit_animation(field.rendertarget_pixel, field.fcoords, scale,
137 		                    field.owner->tribe().flag_animation(), 0,
138 		                    &field.owner->get_playercolor());
139 	} else if (const uint32_t pic = player_field.map_object_descr->main_animation()) {
140 		dst->blit_animation(field.rendertarget_pixel, field.fcoords, scale, pic, 0,
141 		                    (field.owner == nullptr) ? nullptr : &field.owner->get_playercolor());
142 	}
143 }
144 
145 }  // namespace
146 
InteractivePlayer(Widelands::Game & g,Section & global_s,Widelands::PlayerNumber const plyn,bool const multiplayer,ChatProvider * chat_provider)147 InteractivePlayer::InteractivePlayer(Widelands::Game& g,
148                                      Section& global_s,
149                                      Widelands::PlayerNumber const plyn,
150                                      bool const multiplayer,
151                                      ChatProvider* chat_provider)
152    : InteractiveGameBase(g, global_s, NONE, multiplayer, chat_provider),
153      auto_roadbuild_mode_(global_s.get_bool("auto_roadbuild_mode", true)),
154      flag_to_connect_(Widelands::Coords::null()),
155      statisticsmenu_(toolbar(),
156                      "dropdown_menu_statistics",
157                      0,
158                      0,
159                      34U,
160                      10,
161                      34U,
162                      /** TRANSLATORS: Title for the statistics menu button in the game */
163                      _("Statistics"),
164                      UI::DropdownType::kPictorialMenu,
165                      UI::PanelStyle::kWui,
166                      UI::ButtonStyle::kWuiPrimary),
167      grid_marker_pic_(g_gr->images().get("images/wui/overlays/grid_marker.png")) {
168 	add_main_menu();
169 
170 	set_display_flag(InteractiveBase::dfShowWorkareaOverlap, true);  // enable by default
171 
172 	toolbar()->add_space(15);
173 
174 	add_mapview_menu(MiniMapType::kStaticViewWindow);
175 	add_showhide_menu();
176 	add_gamespeed_menu();
177 
178 	toolbar()->add_space(15);
179 	if (multiplayer) {
180 		add_chat_ui();
181 		toolbar()->add_space(15);
182 	}
183 
184 	add_statistics_menu();
185 
186 	add_toolbar_button("wui/menus/objectives", "objectives", _("Objectives"), &objectives_, true);
187 	objectives_.open_window = [this] { new GameObjectivesMenu(this, objectives_); };
188 
189 	toggle_message_menu_ =
190 	   add_toolbar_button("wui/menus/message_old", "messages", _("Messages"), &message_menu_, true);
191 	message_menu_.open_window = [this] { new GameMessageMenu(*this, message_menu_); };
192 
193 	toolbar()->add_space(15);
194 
195 	add_toolbar_button("ui_basic/menu_help", "help", _("Help"), &encyclopedia_, true);
196 	encyclopedia_.open_window = [this] {
197 		new TribalEncyclopedia(*this, encyclopedia_, &game().lua());
198 	};
199 
200 	set_player_number(plyn);
201 	map_view()->field_clicked.connect([this](const Widelands::NodeAndTriangle<>& node_and_triangle) {
202 		node_action(node_and_triangle);
203 	});
204 
205 	finalize_toolbar();
206 
207 #ifndef NDEBUG  //  only in debug builds
208 	addCommand(
209 	   "switchplayer", [this](const std::vector<std::string>& str) { cmdSwitchPlayer(str); });
210 #endif
211 
212 	map_options_subscriber_ = Notifications::subscribe<NoteMapOptions>(
213 	   [this](const NoteMapOptions&) { rebuild_statistics_menu(); });
214 }
215 
add_statistics_menu()216 void InteractivePlayer::add_statistics_menu() {
217 	statisticsmenu_.set_image(g_gr->images().get("images/wui/menus/statistics.png"));
218 	toolbar()->add(&statisticsmenu_);
219 
220 	menu_windows_.stats_seafaring.open_window = [this] {
221 		new SeafaringStatisticsMenu(*this, menu_windows_.stats_seafaring);
222 	};
223 
224 	menu_windows_.stats_stock.open_window = [this] {
225 		new StockMenu(*this, menu_windows_.stats_stock);
226 	};
227 
228 	menu_windows_.stats_buildings.open_window = [this] {
229 		new BuildingStatisticsMenu(*this, menu_windows_.stats_buildings);
230 	};
231 
232 	menu_windows_.stats_wares.open_window = [this] {
233 		new WareStatisticsMenu(*this, menu_windows_.stats_wares);
234 	};
235 
236 	menu_windows_.stats_general.open_window = [this] {
237 		new GeneralStatisticsMenu(*this, menu_windows_.stats_general);
238 	};
239 
240 	// NoteMapOptions takes care of the rebuilding
241 
242 	statisticsmenu_.selected.connect(
243 	   [this] { statistics_menu_selected(statisticsmenu_.get_selected()); });
244 }
245 
rebuild_statistics_menu()246 void InteractivePlayer::rebuild_statistics_menu() {
247 	statisticsmenu_.clear();
248 
249 	if (egbase().map().allows_seafaring()) {
250 		/** TRANSLATORS: An entry in the game's statistics menu */
251 		statisticsmenu_.add(_("Seafaring"), StatisticsMenuEntry::kSeafaring,
252 		                    g_gr->images().get("images/wui/menus/statistics_seafaring.png"), false,
253 		                    "", "E");
254 	}
255 
256 	/** TRANSLATORS: An entry in the game's statistics menu */
257 	statisticsmenu_.add(_("Stock"), StatisticsMenuEntry::kStock,
258 	                    g_gr->images().get("images/wui/menus/statistics_stock.png"), false, "", "I");
259 
260 	/** TRANSLATORS: An entry in the game's statistics menu */
261 	statisticsmenu_.add(_("Buildings"), StatisticsMenuEntry::kBuildings,
262 	                    g_gr->images().get("images/wui/menus/statistics_buildings.png"), false, "",
263 	                    "B");
264 
265 	/** TRANSLATORS: An entry in the game's statistics menu */
266 	statisticsmenu_.add(_("Wares"), StatisticsMenuEntry::kWare,
267 	                    g_gr->images().get("images/wui/menus/statistics_wares.png"), false, "", "P");
268 
269 	/** TRANSLATORS: An entry in the game's statistics menu */
270 	statisticsmenu_.add(_("General"), StatisticsMenuEntry::kGeneral,
271 	                    g_gr->images().get("images/wui/menus/statistics_general.png"), false, "",
272 	                    "G");
273 }
274 
statistics_menu_selected(StatisticsMenuEntry entry)275 void InteractivePlayer::statistics_menu_selected(StatisticsMenuEntry entry) {
276 	switch (entry) {
277 	case StatisticsMenuEntry::kGeneral: {
278 		menu_windows_.stats_general.toggle();
279 	} break;
280 	case StatisticsMenuEntry::kWare: {
281 		menu_windows_.stats_wares.toggle();
282 	} break;
283 	case StatisticsMenuEntry::kBuildings: {
284 		menu_windows_.stats_buildings.toggle();
285 	} break;
286 	case StatisticsMenuEntry::kStock: {
287 		menu_windows_.stats_stock.toggle();
288 	} break;
289 	case StatisticsMenuEntry::kSeafaring: {
290 		if (egbase().map().allows_seafaring()) {
291 			menu_windows_.stats_seafaring.toggle();
292 		}
293 	} break;
294 	}
295 	statisticsmenu_.toggle();
296 }
297 
rebuild_showhide_menu()298 void InteractivePlayer::rebuild_showhide_menu() {
299 	InteractiveGameBase::rebuild_showhide_menu();
300 
301 	showhidemenu_.add(
302 	   get_display_flag(dfShowWorkareaOverlap) ?
303 	      /** TRANSLATORS: An entry in the game's show/hide menu to toggle whether workarea overlaps
304 	       * are highlighted */
305 	      _("Hide Workarea Overlaps") :
306 	      /** TRANSLATORS: An entry in the game's show/hide menu to toggle whether workarea overlaps
307 	       * are highlighted */
308 	      _("Show Workarea Overlaps"),
309 	   ShowHideEntry::kWorkareaOverlap,
310 	   g_gr->images().get("images/wui/menus/show_workarea_overlap.png"), false,
311 	   _("Toggle whether overlapping workareas are indicated when placing a constructionsite"), "W");
312 }
313 
think()314 void InteractivePlayer::think() {
315 	InteractiveBase::think();
316 
317 	if (flag_to_connect_) {
318 		Widelands::Field& field = egbase().map()[flag_to_connect_];
319 		if (upcast(Widelands::Flag const, flag, field.get_immovable())) {
320 			if (!flag->has_road() && !in_road_building_mode())
321 				if (auto_roadbuild_mode_) {
322 					//  There might be a fieldaction window open, showing a button
323 					//  for roadbuilding. If that dialog remains open so that the
324 					//  button is clicked, we would enter roadbuilding mode while
325 					//  we are already in roadbuilding mode from the call below.
326 					//  That is not allowed. Therefore we must delete the
327 					//  fieldaction window before entering roadbuilding mode here.
328 					fieldaction_.destroy();
329 					map_view()->mouse_to_field(flag_to_connect_, MapView::Transition::Jump);
330 					set_sel_pos(Widelands::NodeAndTriangle<>{
331 					   flag_to_connect_,
332 					   Widelands::TCoords<>(flag_to_connect_, Widelands::TriangleIndex::D)});
333 					start_build_road(flag_to_connect_, field.get_owned_by(), RoadBuildingType::kRoad);
334 				}
335 			flag_to_connect_ = Widelands::Coords::null();
336 		}
337 	}
338 	{
339 		char const* msg_icon = "images/wui/menus/message_old.png";
340 		std::string msg_tooltip = _("Messages");
341 		if (uint32_t const nr_new_messages =
342 		       player().messages().nr_messages(Widelands::Message::Status::kNew)) {
343 			msg_icon = "images/wui/menus/message_new.png";
344 			msg_tooltip =
345 			   (boost::format(ngettext("%u new message", "%u new messages", nr_new_messages)) %
346 			    nr_new_messages)
347 			      .str();
348 		}
349 		toggle_message_menu_->set_pic(g_gr->images().get(msg_icon));
350 		toggle_message_menu_->set_tooltip(msg_tooltip);
351 	}
352 }
353 
draw(RenderTarget & dst)354 void InteractivePlayer::draw(RenderTarget& dst) {
355 	// Bail out if the game isn't actually loaded.
356 	// This fixes a crash with displaying an error dialog during loading.
357 	if (!game().is_loaded())
358 		return;
359 
360 	draw_map_view(map_view(), &dst);
361 }
362 
draw_map_view(MapView * given_map_view,RenderTarget * dst)363 void InteractivePlayer::draw_map_view(MapView* given_map_view, RenderTarget* dst) {
364 	// In-game, selection can never be on triangles or have a radius.
365 	assert(get_sel_radius() == 0);
366 	assert(!get_sel_triangles());
367 
368 	const Widelands::Player& plr = player();
369 	const auto& gbase = egbase();
370 	const Widelands::Map& map = gbase.map();
371 	const uint32_t gametime = gbase.get_gametime();
372 
373 	Workareas workareas = get_workarea_overlays(map);
374 	auto* fields_to_draw = given_map_view->draw_terrain(gbase, workareas, false, dst);
375 	const auto& road_building_s = road_building_steepness_overlays();
376 
377 	const float scale = 1.f / given_map_view->view().zoom;
378 
379 	for (size_t idx = 0; idx < fields_to_draw->size(); ++idx) {
380 		auto* f = fields_to_draw->mutable_field(idx);
381 
382 		const Widelands::Player::Field& player_field =
383 		   plr.fields()[map.get_index(f->fcoords, map.get_width())];
384 
385 		// Adjust this field for visibility for this player.
386 		if (!plr.see_all()) {
387 			f->brightness = adjusted_field_brightness(f->fcoords, gametime, player_field);
388 			f->road_e = player_field.r_e;
389 			f->road_se = player_field.r_se;
390 			f->road_sw = player_field.r_sw;
391 			f->vision = player_field.vision;
392 			if (player_field.vision == 1) {
393 				f->owner = player_field.owner != 0 ? gbase.get_player(player_field.owner) : nullptr;
394 				f->is_border = player_field.border;
395 			}
396 		}
397 
398 		// Add road building overlays if applicable.
399 		if (f->vision > 0) {
400 			draw_road_building(*f);
401 
402 			draw_bridges(dst, f, f->vision > 1 ? gametime : 0, scale);
403 			draw_border_markers(*f, scale, *fields_to_draw, dst);
404 
405 			// Render stuff that belongs to the node.
406 			if (f->vision > 1) {
407 				const auto info_to_draw = get_info_to_draw(!given_map_view->is_animating());
408 				draw_immovables_for_visible_field(gbase, *f, scale, info_to_draw, plr, dst);
409 				draw_bobs_for_visible_field(gbase, *f, scale, info_to_draw, plr, dst);
410 			} else if (f->vision == 1) {
411 				// We never show census or statistics for objects in the fog.
412 				draw_immovable_for_formerly_visible_field(*f, player_field, scale, dst);
413 			}
414 		}
415 
416 		// Draw work area markers.
417 		if (has_workarea_preview(f->fcoords, &map)) {
418 			blit_field_overlay(dst, *f, grid_marker_pic_,
419 			                   Vector2i(grid_marker_pic_->width() / 2, grid_marker_pic_->height() / 2),
420 			                   scale);
421 		}
422 
423 		if (f->vision > 0) {
424 			// Draw build help.
425 			bool show_port_space = has_expedition_port_space(f->fcoords);
426 			if (show_port_space || buildhelp()) {
427 				const auto* overlay = get_buildhelp_overlay(
428 				   show_port_space ? f->fcoords.field->maxcaps() : plr.get_buildcaps(f->fcoords));
429 				if (overlay != nullptr) {
430 					blit_field_overlay(dst, *f, overlay->pic, overlay->hotspot, scale);
431 				}
432 			}
433 
434 			// Blit the selection marker.
435 			if (g_mouse_cursor->is_visible() && f->fcoords == get_sel_pos().node) {
436 				const Image* pic = get_sel_picture();
437 				blit_field_overlay(dst, *f, pic, Vector2i(pic->width() / 2, pic->height() / 2), scale);
438 			}
439 
440 			// Draw road building slopes.
441 			{
442 				const auto itb = road_building_s.find(f->fcoords);
443 				if (itb != road_building_s.end()) {
444 					blit_field_overlay(dst, *f, itb->second,
445 					                   Vector2i(itb->second->width() / 2, itb->second->height() / 2),
446 					                   scale);
447 				}
448 			}
449 		}
450 	}
451 }
452 
popup_message(Widelands::MessageId const id,const Widelands::Message & message)453 void InteractivePlayer::popup_message(Widelands::MessageId const id,
454                                       const Widelands::Message& message) {
455 	message_menu_.create();
456 	dynamic_cast<GameMessageMenu&>(*message_menu_.window).show_new_message(id, message);
457 }
458 
can_see(Widelands::PlayerNumber const p) const459 bool InteractivePlayer::can_see(Widelands::PlayerNumber const p) const {
460 	return p == player_number() || player().see_all();
461 }
can_act(Widelands::PlayerNumber const p) const462 bool InteractivePlayer::can_act(Widelands::PlayerNumber const p) const {
463 	return p == player_number();
464 }
player_number() const465 Widelands::PlayerNumber InteractivePlayer::player_number() const {
466 	return player_number_;
467 }
468 
469 /// Player has clicked on the given node; bring up the context menu.
node_action(const Widelands::NodeAndTriangle<> & node_and_triangle)470 void InteractivePlayer::node_action(const Widelands::NodeAndTriangle<>& node_and_triangle) {
471 	const Map& map = egbase().map();
472 	if (1 < player().vision(Map::get_index(node_and_triangle.node, map.get_width()))) {
473 		// Special case for buildings
474 		if (upcast(Building, building, map.get_immovable(node_and_triangle.node))) {
475 			if (can_see(building->owner().player_number())) {
476 				show_building_window(node_and_triangle.node, false, false);
477 				return;
478 			}
479 		}
480 
481 		if (!in_road_building_mode()) {
482 			if (try_show_ship_window()) {
483 				return;
484 			}
485 		}
486 
487 		// everything else can bring up the temporary dialog
488 		show_field_action(this, get_player(), &fieldaction_);
489 	}
490 }
491 
492 /**
493  * Global in-game keypresses:
494  * \li Space: toggles buildhelp
495  * \li i: show stock (inventory)
496  * \li m: show minimap
497  * \li o: show objectives window
498  * \li c: toggle census
499  * \li s: toggle building statistics
500  * \li Home: go to starting position
501  * \li PageUp/PageDown: change game speed
502  * \li Pause: pauses the game
503  * \li Return: write chat message
504  */
handle_key(bool const down,SDL_Keysym const code)505 bool InteractivePlayer::handle_key(bool const down, SDL_Keysym const code) {
506 	if (down) {
507 		switch (code.sym) {
508 
509 		case SDLK_i:
510 			menu_windows_.stats_stock.toggle();
511 			return true;
512 
513 		case SDLK_n:
514 			message_menu_.toggle();
515 			return true;
516 
517 		case SDLK_o:
518 			objectives_.toggle();
519 			return true;
520 
521 		case SDLK_p:
522 			menu_windows_.stats_wares.toggle();
523 			return true;
524 
525 		case SDLK_F1:
526 			encyclopedia_.toggle();
527 			return true;
528 
529 		case SDLK_b:
530 			if (menu_windows_.stats_buildings.window == nullptr) {
531 				new BuildingStatisticsMenu(*this, menu_windows_.stats_buildings);
532 			} else {
533 				menu_windows_.stats_buildings.toggle();
534 			}
535 			return true;
536 
537 		case SDLK_e:
538 			if (game().map().allows_seafaring()) {
539 				if (menu_windows_.stats_seafaring.window == nullptr) {
540 					new SeafaringStatisticsMenu(*this, menu_windows_.stats_seafaring);
541 				} else {
542 					menu_windows_.stats_seafaring.toggle();
543 				}
544 			}
545 			return true;
546 
547 		case SDLK_w:
548 			set_display_flag(dfShowWorkareaOverlap, !get_display_flag(dfShowWorkareaOverlap));
549 			return true;
550 
551 		case SDLK_KP_5:
552 			if (code.mod & KMOD_NUM)
553 				break;
554 			FALLS_THROUGH;
555 		case SDLK_HOME:
556 			map_view()->scroll_to_field(
557 			   game().map().get_starting_pos(player_number_), MapView::Transition::Smooth);
558 			return true;
559 
560 		default:
561 			break;
562 		}
563 	}
564 
565 	return InteractiveGameBase::handle_key(down, code);
566 }
567 
568 /**
569  * Set the player and the visibility to this
570  * player
571  */
set_player_number(uint32_t const n)572 void InteractivePlayer::set_player_number(uint32_t const n) {
573 	player_number_ = n;
574 }
575 
576 /**
577  * Cleanup any game-related data before loading a new game
578  * while a game is currently playing.
579  */
cleanup_for_load()580 void InteractivePlayer::cleanup_for_load() {
581 }
582 
postload()583 void InteractivePlayer::postload() {
584 	InteractiveGameBase::postload();
585 
586 	ToolbarImageset* imageset = player().tribe().toolbar_image_set();
587 	if (imageset != nullptr) {
588 		set_toolbar_imageset(*imageset);
589 	}
590 }
591 
player_hears_field(const Widelands::Coords & coords) const592 bool InteractivePlayer::player_hears_field(const Widelands::Coords& coords) const {
593 	const Widelands::Player& plr = player();
594 	if (plr.see_all()) {
595 		return true;
596 	}
597 	const Widelands::Map& map = egbase().map();
598 	const Widelands::Player::Field& player_field =
599 	   plr.fields()[map.get_index(coords, map.get_width())];
600 	return (player_field.vision > 1);
601 }
602 
cmdSwitchPlayer(const std::vector<std::string> & args)603 void InteractivePlayer::cmdSwitchPlayer(const std::vector<std::string>& args) {
604 	if (args.size() != 2) {
605 		DebugConsole::write("Usage: switchplayer <nr>");
606 		return;
607 	}
608 
609 	int const n = atoi(args[1].c_str());
610 	if (n < 1 || n > kMaxPlayers || !game().get_player(n)) {
611 		DebugConsole::write(str(boost::format("Player #%1% does not exist.") % n));
612 		return;
613 	}
614 
615 	DebugConsole::write(
616 	   str(boost::format("Switching from #%1% to #%2%.") % static_cast<int>(player_number_) % n));
617 	player_number_ = n;
618 
619 	if (UI::UniqueWindow* const building_statistics_window = menu_windows_.stats_buildings.window) {
620 		dynamic_cast<BuildingStatisticsMenu&>(*building_statistics_window).update();
621 	}
622 }
623