← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/bug-794407-soldier-stats into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/bug-794407-soldier-stats into lp:widelands.

Commit message:
Improve soldier level graphics and chat menu code
- Improve visibility of health bar graphs
- Add toggle for soldier level/health bar graphics
- Small refactoring for cat menu code

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #794407 in widelands: "Improve soldiers status display"
  https://bugs.launchpad.net/widelands/+bug/794407
  Bug #968984 in widelands: "Improve soldiers' health bar graphics"
  https://bugs.launchpad.net/widelands/+bug/968984

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/bug-794407-soldier-stats/+merge/371952
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/bug-794407-soldier-stats into lp:widelands.
=== added file 'data/images/wui/menus/toggle_soldier_levels.png'
Binary files data/images/wui/menus/toggle_soldier_levels.png	1970-01-01 00:00:00 +0000 and data/images/wui/menus/toggle_soldier_levels.png	2019-08-28 20:08:47 +0000 differ
=== modified file 'data/tribes/scripting/help/controls.lua'
--- data/tribes/scripting/help/controls.lua	2018-04-28 09:24:11 +0000
+++ data/tribes/scripting/help/controls.lua	2019-08-28 20:08:47 +0000
@@ -50,6 +50,8 @@
                dl(help_format_hotkey("C"), _"Toggle census") ..
                -- TRANSLATORS: This is an access key combination. The hotkey is 's'
                dl(help_format_hotkey("S"), _"Toggle statistics") ..
+               -- TRANSLATORS: This is an access key combination. The hotkey is 'l'
+               dl(help_format_hotkey("L"), _"Toggle soldier health bars and level icons") ..
                toggle_minimap_hotkey ..
                toggle_building_spaces_hotkey ..
                -- TRANSLATORS: This is an access key combination. The hotkey is 'o'

=== modified file 'src/economy/flag.cc'
--- src/economy/flag.cc	2019-07-05 11:16:24 +0000
+++ src/economy/flag.cc	2019-08-28 20:08:47 +0000
@@ -785,7 +785,7 @@
 }
 
 void Flag::draw(uint32_t gametime,
-                const TextToDraw,
+                const InfoToDraw,
                 const Vector2f& field_on_dst,
                 const Coords& coords,
                 float scale,

=== modified file 'src/economy/flag.h'
--- src/economy/flag.h	2019-04-24 06:01:37 +0000
+++ src/economy/flag.h	2019-08-28 20:08:47 +0000
@@ -26,7 +26,7 @@
 
 #include "base/macros.h"
 #include "economy/routing_node.h"
-#include "logic/map_objects/draw_text.h"
+#include "logic/map_objects/info_to_draw.h"
 #include "logic/map_objects/immovable.h"
 #include "logic/map_objects/walkingdir.h"
 
@@ -154,7 +154,7 @@
 	void cleanup(EditorGameBase&) override;
 
 	void draw(uint32_t gametime,
-	          TextToDraw draw_text,
+	          InfoToDraw info_to_draw,
 	          const Vector2f& point_on_dst,
 	          const Coords& coords,
 	          float scale,

=== modified file 'src/economy/portdock.h'
--- src/economy/portdock.h	2019-07-05 11:16:24 +0000
+++ src/economy/portdock.h	2019-08-28 20:08:47 +0000
@@ -135,8 +135,12 @@
 	friend struct Fleet;
 
 	// Does nothing - we do not show them on the map
-	void draw(uint32_t, TextToDraw, const Vector2f&, const Coords&, float, RenderTarget*) override {
-	}
+	void draw(uint32_t,
+	          InfoToDraw,
+	          const Vector2f&,
+	          const Coords&,
+	          float,
+			  RenderTarget*) override {}
 
 	void init_fleet(EditorGameBase& egbase);
 	void set_fleet(Fleet* fleet);

=== modified file 'src/economy/road.h'
--- src/economy/road.h	2019-07-05 11:16:24 +0000
+++ src/economy/road.h	2019-08-28 20:08:47 +0000
@@ -132,8 +132,12 @@
 
 private:
 	/** The road is drawn by the terrain renderer via marked fields. */
-	void draw(uint32_t, TextToDraw, const Vector2f&, const Coords&, float, RenderTarget*) override {
-	}
+	void draw(uint32_t,
+	          InfoToDraw,
+	          const Vector2f&,
+	          const Coords&,
+	          float,
+			  RenderTarget*) override {}
 
 	void set_path(EditorGameBase&, const Path&);
 

=== modified file 'src/editor/editorinteractive.cc'
--- src/editor/editorinteractive.cc	2019-08-28 06:12:07 +0000
+++ src/editor/editorinteractive.cc	2019-08-28 20:08:47 +0000
@@ -588,7 +588,7 @@
 			Widelands::BaseImmovable* const imm = field.fcoords.field->get_immovable();
 			if (imm != nullptr && imm->get_positions(ebase).front() == field.fcoords) {
 				imm->draw(
-				   gametime, TextToDraw::kNone, field.rendertarget_pixel, field.fcoords, scale, &dst);
+				   gametime, InfoToDraw::kNone, field.rendertarget_pixel, field.fcoords, scale, &dst);
 			}
 		}
 
@@ -596,7 +596,7 @@
 			for (Widelands::Bob* bob = field.fcoords.field->get_first_bob(); bob;
 			     bob = bob->get_next_bob()) {
 				bob->draw(
-				   ebase, TextToDraw::kNone, field.rendertarget_pixel, field.fcoords, scale, &dst);
+				   ebase, InfoToDraw::kNone, field.rendertarget_pixel, field.fcoords, scale, &dst);
 			}
 		}
 
@@ -784,19 +784,10 @@
 				select_tool(tools_->current(), EditorTool::Third);
 			return true;
 
-		case SDLK_SPACE:
-			toggle_buildhelp();
-			return true;
-
 		case SDLK_g:
 			toggle_grid();
 			return true;
 
-		case SDLK_c:
-			set_display_flag(
-			   InteractiveBase::dfShowCensus, !get_display_flag(InteractiveBase::dfShowCensus));
-			return true;
-
 		case SDLK_h:
 			mainmenu_.toggle();
 			return true;

=== modified file 'src/logic/map_objects/CMakeLists.txt'
--- src/logic/map_objects/CMakeLists.txt	2019-08-09 17:29:40 +0000
+++ src/logic/map_objects/CMakeLists.txt	2019-08-28 20:08:47 +0000
@@ -29,7 +29,6 @@
     buildcost.h
     checkstep.cc
     checkstep.h
-    draw_text.h
     findbob.cc
     findbob.h
     findimmovable.cc

