← Back to team overview

widelands-dev team mailing list archive

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

 

Benedikt Straub has proposed merging lp:~widelands-dev/widelands/peaceful into lp:widelands.

Commit message:
Allow scripts to override the hostility of each player to each other player.
Add an option »Peaceful Mode« to the launch game screen.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1794959 in widelands: "Implement independent stance from every tribe to every other tribe"
  https://bugs.launchpad.net/widelands/+bug/1794959

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/peaceful/+merge/365273
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/peaceful into lp:widelands.
=== modified file 'data/campaigns/fri02.wmf/scripting/mission_thread.lua'
--- data/campaigns/fri02.wmf/scripting/mission_thread.lua	2018-09-29 08:43:57 +0000
+++ data/campaigns/fri02.wmf/scripting/mission_thread.lua	2019-03-29 10:22:28 +0000
@@ -280,7 +280,8 @@
    if not p2.defeated then
       campaign_message_box(defeat_murilius_1)
       campaign_message_box(defeat_murilius_2)
-      p2.team = 2
+      p1:set_attack_forbidden(2, false)
+      p2:set_attack_forbidden(1, false)
       o = add_campaign_objective(obj_defeat_murilius)
       local def = false
       while not def do
@@ -299,7 +300,8 @@
 end
 
 function defeat_murilius()
-   p2.team = 2
+   p1:set_attack_forbidden(2, false)
+   p2:set_attack_forbidden(1, false)
    campaign_message_box(defeat_both)
    local o = add_campaign_objective(obj_defeat_both)
    while not (p2.defeated and p3.defeated) do sleep(4829) end
@@ -333,13 +335,27 @@
    include "map:scripting/starting_conditions.lua"
    sleep(5000)
 
