← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/warehouse_refactor into lp:widelands

 

SirVer has proposed merging lp:~widelands-dev/widelands/warehouse_refactor into lp:widelands.

Commit message:
Refactor Attackable.

- Getting rid of some multiple inheritance in Warehouse and MilitarySite by
  composing Attackable as a member into Building.
- Rename Attackable -> AttackTarget and change the interface to be cleaner.
- Mild related cleanups.


Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/warehouse_refactor/+merge/324367
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/warehouse_refactor into lp:widelands.
=== modified file 'src/ai/defaultai_warfare.cc'
--- src/ai/defaultai_warfare.cc	2017-01-28 14:53:28 +0000
+++ src/ai/defaultai_warfare.cc	2017-05-20 22:45:32 +0000
@@ -122,7 +122,7 @@
 		// get list of immovable around this our military site
 		std::vector<ImmovableFound> immovables;
 		map.find_immovables(Area<FCoords>(f, (vision + 3 < 13) ? 13 : vision + 3), &immovables,
-		                    FindImmovableAttackable());
+		                    FindImmovableAttackTarget());
 
 		for (uint32_t j = 0; j < immovables.size(); ++j) {
 			if (upcast(MilitarySite const, bld, immovables.at(j).object)) {
@@ -229,7 +229,7 @@
 				defenders_strength = calculate_strength(defenders);
 
 				flag = &bld->base_flag();
-				if (is_visible && bld->can_attack()) {
+				if (is_visible && bld->attack_target()->can_be_attacked()) {
 					is_attackable = true;
 				}
 				owner_number = bld->owner().player_number();
@@ -244,7 +244,7 @@
 
 				flag = &Wh->base_flag();
 				is_warehouse = true;
-				if (is_visible && Wh->can_attack()) {
+				if (is_visible && Wh->attack_target()->can_be_attacked()) {
 					is_attackable = true;
 				}
 				owner_number = Wh->owner().player_number();

=== modified file 'src/economy/route.h'
--- src/economy/route.h	2017-01-25 18:55:59 +0000
+++ src/economy/route.h	2017-05-20 22:45:32 +0000
@@ -23,14 +23,16 @@
 #include <vector>
 
 #include "economy/iroute.h"
+#include "io/fileread.h"
+#include "io/filewrite.h"
 #include "logic/map_objects/map_object.h"
 
 namespace Widelands {
 
+class EditorGameBase;
+class MapObjectLoader;
 struct Flag;
-class EditorGameBase;
 struct MapObjectSaver;
-class MapObjectLoader;
 struct RoutingNode;
 
 /**

=== modified file 'src/logic/CMakeLists.txt'
--- src/logic/CMakeLists.txt	2017-05-14 18:17:16 +0000
+++ src/logic/CMakeLists.txt	2017-05-20 22:45:32 +0000
@@ -128,7 +128,7 @@
     widelands_geometry_io.cc
     widelands_geometry_io.h
     map_objects/draw_text.h
-    map_objects/attackable.h
+    map_objects/attack_target.h
     map_objects/bob.cc
     map_objects/bob.h
     map_objects/buildcost.cc

=== modified file 'src/logic/cmd_queue.h'
--- src/logic/cmd_queue.h	2017-01-25 18:55:59 +0000
+++ src/logic/cmd_queue.h	2017-05-20 22:45:32 +0000
@@ -27,7 +27,6 @@
 #include <stdint.h>
 
 #include "logic/queue_cmd_ids.h"
-#include <stdint.h>
 
 class FileRead;
 class FileWrite;

=== modified file 'src/logic/findimmovable.cc'
--- src/logic/findimmovable.cc	2017-01-25 18:55:59 +0000
+++ src/logic/findimmovable.cc	2017-05-20 22:45:32 +0000
@@ -21,7 +21,6 @@
 
 #include "base/macros.h"
 #include "economy/flag.h"
-#include "logic/map_objects/attackable.h"
 #include "logic/map_objects/immovable.h"
 #include "logic/map_objects/tribes/militarysite.h"
 
@@ -61,8 +60,11 @@
 	return false;
 }
 
-bool FindImmovableAttackable::accept(const BaseImmovable& imm) const {
-	return dynamic_cast<Attackable const*>(&imm);
+bool FindImmovableAttackTarget::accept(const BaseImmovable& imm) const {
+	if (upcast(Building const, b, &imm)) {
+		return b->attack_target() != nullptr;
+	}
+	return false;
 }
 
 bool FindImmovableByDescr::accept(const BaseImmovable& baseimm) const {

=== modified file 'src/logic/findimmovable.h'
--- src/logic/findimmovable.h	2017-01-25 18:55:59 +0000
+++ src/logic/findimmovable.h	2017-05-20 22:45:32 +0000
@@ -132,8 +132,8 @@
 
 	const Player& player;
 };
-struct FindImmovableAttackable {
-	FindImmovableAttackable() {
+struct FindImmovableAttackTarget {
+	FindImmovableAttackTarget() {
 	}
 
 	bool accept(const BaseImmovable&) const;

=== renamed file 'src/logic/map_objects/attackable.h' => 'src/logic/map_objects/attack_target.h'
--- src/logic/map_objects/attackable.h	2017-01-25 18:55:59 +0000
+++ src/logic/map_objects/attack_target.h	2017-05-20 22:45:32 +0000
@@ -17,68 +17,55 @@
  *
  */
 
-#ifndef WL_LOGIC_MAP_OBJECTS_ATTACKABLE_H
-#define WL_LOGIC_MAP_OBJECTS_ATTACKABLE_H
+#ifndef WL_LOGIC_MAP_OBJECTS_ATTACK_TARGET_H
+#define WL_LOGIC_MAP_OBJECTS_ATTACK_TARGET_H
+
+#include "base/macros.h"
 
 namespace Widelands {
 
-class Player;
 class Soldier;
 
-enum {
-	/**
-    * This is the maximum radius that a military building can protect
-    * in the sense that an enemy soldier that enters the player's territory
-    * will call \ref Attackable::aggressor if it is that close.
-    */
-	MaxProtectionRadius = 25
-};
-
-/**
- * Buildings can implement this interface to indicate that
- * they can be attacked.
- */
-struct Attackable {
-	/**
-	 * Return the player that owns this attackable.
-	 */
-	virtual Player& owner() const = 0;
-
-	/**
-	 * Determines whether this building can be attacked right now.
-	 *
-	 * This should only return false for military sites that have not
-	 * been occupied yet.
-	 */
-	virtual bool can_attack() = 0;
-
-	/**
-	 * Called by an enemy soldier that enters a node with distance
-	 * less than or equal to \ref MaxProtectionRadius from the building.
-	 *
-	 * This allows the building to send protective forces to intercept
-	 * the soldier.
-	 */
-	virtual void aggressor(Soldier&) = 0;
-
-	/**
-	 * Called by a soldier who is standing on the building's flag
-	 * to attack the building.
-	 *
-	 * The building must send a soldier for defense, and return \c true.
-	 * Otherwise, i.e. if the building cannot defend itself anymore,
-	 * it must destroy itself or turn over to the attacking player,
-	 * and return \c false.
-	 *
-	 * \return \c true if a soldier was launched in defense of the building,
-	 * or \c false if the building cannot defend itself any longer.
-	 */
-	virtual bool attack(Soldier&) = 0;
-
-protected:
-	virtual ~Attackable() {
+// This is the maximum radius that a military building can protect in the sense
+// that an enemy soldier that enters the player's territory will call \ref
+// AttackTarget::enemy_soldier_approaches if it is that close.
+constexpr int kMaxProtectionRadius = 25;
+
+class AttackTarget {
+public:
+	AttackTarget() = default;
+	virtual ~AttackTarget() {
 	}
+
+	// Determines whether this building can be attacked right now.
+	virtual bool can_be_attacked() const = 0;
+
+	// Called by an enemy soldier that enters a node with distance
+	// less than or equal to \ref kMaxProtectionRadius from the building.
+	//
+	// This allows the building to send protective forces to intercept
+	// the soldier.
+	virtual void enemy_soldier_approaches(const Soldier& enemy) const = 0;
+
+	// Called by a soldier who is standing on the building's flag
+	// to attack the building.
+	//
+	// The building must send a soldier for defense, and return \c true.
+	// Otherwise, i.e. if the building cannot defend itself anymore,
+	// it must destroy itself or turn over to the attacking player,
+	// and return \c false.
+	enum class AttackResult {
+		// Returned when a soldier was launched in defense of the building.
+		DefenderLaunched,
+
+		// Returned if the building cannot defend itself any longer.
+		Defenseless
+	};
+	virtual AttackResult attack(Soldier* attacker) const = 0;
+
+private:
+	DISALLOW_COPY_AND_ASSIGN(AttackTarget);
 };
 }
 
-#endif  // end of include guard: WL_LOGIC_MAP_OBJECTS_ATTACKABLE_H
+#endif  // end of include guard: WL_LOGIC_MAP_OBJECTS_ATTACK_TARGET_H

=== modified file 'src/logic/map_objects/map_object.h'
--- src/logic/map_objects/map_object.h	2017-04-23 12:11:19 +0000
+++ src/logic/map_objects/map_object.h	2017-05-20 22:45:32 +0000
@@ -491,12 +491,12 @@
 	ObjectPointer() {
 		serial_ = 0;
 	}
-	ObjectPointer(MapObject* const obj) {
+	ObjectPointer(const MapObject* const obj) {
 		serial_ = obj ? obj->serial_ : 0;
 	}
 	// can use standard copy constructor and assignment operator
 
-	ObjectPointer& operator=(MapObject* const obj) {
+	ObjectPointer& operator=(const MapObject* const obj) {
 		serial_ = obj ? obj->serial_ : 0;
 		return *this;
 	}

=== modified file 'src/logic/map_objects/tribes/building.cc'
--- src/logic/map_objects/tribes/building.cc	2017-05-09 09:18:14 +0000
+++ src/logic/map_objects/tribes/building.cc	2017-05-20 22:45:32 +0000
@@ -235,7 +235,8 @@
      animstart_(0),
      leave_time_(0),
      defeating_player_(0),
-     seeing_(false) {
+     seeing_(false),
+     attack_target_(nullptr) {
 }
 
 void Building::load_finish(EditorGameBase& egbase) {
@@ -692,6 +693,11 @@
 	Notifications::publish(NoteBuilding(serial(), NoteBuilding::Action::kWorkersChanged));
 }
 
+void Building::set_attack_target(AttackTarget* attack_target) {
+	assert(attack_target_ == nullptr);
+	attack_target_ = attack_target;
+}
+
 /**
  * Change whether this building sees its vision range based on workers
  * inside the building.

=== modified file 'src/logic/map_objects/tribes/building.h'
--- src/logic/map_objects/tribes/building.h	2017-05-06 19:21:47 +0000
+++ src/logic/map_objects/tribes/building.h	2017-05-20 22:45:32 +0000
@@ -28,6 +28,7 @@
 
 #include "ai/ai_hints.h"
 #include "base/macros.h"
+#include "logic/map_objects/attack_target.h"
 #include "logic/map_objects/buildcost.h"
 #include "logic/map_objects/immovable.h"
 #include "logic/map_objects/tribes/bill_of_materials.h"
@@ -293,6 +294,13 @@
 	void add_worker(Worker&) override;
 	void remove_worker(Worker&) override;
 
+	// Returns the AttackTarget object associated with this building. If the
+	// building can never be attacked (for example productionsites) this will be
+	// nullptr.
+	const AttackTarget* attack_target() const {
+		return attack_target_;
+	}
+
 	void send_message(Game& game,
 	                  const Message::Type msgtype,
 	                  const std::string& title,
@@ -324,6 +332,7 @@
 	draw_info(TextToDraw draw_text, const Vector2f& point_on_dst, float scale, RenderTarget* dst);
 
 	void set_seeing(bool see);
+	void set_attack_target(AttackTarget* attack_target);
 
 	Coords position_;
 	Flag* flag_;
@@ -350,6 +359,7 @@
 
 private:
 	std::string statistics_string_;
+	AttackTarget* attack_target_;  // owned by the base classes, set by 'set_attack_target'.
 };
 }
 

=== modified file 'src/logic/map_objects/tribes/militarysite.cc'
--- src/logic/map_objects/tribes/militarysite.cc	2017-04-23 12:11:19 +0000
+++ src/logic/map_objects/tribes/militarysite.cc	2017-05-20 22:45:32 +0000
@@ -43,6 +43,145 @@
 
 namespace Widelands {
 
+bool MilitarySite::AttackTarget::can_be_attacked() const {
+	return military_site_->didconquer_;
+}
+
+void MilitarySite::AttackTarget::enemy_soldier_approaches(const Soldier& enemy) const {
+	auto& owner = military_site_->owner();
+	Game& game = dynamic_cast<Game&>(owner.egbase());
+	Map& map = game.map();
+	if (enemy.get_owner() == &owner || enemy.get_battle() ||
+	    military_site_->descr().get_conquers() <=
+	       map.calc_distance(enemy.get_position(), military_site_->get_position()))
+		return;
+
+	if (map.find_bobs(Area<FCoords>(map.get_fcoords(military_site_->base_flag().get_position()), 2),
+	                  nullptr, FindBobEnemySoldier(&owner)))
+		return;
+
+	// We're dealing with a soldier that we might want to keep busy
+	// Now would be the time to implement some player-definable
+	// policy as to how many soldiers are allowed to leave as defenders
+	std::vector<Soldier*> present = military_site_->present_soldiers();
+
+	if (1 < present.size()) {
+		for (Soldier* temp_soldier : present) {
+			if (!military_site_->has_soldier_job(*temp_soldier)) {
+				SoldierJob sj;
+				sj.soldier = temp_soldier;
+				sj.enemy = &enemy;
+				sj.stayhome = false;
+				military_site_->soldierjobs_.push_back(sj);
+				temp_soldier->update_task_buildingwork(game);
+				return;
+			}
+		}
+	}
+
+	// Inform the player, that we are under attack by adding a new entry to the
+	// message queue - a sound will automatically be played.
+	military_site_->notify_player(game, true);
+}
+
+AttackTarget::AttackResult MilitarySite::AttackTarget::attack(Soldier* enemy) const {
+	Game& game = dynamic_cast<Game&>(military_site_->owner().egbase());
+
+	std::vector<Soldier*> present = military_site_->present_soldiers();
+	Soldier* defender = nullptr;
+
+	if (!present.empty()) {
+		// Find soldier with greatest health
+		uint32_t current_max = 0;
+		for (Soldier* temp_soldier : present) {
+			if (temp_soldier->get_current_health() > current_max) {
+				defender = temp_soldier;
+				current_max = defender->get_current_health();
+			}
+		}
+	} else {
+		// If one of our stationed soldiers is currently walking into the
+		// building, give us another chance.
+		std::vector<Soldier*> stationed = military_site_->stationed_soldiers();
+		for (Soldier* temp_soldier : stationed) {
+			if (temp_soldier->get_position() == military_site_->get_position()) {
+				defender = temp_soldier;
+				break;
+			}
+		}
+	}
+
+	if (defender) {
+		military_site_->pop_soldier_job(defender);  // defense overrides all other jobs
+
+		SoldierJob sj;
+		sj.soldier = defender;
+		sj.enemy = enemy;
+		sj.stayhome = true;
+		military_site_->soldierjobs_.push_back(sj);
+
+		defender->update_task_buildingwork(game);
+
+		// Inform the player, that we are under attack by adding a new entry to
+		// the message queue - a sound will automatically be played.
+		military_site_->notify_player(game);
+
+		return AttackTarget::AttackResult::DefenderLaunched;
+	}
+
+	// The enemy has defeated our forces, we should inform the player
+	const Coords coords = military_site_->get_position();
+	{
+		military_site_->send_message(game, Message::Type::kWarfareSiteLost,
+		                             /** TRANSLATORS: Militarysite lost (taken/destroyed by enemy) */
+		                             pgettext("building", "Lost!"),
+		                             military_site_->descr().icon_filename(), _("Militarysite lost!"),
+		                             military_site_->descr().defeated_enemy_str_, false);
+	}
+
+	// Now let's see whether the enemy conquers our militarysite, or whether
+	// we still hold the bigger military presence in that area (e.g. if there
+	// is a fortress one or two points away from our sentry, the fortress has
+	// a higher presence and thus the enemy can just burn down the sentry.
+	if (military_site_->military_presence_kept(game)) {
+		// Okay we still got the higher military presence, so the attacked
+		// militarysite will be destroyed.
+		military_site_->set_defeating_player(enemy->owner().player_number());
+		military_site_->schedule_destroy(game);
+		return AttackTarget::AttackResult::Defenseless;
+	}
+
+	// The enemy conquers the building
+	// In fact we do not conquer it, but place a new building of same type at
+	// the old location.
+
+	Building::FormerBuildings former_buildings = military_site_->old_buildings_;
+
+	// The enemy conquers the building
+	// In fact we do not conquer it, but place a new building of same type at
+	// the old location.
+	Player* enemyplayer = enemy->get_owner();
+
+	// Now we destroy the old building before we place the new one.
+	military_site_->set_defeating_player(enemyplayer->player_number());
+	military_site_->schedule_destroy(game);
+
+	enemyplayer->force_building(coords, former_buildings);
+	BaseImmovable* const newimm = game.map()[coords].get_immovable();
+	upcast(MilitarySite, newsite, newimm);
+	newsite->reinit_after_conqueration(game);
+
+	// Of course we should inform the victorious player as well
+	newsite->send_message(
+	   game, Message::Type::kWarfareSiteDefeated,
+	   /** TRANSLATORS: Message title. */
+	   /** TRANSLATORS: If you run out of space, you can also translate this as "Success!" */
+	   _("Enemy Defeated!"), newsite->descr().icon_filename(), _("Enemy at site defeated!"),
+	   newsite->descr().defeated_you_str_, true);
+
+	return AttackTarget::AttackResult::Defenseless;
+}
+
 /**
   * The contents of 'table' are documented in
   * /data/tribes/buildings/militarysites/atlanteans/castle/init.lua
@@ -91,6 +230,7 @@
 
 MilitarySite::MilitarySite(const MilitarySiteDescr& ms_descr)
    : Building(ms_descr),
+     attack_target_(this),
      didconquer_(false),
      capacity_(ms_descr.get_max_number_of_soldiers()),
      nexthealtime_(0),
@@ -98,6 +238,7 @@
      soldier_upgrade_try_(false),
      doing_upgrade_request_(false) {
 	next_swap_soldiers_time_ = 0;
+	set_attack_target(&attack_target_);
 }
 
 MilitarySite::~MilitarySite() {
@@ -684,142 +825,6 @@
 	didconquer_ = true;
 }
 
-bool MilitarySite::can_attack() {
-	return didconquer_;
-}
-
-void MilitarySite::aggressor(Soldier& enemy) {
-	Game& game = dynamic_cast<Game&>(owner().egbase());
-	Map& map = game.map();
-	if (enemy.get_owner() == &owner() || enemy.get_battle() ||
-	    descr().get_conquers() <= map.calc_distance(enemy.get_position(), get_position()))
-		return;
-
-	if (map.find_bobs(Area<FCoords>(map.get_fcoords(base_flag().get_position()), 2), nullptr,
-	                  FindBobEnemySoldier(&owner())))
-		return;
-
-	// We're dealing with a soldier that we might want to keep busy
-	// Now would be the time to implement some player-definable
-	// policy as to how many soldiers are allowed to leave as defenders
-	std::vector<Soldier*> present = present_soldiers();
-
-	if (1 < present.size()) {
-		for (Soldier* temp_soldier : present) {
-			if (!has_soldier_job(*temp_soldier)) {
-				SoldierJob sj;
-				sj.soldier = temp_soldier;
-				sj.enemy = &enemy;
-				sj.stayhome = false;
-				soldierjobs_.push_back(sj);
-				temp_soldier->update_task_buildingwork(game);
-				return;
-			}
-		}
-	}
-
-	// Inform the player, that we are under attack by adding a new entry to the
-	// message queue - a sound will automatically be played.
-	notify_player(game, true);
-}
-
-bool MilitarySite::attack(Soldier& enemy) {
-	Game& game = dynamic_cast<Game&>(owner().egbase());
-
-	std::vector<Soldier*> present = present_soldiers();
-	Soldier* defender = nullptr;
-
-	if (!present.empty()) {
-		// Find soldier with greatest health
-		uint32_t current_max = 0;
-		for (Soldier* temp_soldier : present) {
-			if (temp_soldier->get_current_health() > current_max) {
-				defender = temp_soldier;
-				current_max = defender->get_current_health();
-			}
-		}
-	} else {
-		// If one of our stationed soldiers is currently walking into the
-		// building, give us another chance.
-		std::vector<Soldier*> stationed = stationed_soldiers();
-		for (Soldier* temp_soldier : stationed) {
-			if (temp_soldier->get_position() == get_position()) {
-				defender = temp_soldier;
-				break;
-			}
-		}
-	}
-
-	if (defender) {
-		pop_soldier_job(defender);  // defense overrides all other jobs
-
-		SoldierJob sj;
-		sj.soldier = defender;
-		sj.enemy = &enemy;
-		sj.stayhome = true;
-		soldierjobs_.push_back(sj);
-
-		defender->update_task_buildingwork(game);
-
-		// Inform the player, that we are under attack by adding a new entry to
-		// the message queue - a sound will automatically be played.
-		notify_player(game);
-
-		return true;
-	} else {
-		// The enemy has defeated our forces, we should inform the player
-		const Coords coords = get_position();
-		{
-			send_message(game, Message::Type::kWarfareSiteLost,
-			             /** TRANSLATORS: Militarysite lost (taken/destroyed by enemy) */
-			             pgettext("building", "Lost!"), descr().icon_filename(),
-			             _("Militarysite lost!"), descr().defeated_enemy_str_, false);
-		}
-
-		// Now let's see whether the enemy conquers our militarysite, or whether
-		// we still hold the bigger military presence in that area (e.g. if there
-		// is a fortress one or two points away from our sentry, the fortress has
-		// a higher presence and thus the enemy can just burn down the sentry.
-		if (military_presence_kept(game)) {
-			// Okay we still got the higher military presence, so the attacked
-			// militarysite will be destroyed.
-			set_defeating_player(enemy.owner().player_number());
-			schedule_destroy(game);
-			return false;
-		}
-
-		// The enemy conquers the building
-		// In fact we do not conquer it, but place a new building of same type at
-		// the old location.
-
-		Building::FormerBuildings former_buildings = old_buildings_;
-
-		// The enemy conquers the building
-		// In fact we do not conquer it, but place a new building of same type at
-		// the old location.
-		Player* enemyplayer = enemy.get_owner();
-
-		// Now we destroy the old building before we place the new one.
-		set_defeating_player(enemyplayer->player_number());
-		schedule_destroy(game);
-
-		enemyplayer->force_building(coords, former_buildings);
-		BaseImmovable* const newimm = game.map()[coords].get_immovable();
-		upcast(MilitarySite, newsite, newimm);
-		newsite->reinit_after_conqueration(game);
-
-		// Of course we should inform the victorious player as well
-		newsite->send_message(
-		   game, Message::Type::kWarfareSiteDefeated,
-		   /** TRANSLATORS: Message title. */
-		   /** TRANSLATORS: If you run out of space, you can also translate this as "Success!" */
-		   _("Enemy Defeated!"), newsite->descr().icon_filename(), _("Enemy at site defeated!"),
-		   newsite->descr().defeated_you_str_, true);
-
-		return false;
-	}
-}
-
 /// Initialises the militarysite after it was "conquered" (the old was replaced)
 void MilitarySite::reinit_after_conqueration(Game& game) {
 	clear_requirements();

=== modified file 'src/logic/map_objects/tribes/militarysite.h'
--- src/logic/map_objects/tribes/militarysite.h	2017-04-23 12:11:19 +0000
+++ src/logic/map_objects/tribes/militarysite.h	2017-05-20 22:45:32 +0000
@@ -24,7 +24,6 @@
 
 #include "base/macros.h"
 #include "economy/request.h"
-#include "logic/map_objects/attackable.h"
 #include "logic/map_objects/tribes/building.h"
 #include "logic/map_objects/tribes/requirements.h"
 #include "logic/map_objects/tribes/soldiercontrol.h"
@@ -69,7 +68,7 @@
 	DISALLOW_COPY_AND_ASSIGN(MilitarySiteDescr);
 };
 
-class MilitarySite : public Building, public SoldierControl, public Attackable {
+class MilitarySite : public Building, public SoldierControl {
 	friend class MapBuildingdataPacket;
 	MO_DESCR(MilitarySiteDescr)
 
@@ -102,15 +101,6 @@
 	void drop_soldier(Soldier&) override;
 	int incorporate_soldier(EditorGameBase& game, Soldier& s) override;
 
-	// Begin implementation of Attackable
-	Player& owner() const override {
-		return Building::owner();
-	}
-	bool can_attack() override;
-	void aggressor(Soldier&) override;
-	bool attack(Soldier&) override;
-	// End implementation of Attackable
-
 	/// Launch the given soldier on an attack towards the given
 	/// target building.
 	void send_attacker(Soldier&, Building&);
@@ -153,6 +143,21 @@
 	bool drop_least_suited_soldier(bool new_has_arrived, Soldier* s);
 
 private:
+	// We can be attacked if we have stationed soldiers.
+	class AttackTarget : public Widelands::AttackTarget {
+	public:
+		explicit AttackTarget(MilitarySite* military_site) : military_site_(military_site) {
+		}
+
+		bool can_be_attacked() const override;
+		void enemy_soldier_approaches(const Soldier&) const override;
+		Widelands::AttackTarget::AttackResult attack(Soldier*) const override;
+
+	private:
+		MilitarySite* const military_site_;
+	};
+
+	AttackTarget attack_target_;
 	Requirements soldier_requirements_;  // This is used to grab a bunch of soldiers: Anything goes
 	RequireAttribute soldier_upgrade_requirements_;     // This is used when exchanging soldiers.
 	std::unique_ptr<Request> normal_soldier_request_;   // filling the site

=== modified file 'src/logic/map_objects/tribes/soldier.cc'
--- src/logic/map_objects/tribes/soldier.cc	2017-05-13 18:48:26 +0000
+++ src/logic/map_objects/tribes/soldier.cc	2017-05-20 22:45:32 +0000
@@ -42,7 +42,6 @@
 #include "logic/game.h"
 #include "logic/game_controller.h"
 #include "logic/game_data_error.h"
-#include "logic/map_objects/attackable.h"
 #include "logic/map_objects/checkstep.h"
 #include "logic/map_objects/tribes/battle.h"
 #include "logic/map_objects/tribes/building.h"
@@ -602,7 +601,7 @@
 	return false;
 }
 
-Battle* Soldier::get_battle() {
+Battle* Soldier::get_battle() const {
 	return battle_;
 }
 
@@ -902,12 +901,14 @@
 		}
 	}
 
-	upcast(Attackable, attackable, enemy);
-	assert(attackable);
+	assert(enemy->attack_target() != nullptr);
 
 	molog("[attack] attacking target building\n");
 	//  give the enemy soldier some time to act
-	schedule_act(game, attackable->attack(*this) ? 1000 : 10);
+	schedule_act(
+	   game, enemy->attack_target()->attack(this) == AttackTarget::AttackResult::DefenderLaunched ?
+	            1000 :
+	            10);
 }
 
 void Soldier::attack_pop(Game& game, State&) {
@@ -1520,20 +1521,21 @@
 	PlayerNumber const land_owner = get_position().field->get_owned_by();
 	// First check if the soldier is standing on someone else's territory
 	if (land_owner != owner().player_number()) {
-		// Let's collect all reachable attackable sites in vicinity (militarysites mainly)
-		std::vector<BaseImmovable*> attackables;
+		// Let's collect all reachable attack_target sites in vicinity (militarysites mainly)
+		std::vector<BaseImmovable*> attack_targets;
 		game.map().find_reachable_immovables_unique(
-		   Area<FCoords>(get_position(), MaxProtectionRadius), attackables,
-		   CheckStepWalkOn(descr().movecaps(), false), FindImmovableAttackable());
+		   Area<FCoords>(get_position(), kMaxProtectionRadius), attack_targets,
+		   CheckStepWalkOn(descr().movecaps(), false), FindImmovableAttackTarget());
 
-		for (BaseImmovable* temp_attackable : attackables) {
-			const Player* attackable_player =
-			   dynamic_cast<const PlayerImmovable&>(*temp_attackable).get_owner();
+		for (BaseImmovable* temp_attack_target : attack_targets) {
+			Building* building = dynamic_cast<Building*>(temp_attack_target);
+			assert(building->attack_target() != nullptr);
+			const Player& attack_target_player = building->owner();
 			// Let's inform the site that this (=enemy) soldier is nearby and within the site's owner's
 			// territory
-			if (attackable_player->player_number() == land_owner &&
-			    attackable_player->is_hostile(*get_owner())) {
-				dynamic_cast<Attackable&>(*temp_attackable).aggressor(*this);
+			if (attack_target_player.player_number() == land_owner &&
+			    attack_target_player.is_hostile(*get_owner())) {
+				building->attack_target()->enemy_soldier_approaches(*this);
 			}
 		}
 	}

=== modified file 'src/logic/map_objects/tribes/soldier.h'
--- src/logic/map_objects/tribes/soldier.h	2017-05-13 13:14:29 +0000
+++ src/logic/map_objects/tribes/soldier.h	2017-05-20 22:45:32 +0000
@@ -262,7 +262,7 @@
 
 	bool is_on_battlefield();
 	bool is_attacking_player(Game&, Player&);
-	Battle* get_battle();
+	Battle* get_battle() const;
 	bool can_be_challenged();
 	bool check_node_blocked(Game&, const FCoords&, bool commit) override;
 

=== modified file 'src/logic/map_objects/tribes/warehouse.cc'
--- src/logic/map_objects/tribes/warehouse.cc	2017-05-03 10:34:46 +0000
+++ src/logic/map_objects/tribes/warehouse.cc	2017-05-20 22:45:32 +0000
@@ -69,6 +69,57 @@
 
 }  // namespace
 
+bool Warehouse::AttackTarget::can_be_attacked() const {
+	return warehouse_->descr().get_conquers() > 0;
+}
+
+void Warehouse::AttackTarget::enemy_soldier_approaches(const Soldier& enemy) const {
+	if (!warehouse_->descr().get_conquers())
+		return;
+
+	Player& owner = warehouse_->owner();
+	Game& game = dynamic_cast<Game&>(owner.egbase());
+	Map& map = game.map();
+	if (enemy.get_owner() == &owner || enemy.get_battle() ||
+	    warehouse_->descr().get_conquers() <=
+	       map.calc_distance(enemy.get_position(), warehouse_->get_position()))
+		return;
+
+	if (game.map().find_bobs(
+	       Area<FCoords>(map.get_fcoords(warehouse_->base_flag().get_position()), 2), nullptr,
+	       FindBobEnemySoldier(&owner)))
+		return;
+
+	DescriptionIndex const soldier_index = owner.tribe().soldier();
+	Requirements noreq;
+
+	if (!warehouse_->count_workers(game, soldier_index, noreq, Match::kCompatible))
+		return;
+
+	Soldier& defender =
+	   dynamic_cast<Soldier&>(warehouse_->launch_worker(game, soldier_index, noreq));
+	defender.start_task_defense(game, false);
+}
+
+AttackTarget::AttackResult Warehouse::AttackTarget::attack(Soldier* enemy) const {
+	Player& owner = warehouse_->owner();
+	Game& game = dynamic_cast<Game&>(owner.egbase());
+	DescriptionIndex const soldier_index = owner.tribe().soldier();
+	Requirements noreq;
+
+	if (warehouse_->count_workers(game, soldier_index, noreq, Match::kCompatible)) {
+		Soldier& defender =
+		   dynamic_cast<Soldier&>(warehouse_->launch_worker(game, soldier_index, noreq));
+		defender.start_task_defense(game, true);
+		enemy->send_signal(game, "sleep");
+		return AttackTarget::AttackResult::DefenderLaunched;
+	}
+
+	warehouse_->set_defeating_player(enemy->owner().player_number());
+	warehouse_->schedule_destroy(game);
+	return AttackTarget::AttackResult::Defenseless;
+}
+
 WarehouseSupply::~WarehouseSupply() {
 	if (economy_) {
 		log("WarehouseSupply::~WarehouseSupply: Warehouse %u still belongs to "
@@ -260,11 +311,13 @@
 
 Warehouse::Warehouse(const WarehouseDescr& warehouse_descr)
    : Building(warehouse_descr),
+     attack_target_(this),
      supply_(new WarehouseSupply(this)),
      next_military_act_(0),
      portdock_(nullptr) {
 	next_stock_remove_act_ = 0;
 	cleanup_in_progress_ = false;
+	set_attack_target(&attack_target_);
 }
 
 Warehouse::~Warehouse() {
@@ -1165,51 +1218,6 @@
 	next_worker_without_cost_spawn_[worker_types_without_cost_index] = never();
 }
 
-bool Warehouse::can_attack() {
-	return descr().get_conquers() > 0;
-}
-
-void Warehouse::aggressor(Soldier& enemy) {
-	if (!descr().get_conquers())
-		return;
-
-	Game& game = dynamic_cast<Game&>(owner().egbase());
-	Map& map = game.map();
-	if (enemy.get_owner() == &owner() || enemy.get_battle() ||
-	    descr().get_conquers() <= map.calc_distance(enemy.get_position(), get_position()))
-		return;
-
-	if (game.map().find_bobs(Area<FCoords>(map.get_fcoords(base_flag().get_position()), 2), nullptr,
-	                         FindBobEnemySoldier(&owner())))
-		return;
-
-	DescriptionIndex const soldier_index = owner().tribe().soldier();
-	Requirements noreq;
-
-	if (!count_workers(game, soldier_index, noreq, Match::kCompatible))
-		return;
-
-	Soldier& defender = dynamic_cast<Soldier&>(launch_worker(game, soldier_index, noreq));
-	defender.start_task_defense(game, false);
-}
-
-bool Warehouse::attack(Soldier& enemy) {
-	Game& game = dynamic_cast<Game&>(owner().egbase());
-	DescriptionIndex const soldier_index = owner().tribe().soldier();
-	Requirements noreq;
-
-	if (count_workers(game, soldier_index, noreq, Match::kCompatible)) {
-		Soldier& defender = dynamic_cast<Soldier&>(launch_worker(game, soldier_index, noreq));
-		defender.start_task_defense(game, true);
-		enemy.send_signal(game, "sleep");
-		return true;
-	}
-
-	set_defeating_player(enemy.owner().player_number());
-	schedule_destroy(game);
-	return false;
-}
-
 void Warehouse::PlannedWorkers::cleanup() {
 	while (!requests.empty()) {
 		delete requests.back();

=== modified file 'src/logic/map_objects/tribes/warehouse.h'
--- src/logic/map_objects/tribes/warehouse.h	2017-04-23 12:11:19 +0000
+++ src/logic/map_objects/tribes/warehouse.h	2017-05-20 22:45:32 +0000
@@ -24,7 +24,6 @@
 #include "base/wexception.h"
 #include "economy/request.h"
 #include "economy/wares_queue.h"
-#include "logic/map_objects/attackable.h"
 #include "logic/map_objects/tribes/building.h"
 #include "logic/map_objects/tribes/soldiercontrol.h"
 #include "logic/map_objects/tribes/wareworker.h"
@@ -71,7 +70,7 @@
 	DISALLOW_COPY_AND_ASSIGN(WarehouseDescr);
 };
 
-class Warehouse : public Building, public Attackable, public SoldierControl {
+class Warehouse : public Building, public SoldierControl {
 	friend class PortDock;
 	friend class MapBuildingdataPacket;
 
@@ -221,15 +220,6 @@
 	void enable_spawn(Game&, uint8_t worker_types_without_cost_index);
 	void disable_spawn(uint8_t worker_types_without_cost_index);
 
-	// Begin Attackable implementation
-	Player& owner() const override {
-		return Building::owner();
-	}
-	bool can_attack() override;
-	void aggressor(Soldier&) override;
-	bool attack(Soldier&) override;
-	// End Attackable implementation
-
 	void receive_ware(Game&, DescriptionIndex ware) override;
 	void receive_worker(Game&, Worker& worker) override;
 
@@ -250,13 +240,26 @@
 
 	void log_general_info(const EditorGameBase&) override;
 
-protected:
+private:
+	// A warehouse that conquers space can also be attacked.
+	class AttackTarget : public Widelands::AttackTarget {
+	public:
+		AttackTarget(Warehouse* warehouse) : warehouse_(warehouse) {
+		}
+
+		bool can_be_attacked() const override;
+		void enemy_soldier_approaches(const Soldier&) const override;
+		Widelands::AttackTarget::AttackResult attack(Soldier*) const override;
+
+	private:
+		Warehouse* const warehouse_;
+	};
+
+	void init_portdock(EditorGameBase& egbase);
+
 	/// Initializes the container sizes for the owner's tribe.
 	void init_containers(Player& owner);
 
-private:
-	void init_portdock(EditorGameBase& egbase);
-
 	/**
 	 * Plan to produce a certain worker type in this warehouse. This means
 	 * requesting all the necessary wares, if multiple different wares types are
@@ -282,6 +285,7 @@
 	void update_planned_workers(Game&, PlannedWorkers& pw);
 	void update_all_planned_workers(Game&);
 
+	AttackTarget attack_target_;
 	WarehouseSupply* supply_;
 
 	std::vector<StockPolicy> ware_policy_;

=== modified file 'src/logic/player.cc'
--- src/logic/player.cc	2017-04-22 05:35:46 +0000
+++ src/logic/player.cc	2017-05-20 22:45:32 +0000
@@ -452,8 +452,8 @@
 		log("Clearing for road at (%i, %i)\n", c.x, c.y);
 
 		//  Make sure that the player owns the area around.
-		dynamic_cast<Game&>(egbase())
-		   .conquer_area_no_building(PlayerArea<Area<FCoords>>(player_number(), Area<FCoords>(c, 1)));
+		dynamic_cast<Game&>(egbase()).conquer_area_no_building(
+		   PlayerArea<Area<FCoords>>(player_number(), Area<FCoords>(c, 1)));
 
 		if (BaseImmovable* const immovable = c.field->get_immovable()) {
 			assert(immovable != &start);
@@ -869,8 +869,8 @@
 		log("enemyflagaction: count is 0\n");
 	else if (is_hostile(flag.owner())) {
 		if (Building* const building = flag.get_building()) {
-			if (upcast(Attackable, attackable, building)) {
-				if (attackable->can_attack()) {
+			if (const AttackTarget* attack_target = building->attack_target()) {
+				if (attack_target->can_be_attacked()) {
 					std::vector<Soldier*> attackers;
 					find_attack_soldiers(flag, &attackers, count);
 					assert(attackers.size() <= count);

=== modified file 'src/wui/attack_box.h'
--- src/wui/attack_box.h	2017-02-23 17:58:25 +0000
+++ src/wui/attack_box.h	2017-05-20 22:45:32 +0000
@@ -26,7 +26,6 @@
 #include "graphic/font_handler1.h"
 #include "graphic/text/font_set.h"
 #include "graphic/text_constants.h"
-#include "logic/map_objects/attackable.h"
 #include "logic/map_objects/bob.h"
 #include "logic/map_objects/tribes/soldier.h"
 #include "logic/player.h"

=== modified file 'src/wui/fieldaction.cc'
--- src/wui/fieldaction.cc	2017-02-27 19:10:24 +0000
+++ src/wui/fieldaction.cc	2017-05-20 22:45:32 +0000
@@ -26,7 +26,7 @@
 #include "economy/road.h"
 #include "graphic/graphic.h"
 #include "logic/cmd_queue.h"
-#include "logic/map_objects/attackable.h"
+#include "logic/map_objects/attack_target.h"
 #include "logic/map_objects/tribes/soldier.h"
 #include "logic/map_objects/tribes/tribe_descr.h"
 #include "logic/map_objects/tribes/warehouse.h"
@@ -380,13 +380,16 @@
 void FieldActionWindow::add_buttons_attack() {
 	UI::Box& a_box = *new UI::Box(&tabpanel_, 0, 0, UI::Box::Horizontal);
 
-	if (upcast(Widelands::Attackable, attackable, map_->get_immovable(node_))) {
-		if (player_ && player_->is_hostile(attackable->owner()) && attackable->can_attack()) {
-			attack_box_ = new AttackBox(&a_box, player_, &node_, 0, 0);
-			a_box.add(attack_box_);
+	if (upcast(Widelands::Building, building, map_->get_immovable(node_))) {
+		if (const Widelands::AttackTarget* attack_target = building->attack_target()) {
+			if (player_ && player_->is_hostile(building->owner()) &&
+			    attack_target->can_be_attacked()) {
+				attack_box_ = new AttackBox(&a_box, player_, &node_, 0, 0);
+				a_box.add(attack_box_);
 
-			set_fastclick_panel(&add_button(
-			   &a_box, "attack", pic_attack, &FieldActionWindow::act_attack, _("Start attack")));
+				set_fastclick_panel(&add_button(
+				   &a_box, "attack", pic_attack, &FieldActionWindow::act_attack, _("Start attack")));
+			}
 		}
 	}
 


References