=== modified file 'src/logic/map_objects/bob.cc'
--- src/logic/map_objects/bob.cc	2019-05-11 13:48:12 +0000
+++ src/logic/map_objects/bob.cc	2019-08-28 20:08:47 +0000
@@ -758,7 +758,7 @@
 /// Note that the current node is actually the node that we are walking to, not
 /// the the one that we start from.
 void Bob::draw(const EditorGameBase& egbase,
-               const TextToDraw&,
+               const InfoToDraw&,
                const Vector2f& field_on_dst,
                const Widelands::Coords& coords,
                const float scale,

=== modified file 'src/logic/map_objects/bob.h'
--- src/logic/map_objects/bob.h	2019-04-24 06:01:37 +0000
+++ src/logic/map_objects/bob.h	2019-08-28 20:08:47 +0000
@@ -25,7 +25,7 @@
 #include "economy/route.h"
 #include "graphic/animation.h"
 #include "graphic/diranimations.h"
-#include "logic/map_objects/draw_text.h"
+#include "logic/map_objects/info_to_draw.h"
 #include "logic/map_objects/map_object.h"
 #include "logic/map_objects/walkingdir.h"
 #include "logic/widelands_geometry.h"
@@ -264,7 +264,7 @@
 	// starting field) in pixel space of 'dst' (including scale). The 'scale' is
 	// required to draw the bob in the right size.
 	virtual void draw(const EditorGameBase&,
-	                  const TextToDraw& draw_text,
+	                  const InfoToDraw& info_to_draw,
 	                  const Vector2f& field_on_dst,
 	                  const Coords& coords,
 	                  float scale,

=== removed file 'src/logic/map_objects/draw_text.h'
--- src/logic/map_objects/draw_text.h	2019-03-29 16:27:34 +0000
+++ src/logic/map_objects/draw_text.h	1970-01-01 00:00:00 +0000
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2006-2019 by the Widelands Development Team
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- */
-
-#ifndef WL_LOGIC_MAP_OBJECTS_DRAW_TEXT_H
-#define WL_LOGIC_MAP_OBJECTS_DRAW_TEXT_H
-
-// Only declare powers of 2 and adjust the ~ operator below if you add any values to this enum
-// class.
-enum class TextToDraw {
-	kNone = 0,
-	kCensus = 1,
-	kStatistics = 2,
-};
-
-inline TextToDraw operator|(TextToDraw a, TextToDraw b) {
-	return static_cast<TextToDraw>(static_cast<int>(a) | static_cast<int>(b));
-}
-inline TextToDraw operator&(TextToDraw a, TextToDraw b) {
-	return static_cast<TextToDraw>(static_cast<int>(a) & static_cast<int>(b));
-}
-inline TextToDraw removeFromTextToDraw(TextToDraw base, TextToDraw remove) {
-	const int result = static_cast<int>(base) & ~static_cast<int>(remove);
-	assert(result >= 0);
-	assert(result <= 2);
-	return static_cast<TextToDraw>(result);
-}
-
-#endif  // end of include guard: WL_LOGIC_MAP_OBJECTS_DRAW_TEXT_H

=== modified file 'src/logic/map_objects/immovable.cc'
--- src/logic/map_objects/immovable.cc	2019-05-28 17:01:30 +0000
+++ src/logic/map_objects/immovable.cc	2019-08-28 20:08:47 +0000
@@ -463,7 +463,7 @@
 }
 
 void Immovable::draw(uint32_t gametime,
-                     const TextToDraw draw_text,
+                     const InfoToDraw info_to_draw,
                      const Vector2f& point_on_dst,
                      const Widelands::Coords& coords,
                      float scale,
@@ -474,15 +474,15 @@
 	if (!anim_construction_total_) {
 		dst->blit_animation(point_on_dst, coords, scale, anim_, gametime - animstart_);
 		if (former_building_descr_) {
-			do_draw_info(draw_text, former_building_descr_->descname(), "", point_on_dst, scale, dst);
+			do_draw_info(info_to_draw, former_building_descr_->descname(), "", point_on_dst, scale, dst);
 		}
 	} else {
-		draw_construction(gametime, draw_text, point_on_dst, coords, scale, dst);
+		draw_construction(gametime, info_to_draw, point_on_dst, coords, scale, dst);
 	}
 }
 
 void Immovable::draw_construction(const uint32_t gametime,
-                                  const TextToDraw draw_text,
+                                  const InfoToDraw info_to_draw,
                                   const Vector2f& point_on_dst,
                                   const Widelands::Coords& coords,
                                   const float scale,
@@ -525,7 +525,7 @@
 
 	// Additionally, if statistics are enabled, draw a progression string
 	do_draw_info(
-	   draw_text, descr().descname(),
+	   info_to_draw, descr().descname(),
 	   g_gr->styles().color_tag((boost::format(_("%i%% built")) % (100 * done / total)).str(),
 	                            g_gr->styles().building_statistics_style().construction_color()),
 	   point_on_dst, scale, dst);

=== modified file 'src/logic/map_objects/immovable.h'
--- src/logic/map_objects/immovable.h	2019-04-24 06:01:37 +0000
+++ src/logic/map_objects/immovable.h	2019-08-28 20:08:47 +0000
@@ -26,7 +26,7 @@
 #include "base/macros.h"
 #include "graphic/animation.h"
 #include "logic/map_objects/buildcost.h"
-#include "logic/map_objects/draw_text.h"
+#include "logic/map_objects/info_to_draw.h"
 #include "logic/map_objects/map_object.h"
 #include "logic/widelands_geometry.h"
 #include "notifications/note_ids.h"
@@ -97,13 +97,13 @@
 	virtual PositionList get_positions(const EditorGameBase&) const = 0;
 
 	// Draw this immovable onto 'dst' choosing the frame appropriate for
-	// 'gametime'. 'draw_text' decides if census and statistics are written too.
+	// 'gametime'. 'info_to_draw' decides if census and statistics are written too.
 	// The 'coords_to_draw' are passed one to give objects that occupy multiple
 	// fields a way to only draw themselves once. The 'point_on_dst' determines
 	// the point for the hotspot of the animation and 'scale' determines how big
 	// the immovable will be plotted.
 	virtual void draw(uint32_t gametime,
-	                  TextToDraw draw_text,
+	                  InfoToDraw info_to_draw,
 	                  const Vector2f& point_on_dst,
 	                  const Coords& coords,
 	                  float scale,
@@ -229,7 +229,7 @@
 	void cleanup(EditorGameBase&) override;
 	void act(Game&, uint32_t data) override;
 	void draw(uint32_t gametime,
-	          TextToDraw draw_text,
+	          InfoToDraw info_to_draw,
 	          const Vector2f& point_on_dst,
 	          const Coords& coords,
 	          float scale,
@@ -316,7 +316,7 @@
 
 	void increment_program_pointer();
 	void draw_construction(uint32_t gametime,
-	                       TextToDraw draw_text,
+	                       InfoToDraw info_to_draw,
 	                       const Vector2f& point_on_dst,
 	                       const Widelands::Coords& coords,
 	                       float scale,

=== added file 'src/logic/map_objects/info_to_draw.h'
--- src/logic/map_objects/info_to_draw.h	1970-01-01 00:00:00 +0000
+++ src/logic/map_objects/info_to_draw.h	2019-08-28 20:08:47 +0000
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2006-2017 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef WL_LOGIC_MAP_OBJECTS_INFO_TO_DRAW_H
+#define WL_LOGIC_MAP_OBJECTS_INFO_TO_DRAW_H
+
+enum InfoToDraw {
+	kNone = 0,
+	kCensus = 1,
+	kStatistics = 2,
+	kSoldierLevels = 4,
+};
+
+inline InfoToDraw operator|(InfoToDraw a, InfoToDraw b) {
+	return static_cast<InfoToDraw>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+#endif  // end of include guard: WL_LOGIC_MAP_OBJECTS_INFO_TO_DRAW_H

=== modified file 'src/logic/map_objects/map_object.cc'
--- src/logic/map_objects/map_object.cc	2019-05-27 21:04:13 +0000
+++ src/logic/map_objects/map_object.cc	2019-08-28 20:08:47 +0000
@@ -506,20 +506,24 @@
 	egbase.objects().remove(*this);
 }
 
-void MapObject::do_draw_info(const TextToDraw& draw_text,
+void MapObject::do_draw_info(const InfoToDraw& info_to_draw,
                              const std::string& census,
                              const std::string& statictics,
                              const Vector2f& field_on_dst,
                              float scale,
                              RenderTarget* dst) const {
-	if (draw_text == TextToDraw::kNone) {
+	if (!(info_to_draw & (InfoToDraw::kCensus | InfoToDraw::kStatistics))) {
 		return;
 	}
 
 	// Rendering text is expensive, so let's just do it for only a few sizes.
+	const float granularity = 4.f;
+	float text_scale = scale;
 	// The formula is a bit fancy to avoid too much text overlap.
-	scale = std::round(2.f * (scale > 1.f ? std::sqrt(scale) : std::pow(scale, 2.f))) / 2.f;
-	if (scale < 1.f) {
+	text_scale = std::round(granularity * (text_scale > 1.f ? std::sqrt(text_scale) : std::pow(text_scale, 2.f))) / granularity;
+
+	// Skip tiny text for performance reasons
+	if (text_scale < 0.5f) {
 		return;
 	}
 
@@ -530,11 +534,12 @@
 	std::shared_ptr<const UI::RenderedText> rendered_census =
 	   UI::g_fh->render(as_richtext_paragraph(census, census_font, UI::Align::kCenter), 120 * scale);
 	Vector2i position = field_on_dst.cast<int>() - Vector2i(0, 48) * scale;
-	if ((draw_text & TextToDraw::kCensus) != TextToDraw::kNone) {
+	if (info_to_draw & InfoToDraw::kCensus) {
 		rendered_census->draw(*dst, position, UI::Align::kCenter);
 	}
 
-	if ((draw_text & TextToDraw::kStatistics) != TextToDraw::kNone && !statictics.empty()) {
+	// Draw statistics if we want them, they are available and they fill fit
+	if (info_to_draw & InfoToDraw::kStatistics && !statictics.empty() && scale >= 0.5f) {
 		UI::FontStyleInfo statistics_font(
 		   g_gr->styles().building_statistics_style().statistics_font());
 		statistics_font.set_size(scale * statistics_font.size());

=== modified file 'src/logic/map_objects/map_object.h'
--- src/logic/map_objects/map_object.h	2019-05-25 08:51:42 +0000
+++ src/logic/map_objects/map_object.h	2019-08-28 20:08:47 +0000
@@ -35,7 +35,7 @@
 #include "graphic/color.h"
 #include "graphic/image.h"
 #include "logic/cmd_queue.h"
-#include "logic/map_objects/draw_text.h"
+#include "logic/map_objects/info_to_draw.h"
 #include "logic/map_objects/tribes/training_attribute.h"
 #include "logic/widelands.h"
 #include "scripting/lua_table.h"
@@ -407,7 +407,7 @@
 	virtual void cleanup(EditorGameBase&);
 
 	/// Draws census and statistics on screen
-	void do_draw_info(const TextToDraw& draw_text,
+	void do_draw_info(const InfoToDraw& info_to_draw,
 	                  const std::string& census,
 	                  const std::string& statictics,
 	                  const Vector2f& field_on_dst,

=== modified file 'src/logic/map_objects/tribes/building.cc'
--- src/logic/map_objects/tribes/building.cc	2019-06-25 08:03:30 +0000
+++ src/logic/map_objects/tribes/building.cc	2019-08-28 20:08:47 +0000
@@ -605,7 +605,7 @@
 }
 
 void Building::draw(uint32_t gametime,
-                    const TextToDraw draw_text,
+                    const InfoToDraw info_to_draw,
                     const Vector2f& point_on_dst,
                     const Widelands::Coords& coords,
                     const float scale,
@@ -616,7 +616,7 @@
 	//  door animation?
 
 	//  overlay strings (draw when enabled)
-	draw_info(draw_text, point_on_dst, scale, dst);
+	draw_info(info_to_draw, point_on_dst, scale, dst);
 }
 
 /*
@@ -624,15 +624,13 @@
 Draw overlay help strings when enabled.
 ===============
 */
-void Building::draw_info(const TextToDraw draw_text,
+void Building::draw_info(const InfoToDraw info_to_draw,
                          const Vector2f& point_on_dst,
                          const float scale,
                          RenderTarget* dst) {
 	const std::string statistics_string =
-	   ((draw_text & TextToDraw::kStatistics) != TextToDraw::kNone) ?
-	      info_string(InfoStringFormat::kStatistics) :
-	      "";
-	do_draw_info(draw_text, info_string(InfoStringFormat::kCensus), statistics_string, point_on_dst,
+	   (info_to_draw & InfoToDraw::kStatistics) ? info_string(InfoStringFormat::kStatistics) : "";
+	do_draw_info(info_to_draw, info_string(InfoStringFormat::kCensus), statistics_string, point_on_dst,
 	             scale, dst);
 }
 

=== modified file 'src/logic/map_objects/tribes/building.h'
--- src/logic/map_objects/tribes/building.h	2019-06-23 11:41:17 +0000
+++ src/logic/map_objects/tribes/building.h	2019-08-28 20:08:47 +0000
@@ -344,13 +344,13 @@
 	void act(Game&, uint32_t data) override;
 
 	void draw(uint32_t gametime,
-	          TextToDraw draw_text,
+	          InfoToDraw info_to_draw,
 	          const Vector2f& point_on_dst,
 	          const Coords& coords,
 	          float scale,
 	          RenderTarget* dst) override;
 	void
-	draw_info(TextToDraw draw_text, const Vector2f& point_on_dst, float scale, RenderTarget* dst);
+	draw_info(InfoToDraw info_to_draw, const Vector2f& point_on_dst, float scale, RenderTarget* dst);
 
 	void set_seeing(bool see);
 	void set_attack_target(AttackTarget* new_attack_target);

=== modified file 'src/logic/map_objects/tribes/constructionsite.cc'
--- src/logic/map_objects/tribes/constructionsite.cc	2019-06-23 11:41:17 +0000
+++ src/logic/map_objects/tribes/constructionsite.cc	2019-08-28 20:08:47 +0000
@@ -603,7 +603,7 @@
 ===============
 */
 void ConstructionSite::draw(uint32_t gametime,
-                            TextToDraw draw_text,
+                            InfoToDraw info_to_draw,
                             const Vector2f& point_on_dst,
                             const Widelands::Coords& coords,
                             float scale,
@@ -628,6 +628,6 @@
 	info_.draw(point_on_dst, coords, scale, player_color, dst);
 
 	// Draw help strings
-	draw_info(draw_text, point_on_dst, scale, dst);
+	draw_info(info_to_draw, point_on_dst, scale, dst);
 }
 }  // namespace Widelands

=== modified file 'src/logic/map_objects/tribes/constructionsite.h'
--- src/logic/map_objects/tribes/constructionsite.h	2019-06-23 11:41:17 +0000
+++ src/logic/map_objects/tribes/constructionsite.h	2019-08-28 20:08:47 +0000
@@ -143,7 +143,7 @@
 	static void wares_queue_callback(Game&, InputQueue*, DescriptionIndex, Worker*, void* data);
 
 	void draw(uint32_t gametime,
-	          TextToDraw draw_text,
+	          InfoToDraw info_to_draw,
 	          const Vector2f& point_on_dst,
 	          const Coords& coords,
 	          float scale,

=== modified file 'src/logic/map_objects/tribes/dismantlesite.cc'
--- src/logic/map_objects/tribes/dismantlesite.cc	2019-05-26 17:21:15 +0000
+++ src/logic/map_objects/tribes/dismantlesite.cc	2019-08-28 20:08:47 +0000
@@ -228,7 +228,7 @@
 ===============
 */
 void DismantleSite::draw(uint32_t gametime,
-                         const TextToDraw draw_text,
+                         const InfoToDraw info_to_draw,
                          const Vector2f& point_on_dst,
                          const Widelands::Coords& coords,
                          float scale,
@@ -244,6 +244,6 @@
 	                    &player_color, 100 - ((get_built_per64k() * 100) >> 16));
 
 	// Draw help strings
-	draw_info(draw_text, point_on_dst, scale, dst);
+	draw_info(info_to_draw, point_on_dst, scale, dst);
 }
 }  // namespace Widelands

=== modified file 'src/logic/map_objects/tribes/dismantlesite.h'
--- src/logic/map_objects/tribes/dismantlesite.h	2019-05-05 14:05:07 +0000
+++ src/logic/map_objects/tribes/dismantlesite.h	2019-08-28 20:08:47 +0000
@@ -90,7 +90,7 @@
 	}
 
 	void draw(uint32_t gametime,
-	          TextToDraw draw_text,
+	          InfoToDraw info_to_draw,
 	          const Vector2f& point_on_dst,
 	          const Widelands::Coords& coords,
 	          float scale,

=== modified file 'src/logic/map_objects/tribes/productionsite.cc'
--- src/logic/map_objects/tribes/productionsite.cc	2019-08-20 17:35:33 +0000
+++ src/logic/map_objects/tribes/productionsite.cc	2019-08-28 20:08:47 +0000
@@ -311,18 +311,20 @@
  * Display whether we're occupied.
  */
 void ProductionSite::update_statistics_string(std::string* s) {
-	uint32_t const nr_working_positions = descr().nr_working_positions();
-	uint32_t nr_workers = 0;
-	for (uint32_t i = nr_working_positions; i;)
-		nr_workers += working_positions_[--i].worker ? 1 : 0;
-
-	if (nr_workers == 0) {
-		*s = g_gr->styles().color_tag(
-		   _("(not occupied)"), g_gr->styles().building_statistics_style().low_color());
-		return;
+	uint32_t nr_requests = 0;
+	uint32_t nr_coming = 0;
+	for (uint32_t i = 0; i < descr().nr_working_positions(); ++i) {
+		const Widelands::Request* request = working_positions_[i].worker_request;
+		if (request) {
+			if (request->is_open()) {
+				++nr_requests;
+			} else {
+				++nr_coming;
+			}
+		}
 	}
 
-	if (uint32_t const nr_requests = nr_working_positions - nr_workers) {
+	if (nr_requests > 0) {
 		*s = g_gr->styles().color_tag(
 		   (nr_requests == 1 ?
 		       /** TRANSLATORS: Productivity label on a building if there is 1 worker missing */
@@ -334,6 +336,18 @@
 		return;
 	}
 
+	if (nr_coming > 0) {
+		*s = g_gr->styles().color_tag(
+			  (nr_coming == 1 ?
+		          /** TRANSLATORS: Productivity label on a building if there is 1 worker missing */
+		          _("Worker is coming") :
+		          /** TRANSLATORS: Productivity label on a building if there is more than 1 worker
+		             missing. If you need plural forms here, please let us know. */
+		          _("Workers are coming")),
+		        g_gr->styles().building_statistics_style().low_color());
+		return;
+	}
+
 	if (is_stopped_) {
 		*s = g_gr->styles().color_tag(
 		   _("(stopped)"), g_gr->styles().building_statistics_style().neutral_color());

=== modified file 'src/logic/map_objects/tribes/ship.cc'
--- src/logic/map_objects/tribes/ship.cc	2019-05-27 14:28:34 +0000
+++ src/logic/map_objects/tribes/ship.cc	2019-08-28 20:08:47 +0000
@@ -1020,16 +1020,16 @@
 }
 
 void Ship::draw(const EditorGameBase& egbase,
-                const TextToDraw& draw_text,
+                const InfoToDraw& info_to_draw,
                 const Vector2f& point_on_dst,
                 const Widelands::Coords& coords,
                 const float scale,
                 RenderTarget* dst) const {
-	Bob::draw(egbase, draw_text, point_on_dst, coords, scale, dst);
+	Bob::draw(egbase, info_to_draw, point_on_dst, coords, scale, dst);
 
 	// Show ship name and current activity
 	std::string statistics_string;
-	if ((draw_text & TextToDraw::kStatistics) != TextToDraw::kNone) {
+	if (info_to_draw & InfoToDraw::kStatistics) {
 		switch (ship_state_) {
 		case (ShipStates::kTransport):
 			if (destination_.is_set()) {
@@ -1066,7 +1066,7 @@
 		   statistics_string, g_gr->styles().building_statistics_style().medium_color());
 	}
 
-	do_draw_info(draw_text, shipname_, statistics_string, calc_drawpos(egbase, point_on_dst, scale),
+	do_draw_info(info_to_draw, shipname_, statistics_string, calc_drawpos(egbase, point_on_dst, scale),
 	             scale, dst);
 }
 

=== modified file 'src/logic/map_objects/tribes/ship.h'
--- src/logic/map_objects/tribes/ship.h	2019-04-24 06:51:31 +0000
+++ src/logic/map_objects/tribes/ship.h	2019-08-28 20:08:47 +0000
@@ -236,7 +236,7 @@
 
 protected:
 	void draw(const EditorGameBase&,
-	          const TextToDraw& draw_text,
+	          const InfoToDraw& info_to_draw,
 	          const Vector2f& point_on_dst,
 	          const Coords& coords,
 	          float scale,

=== modified file 'src/logic/map_objects/tribes/soldier.cc'
--- src/logic/map_objects/tribes/soldier.cc	2019-05-27 14:25:47 +0000
+++ src/logic/map_objects/tribes/soldier.cc	2019-08-28 20:08:47 +0000
@@ -57,7 +57,7 @@
 namespace Widelands {
 
 namespace {
-
+constexpr int kSoldierHealthBarWidth = 13;
 constexpr int kRetreatWhenHealthDropsBelowThisPercentage = 50;
 }  // namespace
 
@@ -548,7 +548,7 @@
  * Draw this soldier. This basically draws him as a worker, but add health points
  */
 void Soldier::draw(const EditorGameBase& game,
-                   const TextToDraw&,
+                   const InfoToDraw& info_to_draw,
                    const Vector2f& field_on_dst,
                    const Coords& coords,
                    float scale,
@@ -562,133 +562,143 @@
 	draw_info_icon(
 	   point_on_dst.cast<int>() -
 	      Vector2i(0, (g_gr->animations().get_animation(get_current_anim()).height() - 7) * scale),
-	   scale, true, dst);
+	   scale, InfoMode::kWalkingAround, info_to_draw, dst);
 	draw_inner(game, point_on_dst, coords, scale, dst);
 }
 
 /**
  * Draw the info icon (level indicators + health bar) for this soldier.
+ * 'draw_mode' determines whether the soldier info is displayed in a building window
+ * or on top of a soldier walking around. 'info_to_draw' checks which info the user wants to see
+ * for soldiers walking around.
  */
 void Soldier::draw_info_icon(Vector2i draw_position,
                              float scale,
-                             const bool anchor_below,
+                             const InfoMode draw_mode,
+                             const InfoToDraw info_to_draw,
                              RenderTarget* dst) const {
+	if (!(info_to_draw & InfoToDraw::kSoldierLevels)) {
+		return;
+	}
+
 	// Since the graphics below are all pixel perfect and scaling them as floats
-	// looks weird, we round to the nearest fullest integer.
-	scale = std::round(scale);
-	if (scale == 0.f) {
-		return;
-	}
-
-	const Image* healthpic = get_health_level_pic();
-	const Image* attackpic = get_attack_level_pic();
-	const Image* defensepic = get_defense_level_pic();
-	const Image* evadepic = get_evade_level_pic();
+	// looks weird, we round to the nearest fullest integer. We do allow half size though.
+	scale = std::max(0.5f, std::round(scale));
 
 #ifndef NDEBUG
-	// This function assumes stuff about our data files: level icons are all the
-	// same size and this is smaller than the width of the healthbar. This
-	// simplifies the drawing code below a lot. Before it had a lot of if () that
-	// were never tested - since our data files never changed.
-	const int dimension = attackpic->width();
-	assert(attackpic->height() == dimension);
-	assert(healthpic->width() == dimension);
-	assert(healthpic->height() == dimension);
-	assert(defensepic->width() == dimension);
-	assert(defensepic->height() == dimension);
-	assert(evadepic->width() == dimension);
-	assert(evadepic->height() == dimension);
-	assert(kSoldierHealthBarWidth > dimension);
+	{
+		// This function assumes stuff about our data files: level icons are all the
+		// same size and this is smaller than the width of the healthbar. This
+		// simplifies the drawing code below a lot. Before it had a lot of if () that
+		// were never tested - since our data files never changed.
+		const Image* healthpic = get_health_level_pic();
+
+		const Image* attackpic = get_attack_level_pic();
+		const Image* defensepic = get_defense_level_pic();
+		const Image* evadepic = get_evade_level_pic();
+
+		const int dimension = attackpic->width();
+		assert(attackpic->height() == dimension);
+		assert(healthpic->width() == dimension);
+		assert(healthpic->height() == dimension);
+		assert(defensepic->width() == dimension);
+		assert(defensepic->height() == dimension);
+		assert(evadepic->width() == dimension);
+		assert(evadepic->height() == dimension);
+		assert(kSoldierHealthBarWidth > dimension);
+	}
 #endif
 
-	const int icon_size = healthpic->width();
-
-	if (!anchor_below) {
+	const int icon_size = get_health_level_pic()->height();
+
+	// Draw health info in building windows, or if kSoldierLevels is on.
+	const bool draw_health_bar = draw_mode == InfoMode::kInBuilding || (info_to_draw & InfoToDraw::kSoldierLevels);
+
+	switch (draw_mode) {
+	case InfoMode::kInBuilding:
 		draw_position.x += kSoldierHealthBarWidth * scale;
 		draw_position.y += 2 * icon_size * scale;
-	} else {
-		draw_position.y -= 5 * scale;
+		break;
+	case InfoMode::kWalkingAround:
+		if (draw_health_bar) {
+			draw_position.y -= 5 * scale;
+		}
 	}
 
-	// Draw energy bar
-	assert(get_max_health());
-	const Recti energy_outer(draw_position - Vector2i(kSoldierHealthBarWidth, 0) * scale,
-	                         kSoldierHealthBarWidth * 2 * scale, 5 * scale);
-	dst->fill_rect(energy_outer, RGBColor(255, 255, 255));
-
-	// Adjust health to current animation tick
-	uint32_t health_to_show = current_health_;
-	if (battle_) {
-		uint32_t pending_damage = battle_->get_pending_damage(this);
-		if (pending_damage > 0) {
-			int32_t timeshift = owner().egbase().get_gametime() - get_animstart();
-			timeshift = std::min(std::max(0, timeshift), 1000);
-
-			pending_damage *= timeshift;
-			pending_damage /= 1000;
-
-			if (pending_damage > health_to_show) {
-				health_to_show = 0;
-			} else {
-				health_to_show -= pending_damage;
+	if (draw_health_bar) {
+		// Draw energy bar
+		assert(get_max_health());
+		const RGBColor& color = owner().get_playercolor();
+		const uint16_t color_sum = color.r + color.g + color.b;
+
+		// The frame gets a slight tint of player color
+		const Recti energy_outer(draw_position - Vector2i(kSoldierHealthBarWidth, 0) * scale,
+		                         kSoldierHealthBarWidth * 2 * scale, 5 * scale);
+		dst->fill_rect(energy_outer, color);
+		dst->brighten_rect(energy_outer, 230 - color_sum / 3);
+
+
+		// Adjust health to current animation tick
+		uint32_t health_to_show = current_health_;
+		if (battle_) {
+			uint32_t pending_damage = battle_->get_pending_damage(this);
+			if (pending_damage > 0) {
+				int32_t timeshift = owner().egbase().get_gametime() - get_animstart();
+				timeshift = std::min(std::max(0, timeshift), 1000);
+
+				pending_damage *= timeshift;
+				pending_damage /= 1000;
+
+				if (pending_damage > health_to_show) {
+					health_to_show = 0;
+				} else {
+					health_to_show -= pending_damage;
+				}
 			}
 		}
-	}
-
-	int health_width = 2 * (kSoldierHealthBarWidth - 1) * health_to_show / get_max_health();
-	Recti energy_inner(draw_position + Vector2i(-kSoldierHealthBarWidth + 1, 1) * scale,
-	                   health_width * scale, 3 * scale);
-	Recti energy_complement(energy_inner.origin() + Vector2i(health_width, 0) * scale,
-	                        (2 * (kSoldierHealthBarWidth - 1) - health_width) * scale, 3 * scale);
-
-	const RGBColor& color = owner().get_playercolor();
-	RGBColor complement_color;
-	if (static_cast<uint32_t>(color.r) + color.g + color.b > 128 * 3) {
-		complement_color = RGBColor(32, 32, 32);
-	} else {
-		complement_color = RGBColor(224, 224, 224);
-	}
-
-	dst->fill_rect(energy_inner, color);
-	dst->fill_rect(energy_complement, complement_color);
-
-	const auto draw_level_image = [icon_size, scale, &draw_position, dst](
-	                                 const Vector2i& offset, const Image* image) {
-		dst->blitrect_scale(
-		   Rectf(draw_position + offset * icon_size * scale, icon_size * scale, icon_size * scale),
-		   image, Recti(0, 0, icon_size, icon_size), 1.f, BlendMode::UseAlpha);
-	};
-	draw_level_image(Vector2i(-1, -2), attackpic);
-	draw_level_image(Vector2i(0, -2), defensepic);
-	draw_level_image(Vector2i(-1, -1), healthpic);
-	draw_level_image(Vector2i(0, -1), evadepic);
+
+		// Now draw the health bar itself
+		const int health_width =
+		   2 * (kSoldierHealthBarWidth - 1) * health_to_show / get_max_health();
+
+		Recti energy_inner(draw_position + Vector2i(-kSoldierHealthBarWidth + 1, 1) * scale,
+		                   health_width * scale, 3 * scale);
+		Recti energy_complement(energy_inner.origin() + Vector2i(health_width, 0) * scale,
+		                        (2 * (kSoldierHealthBarWidth - 1) - health_width) * scale, 3 * scale);
+
+		const RGBColor complement_color = color_sum > 128 * 3 ? RGBColor(32, 32, 32) : RGBColor(224, 224, 224);
+		dst->fill_rect(energy_inner, color);
+		dst->fill_rect(energy_complement, complement_color);
+	}
+
+	// Draw level info in building windows, or if kSoldierLevels is on.
+	if (draw_mode == InfoMode::kInBuilding || (info_to_draw & InfoToDraw::kSoldierLevels)) {
+		const auto draw_level_image = [icon_size, scale, &draw_position, dst](
+			const Vector2i& offset, const Image* image) {
+			dst->blitrect_scale(Rectf(draw_position + offset * icon_size * scale, icon_size * scale,
+											  icon_size * scale),
+									  image, Recti(0, 0, icon_size, icon_size), 1.f, BlendMode::UseAlpha);
+		};
+
+		draw_level_image(Vector2i(-1, -2), get_attack_level_pic());
+		draw_level_image(Vector2i(0, -2), get_defense_level_pic());
+		draw_level_image(Vector2i(-1, -1), get_health_level_pic());
+		draw_level_image(Vector2i(0, -1), get_evade_level_pic());
+	}
 }
 
 /**
  * Compute the size of the info icon (level indicators + health bar) for soldiers of
  * the given tribe.
  */
-void Soldier::calc_info_icon_size(const TribeDescr& tribe, uint32_t& w, uint32_t& h) {
+void Soldier::calc_info_icon_size(const TribeDescr& tribe, int& w, int& h) {
 	const SoldierDescr* soldierdesc =
 	   static_cast<const SoldierDescr*>(tribe.get_worker_descr(tribe.soldier()));
-	const Image* healthpic = soldierdesc->get_health_level_pic(0);
-	const Image* attackpic = soldierdesc->get_attack_level_pic(0);
-	const Image* defensepic = soldierdesc->get_defense_level_pic(0);
-	const Image* evadepic = soldierdesc->get_evade_level_pic(0);
-	uint16_t hpw = healthpic->width();
-	uint16_t hph = healthpic->height();
-	uint16_t atw = attackpic->width();
-	uint16_t ath = attackpic->height();
-	uint16_t dew = defensepic->width();
-	uint16_t deh = defensepic->height();
-	uint16_t evw = evadepic->width();
-	uint16_t evh = evadepic->height();
-
-	uint16_t animw;
-	animw = kSoldierHealthBarWidth;
-
-	w = std::max(std::max(atw + dew, hpw + evw), 2 * animw);
-	h = 5 + std::max(hph + ath, evh + deh);
+	// The function draw_info_icon() already assumes that all icons have the same dimensions,
+	// so we can make the same assumption here too.
+	const int dimension = soldierdesc->get_health_level_pic(0)->height();
+	w = 2 * std::max(dimension, kSoldierHealthBarWidth);
+	h = 5 + 2 * dimension;
 }
 
 void Soldier::pop_task_or_fight(Game& game) {

=== modified file 'src/logic/map_objects/tribes/soldier.h'
--- src/logic/map_objects/tribes/soldier.h	2019-05-27 14:25:47 +0000
+++ src/logic/map_objects/tribes/soldier.h	2019-08-28 20:08:47 +0000
@@ -210,6 +210,8 @@
 	MO_DESCR(SoldierDescr)
 
 public:
+	enum class InfoMode { kWalkingAround, kInBuilding };
+
 	explicit Soldier(const SoldierDescr&);
 
 	bool init(EditorGameBase&) override;
@@ -246,20 +248,21 @@
 
 	/// Draw this soldier
 	void draw(const EditorGameBase&,
-	          const TextToDraw& draw_text,
+	          const InfoToDraw& info_to_draw,
 	          const Vector2f& point_on_dst,
 	          const Widelands::Coords& coords,
 	          float scale,
 	          RenderTarget* dst) const override;
 
-	static void calc_info_icon_size(const TribeDescr&, uint32_t& w, uint32_t& h);
+	static void calc_info_icon_size(const TribeDescr&, int& w, int& h);
 
 	// Draw the info icon containing health bar and levels. If 'anchor_below' is
 	// true, the icon is drawn horizontally centered above Otherwise, the icon
 	// is drawn below and right of 'draw_position'.
 	void draw_info_icon(Vector2i draw_position,
 	                    const float scale,
-	                    const bool anchor_below,
+	                    const InfoMode draw_mode,
+	                    const InfoToDraw info_to_draw,
 	                    RenderTarget*) const;
 
 	uint32_t get_current_health() const {
@@ -371,8 +374,6 @@
 	std::pair<std::unique_ptr<SoldierLevelRange>, std::unique_ptr<DirAnimations>>
 	   walking_animations_cache_;
 
-	static constexpr uint8_t kSoldierHealthBarWidth = 13;
-
 	/// Number of consecutive blocked signals until the soldiers are considered permanently stuck
 	static constexpr uint8_t kBockCountIsStuck = 10;
 

=== modified file 'src/logic/map_objects/tribes/worker.cc'
--- src/logic/map_objects/tribes/worker.cc	2019-05-28 17:01:30 +0000
+++ src/logic/map_objects/tribes/worker.cc	2019-08-28 20:08:47 +0000
@@ -3024,7 +3024,7 @@
  * Draw the worker, taking the carried ware into account.
  */
 void Worker::draw(const EditorGameBase& egbase,
-                  const TextToDraw&,
+                  const InfoToDraw&,
                   const Vector2f& field_on_dst,
                   const Widelands::Coords& coords,
                   const float scale,

=== modified file 'src/logic/map_objects/tribes/worker.h'
--- src/logic/map_objects/tribes/worker.h	2019-04-24 06:01:37 +0000
+++ src/logic/map_objects/tribes/worker.h	2019-08-28 20:08:47 +0000
@@ -183,7 +183,7 @@
 	                        const float scale,
 	                        RenderTarget* dst) const;
 	void draw(const EditorGameBase&,
-	          const TextToDraw& draw_text,
+	          const InfoToDraw& info_to_draw,
 	          const Vector2f& field_on_dst,
 	          const Widelands::Coords& coords,
 	          float scale,

=== modified file 'src/network/gameclient.cc'
--- src/network/gameclient.cc	2019-08-28 06:12:07 +0000
+++ src/network/gameclient.cc	2019-08-28 20:08:47 +0000
@@ -164,12 +164,12 @@
 	   (boost::format("%s_netclient%u") % kAutosavePrefix % static_cast<unsigned int>(pn)).str());
 	InteractiveGameBase* igb;
 	if (pn > 0) {
-		igb = new InteractivePlayer(*game, get_config_section(), pn, true);
+		igb = new InteractivePlayer(*game, get_config_section(), pn, true, parent);
 	} else {
-		igb = new InteractiveSpectator(*game, get_config_section(), true);
+		igb = new InteractiveSpectator(*game, get_config_section(), true, parent);
 	}
+
 	game->set_ibase(igb);
-	igb->set_chat_provider(*parent);
 	if (settings.savegame) {  // savegame
 		game->init_savegame(loader, settings);
 	} else {  //  new map
@@ -274,7 +274,6 @@
 		d->game = &game;
 		InteractiveGameBase* igb = d->init_game(this, loader_ui.get());
 		d->run_game(igb, loader_ui.get());
-
 	} catch (...) {
 		WLApplication::emergency_save(game);
 		d->game = nullptr;

=== modified file 'src/network/gamehost.cc'
--- src/network/gamehost.cc	2019-08-28 06:12:07 +0000
+++ src/network/gamehost.cc	2019-08-28 20:08:47 +0000
@@ -668,11 +668,10 @@
 		}
 
 		if ((pn > 0) && (pn <= UserSettings::highest_playernum())) {
-			igb = new InteractivePlayer(game, get_config_section(), pn, true);
+			igb = new InteractivePlayer(game, get_config_section(), pn, true, &d->chat);
 		} else {
-			igb = new InteractiveSpectator(game, get_config_section(), true);
+			igb = new InteractiveSpectator(game, get_config_section(), true, &d->chat);
 		}
-		igb->set_chat_provider(d->chat);
 		game.set_ibase(igb);
 
 		if (!d->settings.savegame)  // new game

=== modified file 'src/wui/attack_box.cc'
--- src/wui/attack_box.cc	2019-05-26 17:21:15 +0000
+++ src/wui/attack_box.cc	2019-08-28 20:08:47 +0000
@@ -403,7 +403,7 @@
 	int32_t row = 0;
 	for (uint32_t i = 0; i < nr_soldiers; ++i) {
 		Vector2i location(column * kSoldierIconWidth, row * kSoldierIconHeight);
-		soldiers_[i]->draw_info_icon(location, 1.0f, false, &dst);
+		soldiers_[i]->draw_info_icon(location, 1.0f, Soldier::InfoMode::kInBuilding, InfoToDraw::kSoldierLevels, &dst);
 		if (restricted_row_number_) {
 			++row;
 			if (row >= current_size_) {

=== modified file 'src/wui/interactive_base.cc'
--- src/wui/interactive_base.cc	2019-08-25 14:50:16 +0000
+++ src/wui/interactive_base.cc	2019-08-28 20:08:47 +0000
@@ -178,9 +178,9 @@
      workareas_cache_(nullptr),
      egbase_(the_egbase),
 #ifndef NDEBUG  //  not in releases
-     display_flags_(dfDebug),
+     display_flags_(dfDebug | kSoldierLevels),
 #else
-     display_flags_(0),
+     display_flags_(kSoldierLevels),
 #endif
      lastframe_(SDL_GetTicks()),
      frametime_(0),
@@ -389,16 +389,22 @@
 	set_sel_pos(get_sel_pos());  //  redraw
 }
 
-TextToDraw InteractiveBase::get_text_to_draw() const {
-	TextToDraw text_to_draw = TextToDraw::kNone;
+InfoToDraw InteractiveBase::get_info_to_draw(bool show) const {
+	InfoToDraw info_to_draw = InfoToDraw::kNone;
+	if (!show) {
+		return info_to_draw;
+	}
 	auto display_flags = get_display_flags();
 	if (display_flags & InteractiveBase::dfShowCensus) {
-		text_to_draw = text_to_draw | TextToDraw::kCensus;
+		info_to_draw = info_to_draw | InfoToDraw::kCensus;
 	}
 	if (display_flags & InteractiveBase::dfShowStatistics) {
-		text_to_draw = text_to_draw | TextToDraw::kStatistics;
-	}
-	return text_to_draw;
+		info_to_draw = info_to_draw | InfoToDraw::kStatistics;
+	}
+	if (display_flags & InteractiveBase::dfShowSoldierLevels) {
+		info_to_draw = info_to_draw | InfoToDraw::kSoldierLevels;
+	}
+	return info_to_draw;
 }
 
 void InteractiveBase::unset_sel_picture() {
@@ -1169,6 +1175,10 @@
 			   this, debugconsole_, *DebugConsole::get_chat_provider());
 			return true;
 #endif
+		// Common shortcuts for InteractivePlayer, InteractiveSpectator and EditorInteractive
+		case SDLK_SPACE:
+			toggle_buildhelp();
+			return true;
 		case SDLK_m:
 			toggle_minimap();
 			return true;

=== modified file 'src/wui/interactive_base.h'
--- src/wui/interactive_base.h	2019-08-25 14:50:16 +0000
+++ src/wui/interactive_base.h	2019-08-28 20:08:47 +0000
@@ -61,11 +61,12 @@
 class InteractiveBase : public UI::Panel, public DebugConsole::Handler {
 public:
 	enum {
-		dfShowCensus = 1,      ///< show census report on buildings
-		dfShowStatistics = 2,  ///< show statistics report on buildings
-		dfDebug = 4,           ///< general debugging info
+		dfShowCensus = 1,             /// show census report on buildings
+		dfShowStatistics = 2,         /// show statistics report on buildings
+		dfShowSoldierLevels = 4,      /// show level information above soldiers
 		dfShowWorkareaOverlap =
 		   8,  ///< highlight overlapping workareas when placing a constructionsite
+		dfDebug = 16                  /// general debugging info
 	};
 
 	// Overlays displayed while a road is under construction.
@@ -252,7 +253,8 @@
 	}
 
 	// Returns the information which overlay text should currently be drawn.
-	TextToDraw get_text_to_draw() const;
+	// Returns InfoToDraw::kNone if not 'show'
+	InfoToDraw get_info_to_draw(bool show) const;
 
 	// Returns the current overlays for the work area previews.
 	Workareas get_workarea_overlays(const Widelands::Map& map);

=== modified file 'src/wui/interactive_gamebase.cc'
--- src/wui/interactive_gamebase.cc	2019-08-25 14:50:16 +0000
+++ src/wui/interactive_gamebase.cc	2019-08-28 20:08:47 +0000
@@ -64,9 +64,10 @@
 InteractiveGameBase::InteractiveGameBase(Widelands::Game& g,
                                          Section& global_s,
                                          PlayerType pt,
-                                         bool const multiplayer)
+                                         bool const multiplayer,
+                                         ChatProvider* chat_provider)
    : InteractiveBase(g, global_s),
-     chat_provider_(nullptr),
+     chat_provider_(chat_provider),
      multiplayer_(multiplayer),
      playertype_(pt),
      showhidemenu_(toolbar(),
@@ -132,6 +133,10 @@
 			   break;
 		   }
 	   });
+
+	if (chat_provider_ != nullptr) {
+		chat_overlay()->set_chat_provider(*chat_provider_);
+	}
 }
 
 void InteractiveGameBase::add_main_menu() {
@@ -218,11 +223,20 @@
 
 	showhidemenu_.add(get_display_flag(dfShowStatistics) ?
 	                     /** TRANSLATORS: An entry in the game's show/hide menu to toggle whether
-	                      * building staristics are shown */
+	                      * building statistics are shown */
 	                     _("Hide Statistics") :
 	                     _("Show Statistics"),
 	                  ShowHideEntry::kStatistics,
 	                  g_gr->images().get("images/wui/menus/toggle_statistics.png"), false, "", "s");
+
+
+	showhidemenu_.add(get_display_flag(dfShowSoldierLevels) ?
+	                     /** TRANSLATORS: An entry in the game's show/hide menu to toggle whether
+	                      * level information is sown above soldiers' heads */
+	                     _("Hide Soldier Levels") :
+	                     _("Show Soldier Levels"),
+	                  ShowHideEntry::kSoldierLevels,
+	                  g_gr->images().get("images/wui/menus/toggle_soldier_levels.png"), false, "", "l");
 }
 
 void InteractiveGameBase::showhide_menu_selected(ShowHideEntry entry) {
@@ -236,6 +250,9 @@
 	case ShowHideEntry::kStatistics: {
 		set_display_flag(dfShowStatistics, !get_display_flag(dfShowStatistics));
 	} break;
+	case ShowHideEntry::kSoldierLevels: {
+		set_display_flag(dfShowSoldierLevels, !get_display_flag(dfShowSoldierLevels));
+	} break;
 	case ShowHideEntry::kWorkareaOverlap: {
 		set_display_flag(dfShowWorkareaOverlap, !get_display_flag(dfShowWorkareaOverlap));
 	} break;
@@ -299,6 +316,15 @@
 	}
 }
 
+void InteractiveGameBase::add_chat_ui() {
+	add_toolbar_button("wui/menus/chat", "chat", _("Chat"), &chat_, true);
+	chat_.open_window = [this] {
+		if (chat_provider_) {
+			GameChatMenu::create_chat_console(this, chat_, *chat_provider_);
+		}
+	};
+}
+
 void InteractiveGameBase::increase_gamespeed() {
 	if (GameController* const ctrl = get_game()->game_controller()) {
 		ctrl->set_desired_speed(ctrl->desired_speed() + 1000);
@@ -348,6 +374,24 @@
 		case SDLK_PAGEDOWN:
 			decrease_gamespeed();
 			return true;
+
+		case SDLK_c:
+			set_display_flag(
+			   InteractiveBase::dfShowCensus, !get_display_flag(InteractiveBase::dfShowCensus));
+			return true;
+
+		case SDLK_l:
+			set_display_flag(dfShowSoldierLevels, !get_display_flag(dfShowSoldierLevels));
+			return true;
+
+		case SDLK_s:
+			if (code.mod & (KMOD_LCTRL | KMOD_RCTRL)) {
+				new GameMainMenuSaveGame(*this, menu_windows_.savegame);
+			} else {
+				set_display_flag(dfShowStatistics, !get_display_flag(dfShowStatistics));
+			}
+			return true;
+
 		default:
 			break;
 		}
@@ -364,15 +408,6 @@
 	return dynamic_cast<Widelands::Game&>(egbase());
 }
 
-void InteractiveGameBase::set_chat_provider(ChatProvider& chat) {
-	chat_provider_ = &chat;
-	chat_overlay()->set_chat_provider(chat);
-}
-
-ChatProvider* InteractiveGameBase::get_chat_provider() {
-	return chat_provider_;
-}
-
 void InteractiveGameBase::draw_overlay(RenderTarget& dst) {
 	InteractiveBase::draw_overlay(dst);
 

=== modified file 'src/wui/interactive_gamebase.h'
--- src/wui/interactive_gamebase.h	2019-08-25 14:50:16 +0000
+++ src/wui/interactive_gamebase.h	2019-08-28 20:08:47 +0000
@@ -38,17 +38,14 @@
 public:
 	InteractiveGameBase(Widelands::Game&,
 	                    Section& global_s,
-	                    PlayerType pt = NONE,
-	                    bool multiplayer = false);
+	                    PlayerType pt,
+	                    bool multiplayer,
+							  ChatProvider* chat_provider);
 	~InteractiveGameBase() override {
 	}
 	Widelands::Game* get_game() const;
 	Widelands::Game& game() const;
 
-	// Chat messages
-	void set_chat_provider(ChatProvider&);
-	ChatProvider* get_chat_provider();
-
 	virtual bool can_see(Widelands::PlayerNumber) const = 0;
 	virtual bool can_act(Widelands::PlayerNumber) const = 0;
 	virtual Widelands::PlayerNumber player_number() const = 0;
@@ -90,7 +87,7 @@
 
 protected:
 	// For referencing the items in showhidemenu_
-	enum class ShowHideEntry { kBuildingSpaces, kCensus, kStatistics, kWorkareaOverlap };
+	enum class ShowHideEntry { kBuildingSpaces, kCensus, kStatistics, kSoldierLevels, kWorkareaOverlap };
 
 	// Adds the mapviewmenu_ to the toolbar
 	void add_main_menu();
@@ -100,6 +97,9 @@
 	// Adds the gamespeedmenu_ to the toolbar
 	void add_gamespeed_menu();
 
+	// Adds a chat toolbar button and registers the chat console window
+	void add_chat_ui();
+
 	bool handle_key(bool down, SDL_Keysym code) override;
 
 	void draw_overlay(RenderTarget&) override;
@@ -119,6 +119,7 @@
 	} menu_windows_;
 
 	ChatProvider* chat_provider_;
+	UI::UniqueWindow::Registry chat_;
 	bool multiplayer_;
 	PlayerType playertype_;
 

=== modified file 'src/wui/interactive_player.cc'
--- src/wui/interactive_player.cc	2019-08-25 14:50:16 +0000
+++ src/wui/interactive_player.cc	2019-08-28 20:08:47 +0000
@@ -44,7 +44,6 @@
 #include "wui/debugconsole.h"
 #include "wui/fieldaction.h"
 #include "wui/game_chat_menu.h"
-#include "wui/game_main_menu_save_game.h"
 #include "wui/game_message_menu.h"
 #include "wui/game_objectives_menu.h"
 #include "wui/general_statistics_menu.h"
@@ -81,28 +80,28 @@
 	}
 	return brightness / 255.;
 }
-
 // Remove statistics from the text to draw if the player does not match the map object's owner
-TextToDraw filter_text_to_draw(TextToDraw text_to_draw,
+InfoToDraw filter_info_to_draw(InfoToDraw info_to_draw,
                                const Widelands::MapObject* object,
                                const Widelands::Player& player) {
-	TextToDraw result = text_to_draw;
+	InfoToDraw result = info_to_draw;
 	const Widelands::Player* owner = object->get_owner();
 	if (owner != nullptr && !player.see_all() && player.is_hostile(*owner)) {
-		result = removeFromTextToDraw(result, TextToDraw::kStatistics);
+		result = static_cast<InfoToDraw>(result & ~InfoToDraw::kStatistics);
 	}
 	return result;
 }
 
-void draw_immovable_for_visible_field(const Widelands::EditorGameBase& egbase,
-                                      const FieldsToDraw::Field& field,
-                                      const float scale,
-                                      const TextToDraw text_to_draw,
-                                      const Widelands::Player& player,
-                                      RenderTarget* dst) {
+
+void draw_immovables_for_visible_field(const Widelands::EditorGameBase& egbase,
+                                       const FieldsToDraw::Field& field,
+                                       const float scale,
+                                       const InfoToDraw info_to_draw,
+                                       const Widelands::Player& player,
+                                       RenderTarget* dst) {
 	Widelands::BaseImmovable* const imm = field.fcoords.field->get_immovable();
 	if (imm != nullptr && imm->get_positions(egbase).front() == field.fcoords) {
-		imm->draw(egbase.get_gametime(), filter_text_to_draw(text_to_draw, imm, player),
+		imm->draw(egbase.get_gametime(), filter_info_to_draw(info_to_draw, imm, player),
 		          field.rendertarget_pixel, field.fcoords, scale, dst);
 	}
 }
@@ -110,12 +109,12 @@
 void draw_bobs_for_visible_field(const Widelands::EditorGameBase& egbase,
                                  const FieldsToDraw::Field& field,
                                  const float scale,
-                                 const TextToDraw text_to_draw,
+                                 const InfoToDraw info_to_draw,
                                  const Widelands::Player& player,
                                  RenderTarget* dst) {
 	for (Widelands::Bob* bob = field.fcoords.field->get_first_bob(); bob;
 	     bob = bob->get_next_bob()) {
-		bob->draw(egbase, filter_text_to_draw(text_to_draw, bob, player), field.rendertarget_pixel,
+		bob->draw(egbase, filter_info_to_draw(info_to_draw, bob, player), field.rendertarget_pixel,
 		          field.fcoords, scale, dst);
 	}
 }
@@ -154,8 +153,9 @@
 InteractivePlayer::InteractivePlayer(Widelands::Game& g,
                                      Section& global_s,
                                      Widelands::PlayerNumber const plyn,
-                                     bool const multiplayer)
-   : InteractiveGameBase(g, global_s, NONE, multiplayer),
+                                     bool const multiplayer,
+                                     ChatProvider* chat_provider)
+   : InteractiveGameBase(g, global_s, NONE, multiplayer, chat_provider),
      auto_roadbuild_mode_(global_s.get_bool("auto_roadbuild_mode", true)),
      flag_to_connect_(Widelands::Coords::null()),
      statisticsmenu_(toolbar(),
@@ -183,12 +183,7 @@
 
 	toolbar()->add_space(15);
 	if (multiplayer) {
-		toggle_chat_ = add_toolbar_button("wui/menus/chat", "chat", _("Chat"), &chat_, true);
-		chat_.open_window = [this] {
-			if (chat_provider_) {
-				GameChatMenu::create_chat_console(this, chat_, *chat_provider_);
-			}
-		};
+		add_chat_ui();
 		toolbar()->add_space(15);
 	}
 
@@ -344,10 +339,6 @@
 			flag_to_connect_ = Widelands::Coords::null();
 		}
 	}
-	if (is_multiplayer()) {
-		toggle_chat_->set_visible(chat_provider_);
-		toggle_chat_->set_enabled(chat_provider_);
-	}
 	{
 		char const* msg_icon = "images/wui/menus/message_old.png";
 		std::string msg_tooltip = _("Messages");
@@ -417,9 +408,9 @@
 
 			// Render stuff that belongs to the node.
 			if (f->vision > 1) {
-				const auto text_to_draw = get_text_to_draw();
-				draw_immovable_for_visible_field(gbase, *f, scale, text_to_draw, plr, dst);
-				draw_bobs_for_visible_field(gbase, *f, scale, text_to_draw, plr, dst);
+				const auto info_to_draw = get_info_to_draw(!given_map_view->is_animating());
+				draw_immovables_for_visible_field(gbase, *f, scale, info_to_draw, plr, dst);
+				draw_bobs_for_visible_field(gbase, *f, scale, info_to_draw, plr, dst);
 			} else if (f->vision == 1) {
 				// We never show census or statistics for objects in the fog.
 				draw_immovable_for_formerly_visible_field(*f, player_field, scale, dst);
@@ -516,9 +507,6 @@
 bool InteractivePlayer::handle_key(bool const down, SDL_Keysym const code) {
 	if (down) {
 		switch (code.sym) {
-		case SDLK_SPACE:
-			toggle_buildhelp();
-			return true;
 
 		case SDLK_i:
 			menu_windows_.stats_stock.toggle();
@@ -536,10 +524,6 @@
 			encyclopedia_.toggle();
 			return true;
 
-		case SDLK_c:
-			set_display_flag(dfShowCensus, !get_display_flag(dfShowCensus));
-			return true;
-
 		case SDLK_b:
 			if (menu_windows_.stats_buildings.window == nullptr) {
 				new BuildingStatisticsMenu(*this, menu_windows_.stats_buildings);
@@ -558,13 +542,6 @@
 			}
 			return true;
 
-		case SDLK_s:
-			if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
-				new GameMainMenuSaveGame(*this, menu_windows_.savegame);
-			else
-				set_display_flag(dfShowStatistics, !get_display_flag(dfShowStatistics));
-			return true;
-
 		case SDLK_w:
 			set_display_flag(dfShowWorkareaOverlap, !get_display_flag(dfShowWorkareaOverlap));
 			return true;

=== modified file 'src/wui/interactive_player.h'
--- src/wui/interactive_player.h	2019-08-25 14:50:16 +0000
+++ src/wui/interactive_player.h	2019-08-28 20:08:47 +0000
@@ -42,7 +42,8 @@
 	InteractivePlayer(Widelands::Game&,
 	                  Section& global_s,
 	                  Widelands::PlayerNumber,
-	                  bool multiplayer);
+	                  bool multiplayer,
+	                  ChatProvider* chat_provider = nullptr);
 
 	bool can_see(Widelands::PlayerNumber) const override;
 	bool can_act(Widelands::PlayerNumber) const override;
@@ -96,7 +97,6 @@
 	bool auto_roadbuild_mode_;
 	Widelands::Coords flag_to_connect_;
 
-	UI::Button* toggle_chat_;
 	UI::Button* toggle_message_menu_;
 
 	// Statistics menu on the toolbar

=== modified file 'src/wui/interactive_spectator.cc'
--- src/wui/interactive_spectator.cc	2019-08-25 14:50:16 +0000
+++ src/wui/interactive_spectator.cc	2019-08-28 20:08:47 +0000
@@ -36,8 +36,9 @@
  */
 InteractiveSpectator::InteractiveSpectator(Widelands::Game& g,
                                            Section& global_s,
-                                           bool const multiplayer)
-   : InteractiveGameBase(g, global_s, OBSERVER, multiplayer) {
+                                           bool const multiplayer,
+                                           ChatProvider* chat_provider)
+   : InteractiveGameBase(g, global_s, OBSERVER, multiplayer, chat_provider) {
 	add_main_menu();
 
 	add_toolbar_button("wui/menus/statistics_general", "general_stats", _("Statistics"),
@@ -55,12 +56,7 @@
 	toolbar()->add_space(15);
 
 	if (is_multiplayer()) {
-		add_toolbar_button("wui/menus/chat", "chat", _("Chat"), &chat_, true);
-		chat_.open_window = [this] {
-			if (chat_provider_) {
-				GameChatMenu::create_chat_console(this, chat_, *chat_provider_);
-			}
-		};
+		add_chat_ui();
 	}
 
 	finalize_toolbar();
@@ -95,7 +91,7 @@
 	const float scale = 1.f / given_map_view->view().zoom;
 	const uint32_t gametime = the_game.get_gametime();
 
-	const auto text_to_draw = get_text_to_draw();
+	const auto info_to_draw = get_info_to_draw(!given_map_view->is_animating());
 	for (size_t idx = 0; idx < fields_to_draw->size(); ++idx) {
 		const FieldsToDraw::Field& field = fields_to_draw->at(idx);
 
@@ -103,12 +99,12 @@
 
 		Widelands::BaseImmovable* const imm = field.fcoords.field->get_immovable();
 		if (imm != nullptr && imm->get_positions(the_game).front() == field.fcoords) {
-			imm->draw(gametime, text_to_draw, field.rendertarget_pixel, field.fcoords, scale, dst);
+			imm->draw(gametime, info_to_draw, field.rendertarget_pixel, field.fcoords, scale, dst);
 		}
 
 		for (Widelands::Bob* bob = field.fcoords.field->get_first_bob(); bob;
 		     bob = bob->get_next_bob()) {
-			bob->draw(the_game, text_to_draw, field.rendertarget_pixel, field.fcoords, scale, dst);
+			bob->draw(the_game, info_to_draw, field.rendertarget_pixel, field.fcoords, scale, dst);
 		}
 
 		// Draw build help.
@@ -190,23 +186,8 @@
  * Global in-game keypresses:
  */
 bool InteractiveSpectator::handle_key(bool const down, SDL_Keysym const code) {
-	if (down)
+	if (down) {
 		switch (code.sym) {
-		case SDLK_SPACE:
-			toggle_buildhelp();
-			return true;
-
-		case SDLK_c:
-			set_display_flag(dfShowCensus, !get_display_flag(dfShowCensus));
-			return true;
-
-		case SDLK_s:
-			if (code.mod & (KMOD_LCTRL | KMOD_RCTRL)) {
-				new GameMainMenuSaveGame(*this, menu_windows_.savegame);
-			} else
-				set_display_flag(dfShowStatistics, !get_display_flag(dfShowStatistics));
-			return true;
-
 		case SDLK_RETURN:
 		case SDLK_KP_ENTER:
 			if (chat_provider_) {
@@ -219,6 +200,7 @@
 		default:
 			break;
 		}
+	}
 
 	return InteractiveGameBase::handle_key(down, code);
 }

=== modified file 'src/wui/interactive_spectator.h'
--- src/wui/interactive_spectator.h	2019-08-25 14:50:16 +0000
+++ src/wui/interactive_spectator.h	2019-08-28 20:08:47 +0000
@@ -38,7 +38,10 @@
  * This class provides the UI, runs the game logic, etc.
  */
 struct InteractiveSpectator : public InteractiveGameBase {
-	InteractiveSpectator(Widelands::Game&, Section& global_s, bool multiplayer = false);
+	InteractiveSpectator(Widelands::Game&,
+	                     Section& global_s,
+	                     bool multiplayer = false,
+	                     ChatProvider* chat_provider = nullptr);
 
 	Widelands::Player* get_player() const override;
 
@@ -54,8 +57,6 @@
 	bool can_act(Widelands::PlayerNumber) const override;
 	Widelands::PlayerNumber player_number() const override;
 	void node_action(const Widelands::NodeAndTriangle<>& node_and_triangle) override;
-
-	UI::UniqueWindow::Registry chat_;
 };
 
 #endif  // end of include guard: WL_WUI_INTERACTIVE_SPECTATOR_H

=== modified file 'src/wui/soldierlist.cc'
--- src/wui/soldierlist.cc	2019-05-26 17:21:15 +0000
+++ src/wui/soldierlist.cc	2019-08-28 20:08:47 +0000
@@ -44,7 +44,7 @@
 
 constexpr uint32_t kMaxColumns = 6;
 constexpr uint32_t kAnimateSpeed = 300;  ///< in pixels per second
-constexpr uint32_t kIconBorder = 2;
+constexpr int kIconBorder = 2;
 
 }  // namespace
 
@@ -105,8 +105,8 @@
 	uint32_t rows_;
 	uint32_t cols_;
 
-	uint32_t icon_width_;
-	uint32_t icon_height_;
+	int icon_width_;
+	int icon_height_;
 
 	int32_t last_animate_time_;
 };
@@ -296,7 +296,7 @@
 			continue;
 
 		constexpr float kNoZoom = 1.f;
-		soldier->draw_info_icon(icon.pos + Vector2i(kIconBorder, kIconBorder), kNoZoom, false, &dst);
+		soldier->draw_info_icon(icon.pos + Vector2i(kIconBorder, kIconBorder), kNoZoom, Soldier::InfoMode::kInBuilding, InfoToDraw::kSoldierLevels, &dst);
 	}
 }
 


Follow ups