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