widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #16413
[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
-
[Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: noreply, 2019-04-27
-
Re: [Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: GunChleoc, 2019-04-27
-
[Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: bunnybot, 2019-04-27
-
Re: [Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: Benedikt Straub, 2019-04-26
-
Re: [Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: GunChleoc, 2019-04-26
-
Re: [Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: GunChleoc, 2019-04-26
-
Re: [Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: Benedikt Straub, 2019-04-26
-
Re: [Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: GunChleoc, 2019-04-24
-
Re: [Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: Benedikt Straub, 2019-03-30
-
Re: [Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: GunChleoc, 2019-03-29
-
[Merge] lp:~widelands-dev/widelands/peaceful into lp:widelands
From: bunnybot, 2019-03-29