-   p1.team = 1
-   p2.team = 1
-   p3.team = 2
--- TODO: instead of alliances, just forbid certain players to attack each other:
---     · Beginning:          forbid 1>2, 2>1, 2>3
---     · Refusing alliance:  forbid only 2>3
---     · Accepting alliance: first unchanged, after p3 defeated: allow all
+   p1:set_attack_forbidden(2, true)
+   p2:set_attack_forbidden(1, true)
+   p2:set_attack_forbidden(3, true)
+   run(function()
+      while true do
+         local conquered = (#p3:get_buildings("empire_sentry") +
+                            #p3:get_buildings("empire_blockhouse") +
+                            #p3:get_buildings("empire_outpost") +
+                            #p3:get_buildings("empire_barrier") +
+                            #p3:get_buildings("empire_tower") +
+                            #p3:get_buildings("empire_fortress") +
+                            #p3:get_buildings("empire_castle") -
+                            #p2:get_buildings("barbarians_sentry") -
+                            #p2:get_buildings("barbarians_barrier") -
+                            #p2:get_buildings("barbarians_tower") -
+                            #p2:get_buildings("barbarians_fortress") -
+                            #p2:get_buildings("barbarians_citadel"))
+         p2:set_attack_forbidden(3, conquered <= 0)
+         sleep(6913)
+      end
+   end)
 
    campaign_message_box(intro_3)
    local o = add_campaign_objective(obj_new_home)

=== modified file 'src/game_io/game_player_info_packet.cc'
--- src/game_io/game_player_info_packet.cc	2019-02-23 11:00:49 +0000
+++ src/game_io/game_player_info_packet.cc	2019-03-29 10:22:28 +0000
@@ -30,14 +30,14 @@
 
 namespace Widelands {
 
-constexpr uint16_t kCurrentPacketVersion = 22;
+constexpr uint16_t kCurrentPacketVersion = 23;
 
 void GamePlayerInfoPacket::read(FileSystem& fs, Game& game, MapObjectLoader*) {
 	try {
 		FileRead fr;
 		fr.open(fs, "binary/player_info");
 		uint16_t const packet_version = fr.unsigned_16();
-		if (packet_version == kCurrentPacketVersion) {
+		if (packet_version <= kCurrentPacketVersion && packet_version >= 22) {
 			uint32_t const max_players = fr.unsigned_16();
 			for (uint32_t i = 1; i < max_players + 1; ++i) {
 				game.remove_player(i);
@@ -59,6 +59,15 @@
 					player->set_see_all(see_all);
 
 					player->set_ai(fr.c_string());
+
+					if (packet_version == kCurrentPacketVersion) {
+						player->forbid_attack_.clear();
+						uint8_t size = fr.unsigned_8();
+						for (uint8_t j = 0; j < size; ++j) {
+							player->forbid_attack_.emplace(fr.unsigned_8());
+						}
+					}
+
 					player->read_statistics(fr, packet_version);
 					player->read_remaining_shipnames(fr);
 
@@ -118,6 +127,11 @@
 		fw.c_string(plr->name_.c_str());
 		fw.c_string(plr->ai_.c_str());
 
+		fw.unsigned_8(plr->forbid_attack_.size());
+		for (const auto& it : plr->forbid_attack_) {
+			fw.unsigned_8(it);
+		}
+
 		plr->write_statistics(fw);
 		plr->write_remaining_shipnames(fw);
 		fw.unsigned_32(plr->casualties());

=== modified file 'src/logic/game.cc'
--- src/logic/game.cc	2019-03-24 11:58:54 +0000
+++ src/logic/game.cc	2019-03-29 10:22:28 +0000
@@ -322,6 +322,19 @@
 	// Check for win_conditions
 	if (!settings.scenario) {
 		loader_ui->step(_("Initializing game…"));
+		if (settings.peaceful) {
+			for (uint32_t i = 1; i < settings.players.size(); ++i) {
+				if (Player* p1 = get_player(i)) {
+					for (uint32_t j = i + 1; j <= settings.players.size(); ++j) {
+						if (Player* p2 = get_player(j)) {
+							p1->set_attack_forbidden(j, true);
+							p2->set_attack_forbidden(i, true);
+						}
+					}
+				}
+			}
+		}
+
 		std::unique_ptr<LuaTable> table(lua().run_script(settings.win_condition_script));
 		table->do_not_warn_about_unaccessed_keys();
 		win_condition_displayname_ = table->get_string("name");

=== modified file 'src/logic/game_settings.h'
--- src/logic/game_settings.h	2019-02-23 11:00:49 +0000
+++ src/logic/game_settings.h	2019-03-29 10:22:28 +0000
@@ -111,7 +111,7 @@
  * Think of it as the Model in MVC.
  */
 struct GameSettings {
-	GameSettings() : playernum(0), usernum(0), scenario(false), multiplayer(false), savegame(false) {
+	GameSettings() : playernum(0), usernum(0), scenario(false), multiplayer(false), savegame(false), peaceful(false) {
 		std::unique_ptr<LuaInterface> lua(new LuaInterface);
 		std::unique_ptr<LuaTable> win_conditions(
 		   lua->run_script("scripting/win_conditions/init.lua"));
@@ -156,6 +156,9 @@
 	/// Is a savegame selected for loading?
 	bool savegame;
 
+	// Is all fighting forbidden?
+	bool peaceful;
+
 	/// List of tribes that players are allowed to choose
 	std::vector<Widelands::TribeBasicInfo> tribes;
 
@@ -210,6 +213,9 @@
 	virtual void set_win_condition_script(const std::string& wc) = 0;
 	virtual std::string get_win_condition_script() = 0;
 
+	virtual void set_peaceful_mode(bool peace) = 0;
+	virtual bool is_peaceful_mode() = 0;
+
 	// For retrieving tips texts
 	struct NoTribe {};
 	const std::string& get_players_tribe() {

=== modified file 'src/logic/player.cc'
--- src/logic/player.cc	2019-03-09 08:58:52 +0000
+++ src/logic/player.cc	2019-03-29 10:22:28 +0000
@@ -250,7 +250,8 @@
  * each other.
  */
 bool Player::is_hostile(const Player& other) const {
-	return &other != this && (!team_number_ || team_number_ != other.team_number_);
+	return &other != this && (!team_number_ || team_number_ != other.team_number_) &&
+			!is_attack_forbidden(other.player_number());
 }
 
 bool Player::is_defeated() const {
@@ -1326,6 +1327,21 @@
 	return ai_;
 }
 
+bool Player::is_attack_forbidden(PlayerNumber who) const {
+	return forbid_attack_.find(who) != forbid_attack_.end();
+}
+
+void Player::set_attack_forbidden(PlayerNumber who, bool forbid) {
+	const auto it = forbid_attack_.find(who);
+	if (forbid ^ (it == forbid_attack_.end())) {
+		return;
+	} else if (forbid) {
+		forbid_attack_.emplace(who);
+	} else {
+		forbid_attack_.erase(it);
+	}
+}
+
 /**
  * Pick random name from remaining names (if any)
  */

=== modified file 'src/logic/player.h'
--- src/logic/player.h	2019-03-09 08:58:52 +0000
+++ src/logic/player.h	2019-03-29 10:22:28 +0000
@@ -603,6 +603,9 @@
 		further_initializations_.push_back(init);
 	}
 
+	void set_attack_forbidden(PlayerNumber who, bool forbid);
+	bool is_attack_forbidden(PlayerNumber who) const;
+
 	const std::string pick_shipname();
 
 private:
@@ -680,6 +683,8 @@
 	 */
 	std::vector<std::vector<uint32_t>> ware_stocks_;
 
+	std::set<PlayerNumber> forbid_attack_;
+
 	PlayerBuildingStats building_stats_;
 
 	DISALLOW_COPY_AND_ASSIGN(Player);

=== modified file 'src/logic/single_player_game_settings_provider.cc'
--- src/logic/single_player_game_settings_provider.cc	2019-02-23 11:00:49 +0000
+++ src/logic/single_player_game_settings_provider.cc	2019-03-29 10:22:28 +0000
@@ -69,6 +69,14 @@
 	return s.mapfilename;
 }
 
+bool SinglePlayerGameSettingsProvider::is_peaceful_mode() {
+	return s.peaceful;
+}
+
+void SinglePlayerGameSettingsProvider::set_peaceful_mode(bool peace) {
+	s.peaceful = peace;
+}
+
 void SinglePlayerGameSettingsProvider::set_map(const std::string& mapname,
                                                const std::string& mapfilename,
                                                uint32_t const maxplayers,

=== modified file 'src/logic/single_player_game_settings_provider.h'
--- src/logic/single_player_game_settings_provider.h	2019-02-23 11:00:49 +0000
+++ src/logic/single_player_game_settings_provider.h	2019-03-29 10:22:28 +0000
@@ -62,6 +62,9 @@
 	std::string get_win_condition_script() override;
 	void set_win_condition_script(const std::string& wc) override;
 
+	void set_peaceful_mode(bool peace) override;
+	bool is_peaceful_mode() override;
+
 private:
 	GameSettings s;
 };

=== modified file 'src/network/gameclient.cc'
--- src/network/gameclient.cc	2019-02-23 11:00:49 +0000
+++ src/network/gameclient.cc	2019-03-29 10:22:28 +0000
@@ -405,6 +405,15 @@
 	// set_player_number(uint8_t) to the host.
 }
 
+
+void GameClient::set_peaceful_mode(bool peace) {
+	d->settings.peaceful = peace;
+}
+
+bool GameClient::is_peaceful_mode() {
+	return d->settings.peaceful;
+}
+
 std::string GameClient::get_win_condition_script() {
 	return d->settings.win_condition_script;
 }
@@ -819,6 +828,10 @@
 		d->settings.win_condition_script = g_fs->FileSystem::fix_cross_file(packet.string());
 		break;
 	}
+	case NETCMD_PEACEFUL_MODE: {
+		d->settings.peaceful = packet.unsigned_8();
+		break;
+	}
 
 	case NETCMD_LAUNCH: {
 		if (!d->modal || d->game) {

=== modified file 'src/network/gameclient.h'
--- src/network/gameclient.h	2019-02-23 11:00:49 +0000
+++ src/network/gameclient.h	2019-03-29 10:22:28 +0000
@@ -95,6 +95,9 @@
 	void set_win_condition_script(const std::string&) override;
 	std::string get_win_condition_script() override;
 
+	void set_peaceful_mode(bool peace) override;
+	bool is_peaceful_mode() override;
+
 	// ChatProvider interface
 	void send(const std::string& msg) override;
 	const std::vector<ChatMessage>& get_messages() const override;

=== modified file 'src/network/gamehost.cc'
--- src/network/gamehost.cc	2019-02-23 11:00:49 +0000
+++ src/network/gamehost.cc	2019-03-29 10:22:28 +0000
@@ -203,6 +203,13 @@
 		host_->set_win_condition_script(wc);
 	}
 
+	void set_peaceful_mode(bool peace) override {
+		host_->set_peaceful_mode(peace);
+	}
+	bool is_peaceful_mode() override {
+		return host_->settings().peaceful;
+	}
+
 private:
 	GameHost* host_;
 	std::vector<std::string> wincondition_scripts_;
@@ -1368,6 +1375,16 @@
 	broadcast(packet);
 }
 
+void GameHost::set_peaceful_mode(bool peace) {
+	d->settings.peaceful = peace;
+
+	// Broadcast changes
+	SendPacket packet;
+	packet.unsigned_8(NETCMD_PEACEFUL_MODE);
+	packet.unsigned_8(peace ? 1 : 0);
+	broadcast(packet);
+}
+
 void GameHost::switch_to_player(uint32_t user, uint8_t number) {
 	if (number < d->settings.players.size() &&
 	    (d->settings.players.at(number).state != PlayerSettings::State::kOpen &&
@@ -1676,6 +1693,11 @@
 	packet.string(d->settings.win_condition_script);
 	d->net->send(client.sock_id, packet);
 
+	packet.reset();
+	packet.unsigned_8(NETCMD_PEACEFUL_MODE);
+	packet.unsigned_8(d->settings.peaceful ? 1 : 0);
+	d->net->send(client.sock_id, packet);
+
 	// Broadcast new information about the player to everybody
 	packet.reset();
 	packet.unsigned_8(NETCMD_SETTING_USER);
@@ -2143,6 +2165,12 @@
 		}
 		break;
 
+	case NETCMD_PEACEFUL_MODE:
+		if (!d->game) {
+			throw DisconnectException("NO_ACCESS_TO_SERVER");
+		}
+		break;
+
 	case NETCMD_LAUNCH:
 		if (!d->game) {
 			throw DisconnectException("NO_ACCESS_TO_SERVER");

=== modified file 'src/network/gamehost.h'
--- src/network/gamehost.h	2019-02-23 11:00:49 +0000
+++ src/network/gamehost.h	2019-03-29 10:22:28 +0000
@@ -80,6 +80,7 @@
 	void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared);
 	void switch_to_player(uint32_t user, uint8_t number);
 	void set_win_condition_script(const std::string& wc);
+	void set_peaceful_mode(bool peace);
 	void replace_client_with_ai(uint8_t playernumber, const std::string& ai);
 
 	// just visible stuff for the select mapmenu

=== modified file 'src/network/network_protocol.h'
--- src/network/network_protocol.h	2019-02-23 11:00:49 +0000
+++ src/network/network_protocol.h	2019-03-29 10:22:28 +0000
@@ -429,6 +429,14 @@
 	NETCMD_SYSTEM_MESSAGE_CODE = 32,
 
 	/**
+	 * Sent by the host to toggle peaceful mode.
+	 *
+	 * Attached data is:
+	 * \li uint8_t: 1 if peaceful mode is enabled, 0 otherwise
+	 */
+	NETCMD_PEACEFUL_MODE = 33,
+
+	/**
 	 * Sent by the metaserver to a freshly opened game to check connectability
 	 */
 	NETCMD_METASERVER_PING = 64

=== modified file 'src/scripting/lua_game.cc'
--- src/scripting/lua_game.cc	2019-03-09 08:58:52 +0000
+++ src/scripting/lua_game.cc	2019-03-29 10:22:28 +0000
@@ -101,6 +101,8 @@
    METHOD(LuaPlayer, allow_workers),
    METHOD(LuaPlayer, switchplayer),
    METHOD(LuaPlayer, get_produced_wares_count),
+   METHOD(LuaPlayer, set_attack_forbidden),
+   METHOD(LuaPlayer, is_attack_forbidden),
    {nullptr, nullptr},
 };
 const PropertyType<LuaPlayer> LuaPlayer::Properties[] = {
@@ -875,6 +877,39 @@
 	return 1;
 }
 
+/* RST
+   .. method:: is_attack_forbidden(who)
+
+      Returns true if this player is currently forbidden to attack the player with the specified
+      player number. Note that the return value `false` does not necessarily mean that this
+      player *can* attack the other player, as they might for example be in the same team.
+
+      :arg who: player number of the player to query
+      :type who: :class:`int`
+      :rtype: :class:`boolean`
+*/
+int LuaPlayer::is_attack_forbidden(lua_State* L) {
+	lua_pushboolean(L, get(L, get_egbase(L)).is_attack_forbidden(luaL_checkinteger(L, 2)));
+	return 1;
+}
+
+/* RST
+   .. method:: set_attack_forbidden(who, forbid)
+
+      Sets whether this player is forbidden to attack the player with the specified
+      player number. Note that setting this to `false` does not necessarily mean that this
+      player *can* attack the other player, as they might for example be in the same team.
+
+      :arg who: player number of the player to query
+      :type who: :class:`int`
+      :arg forbid: Whether to allow or forbid attacks
+      :type forbid: :class:`boolean`
+*/
+int LuaPlayer::set_attack_forbidden(lua_State* L) {
+	get(L, get_egbase(L)).set_attack_forbidden(luaL_checkinteger(L, 2), luaL_checkboolean(L, 3));
+	return 0;
+}
+
 /*
  ==========================================================
  C METHODS

=== modified file 'src/scripting/lua_game.h'
--- src/scripting/lua_game.h	2019-02-23 11:00:49 +0000
+++ src/scripting/lua_game.h	2019-03-29 10:22:28 +0000
@@ -97,6 +97,8 @@
 	int allow_workers(lua_State* L);
 	int switchplayer(lua_State* L);
 	int get_produced_wares_count(lua_State* L);
+	int set_attack_forbidden(lua_State* L);
+	int is_attack_forbidden(lua_State* L);
 
 	/*
 	 * C methods

=== modified file 'src/ui_fsmenu/launch_game.cc'
--- src/ui_fsmenu/launch_game.cc	2019-02-23 11:00:49 +0000
+++ src/ui_fsmenu/launch_game.cc	2019-03-29 10:22:28 +0000
@@ -55,6 +55,12 @@
                              "",
                              UI::DropdownType::kTextual,
                              UI::PanelStyle::kFsMenu),
+
+     peaceful_(this,
+               Vector2i(get_w() * 7 / 10,
+               get_h() * 19 / 40 + buth_),
+               _("Peaceful mode"),
+               _("Forbid fighting between players")),
      ok_(this, "ok", 0, 0, butw_, buth_, UI::ButtonStyle::kFsMenuPrimary, _("Start game")),
      back_(this, "back", 0, 0, butw_, buth_, UI::ButtonStyle::kFsMenuSecondary, _("Back")),
      // Text labels
@@ -65,6 +71,8 @@
      nr_players_(0) {
 	win_condition_dropdown_.selected.connect(
 	   boost::bind(&FullscreenMenuLaunchGame::win_condition_selected, this));
+	peaceful_.changed.connect(
+	   boost::bind(&FullscreenMenuLaunchGame::toggle_peaceful, this));
 	back_.sigclicked.connect(
 	   boost::bind(&FullscreenMenuLaunchGame::clicked_back, boost::ref(*this)));
 	ok_.sigclicked.connect(boost::bind(&FullscreenMenuLaunchGame::clicked_ok, boost::ref(*this)));
@@ -78,6 +86,14 @@
 	delete lua_;
 }
 
+void FullscreenMenuLaunchGame::update_peaceful_mode() {
+	bool scenario_or_savegame = settings_->settings().scenario || settings_->settings().savegame;
+	peaceful_.set_enabled(!scenario_or_savegame && settings_->can_change_map());
+	if (scenario_or_savegame) {
+		peaceful_.set_state(false);
+	}
+}
+
 bool FullscreenMenuLaunchGame::init_win_condition_label() {
 	if (settings_->settings().scenario) {
 		win_condition_dropdown_.set_enabled(false);
@@ -190,6 +206,10 @@
 	return t;
 }
 
+void FullscreenMenuLaunchGame::toggle_peaceful() {
+	settings_->set_peaceful_mode(peaceful_.get_state());
+}
+
 // Implemented by subclasses
 void FullscreenMenuLaunchGame::clicked_ok() {
 	NEVER_HERE();

=== modified file 'src/ui_fsmenu/launch_game.h'
--- src/ui_fsmenu/launch_game.h	2019-02-23 11:00:49 +0000
+++ src/ui_fsmenu/launch_game.h	2019-03-29 10:22:28 +0000
@@ -26,6 +26,7 @@
 #include "graphic/playercolor.h"
 #include "logic/map.h"
 #include "ui_basic/button.h"
+#include "ui_basic/checkbox.h"
 #include "ui_basic/dropdown.h"
 #include "ui_basic/textarea.h"
 #include "ui_fsmenu/base.h"
@@ -56,6 +57,9 @@
 	/// Creates a blank label/tooltip and returns 'false' otherwise.
 	bool init_win_condition_label();
 
+	/// Enables or disables the peaceful mode checkbox.
+	void update_peaceful_mode();
+
 	/// Loads all win conditions that can be played with the map into the selection dropdown.
 	/// Disables the dropdown if the map is a scenario.
 	void update_win_conditions();
@@ -71,10 +75,13 @@
 	std::unique_ptr<LuaTable> win_condition_if_valid(const std::string& win_condition_script,
 	                                                 std::set<std::string> tags) const;
 
+	void toggle_peaceful();
+
 	uint32_t butw_;
 	uint32_t buth_;
 
 	UI::Dropdown<std::string> win_condition_dropdown_;
+	UI::Checkbox peaceful_;
 	std::string last_win_condition_;
 	UI::Button ok_, back_;
 	UI::Textarea title_;

=== modified file 'src/ui_fsmenu/launch_mpg.cc'
--- src/ui_fsmenu/launch_mpg.cc	2019-02-23 11:00:49 +0000
+++ src/ui_fsmenu/launch_mpg.cc	2019-03-29 10:22:28 +0000
@@ -159,7 +159,7 @@
                UI::PanelStyle::kFsMenu),
      client_info_(this,
                   right_column_x_,
-                  get_h() * 13 / 20 - 2 * label_height_,
+                  get_h() * 15 / 20 - 2 * label_height_,
                   butw_,
                   get_h(),
                   UI::PanelStyle::kFsMenu),
@@ -167,7 +167,8 @@
 
      // Variables and objects used in the menu
      chat_(nullptr) {
-	ok_.set_pos(Vector2i(right_column_x_, get_h() * 12 / 20 - 2 * label_height_));
+	peaceful_.set_pos(Vector2i(right_column_x_, get_h() * 25 / 40 - 2 * label_height_));
+	ok_.set_pos(Vector2i(right_column_x_, get_h() * 14 / 20 - 2 * label_height_));
 	back_.set_pos(Vector2i(right_column_x_, get_h() * 218 / 240));
 	win_condition_dropdown_.set_pos(
 	   Vector2i(right_column_x_, get_h() * 11 / 20 - 2 * label_height_));
@@ -422,6 +423,9 @@
 	change_map_or_save_.set_enabled(settings_->can_change_map());
 	change_map_or_save_.set_visible(settings_->can_change_map());
 
+	update_peaceful_mode();
+	peaceful_.set_state(settings_->is_peaceful_mode());
+
 	if (!settings_->can_change_map() && !init_win_condition_label()) {
 		try {
 			// We do not validate the scripts for the client - it's only a label.

=== modified file 'src/ui_fsmenu/launch_spg.cc'
--- src/ui_fsmenu/launch_spg.cc	2019-02-23 11:00:49 +0000
+++ src/ui_fsmenu/launch_spg.cc	2019-03-29 10:22:28 +0000
@@ -217,6 +217,8 @@
 		select_map_.set_visible(settings_->can_change_map());
 		select_map_.set_enabled(settings_->can_change_map());
 
+		peaceful_.set_state(settings_->is_peaceful_mode());
+
 		set_player_names_and_tribes();
 	}
 
@@ -262,6 +264,7 @@
 	safe_place_for_host(nr_players_);
 	settings_->set_map(mapdata.name, mapdata.filename, nr_players_);
 	update_win_conditions();
+	update_peaceful_mode();
 	update(true);
 	return true;
 }


Follow ups