1 /*
2 * Copyright (C) 2004-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 // UI classes for real-time game debugging
20
21 #include "wui/game_debug_ui.h"
22
23 #include "base/i18n.h"
24 #include "graphic/graphic.h"
25 #include "logic/field.h"
26 #include "logic/map.h"
27 #include "logic/map_objects/bob.h"
28 #include "logic/map_objects/map_object.h"
29 #include "logic/map_objects/tribes/building.h"
30 #include "logic/map_objects/world/resource_description.h"
31 #include "logic/map_objects/world/world.h"
32 #include "logic/player.h"
33 #include "ui_basic/button.h"
34 #include "ui_basic/listselect.h"
35 #include "ui_basic/multilinetextarea.h"
36 #include "ui_basic/panel.h"
37 #include "ui_basic/tabpanel.h"
38 #include "ui_basic/window.h"
39 #include "wui/interactive_base.h"
40
41 struct MapObjectDebugPanel : public UI::Panel, public Widelands::MapObject::LogSink {
42 MapObjectDebugPanel(UI::Panel& parent, const Widelands::EditorGameBase&, Widelands::MapObject&);
43 ~MapObjectDebugPanel() override;
44
45 void log(const std::string& str) override;
46
47 private:
48 const Widelands::EditorGameBase& egbase_;
49 Widelands::ObjectPointer object_;
50
51 UI::MultilineTextarea log_;
52 };
53
MapObjectDebugPanel(UI::Panel & parent,const Widelands::EditorGameBase & egbase,Widelands::MapObject & obj)54 MapObjectDebugPanel::MapObjectDebugPanel(UI::Panel& parent,
55 const Widelands::EditorGameBase& egbase,
56 Widelands::MapObject& obj)
57 : UI::Panel(&parent, 0, 0, 350, 200),
58 egbase_(egbase),
59 object_(&obj),
60 log_(this,
61 0,
62 0,
63 350,
64 200,
65 UI::PanelStyle::kWui,
66 "",
67 UI::Align::kLeft,
68 UI::MultilineTextarea::ScrollMode::kScrollLog) {
69 obj.set_logsink(this);
70 }
71
~MapObjectDebugPanel()72 MapObjectDebugPanel::~MapObjectDebugPanel() {
73 if (Widelands::MapObject* const obj = object_.get(egbase_))
74 if (obj->get_logsink() == this)
75 obj->set_logsink(nullptr);
76 }
77
78 /*
79 ===============
80 Append the string to the log textarea.
81 ===============
82 */
log(const std::string & str)83 void MapObjectDebugPanel::log(const std::string& str) {
84 log_.set_text((log_.get_text() + str).c_str());
85 }
86
87 /*
88 ==============================================================================
89
90 MapObjectDebugWindow
91
92 ==============================================================================
93 */
94
95 /*
96 MapObjectDebugWindow
97 --------------------
98 The map object debug window is basically just a simple container for tabs
99 that are provided by the map object itself via the virtual function
100 collect_debug_tabs().
101 */
102 struct MapObjectDebugWindow : public UI::Window {
103 MapObjectDebugWindow(InteractiveBase& parent, Widelands::MapObject&);
104
ibaseMapObjectDebugWindow105 InteractiveBase& ibase() {
106 return dynamic_cast<InteractiveBase&>(*get_parent());
107 }
108
109 void think() override;
110
111 private:
112 bool log_general_info_;
113 Widelands::ObjectPointer object_;
114 uint32_t serial_;
115 UI::TabPanel tabs_;
116 };
117
MapObjectDebugWindow(InteractiveBase & parent,Widelands::MapObject & obj)118 MapObjectDebugWindow::MapObjectDebugWindow(InteractiveBase& parent, Widelands::MapObject& obj)
119 : UI::Window(&parent, "map_object_debug", 0, 0, 100, 100, ""),
120 log_general_info_(true),
121 object_(&obj),
122 tabs_(this, UI::TabPanelStyle::kWuiLight) {
123 serial_ = obj.serial();
124 set_title(std::to_string(serial_));
125
126 tabs_.add("debug", g_gr->images().get("images/wui/fieldaction/menu_debug.png"),
127 new MapObjectDebugPanel(tabs_, parent.egbase(), obj));
128
129 set_center_panel(&tabs_);
130 }
131
132 /*
133 ===============
134 Remove self when the object disappears.
135 ===============
136 */
think()137 void MapObjectDebugWindow::think() {
138 Widelands::EditorGameBase& egbase = ibase().egbase();
139 if (Widelands::MapObject* const obj = object_.get(egbase)) {
140 if (log_general_info_) {
141 obj->log_general_info(egbase);
142 log_general_info_ = false;
143 }
144 UI::Window::think();
145 } else {
146 set_title((boost::format("DEAD: %u") % serial_).str());
147 }
148 }
149
150 /*
151 ===============
152 show_mapobject_debug
153
154 Show debug window for a MapObject
155 ===============
156 */
show_mapobject_debug(InteractiveBase & parent,Widelands::MapObject & obj)157 void show_mapobject_debug(InteractiveBase& parent, Widelands::MapObject& obj) {
158 new MapObjectDebugWindow(parent, obj);
159 }
160
161 /*
162 ==============================================================================
163
164 FieldDebugWindow
165
166 ==============================================================================
167 */
168
169 struct FieldDebugWindow : public UI::Window {
170 FieldDebugWindow(InteractiveBase& parent, Widelands::Coords);
171
ibaseFieldDebugWindow172 InteractiveBase& ibase() {
173 return dynamic_cast<InteractiveBase&>(*get_parent());
174 }
175
176 void think() override;
177
178 void open_immovable();
179 void open_bob(uint32_t);
180
181 private:
182 const Widelands::Map& map_;
183 Widelands::FCoords const coords_;
184
185 UI::MultilineTextarea ui_field_;
186 UI::Button ui_immovable_;
187 UI::Listselect<intptr_t> ui_bobs_;
188 };
189
FieldDebugWindow(InteractiveBase & parent,Widelands::Coords const coords)190 FieldDebugWindow::FieldDebugWindow(InteractiveBase& parent, Widelands::Coords const coords)
191 : /** TRANSLATORS: Title for a window that shows debug information for a field on the map */
192 UI::Window(&parent, "field_debug", 0, 60, 300, 400, _("Debug Field")),
193 map_(parent.egbase().map()),
194 coords_(map_.get_fcoords(coords)),
195
196 // setup child panels
197 ui_field_(this, 0, 0, 300, 280, UI::PanelStyle::kWui, ""),
198
199 ui_immovable_(this, "immovable", 0, 280, 300, 24, UI::ButtonStyle::kWuiMenu, ""),
200
201 ui_bobs_(this, 0, 304, 300, 96, UI::PanelStyle::kWui) {
202 ui_immovable_.sigclicked.connect([this]() { open_immovable(); });
203
204 assert(0 <= coords_.x);
205 assert(coords_.x < map_.get_width());
206 assert(0 <= coords_.y);
207 assert(coords_.y < map_.get_height());
208 assert(&map_[0] <= coords_.field);
209 assert(coords_.field < &map_[0] + map_.max_index());
210 ui_bobs_.selected.connect([this](uint32_t a) { open_bob(a); });
211 }
212
213 /*
214 ===============
215 Gather information about the field and update the UI elements.
216 This is done every frame in order to have up to date information all the time.
217 ===============
218 */
think()219 void FieldDebugWindow::think() {
220 std::string str;
221
222 UI::Window::think();
223
224 // Select information about the field itself
225 const Widelands::EditorGameBase& egbase =
226 dynamic_cast<const InteractiveBase&>(*get_parent()).egbase();
227 {
228 Widelands::PlayerNumber const owner = coords_.field->get_owned_by();
229 str +=
230 (boost::format("(%i, %i)\nheight: %u\nowner: %u\n") % coords_.x % coords_.y %
231 static_cast<unsigned int>(coords_.field->get_height()) % static_cast<unsigned int>(owner))
232 .str();
233
234 if (owner) {
235 Widelands::NodeCaps const buildcaps = egbase.player(owner).get_buildcaps(coords_);
236 if (buildcaps & Widelands::BUILDCAPS_BIG)
237 str += " can build big building\n";
238 else if (buildcaps & Widelands::BUILDCAPS_MEDIUM)
239 str += " can build medium building\n";
240 else if (buildcaps & Widelands::BUILDCAPS_SMALL)
241 str += " can build small building\n";
242 if (buildcaps & Widelands::BUILDCAPS_FLAG)
243 str += " can place flag\n";
244 if (buildcaps & Widelands::BUILDCAPS_MINE)
245 str += " can build mine\n";
246 if (buildcaps & Widelands::BUILDCAPS_PORT)
247 str += " can build port\n";
248 }
249 }
250 if (coords_.field->nodecaps() & Widelands::MOVECAPS_WALK)
251 str += "is walkable\n";
252 if (coords_.field->nodecaps() & Widelands::MOVECAPS_SWIM)
253 str += "is swimmable\n";
254 Widelands::MapIndex const i = coords_.field - &map_[0];
255 Widelands::PlayerNumber const nr_players = map_.get_nrplayers();
256 iterate_players_existing_const(plnum, nr_players, egbase, player) {
257 const Widelands::Player::Field& player_field = player->fields()[i];
258 str += (boost::format("Player %u:\n") % static_cast<unsigned int>(plnum)).str();
259 str += (boost::format(" military influence: %u\n") % player_field.military_influence).str();
260
261 Widelands::Vision const vision = player_field.vision;
262 str += (boost::format(" vision: %u\n") % vision).str();
263 {
264 Widelands::Time const time_last_surveyed =
265 player_field.time_triangle_last_surveyed[static_cast<int>(Widelands::TriangleIndex::D)];
266
267 if (time_last_surveyed != Widelands::never()) {
268 str += (boost::format(" D triangle last surveyed at %u: amount %u\n") %
269 time_last_surveyed % static_cast<unsigned int>(player_field.resource_amounts.d))
270 .str();
271
272 } else
273 str += " D triangle never surveyed\n";
274 }
275 {
276 Widelands::Time const time_last_surveyed =
277 player_field.time_triangle_last_surveyed[static_cast<int>(Widelands::TriangleIndex::R)];
278
279 if (time_last_surveyed != Widelands::never()) {
280 str += (boost::format(" R triangle last surveyed at %u: amount %u\n") %
281 time_last_surveyed % static_cast<unsigned int>(player_field.resource_amounts.r))
282 .str();
283
284 } else
285 str += " R triangle never surveyed\n";
286 }
287 switch (vision) {
288 case 0:
289 str += " never seen\n";
290 break;
291 case 1: {
292 std::string animation_name = "(no animation)";
293 if (player_field.map_object_descr) {
294 animation_name = "(seen an animation)";
295 }
296 str += (boost::format(" last seen at %u:\n"
297 " owner: %u\n"
298 " immovable animation:\n%s\n"
299 " ") %
300 player_field.time_node_last_unseen %
301 static_cast<unsigned int>(player_field.owner) % animation_name.c_str())
302 .str();
303 break;
304 }
305 default:
306 str += (boost::format(" seen %u times\n") % (vision - 1)).str();
307 break;
308 }
309 }
310 {
311 const Widelands::DescriptionIndex ridx = coords_.field->get_resources();
312
313 if (ridx == Widelands::kNoResource) {
314 str += "Resource: None\n";
315 } else {
316 const Widelands::ResourceAmount ramount = coords_.field->get_resources_amount();
317 const Widelands::ResourceAmount initial_amount = coords_.field->get_initial_res_amount();
318
319 str += (boost::format("Resource: %s\n") %
320 ibase().egbase().world().get_resource(ridx)->name().c_str())
321 .str();
322
323 str += (boost::format(" Amount: %i/%i\n") % static_cast<unsigned int>(ramount) %
324 static_cast<unsigned int>(initial_amount))
325 .str();
326 }
327 }
328
329 ui_field_.set_text(str.c_str());
330
331 // Immovable information
332 if (Widelands::BaseImmovable* const imm = coords_.field->get_immovable()) {
333 ui_immovable_.set_title(
334 (boost::format("%s (%u)") % imm->descr().name().c_str() % imm->serial()).str());
335 ui_immovable_.set_enabled(true);
336 } else {
337 ui_immovable_.set_title("no immovable");
338 ui_immovable_.set_enabled(false);
339 }
340
341 // Bobs information
342 std::vector<Widelands::Bob*> bobs;
343 map_.find_bobs(egbase, Widelands::Area<Widelands::FCoords>(coords_, 0), &bobs);
344
345 // Do not clear the list. Instead parse all bobs and sync lists
346 for (uint32_t idx = 0; idx < ui_bobs_.size(); idx++) {
347 Widelands::MapObject* mo = ibase().egbase().objects().get_object(ui_bobs_[idx]);
348 bool toremove = false;
349 std::vector<Widelands::Bob*>::iterator removeIt;
350 // Nested loop :(
351
352 for (std::vector<Widelands::Bob*>::iterator bob_iter = bobs.begin(); bob_iter != bobs.end();
353 ++bob_iter) {
354
355 if ((*bob_iter) && mo && (*bob_iter)->serial() == mo->serial()) {
356 // Remove from the bob list if we already
357 // have it in our list
358 toremove = true;
359 removeIt = bob_iter;
360 break;
361 }
362 }
363 if (toremove) {
364 bobs.erase(removeIt);
365 continue;
366 }
367 // Remove from our list if its not in the bobs
368 // list, or if it doesn't seem to exist anymore
369 ui_bobs_.remove(idx);
370 idx--; // reiter the same index
371 }
372 // Add remaining
373 for (const Widelands::Bob* temp_bob : bobs) {
374 ui_bobs_.add((boost::format("%s (%u)") % temp_bob->descr().name() % temp_bob->serial()).str(),
375 temp_bob->serial());
376 }
377 }
378
379 /*
380 ===============
381 Open the debug window for the immovable on our position.
382 ===============
383 */
open_immovable()384 void FieldDebugWindow::open_immovable() {
385 if (Widelands::BaseImmovable* const imm = coords_.field->get_immovable())
386 show_mapobject_debug(ibase(), *imm);
387 }
388
389 /*
390 ===============
391 Open the bob debug window for the bob of the given index in the list
392 ===============
393 */
open_bob(const uint32_t index)394 void FieldDebugWindow::open_bob(const uint32_t index) {
395 if (index != UI::Listselect<intptr_t>::no_selection_index())
396 if (Widelands::MapObject* const object =
397 ibase().egbase().objects().get_object(ui_bobs_.get_selected()))
398 show_mapobject_debug(ibase(), *object);
399 }
400
401 /*
402 ===============
403 show_field_debug
404
405 Open a debug window for the given field.
406 ===============
407 */
show_field_debug(InteractiveBase & parent,const Widelands::Coords & coords)408 void show_field_debug(InteractiveBase& parent, const Widelands::Coords& coords) {
409 new FieldDebugWindow(parent, coords);
410 }
411