43
43
#include "logic/maphollowregion.h"
44
44
#include "logic/maptriangleregion.h"
45
45
#include "logic/player.h"
46
#include "logic/widelands_geometry.h"
46
47
#include "profile/profile.h"
47
48
#include "scripting/lua_interface.h"
48
#include "wui/edge_overlay_manager.h"
49
#include "wui/field_overlay_manager.h"
50
49
#include "wui/game_chat_menu.h"
51
50
#include "wui/game_debug_ui.h"
52
51
#include "wui/interactive_player.h"
68
66
using Widelands::MapObject;
69
67
using Widelands::TCoords;
71
struct InteractiveBaseInternals {
73
MiniMap::Registry minimap;
74
std::unique_ptr<QuickNavigation> quicknavigation;
69
int caps_to_buildhelp(const Widelands::NodeCaps caps) {
70
if (caps & Widelands::BUILDCAPS_MINE) {
71
return Widelands::Field::Buildhelp_Mine;
73
if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_BIG) {
74
if (caps & Widelands::BUILDCAPS_PORT) {
75
return Widelands::Field::Buildhelp_Port;
77
return Widelands::Field::Buildhelp_Big;
79
if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_MEDIUM) {
80
return Widelands::Field::Buildhelp_Medium;
82
if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_SMALL) {
83
return Widelands::Field::Buildhelp_Small;
85
if (caps & Widelands::BUILDCAPS_FLAG) {
86
return Widelands::Field::Buildhelp_Flag;
88
return Widelands::Field::Buildhelp_None;
76
explicit InteractiveBaseInternals(QuickNavigation* qnav) : mm(nullptr), quicknavigation(qnav) {
80
93
InteractiveBase::InteractiveBase(EditorGameBase& the_egbase, Section& global_s)
81
: MapView(nullptr, 0, 0, g_gr->get_xres(), g_gr->get_yres(), *this),
94
: UI::Panel(nullptr, 0, 0, g_gr->get_xres(), g_gr->get_yres()),
95
show_workarea_preview_(global_s.get_bool("workareapreview", true)),
97
map_view_(this, the_egbase.map(), 0, 0, g_gr->get_xres(), g_gr->get_yres()),
82
98
// Initialize chatoveraly before the toolbar so it is below
83
show_workarea_preview_(global_s.get_bool("workareapreview", true)),
84
99
chat_overlay_(new ChatOverlay(this, 10, 25, get_w() / 2, get_h() - 25)),
85
100
toolbar_(this, 0, 0, UI::Box::Horizontal),
86
m(new InteractiveBaseInternals(new QuickNavigation(this))),
87
field_overlay_manager_(new FieldOverlayManager()),
88
edge_overlay_manager_(new EdgeOverlayManager()),
101
quick_navigation_(&map_view_),
89
102
egbase_(the_egbase),
90
103
#ifndef NDEBUG // not in releases
91
104
display_flags_(dfDebug),
108
119
g_gr->images().get("images/wui/overlays/workarea2.png"),
109
120
g_gr->images().get("images/wui/overlays/workarea1.png")} {
122
// Load the buildhelp icons.
124
BuildhelpOverlay* buildhelp_overlay = buildhelp_overlays_;
125
const char* filenames[] = {
126
"images/wui/overlays/set_flag.png", "images/wui/overlays/small.png",
127
"images/wui/overlays/medium.png", "images/wui/overlays/big.png",
128
"images/wui/overlays/mine.png", "images/wui/overlays/port.png"};
129
const char* const* filename = filenames;
131
// Special case for flag, which has a different formula for hotspot_y.
132
buildhelp_overlay->pic = g_gr->images().get(*filename);
133
buildhelp_overlay->hotspot =
134
Vector2i(buildhelp_overlay->pic->width() / 2, buildhelp_overlay->pic->height() - 1);
136
const BuildhelpOverlay* const buildhelp_overlays_end =
137
buildhelp_overlay + Widelands::Field::Buildhelp_None;
138
for (;;) { // The other buildhelp overlays.
141
if (buildhelp_overlay == buildhelp_overlays_end)
143
buildhelp_overlay->pic = g_gr->images().get(*filename);
144
buildhelp_overlay->hotspot =
145
Vector2i(buildhelp_overlay->pic->width() / 2, buildhelp_overlay->pic->height() / 2);
111
149
resize_chat_overlay();
113
151
graphic_resolution_changed_subscriber_ = Notifications::subscribe<GraphicResolutionChanged>(
114
152
[this](const GraphicResolutionChanged& message) {
115
153
set_size(message.width, message.height);
154
map_view_.set_size(message.width, message.height);
116
155
resize_chat_overlay();
117
156
adjust_toolbar_position();
119
158
sound_subscriber_ = Notifications::subscribe<NoteSound>([this](const NoteSound& note) {
120
159
if (note.stereo_position != std::numeric_limits<uint32_t>::max()) {
121
160
g_sound_handler.play_fx(note.fx, note.stereo_position, note.priority);
122
} else if (note.coords != Widelands::Coords(-1, -1)) {
161
} else if (note.coords != Widelands::Coords::null()) {
123
162
g_sound_handler.play_fx(note.fx, stereo_position(note.coords), note.priority);
127
166
toolbar_.set_layout_toplevel(true);
128
changeview.connect([this] { mainview_move(); });
167
map_view_.changeview.connect([this] { mainview_move(); });
168
map_view()->field_clicked.connect([this](const Widelands::NodeAndTriangle<>& node_and_triangle) {
169
set_sel_pos(node_and_triangle);
171
map_view_.track_selection.connect([this](const Widelands::NodeAndTriangle<>& node_and_triangle) {
173
set_sel_pos(node_and_triangle);
130
177
set_border_snap_distance(global_s.get_int("border_snap_distance", 0));
131
178
set_panel_snap_distance(global_s.get_int("panel_snap_distance", 10));
200
const InteractiveBase::BuildhelpOverlay*
201
InteractiveBase::get_buildhelp_overlay(const Widelands::NodeCaps caps) const {
202
const int buildhelp_overlay_index = caps_to_buildhelp(caps);
203
if (buildhelp_overlay_index < Widelands::Field::Buildhelp_None) {
204
return &buildhelp_overlays_[buildhelp_overlay_index];
153
209
UniqueWindowHandler& InteractiveBase::unique_windows() {
154
210
return *unique_window_handler_;
157
213
void InteractiveBase::set_sel_pos(Widelands::NodeAndTriangle<> const center) {
158
Map& map = egbase().map();
160
// Remove old sel pointer
162
field_overlay_manager_->remove_overlay(sel_.jobid);
163
const FieldOverlayManager::OverlayId jobid = sel_.jobid =
164
field_overlay_manager_->next_overlay_id();
214
const Map& map = egbase().map();
166
215
sel_.pos = center;
168
// register sel overlay position
169
if (sel_.triangles) {
170
assert(center.triangle.t == TCoords<>::D || center.triangle.t == TCoords<>::R);
171
Widelands::MapTriangleRegion<> mr(map, Area<TCoords<>>(center.triangle, sel_.radius));
173
field_overlay_manager_->register_overlay(
174
mr.location(), sel_.pic, 7, Vector2i::invalid(), jobid);
175
while (mr.advance(map));
177
Widelands::MapRegion<> mr(map, Area<>(center.node, sel_.radius));
179
field_overlay_manager_->register_overlay(
180
mr.location(), sel_.pic, 7, Vector2i::invalid(), jobid);
181
while (mr.advance(map));
182
if (upcast(InteractiveGameBase const, igbase, this))
183
if (upcast(Widelands::ProductionSite, productionsite, map[center.node].get_immovable())) {
184
if (upcast(InteractivePlayer const, iplayer, igbase)) {
185
const Widelands::Player& player = iplayer->player();
186
if (!player.see_all() &&
187
(1 >= player.vision(Widelands::Map::get_index(center.node, map.get_width())) ||
188
player.is_hostile(*productionsite->get_owner())))
189
return set_tooltip("");
192
productionsite->info_string(Widelands::Building::InfoStringFormat::kTooltip));
217
if (upcast(InteractiveGameBase const, igbase, this))
218
if (upcast(Widelands::ProductionSite, productionsite, map[center.node].get_immovable())) {
219
if (upcast(InteractivePlayer const, iplayer, igbase)) {
220
const Widelands::Player& player = iplayer->player();
221
if (!player.see_all() &&
222
(1 >= player.vision(Widelands::Map::get_index(center.node, map.get_width())) ||
223
player.is_hostile(*productionsite->get_owner())))
224
return set_tooltip("");
226
set_tooltip(productionsite->info_string(Widelands::Building::InfoStringFormat::kTooltip));
213
246
sel_.pic = image;
214
247
set_sel_pos(get_sel_pos()); // redraw
250
TextToDraw InteractiveBase::get_text_to_draw() const {
251
TextToDraw text_to_draw = TextToDraw::kNone;
252
auto display_flags = get_display_flags();
253
if (display_flags & InteractiveBase::dfShowCensus) {
254
text_to_draw = text_to_draw | TextToDraw::kCensus;
256
if (display_flags & InteractiveBase::dfShowStatistics) {
257
text_to_draw = text_to_draw | TextToDraw::kStatistics;
216
262
void InteractiveBase::unset_sel_picture() {
217
263
set_sel_picture(g_gr->images().get("images/ui_basic/fsel.png"));
220
266
bool InteractiveBase::buildhelp() const {
221
return field_overlay_manager_->buildhelp();
224
270
void InteractiveBase::show_buildhelp(bool t) {
225
field_overlay_manager_->show_buildhelp(t);
226
272
on_buildhelp_changed(t);
229
275
void InteractiveBase::toggle_buildhelp() {
230
show_buildhelp(!field_overlay_manager_->buildhelp());
276
show_buildhelp(!buildhelp());
233
279
UI::Button* InteractiveBase::add_toolbar_button(const std::string& image_basename,
256
302
// Show the given workareas at the given coords and returns the overlay job id associated
257
FieldOverlayManager::OverlayId InteractiveBase::show_work_area(const WorkareaInfo& workarea_info,
258
Widelands::Coords coords) {
259
const uint8_t workareas_nrs = workarea_info.size();
260
WorkareaInfo::size_type wa_index;
261
switch (workareas_nrs) {
263
return 0; // no workarea
274
throw wexception("Encountered unexpected WorkareaInfo size %i", workareas_nrs);
276
Widelands::Map& map = egbase_.map();
277
FieldOverlayManager::OverlayId overlay_id = field_overlay_manager_->next_overlay_id();
279
Widelands::HollowArea<> hollow_area(Widelands::Area<>(coords, 0), 0);
281
// Iterate through the work areas, from building to its enhancement
282
WorkareaInfo::const_iterator it = workarea_info.begin();
283
for (; it != workarea_info.end(); ++it) {
284
hollow_area.radius = it->first;
285
Widelands::MapHollowRegion<> mr(map, hollow_area);
287
field_overlay_manager_->register_overlay(
288
mr.location(), workarea_pics_[wa_index], 0, Vector2i::invalid(), overlay_id);
289
while (mr.advance(map));
291
hollow_area.hole_radius = hollow_area.radius;
296
void InteractiveBase::hide_work_area(FieldOverlayManager::OverlayId overlay_id) {
297
field_overlay_manager_->remove_overlay(overlay_id);
303
void InteractiveBase::show_work_area(const WorkareaInfo& workarea_info, Widelands::Coords coords) {
304
work_area_previews_[coords] = &workarea_info;
307
std::map<Coords, const Image*>
308
InteractiveBase::get_work_area_overlays(const Widelands::Map& map) const {
309
std::map<Coords, const Image*> result;
310
for (const auto& pair : work_area_previews_) {
311
const Coords& coords = pair.first;
312
const WorkareaInfo* workarea_info = pair.second;
313
WorkareaInfo::size_type wa_index;
314
switch (workarea_info->size()) {
316
continue; // no workarea
328
"Encountered unexpected WorkareaInfo size %i", static_cast<int>(workarea_info->size()));
331
Widelands::HollowArea<> hollow_area(Widelands::Area<>(coords, 0), 0);
333
// Iterate through the work areas, from building to its enhancement
334
WorkareaInfo::const_iterator it = workarea_info->begin();
335
for (; it != workarea_info->end(); ++it) {
336
hollow_area.radius = it->first;
337
Widelands::MapHollowRegion<> mr(map, hollow_area);
339
result[mr.location()] = workarea_pics_[wa_index];
340
} while (mr.advance(map));
342
hollow_area.hole_radius = hollow_area.radius;
348
void InteractiveBase::hide_work_area(const Widelands::Coords& coords) {
349
work_area_previews_.erase(coords);
318
370
if (keyboard_free() && Panel::allow_user_input()) {
319
371
if (get_key_state(SDL_SCANCODE_UP) ||
320
372
(get_key_state(SDL_SCANCODE_KP_8) && (SDL_GetModState() ^ KMOD_NUM))) {
321
pan_by(Vector2i(0, -scrollval));
373
map_view_.pan_by(Vector2i(0, -scrollval));
323
375
if (get_key_state(SDL_SCANCODE_DOWN) ||
324
376
(get_key_state(SDL_SCANCODE_KP_2) && (SDL_GetModState() ^ KMOD_NUM))) {
325
pan_by(Vector2i(0, scrollval));
377
map_view_.pan_by(Vector2i(0, scrollval));
327
379
if (get_key_state(SDL_SCANCODE_LEFT) ||
328
380
(get_key_state(SDL_SCANCODE_KP_4) && (SDL_GetModState() ^ KMOD_NUM))) {
329
pan_by(Vector2i(-scrollval, 0));
381
map_view_.pan_by(Vector2i(-scrollval, 0));
331
383
if (get_key_state(SDL_SCANCODE_RIGHT) ||
332
384
(get_key_state(SDL_SCANCODE_KP_6) && (SDL_GetModState() ^ KMOD_NUM))) {
333
pan_by(Vector2i(scrollval, 0));
385
map_view_.pan_by(Vector2i(scrollval, 0));
336
388
egbase().think(); // Call game logic here. The game advances.
351
403
avg_usframetime_ = ((avg_usframetime_ * 15) + (frametime_ * 1000)) / 16;
352
404
lastframe_ = curframe;
406
Game* game = dynamic_cast<Game*>(&egbase());
354
408
// This portion of code keeps the speed of game so that FPS are kept within
355
409
// range 13 - 15, this is used for training of AI
356
if (AItrainingMode) {
357
if (upcast(Game, game, &egbase())) {
410
if (game != nullptr) {
411
if (game->is_auto_speed()) {
358
412
uint32_t cur_fps = 1000000 / avg_usframetime_;
359
413
int32_t speed_diff = 0;
360
414
if (cur_fps < 13) {
379
const Map& map = egbase().map();
380
const bool is_game = dynamic_cast<const Game*>(&egbase());
382
432
// Blit node information when in debug mode.
383
if (get_display_flag(dfDebug) || !is_game) {
433
if (get_display_flag(dfDebug) || game == nullptr) {
384
434
std::string node_text;
435
if (game != nullptr) {
386
436
const std::string gametime(gametimestring(egbase().get_gametime(), true));
387
437
std::shared_ptr<const UI::RenderedText> rendered_text =
388
438
UI::g_fh1->render(as_condensed(gametime));
392
442
node_text = as_condensed((node_format % sel_.pos.node.x % sel_.pos.node.y).str());
393
443
} else { // This is an editor
394
444
static boost::format node_format("(%i, %i, %i)");
395
const int32_t height = map[sel_.pos.node].get_height();
445
const int32_t height = egbase().map()[sel_.pos.node].get_height();
396
446
node_text = as_condensed((node_format % sel_.pos.node.x % sel_.pos.node.y % height).str());
398
448
std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(node_text);
412
462
void InteractiveBase::mainview_move() {
413
if (m->minimap.window) {
414
m->mm->set_view(view_area().rect());
463
if (minimap_registry_.window) {
464
minimap_->set_view(map_view_.view_area().rect());
418
468
// Open the minimap or close it if it's open
419
469
void InteractiveBase::toggle_minimap() {
420
if (m->minimap.window) {
421
delete m->minimap.window;
470
if (minimap_registry_.window) {
471
delete minimap_registry_.window;
423
m->mm = new MiniMap(*this, &m->minimap);
424
m->mm->warpview.connect(
425
[this](const Vector2f& map_pixel) { scroll_to_map_pixel(map_pixel, Transition::Smooth); });
473
minimap_ = new MiniMap(*this, &minimap_registry_);
474
minimap_->warpview.connect([this](const Vector2f& map_pixel) {
475
map_view_.scroll_to_map_pixel(map_pixel, MapView::Transition::Smooth);
430
481
const std::vector<QuickNavigation::Landmark>& InteractiveBase::landmarks() {
431
return m->quicknavigation->landmarks();
482
return quick_navigation_.landmarks();
434
485
void InteractiveBase::set_landmark(size_t key, const MapView::View& landmark_view) {
435
m->quicknavigation->set_landmark(key, landmark_view);
486
quick_navigation_.set_landmark(key, landmark_view);
439
490
* Hide the minimap if it is currently shown; otherwise, do nothing.
441
492
void InteractiveBase::hide_minimap() {
442
m->minimap.destroy();
493
minimap_registry_.destroy();
681
732
void InteractiveBase::roadb_add_overlay() {
682
733
assert(buildroad_);
734
assert(road_building_overlays_.road_previews.empty());
735
assert(road_building_overlays_.steepness_indicators.empty());
684
Map& map = egbase().map();
737
const Map& map = egbase().map();
686
739
// preview of the road
688
jobid_ = field_overlay_manager_->next_overlay_id();
689
740
const CoordPath::StepVector::size_type nr_steps = buildroad_->get_nsteps();
690
741
for (CoordPath::StepVector::size_type idx = 0; idx < nr_steps; ++idx) {
691
742
Widelands::Direction dir = (*buildroad_)[idx];
695
746
map.get_neighbour(c, dir, &c);
696
747
dir = Widelands::get_reverse_dir(dir);
699
749
int32_t const shift = 2 * (dir - Widelands::WALK_E);
701
uint8_t set_to = edge_overlay_manager_->get_overlay(c);
702
set_to |= Widelands::RoadType::kNormal << shift;
703
edge_overlay_manager_->register_overlay(c, set_to, jobid_);
750
road_building_overlays_.road_previews[c] |= (Widelands::RoadType::kNormal << shift);
707
754
Widelands::FCoords endpos = map.get_fcoords(buildroad_->get_end());
709
assert(!road_buildhelp_overlay_jobid_);
710
road_buildhelp_overlay_jobid_ = field_overlay_manager_->next_overlay_id();
711
756
for (int32_t dir = 1; dir <= 6; ++dir) {
712
757
Widelands::FCoords neighb;
748
793
name = "images/wui/overlays/roadb_yellow.png";
750
795
name = "images/wui/overlays/roadb_red.png";
752
field_overlay_manager_->register_overlay(
753
neighb, g_gr->images().get(name), 7, Vector2i::invalid(), road_buildhelp_overlay_jobid_);
796
road_building_overlays_.steepness_indicators[neighb] = g_gr->images().get(name);
762
805
void InteractiveBase::roadb_remove_overlay() {
763
806
assert(buildroad_);
765
// preview of the road
767
edge_overlay_manager_->remove_overlay(jobid_);
772
if (road_buildhelp_overlay_jobid_) {
773
field_overlay_manager_->remove_overlay(road_buildhelp_overlay_jobid_);
775
road_buildhelp_overlay_jobid_ = 0;
807
road_building_overlays_.road_previews.clear();
808
road_building_overlays_.steepness_indicators.clear();
778
811
bool InteractiveBase::handle_key(bool const down, SDL_Keysym const code) {
779
if (m->quicknavigation->handle_key(down, code))
812
if (quick_navigation_.handle_key(down, code))