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