widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #04562
[Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
GunChleoc has proposed merging lp:~widelands-dev/widelands/map_compatibility into lp:widelands.
Requested reviews:
Widelands Developers (widelands-dev)
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/map_compatibility/+merge/276088
There is a problem with loading older maps, so I put the remaining compatibility code for maps back in.
It was removed in r7558.
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/map_compatibility into lp:widelands.
=== modified file 'src/logic/expedition_bootstrap.cc'
--- src/logic/expedition_bootstrap.cc 2015-04-15 12:59:16 +0000
+++ src/logic/expedition_bootstrap.cc 2015-10-29 08:43:55 +0000
@@ -260,22 +260,34 @@
}
}
-void ExpeditionBootstrap::load(Warehouse& warehouse, FileRead& fr,
- Game& game, MapObjectLoader& mol)
+constexpr uint16_t kCurrentPacketVersionWarehouse = 6;
+
+void ExpeditionBootstrap::load
+ (uint32_t warehouse_packet_version, Warehouse& warehouse, FileRead& fr,
+ Game& game, MapObjectLoader& mol)
{
- // Expedition workers
- const uint8_t num_workers = fr.unsigned_8();
- for (uint8_t i = 0; i < num_workers; ++i) {
- workers_.emplace_back(new ExpeditionWorker(nullptr));
- if (fr.unsigned_8() == 1) {
- Request* worker_request = new Request
- (warehouse, 0, ExpeditionBootstrap::worker_callback, wwWORKER);
- workers_.back()->request.reset(worker_request);
- worker_request->read(fr, game, mol);
- workers_.back()->worker = nullptr;
+ try {
+ if (warehouse_packet_version >= kCurrentPacketVersionWarehouse) {
+ // Expedition workers
+ const uint8_t num_workers = fr.unsigned_8();
+ for (uint8_t i = 0; i < num_workers; ++i) {
+ workers_.emplace_back(new ExpeditionWorker(nullptr));
+ if (fr.unsigned_8() == 1) {
+ Request* worker_request = new Request
+ (warehouse, 0, ExpeditionBootstrap::worker_callback, wwWORKER);
+ workers_.back()->request.reset(worker_request);
+ worker_request->read(fr, game, mol);
+ workers_.back()->worker = nullptr;
+ } else {
+ workers_.back()->worker = &mol.get<Worker>(fr.unsigned_32());
+ }
+ }
} else {
- workers_.back()->worker = &mol.get<Worker>(fr.unsigned_32());
+ throw UnhandledVersionError("ExpeditionBootstrap",
+ warehouse_packet_version, kCurrentPacketVersionWarehouse);
}
+ } catch (const WException & e) {
+ throw wexception("expedition bootstrap: %s", e.what());
}
// Expedition WaresQueues
=== modified file 'src/logic/expedition_bootstrap.h'
--- src/logic/expedition_bootstrap.h 2015-04-15 12:59:16 +0000
+++ src/logic/expedition_bootstrap.h 2015-10-29 08:43:55 +0000
@@ -75,8 +75,9 @@
// Save/Load this into a file. The actual data is stored in the buildingdata
// packet, and there in the warehouse data packet.
- void load(Warehouse& warehouse, FileRead& fr,
- Game& game, MapObjectLoader& mol);
+ void load
+ (uint32_t warehouse_packet_version, Warehouse& warehouse,
+ FileRead& fr, Game& game, MapObjectLoader& mol);
void save(FileWrite& fw, Game& game, MapObjectSaver& mos);
private:
=== modified file 'src/map_io/map_buildingdata_packet.cc'
--- src/map_io/map_buildingdata_packet.cc 2015-10-24 15:42:37 +0000
+++ src/map_io/map_buildingdata_packet.cc 2015-10-29 08:43:55 +0000
@@ -52,10 +52,12 @@
namespace Widelands {
// Overall package version
+// Since V3: m_old_buildings vector
constexpr uint16_t kCurrentPacketVersion = 4;
// Building type package versions
constexpr uint16_t kCurrentPacketVersionDismantlesite = 1;
+// Since V3: m_prev_building not written
constexpr uint16_t kCurrentPacketVersionConstructionsite = 3;
constexpr uint16_t kCurrentPacketPFBuilding = 1;
constexpr uint16_t kCurrentPacketVersionWarehouse = 6;
@@ -77,9 +79,17 @@
try {
uint16_t const packet_version = fr.unsigned_16();
- if (packet_version == kCurrentPacketVersion) {
- while (! fr.end_of_file()) {
+ if (1 <= packet_version && packet_version <= kCurrentPacketVersion) {
+ for (;;) {
+ if (2 <= packet_version && fr.end_of_file())
+ break;
Serial const serial = fr.unsigned_32();
+ if (packet_version < 2 && serial == 0xffffffff) {
+ if (!fr.end_of_file())
+ throw GameDataError
+ ("expected end of file after serial 0xffffffff");
+ break;
+ }
try {
Building & building = mol.get<Building>(serial);
@@ -143,19 +153,21 @@
else {
building.m_leave_allow = nullptr;
}
-
- while (fr.unsigned_8()) {
- BuildingIndex oldidx = building.descr().tribe().safe_building_index(fr.c_string());
- building.m_old_buildings.push_back(oldidx);
- }
- // Only construction sites may have an empty list
- if (building.m_old_buildings.empty() &&
- building.descr().type() != MapObjectType::CONSTRUCTIONSITE) {
- throw GameDataError
- ("Failed to read %s %u: No former buildings information.\n"
- "Your savegame is corrupted", building.descr().descname().c_str(), building.serial());
- }
-
+ if (packet_version >= 3) {
+ // For former versions, the former buildings vector
+ // will be built after other data are loaded, see below.
+ // read_formerbuildings_v2()
+ while (fr.unsigned_8()) {
+ BuildingIndex oldidx = building.descr().tribe().safe_building_index(fr.c_string());
+ building.m_old_buildings.push_back(oldidx);
+ }
+ // Only construction sites may have an empty list
+ if (building.m_old_buildings.empty() && !is_a(ConstructionSite, &building)) {
+ throw GameDataError
+ ("Failed to read %s %u: No former buildings informations.\n"
+ "Your savegame is corrupted", building.descr().descname().c_str(), building.serial());
+ }
+ }
if (fr.unsigned_8()) {
if (upcast(ProductionSite, productionsite, &building)) {
if (building.descr().type() == MapObjectType::MILITARYSITE) {
@@ -203,6 +215,10 @@
// {ConstructionSite, Warehouse, ProductionSite}
assert(false);
}
+ if (packet_version < 3) {
+ read_formerbuildings_v2(building, fr, game, mol);
+ }
+
mol.mark_object_as_loaded(building);
} catch (const WException & e) {
throw GameDataError("building %u: %s", serial, e.what());
@@ -216,6 +232,46 @@
}
}
+void MapBuildingdataPacket::read_formerbuildings_v2
+ (Building& b, FileRead&, Game&, MapObjectLoader&)
+{
+ const TribeDescr & t = b.descr().tribe();
+ BuildingIndex b_idx = t.building_index(b.descr().name());
+ if (is_a(ProductionSite, &b)) {
+ assert(b.m_old_buildings.empty());
+ b.m_old_buildings.push_back(b_idx);
+ } else if (is_a(Warehouse, &b)) {
+ assert(b.m_old_buildings.empty());
+ b.m_old_buildings.push_back(b_idx);
+ } else if (is_a(DismantleSite, &b)) {
+ // Former buildings filled with the current one
+ // upon building init.
+ assert(!b.m_old_buildings.empty());
+ } else if (is_a(ConstructionSite, &b)) {
+ // Not needed for csite.
+ return;
+ } else {
+ assert(false);
+ }
+
+ // iterate through all buildings to find first predecessor
+ for (;;) {
+ BuildingIndex former_idx = b.m_old_buildings.front();
+ const BuildingDescr * oldest = t.get_building_descr(former_idx);
+ if (!oldest->is_enhanced()) {
+ break;
+ }
+ for (BuildingIndex i = 0; i < t.get_nrbuildings(); ++i) {
+ BuildingDescr const * ob = t.get_building_descr(i);
+ if (ob->enhancement() == former_idx) {
+ b.m_old_buildings.insert(b.m_old_buildings.begin(), i);
+ break;
+ }
+ }
+ }
+}
+
+
void MapBuildingdataPacket::read_partially_finished_building
(PartiallyFinishedBuilding & pfb,
FileRead & fr,
@@ -286,9 +342,14 @@
{
try {
uint16_t const packet_version = fr.unsigned_16();
- if (packet_version >= kCurrentPacketVersionConstructionsite) {
+ if (packet_version == 1)
+ return read_constructionsite_v1(constructionsite, fr, game, mol);
+
+ if (packet_version >= 2) {
read_partially_finished_building(constructionsite, fr, game, mol);
+ const TribeDescr & tribe = constructionsite.descr().tribe();
+
for (ConstructionSite::Wares::iterator wares_iter = constructionsite.m_wares.begin();
wares_iter != constructionsite.m_wares.end();
++wares_iter) {
@@ -297,6 +358,13 @@
(ConstructionSite::wares_queue_callback, &constructionsite);
}
+ if (packet_version <= 2) {
+ if (fr.unsigned_8()) {
+ BuildingIndex idx = tribe.safe_building_index(fr.c_string());
+ constructionsite.m_old_buildings.push_back(idx);
+ }
+ }
+
constructionsite.m_fetchfromflag = fr. signed_32();
} else {
throw UnhandledVersionError("MapBuildingdataPacket - Constructionsite",
@@ -307,6 +375,66 @@
}
}
+void MapBuildingdataPacket::read_constructionsite_v1
+ (ConstructionSite & constructionsite,
+ FileRead & fr,
+ Game & game,
+ MapObjectLoader & mol)
+{
+ const TribeDescr & tribe = constructionsite.descr().tribe();
+ constructionsite.m_building =
+ tribe.get_building_descr(tribe.safe_building_index(fr.c_string()));
+ if (fr.unsigned_8()) {
+ BuildingIndex bidx = tribe.safe_building_index(fr.c_string());
+ constructionsite.m_old_buildings.push_back(bidx);
+ }
+
+ delete constructionsite.m_builder_request;
+ if (fr.unsigned_8()) {
+ constructionsite.m_builder_request =
+ new Request
+ (constructionsite,
+ 0,
+ ConstructionSite::request_builder_callback,
+ wwWORKER);
+ constructionsite.m_builder_request->read(fr, game, mol);
+ } else
+ constructionsite.m_builder_request = nullptr;
+
+ if (uint32_t const builder_serial = fr.unsigned_32()) {
+ try {
+ constructionsite.m_builder = &mol.get<Worker>(builder_serial);
+ } catch (const WException & e) {
+ throw GameDataError
+ ("builder (%u): %s", builder_serial, e.what());
+ }
+ } else
+ constructionsite.m_builder = nullptr;
+
+ try {
+ uint16_t const size = fr.unsigned_16();
+ constructionsite.m_wares.resize(size);
+ for (uint16_t i = 0; i < constructionsite.m_wares.size(); ++i)
+ {
+ constructionsite.m_wares[i] =
+ new WaresQueue
+ (constructionsite, INVALID_INDEX, 0);
+ constructionsite.m_wares[i]->set_callback
+ (ConstructionSite::wares_queue_callback, &constructionsite);
+ constructionsite.m_wares[i]->read(fr, game, mol);
+ }
+ } catch (const WException & e) {
+ throw GameDataError("wares: %s", e.what());
+ }
+
+ constructionsite.m_fetchfromflag = fr. signed_32();
+
+ constructionsite.m_working = fr.unsigned_8 ();
+ constructionsite.m_work_steptime = fr.unsigned_32();
+ constructionsite.m_work_completed = fr.unsigned_32();
+ constructionsite.m_work_steps = fr.unsigned_32();
+}
+
void MapBuildingdataPacket::read_dismantlesite
(DismantleSite & dms,
FileRead & fr,
@@ -337,7 +465,7 @@
{
try {
uint16_t const packet_version = fr.unsigned_16();
- if (packet_version == kCurrentPacketVersionWarehouse) {
+ if (1 <= packet_version && packet_version <= kCurrentPacketVersionWarehouse) {
WareIndex const nr_wares = warehouse.descr().tribe().get_nrwares();
WareIndex const nr_tribe_workers = warehouse.descr().tribe().get_nrworkers();
warehouse.m_supply->set_nrwares (nr_wares);
@@ -350,24 +478,52 @@
const TribeDescr & tribe = warehouse.descr().tribe();
while (fr.unsigned_8()) {
WareIndex const id = tribe.ware_index(fr.c_string());
- uint32_t amount = fr.unsigned_32();
- Warehouse::StockPolicy policy =
- static_cast<Warehouse::StockPolicy>(fr.unsigned_8());
-
- if (id != INVALID_INDEX) {
- warehouse.insert_wares(id, amount);
- warehouse.set_ware_policy(id, policy);
+ if (packet_version >= 5) {
+ uint32_t amount = fr.unsigned_32();
+ Warehouse::StockPolicy policy =
+ static_cast<Warehouse::StockPolicy>(fr.unsigned_8());
+
+ if (id != INVALID_INDEX) {
+ warehouse.insert_wares(id, amount);
+ warehouse.set_ware_policy(id, policy);
+ }
+ } else {
+ uint16_t amount = fr.unsigned_16();
+
+ if (id != INVALID_INDEX)
+ warehouse.insert_wares(id, amount);
}
}
while (fr.unsigned_8()) {
WareIndex const id = tribe.worker_index(fr.c_string());
- uint32_t amount = fr.unsigned_32();
- Warehouse::StockPolicy policy =
- static_cast<Warehouse::StockPolicy>(fr.unsigned_8());
-
- if (id != INVALID_INDEX) {
- warehouse.insert_workers(id, amount);
- warehouse.set_worker_policy(id, policy);
+ if (packet_version >= 5) {
+ uint32_t amount = fr.unsigned_32();
+ Warehouse::StockPolicy policy =
+ static_cast<Warehouse::StockPolicy>(fr.unsigned_8());
+
+ if (id != INVALID_INDEX) {
+ warehouse.insert_workers(id, amount);
+ warehouse.set_worker_policy(id, policy);
+ }
+ } else {
+ uint16_t amount = fr.unsigned_16();
+
+ if (id != INVALID_INDEX)
+ warehouse.insert_workers(id, amount);
+ }
+ }
+
+ if (packet_version <= 3) {
+ // eat the obsolete idle request structures
+ uint32_t nrrequests = fr.unsigned_16();
+ while (nrrequests--) {
+ std::unique_ptr<Request> req
+ (new Request
+ (warehouse,
+ 0,
+ &Warehouse::request_cb,
+ wwWORKER));
+ req->read(fr, game, mol);
}
}
@@ -379,6 +535,13 @@
try {
Worker & worker = mol.get<Worker>(worker_serial);
+ if (1 == packet_version) {
+ char const * const name = fr.c_string();
+ if (name != worker.descr().name())
+ throw GameDataError
+ ("expected %s but found \"%s\"",
+ worker.descr().name().c_str(), name);
+ }
WareIndex worker_index = tribe.worker_index(worker.descr().name().c_str());
if (!warehouse.m_incorporated_workers.count(worker_index))
warehouse.m_incorporated_workers[worker_index] = std::vector<Worker *>();
@@ -394,89 +557,138 @@
const std::vector<WareIndex> & worker_types_without_cost =
tribe.worker_types_without_cost();
- for (;;) {
- char const * const worker_typename = fr.c_string ();
- if (!*worker_typename) // encountered the terminator ("")
- break;
- uint32_t const next_spawn = fr.unsigned_32();
- WareIndex const worker_index =
- tribe.safe_worker_index(worker_typename);
+ if (1 == packet_version) { // a single next_spawn time for "carrier"
+ uint32_t const next_spawn = fr.unsigned_32();
+ WareIndex const worker_index =
+ tribe.safe_worker_index("carrier");
if (worker_index == INVALID_INDEX) {
log
("WARNING: %s %u has a next_spawn time for nonexistent "
"worker type \"%s\" set to %u, ignoring\n",
warehouse.descr().descname().c_str(), warehouse.serial(),
- worker_typename, next_spawn);
- continue;
- }
- if (tribe.get_worker_descr(worker_index)->buildcost().size()) {
+ "carrier", next_spawn);
+ } else if
+ (tribe.get_worker_descr(worker_index)->buildcost().size())
+ {
log
("WARNING: %s %u has a next_spawn time for worker type "
"\"%s\", that costs something to build, set to %u, "
"ignoring\n",
warehouse.descr().descname().c_str(), warehouse.serial(),
- worker_typename, next_spawn);
- continue;
- }
- for (uint8_t i = 0;; ++i) {
- assert(i < worker_types_without_cost.size());
- if (worker_types_without_cost.at(i) == worker_index) {
- if
- (warehouse.m_next_worker_without_cost_spawn[i]
- !=
- static_cast<uint32_t>(never()))
- throw GameDataError
- (
- "%s %u has a next_spawn time for worker type "
- "\"%s\" set to %u, but it was previously set "
- "to %u\n",
- warehouse.descr().descname().c_str(), warehouse.serial(),
- worker_typename, next_spawn,
- warehouse.m_next_worker_without_cost_spawn[i]);
- warehouse.m_next_worker_without_cost_spawn[i] =
- next_spawn;
+ "carrier", next_spawn);
+ } else
+ for (uint8_t i = 0;; ++i) {
+ assert(i < worker_types_without_cost.size());
+ if (worker_types_without_cost.at(i) == worker_index) {
+ if
+ (warehouse.m_next_worker_without_cost_spawn[i]
+ !=
+ static_cast<uint32_t>(never()))
+ {
+ warehouse.molog
+ ("read_warehouse: "
+ "m_next_worker_without_cost_spawn[%u] = %u\n",
+ i, warehouse.m_next_worker_without_cost_spawn[i]);
+ }
+ assert
+ (warehouse.m_next_worker_without_cost_spawn[i]
+ ==
+ static_cast<uint32_t>(never()));
+ warehouse.m_next_worker_without_cost_spawn[i] =
+ next_spawn;
+ break;
+ }
+ }
+ } else
+ for (;;) {
+ char const * const worker_typename = fr.c_string ();
+ if (!*worker_typename) // encountered the terminator ("")
break;
- }
- }
- }
- // The checks that the warehouse has a next_spawn time for each
- // worker type that the player is allowed to spawn, is in
- // Warehouse::load_finish.
-
- // Read planned worker data
- // Consistency checks are in Warehouse::load_finish
- uint32_t nr_planned_workers = fr.unsigned_32();
- while (nr_planned_workers--) {
- warehouse.m_planned_workers.push_back
- (Warehouse::PlannedWorkers());
- Warehouse::PlannedWorkers & pw =
- warehouse.m_planned_workers.back();
- pw.index = tribe.worker_index(fr.c_string());
- pw.amount = fr.unsigned_32();
-
- uint32_t nr_requests = fr.unsigned_32();
- while (nr_requests--) {
- pw.requests.push_back
- (new Request
- (warehouse,
- 0,
- &Warehouse::request_cb,
- wwWORKER));
- pw.requests.back()->read(fr, game, mol);
- }
- }
-
- warehouse.m_next_stock_remove_act = fr.unsigned_32();
-
- if (warehouse.descr().get_isport()) {
- if (Serial portdock = fr.unsigned_32()) {
- warehouse.m_portdock = &mol.get<PortDock>(portdock);
- warehouse.m_portdock->set_economy(warehouse.get_economy());
- // Expedition specific stuff. This is done in this packet
- // because the "new style" loader is not supported and
- // doesn't lend itself to request and other stuff.
- if (warehouse.m_portdock->expedition_started()) {
- warehouse.m_portdock->expedition_bootstrap()->load(warehouse, fr, game, mol);
+ uint32_t const next_spawn = fr.unsigned_32();
+ WareIndex const worker_index =
+ tribe.safe_worker_index(worker_typename);
+ if (worker_index == INVALID_INDEX) {
+ log
+ ("WARNING: %s %u has a next_spawn time for nonexistent "
+ "worker type \"%s\" set to %u, ignoring\n",
+ warehouse.descr().descname().c_str(), warehouse.serial(),
+ worker_typename, next_spawn);
+ continue;
+ }
+ if (tribe.get_worker_descr(worker_index)->buildcost().size()) {
+ log
+ ("WARNING: %s %u has a next_spawn time for worker type "
+ "\"%s\", that costs something to build, set to %u, "
+ "ignoring\n",
+ warehouse.descr().descname().c_str(), warehouse.serial(),
+ worker_typename, next_spawn);
+ continue;
+ }
+ for (uint8_t i = 0;; ++i) {
+ assert(i < worker_types_without_cost.size());
+ if (worker_types_without_cost.at(i) == worker_index) {
+ if
+ (warehouse.m_next_worker_without_cost_spawn[i]
+ !=
+ static_cast<uint32_t>(never()))
+ throw GameDataError
+ (
+ "%s %u has a next_spawn time for worker type "
+ "\"%s\" set to %u, but it was previously set "
+ "to %u\n",
+ warehouse.descr().descname().c_str(), warehouse.serial(),
+ worker_typename, next_spawn,
+ warehouse.m_next_worker_without_cost_spawn[i]);
+ warehouse.m_next_worker_without_cost_spawn[i] =
+ next_spawn;
+ break;
+ }
+ }
+ }
+ // The checks that the warehouse has a next_spawn time for each
+ // worker type that the player is allowed to spawn, is in
+ // Warehouse::load_finish.
+
+ if (packet_version >= 3) {
+ // Read planned worker data
+ // Consistency checks are in Warehouse::load_finish
+ uint32_t nr_planned_workers = fr.unsigned_32();
+ while (nr_planned_workers--) {
+ warehouse.m_planned_workers.push_back
+ (Warehouse::PlannedWorkers());
+ Warehouse::PlannedWorkers & pw =
+ warehouse.m_planned_workers.back();
+ pw.index = tribe.worker_index(fr.c_string());
+ pw.amount = fr.unsigned_32();
+
+ uint32_t nr_requests = fr.unsigned_32();
+ while (nr_requests--) {
+ pw.requests.push_back
+ (new Request
+ (warehouse,
+ 0,
+ &Warehouse::request_cb,
+ wwWORKER));
+ pw.requests.back()->read(fr, game, mol);
+ }
+ }
+ }
+
+ if (packet_version >= 5)
+ warehouse.m_next_stock_remove_act = fr.unsigned_32();
+
+ if (packet_version >= 6) {
+ if (warehouse.descr().get_isport()) {
+ if (Serial portdock = fr.unsigned_32()) {
+ warehouse.m_portdock = &mol.get<PortDock>(portdock);
+ warehouse.m_portdock->set_economy(warehouse.get_economy());
+ // Expedition specific stuff. This is done in this packet
+ // because the "new style" loader is not supported and
+ // doesn't lend itself to request and other stuff.
+ if (warehouse.m_portdock->expedition_started()) {
+ warehouse.m_portdock->expedition_bootstrap()->load
+ (packet_version, warehouse, fr, game, mol);
+ }
}
}
}
@@ -625,7 +837,7 @@
{
try {
uint16_t const packet_version = fr.unsigned_16();
- if (packet_version == kCurrentPacketVersionProductionsite) {
+ if (1 <= packet_version && packet_version <= kCurrentPacketVersionProductionsite) {
ProductionSite::WorkingPosition & wp_begin =
*productionsite.m_working_positions;
const ProductionSiteDescr & pr_descr = productionsite.descr();
@@ -719,7 +931,7 @@
// skipped programs
uint32_t const gametime = game.get_gametime();
- for (uint8_t i = fr.unsigned_8(); i; --i) {
+ for (uint8_t i = 3 <= packet_version ? fr.unsigned_8() : 0; i; --i) {
char const * const program_name = fr.c_string();
if (pr_descr.programs().count(program_name)) {
uint32_t const skip_time = fr.unsigned_32();
@@ -754,10 +966,12 @@
productionsite.m_stack[i].phase = fr. signed_32();
productionsite.m_stack[i].flags = fr.unsigned_32();
- uint32_t serial = fr.unsigned_32();
- if (serial)
- productionsite.m_stack[i].objvar = &mol.get<MapObject>(serial);
- productionsite.m_stack[i].coord = read_coords_32_allow_null(&fr, game.map().extent());
+ if (packet_version >= 5) {
+ uint32_t serial = fr.unsigned_32();
+ if (serial)
+ productionsite.m_stack[i].objvar = &mol.get<MapObject>(serial);
+ productionsite.m_stack[i].coord = read_coords_32_allow_null(&fr, game.map().extent());
+ }
}
productionsite.m_program_timer = fr.unsigned_8();
productionsite.m_program_time = fr.signed_32();
=== modified file 'src/map_io/map_buildingdata_packet.h'
--- src/map_io/map_buildingdata_packet.h 2014-10-01 17:16:21 +0000
+++ src/map_io/map_buildingdata_packet.h 2015-10-29 08:43:55 +0000
@@ -52,6 +52,8 @@
(DismantleSite &, FileRead &, Game &, MapObjectLoader &);
void read_partially_finished_building
(PartiallyFinishedBuilding &, FileRead &, Game &, MapObjectLoader &);
+ void read_constructionsite_v1
+ (ConstructionSite &, FileRead &, Game &, MapObjectLoader &);
void read_warehouse
(Warehouse &, FileRead &, Game &, MapObjectLoader &);
void read_militarysite
@@ -60,6 +62,8 @@
(TrainingSite &, FileRead &, Game &, MapObjectLoader &);
void read_productionsite
(ProductionSite &, FileRead &, Game &, MapObjectLoader &);
+ void read_formerbuildings_v2
+ (Building &, FileRead &, Game &, MapObjectLoader &);
void write_constructionsite
(const ConstructionSite &, FileWrite &, Game &, MapObjectSaver &);
=== modified file 'src/map_io/map_exploration_packet.cc'
--- src/map_io/map_exploration_packet.cc 2015-10-24 15:42:37 +0000
+++ src/map_io/map_exploration_packet.cc 2015-10-29 08:43:55 +0000
@@ -58,7 +58,21 @@
MapIndex const max_index = map.max_index();
try {
uint16_t const packet_version = fr.unsigned_16();
- if (packet_version == kCurrentPacketVersion) {
+ if (packet_version == 1) {
+ for (MapIndex i = 0; i < max_index; ++i) {
+ uint32_t const data = fr.unsigned_16();
+ for (uint8_t j = 0; j < nr_players; ++j) {
+ bool const see = data & (1 << j);
+ if (Player * const player = egbase.get_player(j + 1))
+ player->m_fields[i].vision = see ? 1 : 0;
+ else if (see)
+ log
+ ("MapExplorationPacket::read: WARNING: Player %u, "
+ "which does not exist, sees field %u.\n",
+ j + 1, i);
+ }
+ }
+ } else if (packet_version == kCurrentPacketVersion) {
for (MapIndex i = 0; i < max_index; ++i) {
uint32_t const data = fr.unsigned_32();
for (uint8_t j = 0; j < nr_players; ++j) {
=== modified file 'src/map_io/map_flagdata_packet.cc'
--- src/map_io/map_flagdata_packet.cc 2015-10-24 15:42:37 +0000
+++ src/map_io/map_flagdata_packet.cc 2015-10-29 08:43:55 +0000
@@ -54,15 +54,42 @@
try {
uint16_t const packet_version = fr.unsigned_16();
- if (packet_version == kCurrentPacketVersion) {
+ if (1 <= packet_version && packet_version <= kCurrentPacketVersion) {
const Map & map = egbase.map();
- while (! fr.end_of_file()) {
+ Extent const extent = map.extent();
+ for (;;) {
+ if (2 <= packet_version && fr.end_of_file())
+ break;
Serial const serial = fr.unsigned_32();
+ if (packet_version < 2 && serial == 0xffffffff) {
+ if (!fr.end_of_file())
+ throw GameDataError
+ ("expected end of file after serial 0xffffffff");
+ break;
+ }
try {
Flag & flag = mol.get<Flag>(serial);
// Owner is already set, nothing to do from PlayerImmovable.
+ if (packet_version < 3) {
+ if
+ (upcast
+ (Flag const,
+ mf,
+ map[flag.m_position = read_coords_32(&fr, extent)]
+ .get_immovable()))
+ {
+ if (mf != &flag)
+ throw GameDataError
+ ("wrong flag (%u) at given position (%i, %i)",
+ mf->serial(),
+ flag.m_position.x, flag.m_position.y);
+ } else
+ throw GameDataError
+ ("no flag at given position (%i, %i)",
+ flag.m_position.x, flag.m_position.y);
+ }
flag.m_animstart = fr.unsigned_16();
{
@@ -72,6 +99,26 @@
dynamic_cast<Building *>
(building_position.field->get_immovable());
}
+ if (packet_version < 3) {
+ if (uint32_t const building_serial = fr.unsigned_32())
+ try {
+ const Building & building =
+ mol.get<Building>(building_serial);
+ if (flag.m_building != &building)
+ throw GameDataError
+ (
+ "has building %u at (%i, %i), which is not "
+ "at the top left node",
+ building_serial,
+ building.get_position().x,
+ building.get_position().y);
+ } catch (const WException & e) {
+ throw GameDataError
+ ("building (%u): %s", building_serial, e.what());
+ }
+ else
+ flag.m_building = nullptr;
+ }
// Roads are set somewhere else.
@@ -87,7 +134,10 @@
flag.m_ware_filled = wares_filled;
for (uint32_t i = 0; i < wares_filled; ++i) {
flag.m_wares[i].pending = fr.unsigned_8();
- flag.m_wares[i].priority = fr.signed_32();
+ if (packet_version < 4)
+ flag.m_wares[i].priority = 0;
+ else
+ flag.m_wares[i].priority = fr.signed_32();
uint32_t const ware_serial = fr.unsigned_32();
try {
flag.m_wares[i].ware =
=== modified file 'src/map_io/map_player_names_and_tribes_packet.cc'
--- src/map_io/map_player_names_and_tribes_packet.cc 2015-10-24 15:42:37 +0000
+++ src/map_io/map_player_names_and_tribes_packet.cc 2015-10-29 08:43:55 +0000
@@ -63,8 +63,7 @@
try {
int32_t const packet_version =
prof.get_safe_section("global").get_int("packet_version");
- // Supporting older versions for map loading
- if (1 <= packet_version && packet_version <= kCurrentPacketVersion) {
+ if (packet_version <= kCurrentPacketVersion) {
PlayerNumber const nr_players = map->get_nrplayers();
iterate_player_numbers(p, nr_players) {
Section & s = prof.get_safe_section((boost::format("player_%u")
=== modified file 'src/map_io/map_players_view_packet.cc'
--- src/map_io/map_players_view_packet.cc 2015-10-24 15:42:37 +0000
+++ src/map_io/map_players_view_packet.cc 2015-10-29 08:43:55 +0000
@@ -139,7 +139,7 @@
throw GameDataError("MapPlayersViewPacket::read: player %u:Could not open " \
"\"%s\" for reading. This file should exist when \"%s\" exists", \
plnum, \
- filename, \
+ filename, \
unseen_times_filename); \
} \
}
@@ -167,6 +167,29 @@
static_cast<long unsigned int>((file).get_size() - (file).get_pos()), \
filename);
+// TODO(unknown): Legacy code deprecated since build18
+template <uint8_t const Size> struct BitInBuffer {
+ static_assert(Size == 1 || Size == 2 || Size == 4, "Unexpected Size.");
+ BitInBuffer(FileRead* fr) : buffer(0), mask(0x00) {
+ m_fr = fr;
+ }
+
+ uint8_t get() {
+ if (mask == 0x00) {
+ buffer = m_fr->unsigned_8();
+ mask = 0xff;
+ }
+ uint8_t const result = buffer >> (8 - Size);
+ buffer <<= Size;
+ mask <<= Size;
+ assert(result < (1 << Size));
+ return result;
+ }
+
+private:
+ FileRead* m_fr;
+ uint8_t buffer, mask;
+};
// Errors for the Read* functions.
struct TribeNonexistent : public FileRead::DataError {
@@ -579,6 +602,15 @@
BORDER_FILENAME_TEMPLATE,
kCurrentPacketVersionBorder);
+ // TODO(unknown): Legacy code deprecated since build18
+ BitInBuffer<2> legacy_node_immovable_kinds_bitbuffer(&node_immovable_kinds_file);
+ BitInBuffer<2> legacy_road_bitbuffer(&roads_file);
+ BitInBuffer<4> legacy_terrains_bitbuffer(&terrains_file);
+ BitInBuffer<2> legacy_triangle_immovable_kinds_bitbuffer(&triangle_immovable_kinds_file);
+ BitInBuffer<1> legacy_surveys_bitbuffer(&surveys_file);
+ BitInBuffer<4> legacy_surveys_amount_bitbuffer(&survey_amounts_file);
+ BitInBuffer<1> legacy_border_bitbuffer(&border_file);
+
for
(FCoords first_in_row(Coords(0, 0), &first_field);
first_in_row.y < mapheight;
@@ -657,12 +689,10 @@
f.x, f.y, owner, nr_players);
}
uint8_t imm_kind = 0;
- if (node_immovable_kinds_file_version == kCurrentPacketVersionImmovableKinds) {
+ if (node_immovable_kinds_file_version < kCurrentPacketVersionImmovableKinds) {
+ imm_kind = legacy_node_immovable_kinds_bitbuffer.get();
+ } else {
imm_kind = node_immovable_kinds_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Node Immovable kinds",
- node_immovable_kinds_file_version,
- kCurrentPacketVersionImmovableKinds);
}
MapObjectData mod =
read_unseen_immovable
@@ -670,17 +700,24 @@
f_player_field.map_object_descr[TCoords<>::None] = mod.map_object_descr;
f_player_field.constructionsite = mod.csi;
- // Read in whether this field had a border the last time it was seen
- if (border_file_version == kCurrentPacketVersionBorder) {
- uint8_t borders = border_file.unsigned_8();
- f_player_field.border = borders & 1;
- f_player_field.border_r = borders & 2;
- f_player_field.border_br = borders & 4;
- f_player_field.border_bl = borders & 8;
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Border file",
- border_file_version, kCurrentPacketVersionBorder);
- }
+ // if there is a border file, read in whether this field had a border the last time it was seen
+ if (border_file_version >= 0) {
+ if (border_file_version < 1) {
+ f_player_field.border = (legacy_border_bitbuffer.get() == 1);
+ f_player_field.border_r = (legacy_border_bitbuffer.get() == 1);
+ f_player_field.border_br = (legacy_border_bitbuffer.get() == 1);
+ f_player_field.border_bl = (legacy_border_bitbuffer.get() == 1);
+ } else {
+ uint8_t borders = border_file.unsigned_8();
+ f_player_field.border = borders & 1;
+ f_player_field.border_r = borders & 2;
+ f_player_field.border_br = borders & 4;
+ f_player_field.border_bl = borders & 8;
+ }
+ } else {
+ throw UnhandledVersionError("MapPlayersViewPacket - Border file",
+ border_file_version, kCurrentPacketVersionBorder);
+ }
break;
}
default:
@@ -719,19 +756,23 @@
} else if (f_everseen | bl_everseen | br_everseen) {
// The player has seen the D triangle but does not see it now.
// Load his information about the triangle from file.
- if (terrains_file_version == kCurrentPacketVersionTerrains) {
+ if (terrains_file_version < kCurrentPacketVersionTerrains) {
+ try {f_player_field.terrains.d = legacy_terrains_bitbuffer.get();}
+ catch (const FileRead::FileBoundaryExceeded &) {
+ throw GameDataError
+ ("MapPlayersViewPacket::read: player %u: in "
+ "\"%s\": node (%i, %i) t = D: unexpected end of file "
+ "while reading terrain",
+ plnum, terrains_filename, f.x, f.y);
+ }
+ } else {
f_player_field.terrains.d = terrains_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Terrains",
- terrains_file_version, kCurrentPacketVersionTerrains);
}
uint8_t im_kind = 0;
- if (triangle_immovable_kinds_file_version == kCurrentPacketVersionImmovableKinds) {
+ if (triangle_immovable_kinds_file_version < kCurrentPacketVersionImmovableKinds) {
+ im_kind = legacy_triangle_immovable_kinds_bitbuffer.get();
+ } else {
im_kind = triangle_immovable_kinds_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Triangle Immovable kinds",
- triangle_immovable_kinds_file_version,
- kCurrentPacketVersionImmovableKinds);
}
MapObjectData mod =
read_unseen_immovable
@@ -748,19 +789,23 @@
} else if (f_everseen | br_everseen | r_everseen) {
// The player has seen the R triangle but does not see it now.
// Load his information about the triangle from file.
- if (terrains_file_version == kCurrentPacketVersionTerrains) {
+ if (terrains_file_version < kCurrentPacketVersionTerrains) {
+ try {f_player_field.terrains.r = legacy_terrains_bitbuffer.get();}
+ catch (const FileRead::FileBoundaryExceeded &) {
+ throw GameDataError
+ ("MapPlayersViewPacket::read: player %u: in "
+ "\"%s\": node (%i, %i) t = R: unexpected end of file "
+ "while reading terrain",
+ plnum, terrains_filename, f.x, f.y);
+ }
+ } else {
f_player_field.terrains.r = terrains_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Terrains",
- terrains_file_version, kCurrentPacketVersionTerrains);
}
uint8_t im_kind = 0;
- if (triangle_immovable_kinds_file_version == kCurrentPacketVersionImmovableKinds) {
+ if (triangle_immovable_kinds_file_version < kCurrentPacketVersionImmovableKinds) {
+ im_kind = legacy_triangle_immovable_kinds_bitbuffer.get();
+ } else {
im_kind = triangle_immovable_kinds_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Triangle Immovable kinds",
- triangle_immovable_kinds_file_version,
- kCurrentPacketVersionImmovableKinds);
}
MapObjectData mod =
read_unseen_immovable
@@ -775,11 +820,17 @@
} else if (f_everseen | bl_everseen) {
// The player has seen the SouthWest edge but does not see
// it now. Load his information about this edge from file.
- if (road_file_version == kCurrentPacketVersionRoads) {
+ if (road_file_version < kCurrentPacketVersionRoads) {
+ try {roads = legacy_road_bitbuffer.get() << RoadType::kSouthWest;}
+ catch (const FileRead::FileBoundaryExceeded &) {
+ throw GameDataError
+ ("MapPlayersViewPacket::read: player %u: in "
+ "\"%s\": node (%i, %i): unexpected end of file while "
+ "reading RoadType::kSouthWest",
+ plnum, roads_filename, f.x, f.y);
+ }
+ } else {
roads = roads_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Road file",
- road_file_version, kCurrentPacketVersionRoads);
}
}
if (f_seen | br_seen) {
@@ -787,11 +838,17 @@
} else if (f_everseen | br_everseen) {
// The player has seen the SouthEast edge but does not see
// it now. Load his information about this edge from file.
- if (road_file_version == kCurrentPacketVersionRoads) {
+ if (road_file_version < kCurrentPacketVersionRoads) {
+ try {roads |= legacy_road_bitbuffer.get() << RoadType::kSouthEast;}
+ catch (const FileRead::FileBoundaryExceeded &) {
+ throw GameDataError
+ ("MapPlayersViewPacket::read: player %u: in "
+ "\"%s\": node (%i, %i): unexpected end of file while "
+ "reading RoadType::kSouthEast",
+ plnum, roads_filename, f.x, f.y);
+ }
+ } else {
roads |= roads_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Road file",
- road_file_version, kCurrentPacketVersionRoads);
}
}
if (f_seen | r_seen) {
@@ -799,11 +856,17 @@
} else if (f_everseen | r_everseen) {
// The player has seen the East edge but does not see
// it now. Load his information about this edge from file.
- if (road_file_version == kCurrentPacketVersionRoads) {
+ if (road_file_version < kCurrentPacketVersionRoads) {
+ try {roads |= legacy_road_bitbuffer.get() << RoadType::kEast;}
+ catch (const FileRead::FileBoundaryExceeded &) {
+ throw GameDataError
+ ("MapPlayersViewPacket::read: player %u: in "
+ "\"%s\": node (%i, %i): unexpected end of file while "
+ "reading RoadType::kEast",
+ plnum, roads_filename, f.x, f.y);
+ }
+ } else {
roads |= roads_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Road file",
- road_file_version, kCurrentPacketVersionRoads);
}
}
roads |= f.field->get_roads() & mask;
@@ -816,20 +879,27 @@
// geologic survey
try {
bool survey = false;
- if (surveys_file_version == kCurrentPacketVersionSurveys) {
+ if (surveys_file_version < kCurrentPacketVersionSurveys) {
+ survey = (f_everseen & bl_everseen & br_everseen)
+ && legacy_surveys_bitbuffer.get();
+ } else {
survey = (f_everseen & bl_everseen & br_everseen)
&& surveys_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Surveys file",
- surveys_file_version, kCurrentPacketVersionSurveys);
}
if (survey) {
- if (survey_amounts_file_version == kCurrentPacketVersionSurveyAmounts) {
+ if (survey_amounts_file_version < kCurrentPacketVersionSurveyAmounts) {
+ try {
+ f_player_field.resource_amounts.d =
+ legacy_surveys_amount_bitbuffer.get();
+ } catch (const FileRead::FileBoundaryExceeded &) {
+ throw GameDataError
+ ("MapPlayersViewPacket::read: player %u: in "
+ "\"%s\": node (%i, %i) t = D: unexpected end of file "
+ "while reading resource amount of surveyed triangle",
+ plnum, survey_amounts_filename, f.x, f.y);
+ }
+ } else {
f_player_field.resource_amounts.d = survey_amounts_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Survey amounts",
- survey_amounts_file_version,
- kCurrentPacketVersionSurveyAmounts);
}
try {
f_player_field.time_triangle_last_surveyed[TCoords<>::D] =
@@ -854,20 +924,27 @@
}
try {
bool survey = false;
- if (surveys_file_version == kCurrentPacketVersionSurveys) {
+ if (surveys_file_version < kCurrentPacketVersionSurveys) {
+ survey = (f_everseen & br_everseen & r_everseen)
+ && legacy_surveys_bitbuffer.get();
+ } else {
survey = (f_everseen & br_everseen & r_everseen)
&& surveys_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Surveys file",
- surveys_file_version, kCurrentPacketVersionSurveys);
}
if (survey) {
- if (survey_amounts_file_version == kCurrentPacketVersionSurveyAmounts) {
+ if (survey_amounts_file_version < kCurrentPacketVersionSurveyAmounts) {
+ try {
+ f_player_field.resource_amounts.r =
+ legacy_surveys_amount_bitbuffer.get();
+ } catch (const FileRead::FileBoundaryExceeded &) {
+ throw GameDataError
+ ("MapPlayersViewPacket::read: player %u: in "
+ "\"%s\": node (%i, %i) t = R: unexpected end of file "
+ "while reading resource amount of surveyed triangle",
+ plnum, survey_amounts_filename, f.x, f.y);
+ }
+ } else {
f_player_field.resource_amounts.r = survey_amounts_file.unsigned_8();
- } else {
- throw UnhandledVersionError("MapPlayersViewPacket - Survey amounts",
- survey_amounts_file_version,
- kCurrentPacketVersionSurveyAmounts);
}
try {
f_player_field.time_triangle_last_surveyed[TCoords<>::R] =
=== modified file 'src/map_io/map_roaddata_packet.cc'
--- src/map_io/map_roaddata_packet.cc 2015-10-24 15:42:37 +0000
+++ src/map_io/map_roaddata_packet.cc 2015-10-29 08:43:55 +0000
@@ -55,11 +55,19 @@
try {
uint16_t const packet_version = fr.unsigned_16();
- if (packet_version == kCurrentPacketVersion) {
+ if (1 <= packet_version && packet_version <= kCurrentPacketVersion) {
const Map & map = egbase.map();
PlayerNumber const nr_players = map.get_nrplayers();
- while (! fr.end_of_file()) {
+ for (;;) {
+ if (2 <= packet_version && fr.end_of_file())
+ break;
Serial const serial = fr.unsigned_32();
+ if (packet_version < 2 && serial == 0xffffffff) {
+ if (!fr.end_of_file())
+ throw GameDataError
+ ("expected end of file after serial 0xffffffff");
+ break;
+ }
try {
Game& game = dynamic_cast<Game&>(egbase);
Road & road = mol.get<Road>(serial);
@@ -72,8 +80,10 @@
Player & plr = egbase.player(player_index);
road.set_owner(&plr);
- road.m_busyness = fr.unsigned_32();
- road.m_busyness_last_update = fr.unsigned_32();
+ if (4 <= packet_version) {
+ road.m_busyness = fr.unsigned_32();
+ road.m_busyness_last_update = fr.unsigned_32();
+ }
road.m_type = fr.unsigned_32();
{
uint32_t const flag_0_serial = fr.unsigned_32();
@@ -122,6 +132,12 @@
uint32_t const count = fr.unsigned_32();
if (!count)
throw GameDataError("no carrier slot");
+ if (packet_version <= 2 && 1 < count)
+ throw GameDataError
+ (
+ "expected 1 but found %u carrier slots in road saved "
+ "with packet version 2 (old)",
+ count);
for (uint32_t i = 0; i < count; ++i) {
Carrier * carrier = nullptr;
@@ -149,7 +165,8 @@
} else {
carrier_request = nullptr;
}
- uint8_t const carrier_type = fr.unsigned_32();
+ uint8_t const carrier_type =
+ packet_version < 3 ? 1 : fr.unsigned_32();
if
(i < road.m_carrier_slots.size() &&
=== modified file 'src/map_io/map_scripting_packet.cc'
--- src/map_io/map_scripting_packet.cc 2015-10-24 15:42:37 +0000
+++ src/map_io/map_scripting_packet.cc 2015-10-29 08:43:55 +0000
@@ -38,7 +38,7 @@
namespace Widelands {
namespace {
-constexpr uint32_t kCurrentPacketVersion = 2;
+constexpr uint32_t kCurrentPacketVersion = 1;
} // namespace
/*
* ========================================================================
@@ -58,17 +58,13 @@
FileRead fr;
if (g && fr.try_open(fs, "scripting/globals.dump"))
{
- try {
- const uint32_t packet_version = fr.unsigned_32();
- if (packet_version == kCurrentPacketVersion) {
- upcast(LuaGameInterface, lgi, &g->lua());
- lgi->read_global_env(fr, mol, fr.unsigned_32());
- } else {
- throw UnhandledVersionError("MapScriptingPacket", packet_version, kCurrentPacketVersion);
- }
- } catch (const WException & e) {
- throw GameDataError("scripting: %s", e.what());
+ const uint32_t sentinel = fr.unsigned_32();
+ const uint32_t packet_version = fr.unsigned_32();
+ if (sentinel != 0xDEADBEEF && packet_version != kCurrentPacketVersion) {
+ throw UnhandledVersionError("MapScriptingPacket", packet_version, kCurrentPacketVersion);
}
+ upcast(LuaGameInterface, lgi, &g->lua());
+ lgi->read_global_env(fr, mol, fr.unsigned_32());
}
}
@@ -95,6 +91,7 @@
// Dump the global environment if this is a game and not in the editor
if (upcast(Game, g, &egbase)) {
FileWrite fw;
+ fw.unsigned_32(0xDEADBEEF); // Sentinel, because there was no packet version.
fw.unsigned_32(kCurrentPacketVersion);
const FileWrite::Pos pos = fw.get_pos();
fw.unsigned_32(0); // N bytes written, follows below
Follow ups
-
[Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: GunChleoc, 2016-01-29
-
Re: [Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: GunChleoc, 2016-01-29
-
Re: [Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: SirVer, 2016-01-29
-
[Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: bunnybot, 2016-01-26
-
[Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: bunnybot, 2016-01-25
-
Bunnybot says...
From: bunnybot, 2016-01-20
-
Bunnybot says...
From: bunnybot, 2016-01-12
-
Bunnybot says...
From: bunnybot, 2016-01-12
-
Bunnybot says...
From: bunnybot, 2016-01-04
-
Bunnybot says...
From: bunnybot, 2016-01-03
-
Bunnybot says...
From: bunnybot, 2016-01-02
-
Bunnybot says...
From: bunnybot, 2016-01-02
-
Re: [Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: GunChleoc, 2015-12-17
-
Re: [Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: TiborB, 2015-12-17
-
Re: [Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: GunChleoc, 2015-11-17
-
Re: [Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: Nasenbaer, 2015-11-16
-
Re: [Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: GunChleoc, 2015-11-16
-
Re: [Merge] lp:~widelands-dev/widelands/map_compatibility into lp:widelands
From: Nasenbaer, 2015-11-14