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_base.h"
21 
22 #include <memory>
23 
24 #include <SDL_timer.h>
25 #include <boost/algorithm/string/join.hpp>
26 
27 #include "base/log.h"
28 #include "base/macros.h"
29 #include "base/math.h"
30 #include "base/time_string.h"
31 #include "economy/flag.h"
32 #include "economy/road.h"
33 #include "economy/waterway.h"
34 #include "graphic/font_handler.h"
35 #include "graphic/rendertarget.h"
36 #include "graphic/text_layout.h"
37 #include "logic/cmd_queue.h"
38 #include "logic/game.h"
39 #include "logic/game_controller.h"
40 #include "logic/map_objects/checkstep.h"
41 #include "logic/map_objects/immovable.h"
42 #include "logic/map_objects/tribes/productionsite.h"
43 #include "logic/maphollowregion.h"
44 #include "logic/maptriangleregion.h"
45 #include "logic/player.h"
46 #include "logic/widelands_geometry.h"
47 #include "scripting/lua_interface.h"
48 #include "sound/sound_handler.h"
49 #include "wlapplication_options.h"
50 #include "wui/game_chat_menu.h"
51 #include "wui/game_debug_ui.h"
52 #include "wui/logmessage.h"
53 #include "wui/mapviewpixelfunctions.h"
54 #include "wui/minimap.h"
55 #include "wui/unique_window_handler.h"
56 
57 namespace {
58 
59 using Widelands::Area;
60 using Widelands::CoordPath;
61 using Widelands::Coords;
62 using Widelands::EditorGameBase;
63 using Widelands::Game;
64 using Widelands::Map;
65 using Widelands::MapObject;
66 using Widelands::TCoords;
67 
caps_to_buildhelp(const Widelands::NodeCaps caps)68 int caps_to_buildhelp(const Widelands::NodeCaps caps) {
69 	if (caps & Widelands::BUILDCAPS_MINE) {
70 		return Widelands::Field::Buildhelp_Mine;
71 	}
72 	if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_BIG) {
73 		if (caps & Widelands::BUILDCAPS_PORT) {
74 			return Widelands::Field::Buildhelp_Port;
75 		}
76 		return Widelands::Field::Buildhelp_Big;
77 	}
78 	if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_MEDIUM) {
79 		return Widelands::Field::Buildhelp_Medium;
80 	}
81 	if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_SMALL) {
82 		return Widelands::Field::Buildhelp_Small;
83 	}
84 	if (caps & Widelands::BUILDCAPS_FLAG) {
85 		return Widelands::Field::Buildhelp_Flag;
86 	}
87 	return Widelands::Field::Buildhelp_None;
88 }
89 
90 }  // namespace
91 
Toolbar(Panel * parent)92 InteractiveBase::Toolbar::Toolbar(Panel* parent)
93    : UI::Panel(parent, 0, 0, parent->get_inner_w(), parent->get_inner_h()),
94      box(this, 0, 0, UI::Box::Horizontal),
95      repeat(0) {
96 }
97 
change_imageset(const ToolbarImageset & images)98 void InteractiveBase::Toolbar::change_imageset(const ToolbarImageset& images) {
99 	imageset = images;
100 	finalize();
101 }
102 
finalize()103 void InteractiveBase::Toolbar::finalize() {
104 	// Set box size and get minimum height
105 	int box_width, height;
106 	box.get_desired_size(&box_width, &height);
107 	box.set_size(box_width, height);
108 
109 	// Calculate repetition and width
110 	repeat = 1;
111 	int width = imageset.left->width() + imageset.center->width() + imageset.right->width();
112 	while (width < box.get_w()) {
113 		++repeat;
114 		width += imageset.left->width() + imageset.right->width();
115 	}
116 	width += imageset.left_corner->width() + imageset.right_corner->width();
117 
118 	// Find the highest image
119 	height = std::max(height, imageset.left_corner->height());
120 	height = std::max(height, imageset.left->height());
121 	height = std::max(height, imageset.center->height());
122 	height = std::max(height, imageset.right->height());
123 	height = std::max(height, imageset.right_corner->height());
124 
125 	// Set size and position
126 	set_size(width, height);
127 	set_pos(
128 	   Vector2i((get_parent()->get_inner_w() - width) >> 1, get_parent()->get_inner_h() - height));
129 	box.set_pos(Vector2i((get_w() - box.get_w()) / 2, get_h() - box.get_h()));
130 
131 	// Notify dropdowns
132 	box.position_changed();
133 }
134 
draw(RenderTarget & dst)135 void InteractiveBase::Toolbar::draw(RenderTarget& dst) {
136 	int x = 0;
137 	// Left corner
138 	dst.blit(Vector2i(x, get_h() - imageset.left_corner->height()), imageset.left_corner);
139 	x += imageset.left_corner->width();
140 	// Repeat left
141 	for (int i = 0; i < repeat; ++i) {
142 		dst.blit(Vector2i(x, get_h() - imageset.left->height()), imageset.left);
143 		x += imageset.left->width();
144 	}
145 	// Center
146 	dst.blit(Vector2i(x, get_h() - imageset.center->height()), imageset.center);
147 	x += imageset.center->width();
148 	// Repeat right
149 	for (int i = 0; i < repeat; ++i) {
150 		dst.blit(Vector2i(x, get_h() - imageset.right->height()), imageset.right);
151 		x += imageset.right->width();
152 	}
153 	// Right corner
154 	dst.blit(Vector2i(x, get_h() - imageset.right_corner->height()), imageset.right_corner);
155 }
156 
InteractiveBase(EditorGameBase & the_egbase,Section & global_s)157 InteractiveBase::InteractiveBase(EditorGameBase& the_egbase, Section& global_s)
158    : UI::Panel(nullptr, 0, 0, g_gr->get_xres(), g_gr->get_yres()),
159      buildhelp_(false),
160      map_view_(this, the_egbase.map(), 0, 0, g_gr->get_xres(), g_gr->get_yres()),
161      // Initialize chatoveraly before the toolbar so it is below
162      chat_overlay_(new ChatOverlay(this, 10, 25, get_w() / 2, get_h() - 25)),
163      toolbar_(this),
164      mapviewmenu_(toolbar(),
165                   "dropdown_menu_mapview",
166                   0,
167                   0,
168                   34U,
169                   10,
170                   34U,
171                   /** TRANSLATORS: Title for the map view menu button in the game */
172                   _("Map View"),
173                   UI::DropdownType::kPictorialMenu,
174                   UI::PanelStyle::kWui,
175                   UI::ButtonStyle::kWuiPrimary),
176      quick_navigation_(&map_view_),
177      workareas_cache_(nullptr),
178      egbase_(the_egbase),
179 #ifndef NDEBUG  //  not in releases
180      display_flags_(dfDebug | kSoldierLevels),
181 #else
182      display_flags_(kSoldierLevels),
183 #endif
184      lastframe_(SDL_GetTicks()),
185      frametime_(0),
186      avg_usframetime_(0),
187      road_building_mode_(nullptr),
188      unique_window_handler_(new UniqueWindowHandler()) {
189 
190 	// Load the buildhelp icons.
191 	{
192 		BuildhelpOverlay* buildhelp_overlay = buildhelp_overlays_;
193 		const char* filenames[] = {
194 		   "images/wui/overlays/set_flag.png", "images/wui/overlays/small.png",
195 		   "images/wui/overlays/medium.png",   "images/wui/overlays/big.png",
196 		   "images/wui/overlays/mine.png",     "images/wui/overlays/port.png"};
197 		const char* const* filename = filenames;
198 
199 		//  Special case for flag, which has a different formula for hotspot_y.
200 		buildhelp_overlay->pic = g_gr->images().get(*filename);
201 		buildhelp_overlay->hotspot =
202 		   Vector2i(buildhelp_overlay->pic->width() / 2, buildhelp_overlay->pic->height() - 1);
203 
204 		const BuildhelpOverlay* const buildhelp_overlays_end =
205 		   buildhelp_overlay + Widelands::Field::Buildhelp_None;
206 		for (;;) {  // The other buildhelp overlays.
207 			++buildhelp_overlay;
208 			++filename;
209 			if (buildhelp_overlay == buildhelp_overlays_end)
210 				break;
211 			buildhelp_overlay->pic = g_gr->images().get(*filename);
212 			buildhelp_overlay->hotspot =
213 			   Vector2i(buildhelp_overlay->pic->width() / 2, buildhelp_overlay->pic->height() / 2);
214 		}
215 	}
216 
217 	resize_chat_overlay();
218 
219 	graphic_resolution_changed_subscriber_ = Notifications::subscribe<GraphicResolutionChanged>(
220 	   [this](const GraphicResolutionChanged& message) {
221 		   set_size(message.new_width, message.new_height);
222 		   map_view_.set_size(message.new_width, message.new_height);
223 		   resize_chat_overlay();
224 		   finalize_toolbar();
225 		   mainview_move();
226 		});
227 	sound_subscriber_ = Notifications::subscribe<NoteSound>(
228 	   [this](const NoteSound& note) { play_sound_effect(note); });
229 	shipnotes_subscriber_ = Notifications::subscribe<Widelands::NoteShip>([this](
230 	   const Widelands::NoteShip& note) {
231 		if (note.action == Widelands::NoteShip::Action::kWaitingForCommand &&
232 		    note.ship->get_ship_state() == Widelands::Ship::ShipStates::kExpeditionPortspaceFound) {
233 			expedition_port_spaces_.emplace(note.ship, note.ship->exp_port_spaces().front());
234 		}
235 	});
236 
237 	toolbar_.set_layout_toplevel(true);
238 	map_view_.changeview.connect([this] { mainview_move(); });
239 	map_view()->field_clicked.connect([this](const Widelands::NodeAndTriangle<>& node_and_triangle) {
240 		set_sel_pos(node_and_triangle);
241 	});
242 	map_view_.track_selection.connect([this](const Widelands::NodeAndTriangle<>& node_and_triangle) {
243 		if (!sel_.freeze) {
244 			set_sel_pos(node_and_triangle);
245 		}
246 	});
247 
248 	set_border_snap_distance(global_s.get_int("border_snap_distance", 0));
249 	set_panel_snap_distance(global_s.get_int("panel_snap_distance", 10));
250 	set_snap_windows_only_when_overlapping(
251 	   global_s.get_bool("snap_windows_only_when_overlapping", false));
252 	set_dock_windows_to_edges(global_s.get_bool("dock_windows_to_edges", false));
253 
254 	//  Having this in the initializer list (before Sys_InitGraphics) will give
255 	//  funny results.
256 	unset_sel_picture();
257 
258 	setDefaultCommand([this](const std::vector<std::string>& str) { cmd_lua(str); });
259 	addCommand("mapobject", [this](const std::vector<std::string>& str) { cmd_map_object(str); });
260 }
261 
~InteractiveBase()262 InteractiveBase::~InteractiveBase() {
263 	if (road_building_mode_) {
264 		abort_build_road();
265 	}
266 }
267 
add_mapview_menu(MiniMapType minimap_type)268 void InteractiveBase::add_mapview_menu(MiniMapType minimap_type) {
269 	mapviewmenu_.set_image(g_gr->images().get("images/wui/menus/toggle_minimap.png"));
270 	toolbar()->add(&mapviewmenu_);
271 
272 	minimap_registry_.open_window = [this] { toggle_minimap(); };
273 	minimap_registry_.minimap_type = minimap_type;
274 	minimap_registry_.closed.connect([this] { rebuild_mapview_menu(); });
275 
276 	rebuild_mapview_menu();
277 	mapviewmenu_.selected.connect([this] { mapview_menu_selected(mapviewmenu_.get_selected()); });
278 }
279 
rebuild_mapview_menu()280 void InteractiveBase::rebuild_mapview_menu() {
281 	mapviewmenu_.clear();
282 
283 	/** TRANSLATORS: An entry in the game's map view menu */
284 	mapviewmenu_.add(minimap_registry_.window != nullptr ? _("Hide Minimap") : _("Show Minimap"),
285 	                 MapviewMenuEntry::kMinimap,
286 	                 g_gr->images().get("images/wui/menus/toggle_minimap.png"), false, "", "M");
287 
288 	/** TRANSLATORS: An entry in the game's map view menu */
289 	mapviewmenu_.add(_("Zoom +"), MapviewMenuEntry::kIncreaseZoom,
290 	                 g_gr->images().get("images/wui/menus/zoom_increase.png"), false, "",
291 	                 pgettext("hotkey", "Ctrl++"));
292 
293 	/** TRANSLATORS: An entry in the game's map view menu */
294 	mapviewmenu_.add(_("Reset zoom"), MapviewMenuEntry::kResetZoom,
295 	                 g_gr->images().get("images/wui/menus/zoom_reset.png"), false, "",
296 	                 pgettext("hotkey", "Ctrl+0"));
297 
298 	/** TRANSLATORS: An entry in the game's map view menu */
299 	mapviewmenu_.add(_("Zoom -"), MapviewMenuEntry::kDecreaseZoom,
300 	                 g_gr->images().get("images/wui/menus/zoom_decrease.png"), false, "",
301 	                 pgettext("hotkey", "Ctrl+-"));
302 }
303 
mapview_menu_selected(MapviewMenuEntry entry)304 void InteractiveBase::mapview_menu_selected(MapviewMenuEntry entry) {
305 	switch (entry) {
306 	case MapviewMenuEntry::kMinimap: {
307 		toggle_minimap();
308 	} break;
309 	case MapviewMenuEntry::kDecreaseZoom: {
310 		map_view()->decrease_zoom();
311 		mapviewmenu_.toggle();
312 	} break;
313 	case MapviewMenuEntry::kIncreaseZoom: {
314 		map_view()->increase_zoom();
315 		mapviewmenu_.toggle();
316 	} break;
317 
318 	case MapviewMenuEntry::kResetZoom: {
319 		map_view()->reset_zoom();
320 		mapviewmenu_.toggle();
321 	} break;
322 	}
323 }
324 
325 const InteractiveBase::BuildhelpOverlay*
get_buildhelp_overlay(const Widelands::NodeCaps caps) const326 InteractiveBase::get_buildhelp_overlay(const Widelands::NodeCaps caps) const {
327 	const int buildhelp_overlay_index = caps_to_buildhelp(caps);
328 	if (buildhelp_overlay_index < Widelands::Field::Buildhelp_None) {
329 		return &buildhelp_overlays_[buildhelp_overlay_index];
330 	}
331 	return nullptr;
332 }
333 
has_workarea_preview(const Widelands::Coords & coords,const Widelands::Map * map) const334 bool InteractiveBase::has_workarea_preview(const Widelands::Coords& coords,
335                                            const Widelands::Map* map) const {
336 	if (!map) {
337 		for (const auto& preview : workarea_previews_) {
338 			if (preview->coords == coords) {
339 				return true;
340 			}
341 		}
342 		return false;
343 	}
344 	for (const auto& preview : workarea_previews_) {
345 		uint32_t radius = 0;
346 		for (const auto& wa : *preview->info) {
347 			radius = std::max(radius, wa.first);
348 		}
349 		if (map->calc_distance(coords, preview->coords) <= radius) {
350 			return true;
351 		}
352 	}
353 	return false;
354 }
355 
set_toolbar_imageset(const ToolbarImageset & imageset)356 void InteractiveBase::set_toolbar_imageset(const ToolbarImageset& imageset) {
357 	toolbar_.change_imageset(imageset);
358 }
359 
unique_windows()360 UniqueWindowHandler& InteractiveBase::unique_windows() {
361 	return *unique_window_handler_;
362 }
363 
set_sel_pos(Widelands::NodeAndTriangle<> const center)364 void InteractiveBase::set_sel_pos(Widelands::NodeAndTriangle<> const center) {
365 	sel_.pos = center;
366 }
367 
finalize_toolbar()368 void InteractiveBase::finalize_toolbar() {
369 	toolbar_.finalize();
370 }
371 
372 /*
373  * Set the current sel selection radius.
374  */
set_sel_radius(const uint32_t n)375 void InteractiveBase::set_sel_radius(const uint32_t n) {
376 	if (n != sel_.radius) {
377 		sel_.radius = n;
378 		set_sel_pos(get_sel_pos());  //  redraw
379 	}
380 }
381 
382 /*
383  * Set/Unset sel picture
384  */
set_sel_picture(const Image * image)385 void InteractiveBase::set_sel_picture(const Image* image) {
386 	sel_.pic = image;
387 	set_sel_pos(get_sel_pos());  //  redraw
388 }
389 
get_info_to_draw(bool show) const390 InfoToDraw InteractiveBase::get_info_to_draw(bool show) const {
391 	InfoToDraw info_to_draw = InfoToDraw::kNone;
392 	if (!show) {
393 		return info_to_draw;
394 	}
395 	auto display_flags = get_display_flags();
396 	if (display_flags & InteractiveBase::dfShowCensus) {
397 		info_to_draw = info_to_draw | InfoToDraw::kCensus;
398 	}
399 	if (display_flags & InteractiveBase::dfShowStatistics) {
400 		info_to_draw = info_to_draw | InfoToDraw::kStatistics;
401 	}
402 	if (display_flags & InteractiveBase::dfShowSoldierLevels) {
403 		info_to_draw = info_to_draw | InfoToDraw::kSoldierLevels;
404 	}
405 	return info_to_draw;
406 }
407 
unset_sel_picture()408 void InteractiveBase::unset_sel_picture() {
409 	set_sel_picture(g_gr->images().get("images/ui_basic/fsel.png"));
410 }
411 
buildhelp() const412 bool InteractiveBase::buildhelp() const {
413 	return buildhelp_;
414 }
415 
show_buildhelp(bool t)416 void InteractiveBase::show_buildhelp(bool t) {
417 	const bool old_value = buildhelp_;
418 	buildhelp_ = t;
419 	if (old_value != t) {
420 		rebuild_showhide_menu();
421 	}
422 }
423 
toggle_buildhelp()424 void InteractiveBase::toggle_buildhelp() {
425 	show_buildhelp(!buildhelp());
426 }
427 
add_toolbar_button(const std::string & image_basename,const std::string & name,const std::string & tooltip_text,UI::UniqueWindow::Registry * window,bool bind_default_toggle)428 UI::Button* InteractiveBase::add_toolbar_button(const std::string& image_basename,
429                                                 const std::string& name,
430                                                 const std::string& tooltip_text,
431                                                 UI::UniqueWindow::Registry* window,
432                                                 bool bind_default_toggle) {
433 	UI::Button* button =
434 	   new UI::Button(&toolbar_.box, name, 0, 0, 34U, 34U, UI::ButtonStyle::kWuiPrimary,
435 	                  g_gr->images().get("images/" + image_basename + ".png"), tooltip_text);
436 	toolbar_.box.add(button);
437 	if (window) {
438 		window->opened.connect([button] { button->set_perm_pressed(true); });
439 		window->closed.connect([button] { button->set_perm_pressed(false); });
440 
441 		if (bind_default_toggle) {
442 			button->sigclicked.connect([window]() { window->toggle(); });
443 		}
444 	}
445 	return button;
446 }
447 
has_expedition_port_space(const Widelands::Coords & coords) const448 bool InteractiveBase::has_expedition_port_space(const Widelands::Coords& coords) const {
449 	for (const auto& pair : expedition_port_spaces_) {
450 		if (pair.second == coords) {
451 			return true;
452 		}
453 	}
454 	return false;
455 }
456 
457 std::map<Widelands::Coords, std::vector<uint8_t>>
road_building_preview_overlays() const458 InteractiveBase::road_building_preview_overlays() const {
459 	if (road_building_mode_) {
460 		return road_building_mode_->overlay_road_previews;
461 	}
462 	return std::map<Widelands::Coords, std::vector<uint8_t>>();
463 }
464 std::map<Widelands::Coords, const Image*>
road_building_steepness_overlays() const465 InteractiveBase::road_building_steepness_overlays() const {
466 	if (road_building_mode_) {
467 		return road_building_mode_->overlay_steepness_indicators;
468 	}
469 	return std::map<Widelands::Coords, const Image*>();
470 }
471 
472 // Show the given workareas at the given coords
show_workarea(const WorkareaInfo & workarea_info,Widelands::Coords coords,std::map<Widelands::TCoords<>,uint32_t> & extra_data)473 void InteractiveBase::show_workarea(const WorkareaInfo& workarea_info,
474                                     Widelands::Coords coords,
475                                     std::map<Widelands::TCoords<>, uint32_t>& extra_data) {
476 	workarea_previews_.insert(
477 	   std::unique_ptr<WorkareaPreview>(new WorkareaPreview{coords, &workarea_info, extra_data}));
478 	workareas_cache_.reset(nullptr);
479 }
480 
show_workarea(const WorkareaInfo & workarea_info,Widelands::Coords coords)481 void InteractiveBase::show_workarea(const WorkareaInfo& workarea_info, Widelands::Coords coords) {
482 	std::map<Widelands::TCoords<>, uint32_t> empty;
483 	show_workarea(workarea_info, coords, empty);
484 }
485 
486 /* Helper function to get the correct index for graphic/gl/workarea_program.cc::workarea_colors .
487  * a, b, c are the indices for the three nodes bordering this triangle.
488  * This function returns the biggest workarea type that matches all three corners.
489  * The indices stand for:
490  * 0 – all three circles
491  * 1 – medium and outer circle
492  * 2 – outer circle
493  * 3 – inner and medium circle
494  * 4 – medium circle
495  * 5 – inner circle
496  * We currently assume that no building will have more than three workarea circles.
497  */
workarea_max(uint8_t a,uint8_t b,uint8_t c)498 static uint8_t workarea_max(uint8_t a, uint8_t b, uint8_t c) {
499 	// Whether all nodes are part of the inner circle
500 	bool inner =
501 	   (a == 0 || a == 3 || a == 5) && (b == 0 || b == 3 || b == 5) && (c == 0 || c == 3 || c == 5);
502 	// Whether all nodes are part of the medium circle
503 	bool medium = (a == 0 || a == 1 || a == 3 || a == 4) && (b == 0 || b == 1 || b == 3 || b == 4) &&
504 	              (c == 0 || c == 1 || c == 3 || c == 4);
505 	// Whether all nodes are part of the outer circle
506 	bool outer = a <= 2 && b <= 2 && c <= 2;
507 
508 	if (medium) {
509 		if (outer && inner) {
510 			return 0;
511 		} else if (inner) {
512 			return 3;
513 		} else if (outer) {
514 			return 1;
515 		} else {
516 			return 4;
517 		}
518 	} else if (outer) {
519 		assert(!inner);
520 		return 2;
521 	} else {
522 		assert(inner);
523 		return 5;
524 	}
525 }
526 
get_workarea_overlays(const Widelands::Map & map)527 Workareas InteractiveBase::get_workarea_overlays(const Widelands::Map& map) {
528 	if (!workareas_cache_) {
529 		workareas_cache_.reset(new Workareas());
530 		for (const auto& preview : workarea_previews_) {
531 			workareas_cache_->push_back(get_workarea_overlay(map, *preview));
532 		}
533 	}
534 	return Workareas(*workareas_cache_);
535 }
536 
537 // static
get_workarea_overlay(const Widelands::Map & map,const WorkareaPreview & workarea)538 WorkareasEntry InteractiveBase::get_workarea_overlay(const Widelands::Map& map,
539                                                      const WorkareaPreview& workarea) {
540 	std::map<Coords, uint8_t> intermediate_result;
541 	const Coords& coords = workarea.coords;
542 	const WorkareaInfo* workarea_info = workarea.info;
543 	intermediate_result[coords] = 0;
544 	WorkareaInfo::size_type wa_index;
545 	const size_t workarea_size = workarea_info->size();
546 	switch (workarea_size) {
547 	case 0:
548 		return WorkareasEntry();  // no workarea
549 	case 1:
550 		wa_index = 5;
551 		break;
552 	case 2:
553 		wa_index = 3;
554 		break;
555 	case 3:
556 		wa_index = 0;
557 		break;
558 	default:
559 		throw wexception(
560 		   "Encountered unexpected WorkareaInfo size %i", static_cast<int>(workarea_info->size()));
561 	}
562 
563 	Widelands::HollowArea<> hollow_area(Widelands::Area<>(coords, 0), 0);
564 
565 	// Iterate through the work areas, from building to its enhancement
566 	WorkareaInfo::const_iterator it = workarea_info->begin();
567 	for (; it != workarea_info->end(); ++it) {
568 		hollow_area.radius = it->first;
569 		Widelands::MapHollowRegion<> mr(map, hollow_area);
570 		do {
571 			intermediate_result[mr.location()] = wa_index;
572 		} while (mr.advance(map));
573 		wa_index++;
574 		hollow_area.hole_radius = hollow_area.radius;
575 	}
576 
577 	WorkareasEntry result;
578 	for (const auto& pair : intermediate_result) {
579 		Coords c;
580 		map.get_brn(pair.first, &c);
581 		const auto brn = intermediate_result.find(c);
582 		if (brn == intermediate_result.end()) {
583 			continue;
584 		}
585 		map.get_bln(pair.first, &c);
586 		const auto bln = intermediate_result.find(c);
587 		map.get_rn(pair.first, &c);
588 		const auto rn = intermediate_result.find(c);
589 		if (bln != intermediate_result.end()) {
590 			TCoords<> tc(pair.first, Widelands::TriangleIndex::D);
591 			WorkareaPreviewData wd(tc, workarea_max(pair.second, brn->second, bln->second));
592 			for (const auto& p : workarea.data) {
593 				if (p.first == tc) {
594 					wd = WorkareaPreviewData(tc, wd.index, p.second);
595 					break;
596 				}
597 			}
598 			result.first.push_back(wd);
599 		}
600 		if (rn != intermediate_result.end()) {
601 			TCoords<> tc(pair.first, Widelands::TriangleIndex::R);
602 			WorkareaPreviewData wd(tc, workarea_max(pair.second, brn->second, rn->second));
603 			for (const auto& p : workarea.data) {
604 				if (p.first == tc) {
605 					wd = WorkareaPreviewData(tc, wd.index, p.second);
606 					break;
607 				}
608 			}
609 			result.first.push_back(wd);
610 		}
611 	}
612 	for (const auto& pair : *workarea_info) {
613 		std::vector<Coords> border;
614 		Coords c = coords;
615 		for (uint32_t i = pair.first; i > 0; --i) {
616 			map.get_tln(c, &c);
617 		}
618 		for (uint32_t i = pair.first; i > 0; --i) {
619 			border.push_back(c);
620 			map.get_rn(c, &c);
621 		}
622 		for (uint32_t i = pair.first; i > 0; --i) {
623 			border.push_back(c);
624 			map.get_brn(c, &c);
625 		}
626 		for (uint32_t i = pair.first; i > 0; --i) {
627 			border.push_back(c);
628 			map.get_bln(c, &c);
629 		}
630 		for (uint32_t i = pair.first; i > 0; --i) {
631 			border.push_back(c);
632 			map.get_ln(c, &c);
633 		}
634 		for (uint32_t i = pair.first; i > 0; --i) {
635 			border.push_back(c);
636 			map.get_tln(c, &c);
637 		}
638 		for (uint32_t i = pair.first; i > 0; --i) {
639 			border.push_back(c);
640 			map.get_trn(c, &c);
641 		}
642 		result.second.push_back(border);
643 	}
644 	return result;
645 }
646 
hide_workarea(const Widelands::Coords & coords,bool is_additional)647 void InteractiveBase::hide_workarea(const Widelands::Coords& coords, bool is_additional) {
648 	for (auto it = workarea_previews_.begin(); it != workarea_previews_.end(); ++it) {
649 		if (it->get()->coords == coords && (is_additional ^ it->get()->data.empty())) {
650 			workarea_previews_.erase(it);
651 			workareas_cache_.reset(nullptr);
652 			return;
653 		}
654 	}
655 }
656 
657 /**
658  * Called by \ref Game::postload at the end of the game loading
659  * sequence.
660  *
661  * Default implementation does nothing.
662  */
postload()663 void InteractiveBase::postload() {
664 }
665 
draw_road_building(FieldsToDraw::Field & field)666 void InteractiveBase::draw_road_building(FieldsToDraw::Field& field) {
667 	const auto rpo = road_building_preview_overlays();
668 	const auto rinfo = rpo.find(field.fcoords);
669 	if (rinfo != rpo.end()) {
670 		for (uint8_t dir : rinfo->second) {
671 			switch (dir) {
672 			case Widelands::WALK_E:
673 				field.road_e = in_road_building_mode(RoadBuildingType::kRoad) ?
674 				                  Widelands::RoadSegment::kNormal :
675 				                  Widelands::RoadSegment::kWaterway;
676 				break;
677 			case Widelands::WALK_SE:
678 				field.road_se = in_road_building_mode(RoadBuildingType::kRoad) ?
679 				                   Widelands::RoadSegment::kNormal :
680 				                   Widelands::RoadSegment::kWaterway;
681 				break;
682 			case Widelands::WALK_SW:
683 				field.road_sw = in_road_building_mode(RoadBuildingType::kRoad) ?
684 				                   Widelands::RoadSegment::kNormal :
685 				                   Widelands::RoadSegment::kWaterway;
686 				break;
687 			default:
688 				throw wexception("Attempt to set road-building overlay for invalid direction %i", dir);
689 			}
690 		}
691 	}
692 }
693 
694 /*
695 ===============
696 Called once per frame by the UI code
697 ===============
698 */
think()699 void InteractiveBase::think() {
700 	egbase().think();  // Call game logic here. The game advances.
701 
702 	// Cleanup found port spaces if the ship sailed on or was destroyed
703 	for (auto it = expedition_port_spaces_.begin(); it != expedition_port_spaces_.end(); ++it) {
704 		if (!egbase().objects().object_still_available(it->first) ||
705 		    it->first->get_ship_state() != Widelands::Ship::ShipStates::kExpeditionPortspaceFound) {
706 			expedition_port_spaces_.erase(it);
707 			// If another port space also needs removing, we'll take care of it in the next frame
708 			return;
709 		}
710 	}
711 
712 	UI::Panel::think();
713 }
714 
average_fps() const715 double InteractiveBase::average_fps() const {
716 	return 1000.0 * 1000.0 / avg_usframetime_;
717 }
718 
719 /*
720 ===============
721 Draw debug overlay when appropriate.
722 ===============
723 */
draw_overlay(RenderTarget & dst)724 void InteractiveBase::draw_overlay(RenderTarget& dst) {
725 	// Timing
726 	uint32_t curframe = SDL_GetTicks();
727 
728 	frametime_ = curframe - lastframe_;
729 	avg_usframetime_ = ((avg_usframetime_ * 15) + (frametime_ * 1000)) / 16;
730 	lastframe_ = curframe;
731 
732 	Game* game = dynamic_cast<Game*>(&egbase());
733 
734 	// This portion of code keeps the speed of game so that FPS are kept within
735 	// range 13 - 15, this is used for training of AI
736 	if (game != nullptr) {
737 		if (game->is_auto_speed()) {
738 			const uint32_t cur_fps = average_fps();
739 			int32_t speed_diff = 0;
740 			if (cur_fps < 13) {
741 				speed_diff = -100;
742 			}
743 			if (cur_fps > 15) {
744 				speed_diff = +100;
745 			}
746 			if (speed_diff != 0) {
747 				if (GameController* const ctrl = game->game_controller()) {
748 					if ((ctrl->desired_speed() > 950 && ctrl->desired_speed() < 30000) ||
749 					    (ctrl->desired_speed() < 1000 && speed_diff > 0) ||
750 					    (ctrl->desired_speed() > 29999 && speed_diff < 0)) {
751 						ctrl->set_desired_speed(ctrl->desired_speed() + speed_diff);
752 					}
753 				}
754 			}
755 		}
756 	}
757 
758 	// Node information
759 	std::string node_text("");
760 	if (game == nullptr) {
761 		// Always blit node information in the editor
762 		static boost::format node_format("(%i, %i, %i)");
763 		const int32_t height = egbase().map()[sel_.pos.node].get_height();
764 		node_text = (node_format % sel_.pos.node.x % sel_.pos.node.y % height).str();
765 	} else if (get_display_flag(dfDebug)) {
766 		// Blit node information for games in debug mode - we're not interested in the height
767 		static boost::format node_format("(%i, %i)");
768 		node_text = (node_format % sel_.pos.node.x % sel_.pos.node.y).str();
769 	}
770 	if (!node_text.empty()) {
771 		std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh->render(
772 		   as_richtext_paragraph(node_text, UI::FontStyle::kWuiGameSpeedAndCoordinates));
773 		rendered_text->draw(
774 		   dst, Vector2i(get_w() - 5, get_h() - rendered_text->height() - 5), UI::Align::kRight);
775 	}
776 
777 	// In-game clock and FPS
778 	if ((game != nullptr) && get_config_bool("game_clock", true)) {
779 		// Blit in-game clock
780 		const std::string gametime(gametimestring(egbase().get_gametime(), true));
781 		std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh->render(
782 		   as_richtext_paragraph(gametime, UI::FontStyle::kWuiGameSpeedAndCoordinates));
783 		rendered_text->draw(dst, Vector2i(5, 5));
784 
785 		// Blit FPS when playing a game in debug mode
786 		if (get_display_flag(dfDebug)) {
787 			static boost::format fps_format("%5.1f fps (avg: %5.1f fps)");
788 			rendered_text = UI::g_fh->render(
789 			   as_richtext_paragraph((fps_format % (1000.0 / frametime_) % average_fps()).str(),
790 			                         UI::FontStyle::kWuiGameSpeedAndCoordinates));
791 			rendered_text->draw(dst, Vector2i((get_w() - rendered_text->width()) / 2, 5));
792 		}
793 	}
794 }
795 
blit_overlay(RenderTarget * dst,const Vector2i & position,const Image * image,const Vector2i & hotspot,float scale)796 void InteractiveBase::blit_overlay(RenderTarget* dst,
797                                    const Vector2i& position,
798                                    const Image* image,
799                                    const Vector2i& hotspot,
800                                    float scale) {
801 	const Recti pixel_perfect_rect =
802 	   Recti(position - hotspot * scale, image->width() * scale, image->height() * scale);
803 	dst->blitrect_scale(pixel_perfect_rect.cast<float>(), image,
804 	                    Recti(0, 0, image->width(), image->height()), 1.f, BlendMode::UseAlpha);
805 }
806 
blit_field_overlay(RenderTarget * dst,const FieldsToDraw::Field & field,const Image * image,const Vector2i & hotspot,float scale)807 void InteractiveBase::blit_field_overlay(RenderTarget* dst,
808                                          const FieldsToDraw::Field& field,
809                                          const Image* image,
810                                          const Vector2i& hotspot,
811                                          float scale) {
812 	blit_overlay(dst, field.rendertarget_pixel.cast<int>(), image, hotspot, scale);
813 }
814 
draw_bridges(RenderTarget * dst,const FieldsToDraw::Field * f,uint32_t gametime,float scale) const815 void InteractiveBase::draw_bridges(RenderTarget* dst,
816                                    const FieldsToDraw::Field* f,
817                                    uint32_t gametime,
818                                    float scale) const {
819 	if (Widelands::is_bridge_segment(f->road_e)) {
820 		dst->blit_animation(f->rendertarget_pixel, f->fcoords, scale,
821 		                    f->owner->tribe().bridge_animation(
822 		                       Widelands::WALK_E, f->road_e == Widelands::RoadSegment::kBridgeBusy),
823 		                    gametime, &f->owner->get_playercolor());
824 	}
825 	if (Widelands::is_bridge_segment(f->road_sw)) {
826 		dst->blit_animation(f->rendertarget_pixel, f->fcoords, scale,
827 		                    f->owner->tribe().bridge_animation(
828 		                       Widelands::WALK_SW, f->road_sw == Widelands::RoadSegment::kBridgeBusy),
829 		                    gametime, &f->owner->get_playercolor());
830 	}
831 	if (Widelands::is_bridge_segment(f->road_se)) {
832 		dst->blit_animation(f->rendertarget_pixel, f->fcoords, scale,
833 		                    f->owner->tribe().bridge_animation(
834 		                       Widelands::WALK_SE, f->road_se == Widelands::RoadSegment::kBridgeBusy),
835 		                    gametime, &f->owner->get_playercolor());
836 	}
837 }
838 
mainview_move()839 void InteractiveBase::mainview_move() {
840 	if (minimap_registry_.window) {
841 		minimap_->set_view(map_view_.view_area().rect());
842 	}
843 }
844 
845 // Open the minimap or close it if it's open
toggle_minimap()846 void InteractiveBase::toggle_minimap() {
847 	if (minimap_registry_.window) {
848 		delete minimap_registry_.window;
849 	} else {
850 		minimap_ = new MiniMap(*this, &minimap_registry_);
851 		minimap_->warpview.connect([this](const Vector2f& map_pixel) {
852 			map_view_.scroll_to_map_pixel(map_pixel, MapView::Transition::Smooth);
853 		});
854 		mainview_move();
855 	}
856 	rebuild_mapview_menu();
857 }
858 
landmarks()859 const QuickNavigation::Landmark* InteractiveBase::landmarks() {
860 	return quick_navigation_.landmarks();
861 }
862 
set_landmark(size_t key,const MapView::View & landmark_view)863 void InteractiveBase::set_landmark(size_t key, const MapView::View& landmark_view) {
864 	quick_navigation_.set_landmark(key, landmark_view);
865 }
866 
867 /**
868  * Hide the minimap if it is currently shown; otherwise, do nothing.
869  */
hide_minimap()870 void InteractiveBase::hide_minimap() {
871 	minimap_registry_.destroy();
872 }
873 
874 /*
875 ===============
876 Return display flags (dfXXX) that modify the view of the map.
877 ===============
878 */
get_display_flags() const879 uint32_t InteractiveBase::get_display_flags() const {
880 	return display_flags_;
881 }
882 
883 /*
884 ===============
885 Change the display flags that modify the view of the map.
886 ===============
887 */
set_display_flags(uint32_t flags)888 void InteractiveBase::set_display_flags(uint32_t flags) {
889 	display_flags_ = flags;
890 }
891 
892 /*
893 ===============
894 Get and set one individual flag of the display flags.
895 ===============
896 */
get_display_flag(uint32_t const flag)897 bool InteractiveBase::get_display_flag(uint32_t const flag) {
898 	return display_flags_ & flag;
899 }
900 
set_display_flag(uint32_t const flag,bool const on)901 void InteractiveBase::set_display_flag(uint32_t const flag, bool const on) {
902 	display_flags_ &= ~flag;
903 
904 	if (on)
905 		display_flags_ |= flag;
906 }
907 
908 /*
909 ===============
910 Begin building a road
911 ===============
912 */
start_build_road(Coords road_start,Widelands::PlayerNumber const player,RoadBuildingType t)913 void InteractiveBase::start_build_road(Coords road_start,
914                                        Widelands::PlayerNumber const player,
915                                        RoadBuildingType t) {
916 	assert(!road_building_mode_);
917 	road_building_mode_.reset(new RoadBuildingMode(player, road_start, t));
918 
919 	road_building_add_overlay();
920 	set_sel_picture(g_gr->images().get(t == RoadBuildingType::kWaterway ?
921 	                                      "images/ui_basic/fsel_waterwaybuilding.png" :
922 	                                      "images/ui_basic/fsel_roadbuilding.png"));
923 
924 	if (t == RoadBuildingType::kWaterway) {
925 		// Show workarea to visualise length limit
926 		const Widelands::Map& map = egbase().map();
927 		const uint32_t len = map.get_waterway_max_length();
928 		assert(len > 1);
929 		road_building_mode_->work_area.reset(new WorkareaInfo());
930 		road_building_mode_->work_area->insert(std::make_pair(len, std::set<std::string>()));
931 
932 		std::map<Widelands::FCoords, bool> reachable_nodes;
933 		Widelands::CheckStepFerry cstep(egbase());
934 		Widelands::MapRegion<Widelands::Area<Widelands::FCoords>> mr(
935 		   map, Widelands::Area<Widelands::FCoords>(map.get_fcoords(road_start), len));
936 		do {
937 			reachable_nodes.insert(
938 			   std::make_pair(mr.location(), mr.location().field->get_owned_by() == player &&
939 			                                    !mr.location().field->is_border() &&
940 			                                    cstep.reachable_dest(map, mr.location())));
941 		} while (mr.advance(map));
942 		std::map<Widelands::TCoords<>, uint32_t> wa_data;
943 		for (const auto& pair : reachable_nodes) {
944 			const auto br = reachable_nodes.find(map.br_n(pair.first));
945 			if (br == reachable_nodes.end()) {
946 				continue;
947 			}
948 			auto it = reachable_nodes.find(map.bl_n(pair.first));
949 			if (it != reachable_nodes.end()) {
950 				wa_data.insert(
951 				   std::make_pair(Widelands::TCoords<>(pair.first, Widelands::TriangleIndex::D),
952 				                  pair.second && br->second && it->second ? 5 : 6));
953 			}
954 			it = reachable_nodes.find(map.r_n(pair.first));
955 			if (it != reachable_nodes.end()) {
956 				wa_data.insert(
957 				   std::make_pair(Widelands::TCoords<>(pair.first, Widelands::TriangleIndex::R),
958 				                  pair.second && br->second && it->second ? 5 : 6));
959 			}
960 		}
961 		show_workarea(*road_building_mode_->work_area, road_start, wa_data);
962 	}
963 }
964 
965 /*
966 ===============
967 Stop building the road
968 ===============
969 */
abort_build_road()970 void InteractiveBase::abort_build_road() {
971 	assert(road_building_mode_);
972 	if (road_building_mode_->type == RoadBuildingType::kWaterway) {
973 		assert(road_building_mode_->work_area);
974 		hide_workarea(road_building_mode_->path.get_start(), true);
975 	}
976 #ifndef NDEBUG
977 	else
978 		assert(!road_building_mode_->work_area);
979 #endif
980 
981 	road_building_remove_overlay();
982 	road_building_mode_.reset(nullptr);
983 	unset_sel_picture();
984 }
985 
986 /*
987 ===============
988 Finally build the road
989 ===============
990 */
finish_build_road()991 void InteractiveBase::finish_build_road() {
992 	assert(road_building_mode_);
993 
994 	if (road_building_mode_->type == RoadBuildingType::kWaterway) {
995 		assert(road_building_mode_->work_area);
996 		hide_workarea(road_building_mode_->path.get_start(), true);
997 	}
998 #ifndef NDEBUG
999 	else
1000 		assert(!road_building_mode_->work_area);
1001 #endif
1002 
1003 	road_building_remove_overlay();
1004 
1005 	const size_t length = road_building_mode_->path.get_nsteps();
1006 	if (road_building_mode_->type == RoadBuildingType::kWaterway &&
1007 	    length > egbase().map().get_waterway_max_length()) {
1008 		log("Refusing to finish waterway building: length is %" PRIuS " but limit is %d\n", length,
1009 		    egbase().map().get_waterway_max_length());
1010 	} else if (length) {
1011 		upcast(Game, g, &egbase());
1012 
1013 		// Build the path as requested
1014 		if (g) {
1015 			if (road_building_mode_->type == RoadBuildingType::kWaterway) {
1016 				g->send_player_build_waterway(
1017 				   road_building_mode_->player, *new Widelands::Path(road_building_mode_->path));
1018 			} else {
1019 				g->send_player_build_road(
1020 				   road_building_mode_->player, *new Widelands::Path(road_building_mode_->path));
1021 			}
1022 		} else {
1023 			if (road_building_mode_->type == RoadBuildingType::kWaterway) {
1024 				egbase()
1025 				   .get_player(road_building_mode_->player)
1026 				   ->build_waterway(*new Widelands::Path(road_building_mode_->path));
1027 			} else {
1028 				egbase()
1029 				   .get_player(road_building_mode_->player)
1030 				   ->build_road(*new Widelands::Path(road_building_mode_->path));
1031 			}
1032 		}
1033 
1034 		if (allow_user_input() && (SDL_GetModState() & KMOD_CTRL)) {
1035 			//  place flags
1036 			const Map& map = egbase().map();
1037 			const std::vector<Coords>& c_vector = road_building_mode_->path.get_coords();
1038 			std::vector<Coords>::const_iterator const first = c_vector.begin() + 2;
1039 			std::vector<Coords>::const_iterator const last = c_vector.end() - 2;
1040 
1041 			auto place_flag = [this, g](const Widelands::FCoords& coords) {
1042 				if (g) {
1043 					g->send_player_build_flag(road_building_mode_->player, coords);
1044 				} else {
1045 					egbase().get_player(road_building_mode_->player)->build_flag(coords);
1046 				}
1047 			};
1048 
1049 			if (SDL_GetModState() & KMOD_SHIFT) {
1050 				//  start to end
1051 				for (std::vector<Coords>::const_iterator it = first; it <= last; ++it) {
1052 					place_flag(map.get_fcoords(*it));
1053 				}
1054 			} else {
1055 				//  end to start
1056 				for (std::vector<Coords>::const_iterator it = last; first <= it; --it) {
1057 					place_flag(map.get_fcoords(*it));
1058 				}
1059 			}
1060 		}
1061 	}
1062 
1063 	road_building_mode_.reset(nullptr);
1064 	unset_sel_picture();
1065 }
1066 
1067 /*
1068 ===============
1069 If field is on the path, remove tail of path.
1070 Otherwise append if possible or return false.
1071 ===============
1072 */
append_build_road(Coords const field)1073 bool InteractiveBase::append_build_road(Coords const field) {
1074 	assert(road_building_mode_);
1075 
1076 	const Map& map = egbase().map();
1077 	const Widelands::Player& player = egbase().player(road_building_mode_->player);
1078 
1079 	{  //  find a path to the clicked-on node
1080 		Widelands::Path path;
1081 		Widelands::CheckStepAnd cstep;
1082 		if (road_building_mode_->type == RoadBuildingType::kWaterway) {
1083 			cstep.add(Widelands::CheckStepFerry(egbase()));
1084 			cstep.add(
1085 			   Widelands::CheckStepRoad(player, Widelands::MOVECAPS_SWIM | Widelands::MOVECAPS_WALK));
1086 		} else {
1087 			cstep.add(Widelands::CheckStepRoad(player, Widelands::MOVECAPS_WALK));
1088 		}
1089 		if (map.findpath(
1090 		       road_building_mode_->path.get_end(), field, 0, path, cstep, Map::fpBidiCost) < 0) {
1091 			return false;  // could not find a path
1092 		}
1093 		road_building_mode_->path.append(map, path);
1094 	}
1095 
1096 	{
1097 		//  Fix the road by finding an optimal path through the set of nodes
1098 		//  currently used by the road. This will not claim any new nodes, so it
1099 		//  is guaranteed to not hinder building placement.
1100 		Widelands::Path path;
1101 		{
1102 			Widelands::CheckStepAnd cstep;
1103 			Widelands::CheckStepLimited clim;
1104 			for (const Coords& coord : road_building_mode_->path.get_coords()) {
1105 				clim.add_allowed_location(coord);
1106 			}
1107 			cstep.add(clim);
1108 			if (road_building_mode_->type == RoadBuildingType::kWaterway) {
1109 				// Waterways (unlike roads) are strictly limited by the terrain around the edges
1110 				cstep.add(Widelands::CheckStepFerry(egbase()));
1111 			}
1112 			map.findpath(
1113 			   road_building_mode_->path.get_start(), field, 0, path, cstep, Map::fpBidiCost);
1114 		}
1115 		road_building_mode_->path.truncate(0);
1116 		road_building_mode_->path.append(map, path);
1117 	}
1118 
1119 	if (road_building_mode_->type == RoadBuildingType::kWaterway &&
1120 	    road_building_mode_->path.get_nsteps() > map.get_waterway_max_length()) {
1121 		road_building_mode_->path.truncate(map.get_waterway_max_length());
1122 	}
1123 
1124 	road_building_remove_overlay();
1125 	road_building_add_overlay();
1126 
1127 	return true;
1128 }
1129 
1130 /*
1131 ===============
1132 Return the current road-building startpoint
1133 ===============
1134 */
get_build_road_start() const1135 Coords InteractiveBase::get_build_road_start() const {
1136 	assert(road_building_mode_);
1137 	return road_building_mode_->path.get_start();
1138 }
1139 
1140 /*
1141 ===============
1142 Return the current road-building endpoint
1143 ===============
1144 */
get_build_road_end() const1145 Coords InteractiveBase::get_build_road_end() const {
1146 	assert(road_building_mode_);
1147 	return road_building_mode_->path.get_end();
1148 }
1149 
get_build_road_path() const1150 Widelands::CoordPath InteractiveBase::get_build_road_path() const {
1151 	assert(road_building_mode_);
1152 	return road_building_mode_->path;
1153 }
1154 
log_message(const std::string & message) const1155 void InteractiveBase::log_message(const std::string& message) const {
1156 	// Send to linked receivers
1157 	LogMessage lm;
1158 	lm.msg = message;
1159 	lm.time = time(nullptr);
1160 	Notifications::publish(lm);
1161 }
1162 
1163 /**
1164  * Plays a sound effect positioned according to the map coordinates in the note.
1165  */
play_sound_effect(const NoteSound & note) const1166 void InteractiveBase::play_sound_effect(const NoteSound& note) const {
1167 	if (!g_sh->is_sound_enabled(note.type)) {
1168 		return;
1169 	}
1170 
1171 	if (note.coords != Widelands::Coords::null() && player_hears_field(note.coords)) {
1172 		constexpr int kSoundMaxDistance = 255;
1173 		constexpr float kSoundDistanceDivisor = 4.f;
1174 
1175 		// Viewpoint is the point of the map in pixel which is shown in the upper
1176 		// left corner of window or fullscreen
1177 		const MapView::ViewArea area = map_view_.view_area();
1178 		const Vector2f position_pix = area.find_pixel_for_coordinates(note.coords);
1179 		const int stereo_pos =
1180 		   static_cast<int>((position_pix.x - area.rect().x) * kStereoRight / area.rect().w);
1181 
1182 		int distance = MapviewPixelFunctions::calc_pix_distance(
1183 		                  egbase().map(), area.rect().center(), position_pix) /
1184 		               kSoundDistanceDivisor;
1185 
1186 		distance = (note.priority == kFxPriorityAlwaysPlay) ?
1187 		              (math::clamp(distance, 0, kSoundMaxDistance) / 2) :
1188 		              distance;
1189 
1190 		if (distance < kSoundMaxDistance) {
1191 			g_sh->play_fx(note.type, note.fx, note.priority,
1192 			              math::clamp(stereo_pos, kStereoLeft, kStereoRight), distance);
1193 		}
1194 	}
1195 }
1196 
1197 // Repositions the chat overlay
resize_chat_overlay()1198 void InteractiveBase::resize_chat_overlay() {
1199 	// 34 is the button height of the bottom menu
1200 	chat_overlay_->set_size(get_w() / 2, get_h() - 25 - 34);
1201 	chat_overlay_->recompute();
1202 }
1203 
1204 /*
1205 ===============
1206 Add road building data to the road overlay
1207 ===============
1208 */
road_building_add_overlay()1209 void InteractiveBase::road_building_add_overlay() {
1210 	assert(road_building_mode_);
1211 	assert(road_building_mode_->overlay_road_previews.empty());
1212 	assert(road_building_mode_->overlay_steepness_indicators.empty());
1213 
1214 	const Map& map = egbase().map();
1215 
1216 	// preview of the road
1217 	const CoordPath::StepVector::size_type nr_steps = road_building_mode_->path.get_nsteps();
1218 	for (CoordPath::StepVector::size_type idx = 0; idx < nr_steps; ++idx) {
1219 		Widelands::Direction dir = (road_building_mode_->path)[idx];
1220 		Coords c = road_building_mode_->path.get_coords()[idx];
1221 
1222 		if (dir < Widelands::WALK_E || dir > Widelands::WALK_SW) {
1223 			map.get_neighbour(c, dir, &c);
1224 			dir = Widelands::get_reverse_dir(dir);
1225 		}
1226 		road_building_mode_->overlay_road_previews.emplace(c, std::vector<uint8_t>());
1227 		road_building_mode_->overlay_road_previews[c].push_back(dir);
1228 	}
1229 
1230 	// build hints
1231 	Widelands::FCoords endpos = map.get_fcoords(road_building_mode_->path.get_end());
1232 
1233 	for (int32_t dir = 1; dir <= 6; ++dir) {
1234 		Widelands::FCoords neighb;
1235 		int32_t caps;
1236 
1237 		map.get_neighbour(endpos, dir, &neighb);
1238 		caps = egbase().player(road_building_mode_->player).get_buildcaps(neighb);
1239 
1240 		if (road_building_mode_->type == RoadBuildingType::kWaterway) {
1241 			Widelands::CheckStepFerry checkstep(egbase());
1242 			if (!checkstep.reachable_dest(map, neighb) ||
1243 			    road_building_mode_->path.get_index(neighb) >= 0 ||
1244 			    !neighb.field->is_interior(road_building_mode_->player)) {
1245 				continue;
1246 			}
1247 
1248 			bool next_to = false;
1249 			Widelands::FCoords nb;
1250 			for (int32_t d = 1; d <= 6; ++d) {
1251 				map.get_neighbour(neighb, d, &nb);
1252 				if (nb != endpos && road_building_mode_->path.get_index(nb) >= 0 &&
1253 				    checkstep.allowed(map, neighb, nb, d, Widelands::CheckStep::StepId::stepNormal)) {
1254 					next_to = true;
1255 					break;
1256 				}
1257 			}
1258 			if (!next_to && road_building_mode_->path.get_nsteps() >= map.get_waterway_max_length()) {
1259 				continue;  // exceeds length limit
1260 			}
1261 		} else if (!(caps & Widelands::MOVECAPS_WALK)) {
1262 			continue;  // need to be able to walk there
1263 		}
1264 
1265 		//  can't build on robusts
1266 		const Widelands::BaseImmovable* imm = map.get_immovable(neighb);
1267 		if (imm && imm->get_size() >= Widelands::BaseImmovable::SMALL &&
1268 		    (!(dynamic_cast<const Widelands::Flag*>(imm) ||
1269 		       (dynamic_cast<const Widelands::RoadBase*>(imm) &&
1270 		        (caps & Widelands::BUILDCAPS_FLAG))))) {
1271 			continue;
1272 		}
1273 		if (road_building_mode_->path.get_index(neighb) >= 0) {
1274 			continue;  // the road can't cross itself
1275 		}
1276 
1277 		int32_t slope;
1278 
1279 		if (Widelands::WALK_E == dir || Widelands::WALK_NE == dir || Widelands::WALK_SE == dir) {
1280 			slope = neighb.field->get_height() - endpos.field->get_height();
1281 		} else {
1282 			slope = endpos.field->get_height() - neighb.field->get_height();
1283 		}
1284 
1285 		const char* name = nullptr;
1286 
1287 		if (road_building_mode_->type == RoadBuildingType::kWaterway) {
1288 			if (slope <= -4) {
1289 				name = "images/wui/overlays/waterway_building_steepdown.png";
1290 			} else if (slope >= 4) {
1291 				name = "images/wui/overlays/waterway_building_steepup.png";
1292 			} else if (slope <= -2) {
1293 				name = "images/wui/overlays/waterway_building_down.png";
1294 			} else if (slope >= 2) {
1295 				name = "images/wui/overlays/waterway_building_up.png";
1296 			} else {
1297 				name = "images/wui/overlays/waterway_building_even.png";
1298 			}
1299 		} else {
1300 			if (slope <= -4) {
1301 				name = "images/wui/overlays/road_building_reddown.png";
1302 			} else if (slope <= -2) {
1303 				name = "images/wui/overlays/road_building_yellowdown.png";
1304 			} else if (slope < 2) {
1305 				name = "images/wui/overlays/road_building_green.png";
1306 			} else if (slope < 4) {
1307 				name = "images/wui/overlays/road_building_yellow.png";
1308 			} else {
1309 				name = "images/wui/overlays/road_building_red.png";
1310 			}
1311 		}
1312 		road_building_mode_->overlay_steepness_indicators[neighb] = g_gr->images().get(name);
1313 	}
1314 }
1315 
1316 /*
1317 ===============
1318 Remove road building data from road overlay
1319 ===============
1320 */
road_building_remove_overlay()1321 void InteractiveBase::road_building_remove_overlay() {
1322 	assert(road_building_mode_);
1323 	road_building_mode_->overlay_road_previews.clear();
1324 	road_building_mode_->overlay_steepness_indicators.clear();
1325 }
1326 
handle_key(bool const down,SDL_Keysym const code)1327 bool InteractiveBase::handle_key(bool const down, SDL_Keysym const code) {
1328 	if (quick_navigation_.handle_key(down, code)) {
1329 		return true;
1330 	}
1331 
1332 	if (down) {
1333 		switch (code.sym) {
1334 #ifndef NDEBUG  //  only in debug builds
1335 		case SDLK_F6:
1336 			GameChatMenu::create_script_console(
1337 			   this, debugconsole_, *DebugConsole::get_chat_provider());
1338 			return true;
1339 #endif
1340 		// Common shortcuts for InteractivePlayer, InteractiveSpectator and EditorInteractive
1341 		case SDLK_SPACE:
1342 			toggle_buildhelp();
1343 			return true;
1344 		case SDLK_m:
1345 			toggle_minimap();
1346 			return true;
1347 		default:
1348 			break;
1349 		}
1350 	}
1351 
1352 	return map_view_.handle_key(down, code);
1353 }
1354 
cmd_lua(const std::vector<std::string> & args)1355 void InteractiveBase::cmd_lua(const std::vector<std::string>& args) {
1356 	const std::string cmd = boost::algorithm::join(args, " ");
1357 
1358 	DebugConsole::write("Starting Lua interpretation!");
1359 	try {
1360 		egbase().lua().interpret_string(cmd);
1361 	} catch (LuaError& e) {
1362 		DebugConsole::write(e.what());
1363 	}
1364 
1365 	DebugConsole::write("Ending Lua interpretation!");
1366 }
1367 
1368 /**
1369  * Show a map object's debug window
1370  */
cmd_map_object(const std::vector<std::string> & args)1371 void InteractiveBase::cmd_map_object(const std::vector<std::string>& args) {
1372 	if (args.size() != 2) {
1373 		DebugConsole::write("usage: mapobject <mapobject serial>");
1374 		return;
1375 	}
1376 
1377 	uint32_t serial = atoi(args[1].c_str());
1378 	MapObject* obj = egbase().objects().get_object(serial);
1379 
1380 	if (!obj) {
1381 		DebugConsole::write(str(boost::format("No MapObject with serial number %1%") % serial));
1382 		return;
1383 	}
1384 
1385 	show_mapobject_debug(*this, *obj);
1386 }
1387