Benedikt Straub has proposed merging lp:~widelands-dev/widelands/constructionsite_options into lp:widelands.
Commit message: Allow players to define settings for and to enhance buildings under construction Requested reviews: Widelands Developers (widelands-dev) Related bugs: Bug #1597310 in widelands: "Possibility to set building options when building is under construction" https://bugs.launchpad.net/widelands/+bug/1597310 For more details, see: https://code.launchpad.net/~widelands-dev/widelands/constructionsite_options/+merge/367428 -- Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/constructionsite_options into lp:widelands.
=== modified file 'src/ai/defaultai_seafaring.cc' --- src/ai/defaultai_seafaring.cc 2019-02-23 11:00:49 +0000 +++ src/ai/defaultai_seafaring.cc 2019-05-14 17:38:23 +0000 @@ -346,7 +346,7 @@ if (site->bo->is(BuildingAttribute::kShipyard)) { for (uint32_t k = 0; k < site->bo->inputs.size(); ++k) { game().send_player_set_ware_priority( - *site->site, wwWARE, site->bo->inputs.at(k), HIGH_PRIORITY); + *site->site, wwWARE, site->bo->inputs.at(k), kPriorityHigh); } } } === modified file 'src/economy/economy.cc' --- src/economy/economy.cc 2019-04-09 16:43:49 +0000 +++ src/economy/economy.cc 2019-05-14 17:38:23 +0000 @@ -971,7 +971,7 @@ static bool accept_warehouse_if_policy(Warehouse& wh, WareWorker type, DescriptionIndex ware, - Warehouse::StockPolicy policy) { + StockPolicy policy) { return wh.get_stock_policy(type, ware) == policy; } @@ -1005,8 +1005,8 @@ for (uint32_t nwh = 0; nwh < warehouses_.size(); ++nwh) { Warehouse* wh = warehouses_[nwh]; - Warehouse::StockPolicy policy = wh->get_stock_policy(type, ware); - if (policy == Warehouse::StockPolicy::kPrefer) { + StockPolicy policy = wh->get_stock_policy(type, ware); + if (policy == StockPolicy::kPrefer) { haveprefer = true; // Getting count of worker/ware @@ -1022,7 +1022,7 @@ preferred_wh_stock = current_stock; } } - if (policy == Warehouse::StockPolicy::kNormal) + if (policy == StockPolicy::kNormal) havenormal = true; } if (!havenormal && !haveprefer && type == wwWARE) @@ -1037,7 +1037,7 @@ (!havenormal) ? WarehouseAcceptFn() : boost::bind(&accept_warehouse_if_policy, _1, type, ware, - Warehouse::StockPolicy::kNormal)); + StockPolicy::kNormal)); } if (!wh) { log("Warning: Economy::handle_active_supplies " === modified file 'src/economy/request.cc' --- src/economy/request.cc 2019-02-23 11:00:49 +0000 +++ src/economy/request.cc 2019-05-14 17:38:23 +0000 @@ -262,7 +262,7 @@ int32_t Request::get_priority(int32_t cost) const { int MAX_IDLE_PRIORITY = 100; bool is_construction_site = false; - int32_t modifier = DEFAULT_PRIORITY; + int32_t modifier = kPriorityNormal; if (target_building_) { modifier = target_building_->get_priority(get_type(), get_index()); === modified file 'src/logic/editor_game_base.cc' --- src/logic/editor_game_base.cc 2019-04-26 05:52:49 +0000 +++ src/logic/editor_game_base.cc 2019-05-14 17:38:23 +0000 @@ -38,6 +38,7 @@ #include "logic/map_objects/map_object.h" #include "logic/map_objects/tribes/battle.h" #include "logic/map_objects/tribes/building.h" +#include "logic/map_objects/tribes/constructionsite.h" #include "logic/map_objects/tribes/dismantlesite.h" #include "logic/map_objects/tribes/tribe_descr.h" #include "logic/map_objects/tribes/tribes.h" @@ -349,10 +350,15 @@ PlayerNumber const owner, DescriptionIndex idx, bool loading, - Building::FormerBuildings former_buildings) { + Building::FormerBuildings former_buildings, + const BuildingSettings* settings) { Player* plr = get_player(owner); const TribeDescr& tribe = plr->tribe(); - return tribe.get_building_descr(idx)->create(*this, plr, c, true, loading, former_buildings); + Building& b = tribe.get_building_descr(idx)->create(*this, plr, c, true, loading, former_buildings); + if (settings) { + dynamic_cast<ConstructionSite&>(b).apply_settings(*settings); + } + return b; } /** === modified file 'src/logic/editor_game_base.h' --- src/logic/editor_game_base.h 2019-02-27 19:00:36 +0000 +++ src/logic/editor_game_base.h 2019-05-14 17:38:23 +0000 @@ -56,6 +56,7 @@ class TribeDescr; struct Flag; struct AttackController; +struct BuildingSettings; struct NoteFieldPossession { CAN_BE_SENT_AS_NOTE(NoteId::FieldPossession) @@ -130,7 +131,8 @@ PlayerNumber, DescriptionIndex, bool loading = false, - Building::FormerBuildings former_buildings = Building::FormerBuildings()); + Building::FormerBuildings former_buildings = Building::FormerBuildings(), + const BuildingSettings* settings = nullptr); Building& warp_dismantlesite(const Coords&, PlayerNumber, === modified file 'src/logic/game.cc' --- src/logic/game.cc 2019-05-11 18:19:20 +0000 +++ src/logic/game.cc 2019-05-14 17:38:23 +0000 @@ -56,6 +56,7 @@ #include "logic/map_objects/tribes/soldier.h" #include "logic/map_objects/tribes/trainingsite.h" #include "logic/map_objects/tribes/tribe_descr.h" +#include "logic/map_objects/tribes/warehouse.h" #include "logic/player.h" #include "logic/playercommand.h" #include "logic/replay.h" @@ -859,6 +860,47 @@ *new CmdProposeTrade(get_gametime(), object->get_owner()->player_number(), trade)); } +void Game::send_player_constructionsite_soldier_capacity(ConstructionSite& cs, uint32_t c) { + send_player_command(*new CmdConstructionsiteSoldierCapacity( + get_gametime(), cs.get_owner()->player_number(), cs, c)); +} + +void Game::send_player_constructionsite_prefer_heroes(ConstructionSite& cs, bool h) { + send_player_command(*new CmdConstructionsitePreferHeroes( + get_gametime(), cs.get_owner()->player_number(), cs, h)); +} + +void Game::send_player_constructionsite_launch_expedition(ConstructionSite& cs, bool l) { + send_player_command(*new CmdConstructionsiteLaunchExpedition( + get_gametime(), cs.get_owner()->player_number(), cs, l)); +} + +void Game::send_player_constructionsite_stock_policy( + ConstructionSite& cs, WareWorker ww, DescriptionIndex di, StockPolicy pol) { + send_player_command(*new CmdConstructionsiteStockPolicy( + get_gametime(), cs.get_owner()->player_number(), cs, ww, di, pol)); +} + +void Game::send_player_constructionsite_input_queue_priority( + ConstructionSite& cs, WareWorker ww, DescriptionIndex di, int32_t p) { + send_player_command(*new CmdConstructionsiteInputQueuePriority( + get_gametime(), cs.get_owner()->player_number(), cs, ww, di, p)); +} + +void Game::send_player_constructionsite_input_queue_max_fill( + ConstructionSite& cs, WareWorker ww, DescriptionIndex di, uint32_t max) { + send_player_command(*new CmdConstructionsiteInputQueueMaxFill( + get_gametime(), cs.get_owner()->player_number(), cs, ww, di, max)); +} + +void Game::send_player_constructionsite_enhance(ConstructionSite& cs) { + send_player_command(*new CmdConstructionsiteEnhance(get_gametime(), cs.get_owner()->player_number(), cs)); +} + +void Game::send_player_constructionsite_startstop(ConstructionSite& cs, bool stop) { + send_player_command(*new CmdConstructionsiteStartStop(get_gametime(), cs.get_owner()->player_number(), cs, stop)); +} + int Game::propose_trade(const Trade& trade) { // TODO(sirver,trading): Check if a trade is possible (i.e. if there is a // path between the two markets); === modified file 'src/logic/game.h' --- src/logic/game.h 2019-05-07 12:14:02 +0000 +++ src/logic/game.h 2019-05-14 17:38:23 +0000 @@ -46,6 +46,7 @@ // See forester_cache_ constexpr int16_t kInvalidForesterEntry = -1; +class ConstructionSite; struct Flag; struct Path; struct PlayerImmovable; @@ -56,6 +57,7 @@ struct PlayerEndStatus; class TrainingSite; class MilitarySite; +enum class StockPolicy; enum { gs_notrunning = 0, // game is being prepared @@ -282,6 +284,16 @@ void send_player_cancel_expedition_ship(Ship&); void send_player_propose_trade(const Trade& trade); + void send_player_constructionsite_soldier_capacity(ConstructionSite&, uint32_t); + void send_player_constructionsite_prefer_heroes(ConstructionSite&, bool); + void send_player_constructionsite_launch_expedition(ConstructionSite&, bool); + void send_player_constructionsite_stock_policy( + ConstructionSite&, WareWorker, DescriptionIndex, StockPolicy); + void send_player_constructionsite_input_queue_priority(ConstructionSite&, WareWorker, DescriptionIndex, int32_t); + void send_player_constructionsite_input_queue_max_fill(ConstructionSite&, WareWorker, DescriptionIndex, uint32_t); + void send_player_constructionsite_enhance(ConstructionSite&); + void send_player_constructionsite_startstop(ConstructionSite&, bool); + InteractivePlayer* get_ipl(); SaveHandler& save_handler() { === modified file 'src/logic/map_objects/CMakeLists.txt' --- src/logic/map_objects/CMakeLists.txt 2019-05-05 18:53:14 +0000 +++ src/logic/map_objects/CMakeLists.txt 2019-05-14 17:38:23 +0000 @@ -49,6 +49,8 @@ tribes/bill_of_materials.h tribes/building.cc tribes/building.h + tribes/building_settings.cc + tribes/building_settings.h tribes/carrier.cc tribes/carrier.h tribes/constructionsite.cc === modified file 'src/logic/map_objects/tribes/building.cc' --- src/logic/map_objects/tribes/building.cc 2019-05-05 14:05:07 +0000 +++ src/logic/map_objects/tribes/building.cc 2019-05-14 17:38:23 +0000 @@ -638,13 +638,13 @@ int32_t Building::get_priority(WareWorker type, DescriptionIndex const ware_index, bool adjust) const { - int32_t priority = DEFAULT_PRIORITY; + int32_t priority = kPriorityNormal; if (type == wwWARE) { // if priority is defined for specific ware, // combine base priority and ware priority std::map<DescriptionIndex, int32_t>::const_iterator it = ware_priorities_.find(ware_index); if (it != ware_priorities_.end()) - priority = adjust ? (priority * it->second / DEFAULT_PRIORITY) : it->second; + priority = adjust ? (priority * it->second / kPriorityNormal) : it->second; } return priority; @@ -660,7 +660,7 @@ std::map<DescriptionIndex, int32_t>& ware_priorities = p[wwWARE]; std::map<DescriptionIndex, int32_t>::const_iterator it; for (it = ware_priorities_.begin(); it != ware_priorities_.end(); ++it) { - if (it->second == DEFAULT_PRIORITY) + if (it->second == kPriorityNormal) continue; ware_priorities[it->first] = it->second; } === modified file 'src/logic/map_objects/tribes/building.h' --- src/logic/map_objects/tribes/building.h 2019-05-11 12:37:45 +0000 +++ src/logic/map_objects/tribes/building.h 2019-05-14 17:38:23 +0000 @@ -31,6 +31,7 @@ #include "logic/map_objects/buildcost.h" #include "logic/map_objects/immovable.h" #include "logic/map_objects/tribes/attack_target.h" +#include "logic/map_objects/tribes/building_settings.h" #include "logic/map_objects/tribes/bill_of_materials.h" #include "logic/map_objects/tribes/soldiercontrol.h" #include "logic/map_objects/tribes/wareworker.h" @@ -51,9 +52,9 @@ class Building; -#define LOW_PRIORITY 2 -#define DEFAULT_PRIORITY 4 -#define HIGH_PRIORITY 8 +constexpr int32_t kPriorityLow = 2; +constexpr int32_t kPriorityNormal = 4; +constexpr int32_t kPriorityHigh = 8; /* * Common to all buildings! @@ -266,8 +267,8 @@ // Get/Set the priority for this waretype for this building. 'type' defines // if this is for a worker or a ware, 'index' is the type of worker or ware. - // If 'adjust' is false, the three possible states HIGH_PRIORITY, - // DEFAULT_PRIORITY and LOW_PRIORITY are returned, otherwise numerical + // If 'adjust' is false, the three possible states kPriorityHigh, + // kPriorityNormal and kPriorityLow are returned, otherwise numerical // values adjusted to the preciousness of the ware in general are returned. virtual int32_t get_priority(WareWorker type, DescriptionIndex, bool adjust = true) const; void set_priority(int32_t type, DescriptionIndex ware_index, int32_t new_priority); @@ -301,6 +302,10 @@ void add_worker(Worker&) override; void remove_worker(Worker&) override; + virtual const BuildingSettings* create_building_settings() const { + return nullptr; + } + // 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 { === added file 'src/logic/map_objects/tribes/building_settings.cc' --- src/logic/map_objects/tribes/building_settings.cc 1970-01-01 00:00:00 +0000 +++ src/logic/map_objects/tribes/building_settings.cc 2019-05-14 17:38:23 +0000 @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2002-2019 by the Widelands Development Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "logic/map_objects/tribes/building_settings.h" + +#include "io/fileread.h" +#include "io/filewrite.h" +#include "logic/game.h" +#include "logic/game_data_error.h" +#include "logic/map_objects/tribes/militarysite.h" +#include "logic/map_objects/tribes/productionsite.h" +#include "logic/map_objects/tribes/trainingsite.h" +#include "logic/map_objects/tribes/tribe_descr.h" +#include "logic/map_objects/tribes/warehouse.h" + +namespace Widelands { + +ProductionsiteSettings::ProductionsiteSettings(const ProductionSiteDescr& descr) + : BuildingSettings(descr.name()), stopped(false) { + for (WareRange i(descr.input_wares()); i; ++i) { + ware_queues.push_back(std::make_pair(i.current->first, + InputQueueSetting{i.current->second, i.current->second, kPriorityNormal})); + } + for (WareRange i(descr.input_workers()); i; ++i) { + worker_queues.push_back(std::make_pair(i.current->first, + InputQueueSetting{i.current->second, i.current->second, kPriorityNormal})); + } +} + +MilitarysiteSettings::MilitarysiteSettings(const MilitarySiteDescr& descr) + : BuildingSettings(descr.name()), + max_capacity(descr.get_max_number_of_soldiers()), + desired_capacity(descr.get_max_number_of_soldiers()), + prefer_heroes(descr.prefers_heroes_at_start_) { +} + +TrainingsiteSettings::TrainingsiteSettings(const TrainingSiteDescr& descr) + : ProductionsiteSettings(descr), + max_capacity(descr.get_max_number_of_soldiers()), + desired_capacity(descr.get_max_number_of_soldiers()) { +} + +WarehouseSettings::WarehouseSettings(const WarehouseDescr& wh, const TribeDescr& tribe) + : BuildingSettings(wh.name()), launch_expedition_allowed(wh.get_isport()), launch_expedition(false) { + for (const DescriptionIndex di : tribe.wares()) { + ware_preferences.emplace(di, StockPolicy::kNormal); + } + for (const DescriptionIndex di : tribe.workers()) { + worker_preferences.emplace(di, StockPolicy::kNormal); + } + for (const DescriptionIndex di : tribe.worker_types_without_cost()) { + worker_preferences.erase(di); + } +} + +void ProductionsiteSettings::apply(const BuildingSettings& bs) { + BuildingSettings::apply(bs); + if (upcast(const ProductionsiteSettings, s, &bs)) { + stopped = s->stopped; + for (auto& pair : ware_queues) { + for (const auto& other : s->ware_queues) { + if (pair.first == other.first) { + pair.second.priority = other.second.priority; + pair.second.desired_fill = std::min(pair.second.max_fill, other.second.desired_fill); + break; + } + } + } + for (auto& pair : worker_queues) { + for (const auto& other : s->worker_queues) { + if (pair.first == other.first) { + pair.second.priority = other.second.priority; + pair.second.desired_fill = std::min(pair.second.max_fill, other.second.desired_fill); + break; + } + } + } + } +} + +void TrainingsiteSettings::apply(const BuildingSettings& bs) { + ProductionsiteSettings::apply(bs); + if (upcast(const TrainingsiteSettings, s, &bs)) { + desired_capacity = std::min(max_capacity, s->desired_capacity); + } +} + +void MilitarysiteSettings::apply(const BuildingSettings& bs) { + BuildingSettings::apply(bs); + if (upcast(const MilitarysiteSettings, s, &bs)) { + desired_capacity = std::min(max_capacity, s->desired_capacity); + prefer_heroes = s->prefer_heroes; + } +} + +void WarehouseSettings::apply(const BuildingSettings& bs) { + BuildingSettings::apply(bs); + if (upcast(const WarehouseSettings, s, &bs)) { + for (auto& pair : ware_preferences) { + const auto it = s->ware_preferences.find(pair.first); + if (it != s->ware_preferences.end()) { + pair.second = it->second; + } + } + for (auto& pair : worker_preferences) { + const auto it = s->worker_preferences.find(pair.first); + if (it != s->worker_preferences.end()) { + pair.second = it->second; + } + } + launch_expedition = launch_expedition_allowed && s->launch_expedition; + } +} + +// Saveloading + +constexpr uint8_t kCurrentPacketVersion = 1; +constexpr uint8_t kCurrentPacketVersionMilitarysite = 1; +constexpr uint8_t kCurrentPacketVersionProductionsite = 1; +constexpr uint8_t kCurrentPacketVersionTrainingsite = 1; +constexpr uint8_t kCurrentPacketVersionWarehouse = 1; + +enum class BuildingType : uint8_t { + kWarehouse = 1, + kProductionsite = 2, + kTrainingsite = 3, + kMilitarysite = 4, +}; + +// static +BuildingSettings* BuildingSettings::load(const Game& game, const TribeDescr& tribe, FileRead& fr) { + try { + const uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersion) { + const std::string name(fr.c_string()); + const DescriptionIndex index = game.tribes().building_index(name); + const BuildingType type = static_cast<BuildingType>(fr.unsigned_8()); + BuildingSettings* result = nullptr; + switch (type) { + case BuildingType::kTrainingsite: { + result = new TrainingsiteSettings(*dynamic_cast<const TrainingSiteDescr*>( + game.tribes().get_building_descr(index))); + break; + } + case BuildingType::kProductionsite: { + result = new ProductionsiteSettings(*dynamic_cast<const ProductionSiteDescr*>( + game.tribes().get_building_descr(index))); + break; + } + case BuildingType::kMilitarysite: { + result = new MilitarysiteSettings(*dynamic_cast<const MilitarySiteDescr*>( + game.tribes().get_building_descr(index))); + break; + } + case BuildingType::kWarehouse: { + result = new WarehouseSettings(*dynamic_cast<const WarehouseDescr*>( + game.tribes().get_building_descr(index)), tribe); + break; + } + default: + throw wexception("Unknown building category %u (%s)", static_cast<uint8_t>(type), name.c_str()); + } + result->read(game, fr); + return result; + } else { + throw UnhandledVersionError( + "BuildingSettings_load", packet_version, kCurrentPacketVersion); + } + } catch (const WException& e) { + throw GameDataError("BuildingSettings_load: %s", e.what()); + } + NEVER_HERE(); +} + +void BuildingSettings::read(const Game&, FileRead&) { + // Header was peeled away by load() +} + +void BuildingSettings::save(const Game&, FileWrite& fw) const { + fw.unsigned_8(kCurrentPacketVersion); + fw.c_string(descr_.c_str()); +} + +void MilitarysiteSettings::read(const Game& game, FileRead& fr) { + BuildingSettings::read(game, fr); + try { + const uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionMilitarysite) { + desired_capacity = fr.unsigned_32(); + prefer_heroes = fr.unsigned_8(); + } else { + throw UnhandledVersionError( + "MilitarysiteSettings", packet_version, kCurrentPacketVersionMilitarysite); + } + } catch (const WException& e) { + throw GameDataError("MilitarysiteSettings: %s", e.what()); + } +} + +void MilitarysiteSettings::save(const Game& game, FileWrite& fw) const { + BuildingSettings::save(game, fw); + fw.unsigned_8(static_cast<uint8_t>(BuildingType::kMilitarysite)); + fw.unsigned_8(kCurrentPacketVersionMilitarysite); + + fw.unsigned_32(desired_capacity); + fw.unsigned_8(prefer_heroes ? 1 : 0); +} + +void ProductionsiteSettings::read(const Game& game, FileRead& fr) { + BuildingSettings::read(game, fr); + try { + const uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionProductionsite) { + stopped = fr.unsigned_8(); + const uint32_t nr_wares = fr.unsigned_32(); + const uint32_t nr_workers = fr.unsigned_32(); + for (uint32_t i = 0; i < nr_wares; ++i) { + const DescriptionIndex di = fr.unsigned_32(); + const uint32_t fill = fr.unsigned_32(); + const int32_t priority = fr.signed_32(); + ware_queues.at(i).first = di; + ware_queues.at(i).second.desired_fill = fill; + ware_queues.at(i).second.priority = priority; + } + for (uint32_t i = 0; i < nr_workers; ++i) { + const DescriptionIndex di = fr.unsigned_32(); + const uint32_t fill = fr.unsigned_32(); + const int32_t priority = fr.signed_32(); + worker_queues.at(i).first = di; + worker_queues.at(i).second.desired_fill = fill; + worker_queues.at(i).second.priority = priority; + } + } else { + throw UnhandledVersionError( + "ProductionsiteSettings", packet_version, kCurrentPacketVersionProductionsite); + } + } catch (const WException& e) { + throw GameDataError("ProductionsiteSettings: %s", e.what()); + } +} + +void ProductionsiteSettings::save(const Game& game, FileWrite& fw) const { + BuildingSettings::save(game, fw); + fw.unsigned_8(static_cast<uint8_t>(BuildingType::kProductionsite)); + fw.unsigned_8(kCurrentPacketVersionProductionsite); + + fw.unsigned_8(stopped ? 1 : 0); + fw.unsigned_32(ware_queues.size()); + fw.unsigned_32(worker_queues.size()); + for (const auto& pair : ware_queues) { + fw.unsigned_32(pair.first); + fw.unsigned_32(pair.second.desired_fill); + fw.signed_32(pair.second.priority); + } + for (const auto& pair : worker_queues) { + fw.unsigned_32(pair.first); + fw.unsigned_32(pair.second.desired_fill); + fw.signed_32(pair.second.priority); + } +} + +void TrainingsiteSettings::read(const Game& game, FileRead& fr) { + ProductionsiteSettings::read(game, fr); + try { + const uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionTrainingsite) { + desired_capacity = fr.unsigned_32(); + } else { + throw UnhandledVersionError( + "TrainingsiteSettings", packet_version, kCurrentPacketVersionTrainingsite); + } + } catch (const WException& e) { + throw GameDataError("TrainingsiteSettings: %s", e.what()); + } +} + +void TrainingsiteSettings::save(const Game& game, FileWrite& fw) const { + ProductionsiteSettings::save(game, fw); + fw.unsigned_8(static_cast<uint8_t>(BuildingType::kTrainingsite)); + fw.unsigned_8(kCurrentPacketVersionTrainingsite); + fw.unsigned_32(desired_capacity); +} + +void WarehouseSettings::read(const Game& game, FileRead& fr) { + BuildingSettings::read(game, fr); + try { + const uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionWarehouse) { + launch_expedition = fr.unsigned_8(); + const uint32_t nr_wares = fr.unsigned_32(); + const uint32_t nr_workers = fr.unsigned_32(); + for (uint32_t i = 0; i < nr_wares; ++i) { + const DescriptionIndex di = fr.unsigned_32(); + const uint8_t pref = fr.unsigned_8(); + ware_preferences[di] = static_cast<StockPolicy>(pref); + } + for (uint32_t i = 0; i < nr_workers; ++i) { + const DescriptionIndex di = fr.unsigned_32(); + const uint8_t pref = fr.unsigned_8(); + worker_preferences[di] = static_cast<StockPolicy>(pref); + } + } else { + throw UnhandledVersionError( + "WarehouseSettings", packet_version, kCurrentPacketVersionWarehouse); + } + } catch (const WException& e) { + throw GameDataError("WarehouseSettings: %s", e.what()); + } +} + +void WarehouseSettings::save(const Game& game, FileWrite& fw) const { + BuildingSettings::save(game, fw); + fw.unsigned_8(static_cast<uint8_t>(BuildingType::kWarehouse)); + fw.unsigned_8(kCurrentPacketVersionWarehouse); + + fw.unsigned_8(launch_expedition ? 1 : 0); + fw.unsigned_32(ware_preferences.size()); + fw.unsigned_32(worker_preferences.size()); + for (const auto& pair : ware_preferences) { + fw.unsigned_32(pair.first); + fw.unsigned_8(static_cast<uint8_t>(pair.second)); + } + for (const auto& pair : worker_preferences) { + fw.unsigned_32(pair.first); + fw.unsigned_8(static_cast<uint8_t>(pair.second)); + } +} + +} // namespace Widelands === added file 'src/logic/map_objects/tribes/building_settings.h' --- src/logic/map_objects/tribes/building_settings.h 1970-01-01 00:00:00 +0000 +++ src/logic/map_objects/tribes/building_settings.h 2019-05-14 17:38:23 +0000 @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2002-2019 by the Widelands Development Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef WL_LOGIC_MAP_OBJECTS_TRIBES_BUILDING_SETTINGS_H +#define WL_LOGIC_MAP_OBJECTS_TRIBES_BUILDING_SETTINGS_H + +#include <map> +#include <string> +#include <vector> + +#include "logic/widelands.h" + +class FileRead; +class FileWrite; + +namespace Widelands { + +class Game; +class MilitarySiteDescr; +class ProductionSiteDescr; +enum class StockPolicy; +class TrainingSiteDescr; +class TribeDescr; +class WarehouseDescr; + +struct BuildingSettings { + BuildingSettings(const std::string& name) : descr_(name) { + } + static BuildingSettings* load(const Game&, const TribeDescr&, FileRead&); + + virtual void save(const Game&, FileWrite&) const; + virtual void read(const Game&, FileRead&); + + virtual void apply(const BuildingSettings&) { + } + +private: + const std::string descr_; +}; + +struct ProductionsiteSettings : public BuildingSettings { + ProductionsiteSettings(const ProductionSiteDescr& descr); + void apply(const BuildingSettings&) override; + + void save(const Game&, FileWrite&) const override; + void read(const Game&, FileRead&) override; + + struct InputQueueSetting { + const uint32_t max_fill; + uint32_t desired_fill; + int32_t priority; + }; + std::vector<std::pair<DescriptionIndex, InputQueueSetting>> ware_queues; + std::vector<std::pair<DescriptionIndex, InputQueueSetting>> worker_queues; + bool stopped; +}; + +struct MilitarysiteSettings : public BuildingSettings { + MilitarysiteSettings(const MilitarySiteDescr&); + void apply(const BuildingSettings&) override; + + void save(const Game&, FileWrite&) const override; + void read(const Game&, FileRead&) override; + + const uint32_t max_capacity; + uint32_t desired_capacity; + bool prefer_heroes; +}; + +struct TrainingsiteSettings : public ProductionsiteSettings { + TrainingsiteSettings(const TrainingSiteDescr&); + void apply(const BuildingSettings&) override; + + void save(const Game&, FileWrite&) const override; + void read(const Game&, FileRead&) override; + + const uint32_t max_capacity; + uint32_t desired_capacity; +}; + +struct WarehouseSettings : public BuildingSettings { + WarehouseSettings(const WarehouseDescr&, const TribeDescr&); + void apply(const BuildingSettings&) override; + + void save(const Game&, FileWrite&) const override; + void read(const Game&, FileRead&) override; + + std::map<DescriptionIndex, StockPolicy> ware_preferences; + std::map<DescriptionIndex, StockPolicy> worker_preferences; + const bool launch_expedition_allowed; + bool launch_expedition; +}; + +} // namespace Widelands + +#endif // end of include guard: WL_LOGIC_MAP_OBJECTS_TRIBES_BUILDING_SETTINGS_H === modified file 'src/logic/map_objects/tribes/constructionsite.cc' --- src/logic/map_objects/tribes/constructionsite.cc 2019-04-26 05:52:49 +0000 +++ src/logic/map_objects/tribes/constructionsite.cc 2019-05-14 17:38:23 +0000 @@ -33,6 +33,11 @@ #include "graphic/text_constants.h" #include "logic/editor_game_base.h" #include "logic/game.h" +#include "logic/game_data_error.h" +#include "logic/map_objects/tribes/militarysite.h" +#include "logic/map_objects/tribes/partially_finished_building.h" +#include "logic/map_objects/tribes/productionsite.h" +#include "logic/map_objects/tribes/trainingsite.h" #include "logic/map_objects/tribes/tribe_descr.h" #include "logic/map_objects/tribes/worker.h" #include "sound/note_sound.h" @@ -47,32 +52,67 @@ const RGBColor& player_color, RenderTarget* dst) const { // Draw the construction site marker - const uint32_t anim_idx = becomes->is_animation_known("build") ? - becomes->get_animation("build") : - becomes->get_unoccupied_animation(); - - const Animation& anim = g_gr->animations().get_animation(anim_idx); - const size_t nr_frames = anim.nr_frames(); - const uint32_t cur_frame = totaltime ? completedtime * nr_frames / totaltime : 0; - uint32_t anim_time = cur_frame * FRAME_LENGTH; - - if (cur_frame) { // not the first pic - // Draw the complete prev pic , so we won't run into trouble if images have different sizes - dst->blit_animation(point_on_dst, Widelands::Coords::null(), scale, anim_idx, - anim_time - FRAME_LENGTH, &player_color); + std::vector<std::pair<uint32_t, uint32_t>> animations; + uint32_t total_frames = 0; + for (const BuildingDescr* d : intermediates) { + const bool known = d->is_animation_known("build"); + const uint32_t anim_idx = known ? + d->get_animation("build") : + d->get_unoccupied_animation(); + // If there is no build animation, we use only the first frame or we + // would get many build steps with almost the same image... + const uint32_t nrframes = known ? g_gr->animations().get_animation(anim_idx).nr_frames() : 1; + assert(nrframes); + total_frames += nrframes; + animations.push_back(std::make_pair(anim_idx, nrframes)); + } + { // Now the same for the final building + const bool known = becomes->is_animation_known("build"); + const uint32_t anim_idx = known ? + becomes->get_animation("build") : + becomes->get_unoccupied_animation(); + const uint32_t nrframes = known ? g_gr->animations().get_animation(anim_idx).nr_frames() : 1; + assert(nrframes); + total_frames += nrframes; + animations.push_back(std::make_pair(anim_idx, nrframes)); + } + + uint32_t frame_index = totaltime ? std::min(completedtime * total_frames / totaltime, total_frames) : 0; + uint32_t animation_index = 0; + while (frame_index >= animations[animation_index].second) { + frame_index -= animations[animation_index].second; + ++animation_index; + assert(animation_index < animations.size()); + } + const uint32_t anim_time = frame_index * FRAME_LENGTH; + + if (frame_index > 0) { + // Not the first pic within this animation – draw the previous one + dst->blit_animation(point_on_dst, Widelands::Coords::null(), scale, + animations[animation_index].first, anim_time - FRAME_LENGTH, &player_color); + } else if (animation_index > 0) { + // The first pic, but not the first series of animations – draw the last pic of the previous series + dst->blit_animation(point_on_dst, Widelands::Coords::null(), scale, + animations[animation_index - 1].first, + FRAME_LENGTH * (animations[animation_index - 1].second - 1), &player_color); } else if (was) { - // Is the first picture but there was another building here before, - // get its most fitting picture and draw it instead. - dst->blit_animation(point_on_dst, Widelands::Coords::null(), scale, - was->get_unoccupied_animation(), anim_time - FRAME_LENGTH, &player_color); + // First pic in first series, but there was another building here before – + // get its most fitting picture and draw it instead + const uint32_t unocc = was->get_unoccupied_animation(); + dst->blit_animation(point_on_dst, Widelands::Coords::null(), scale, unocc, + FRAME_LENGTH * (g_gr->animations().get_animation(unocc).nr_frames() - 1), + &player_color); } // Now blit a segment of the current construction phase from the bottom. - int percent = 100 * completedtime * nr_frames; + int percent = 100 * completedtime * total_frames; if (totaltime) { percent /= totaltime; } - percent -= 100 * cur_frame; - dst->blit_animation(point_on_dst, coords, scale, anim_idx, anim_time, &player_color, percent); + percent -= 100 * frame_index; + for (uint32_t i = 0; i < animation_index; ++i) { + percent -= 100 * animations[i].second; + } + dst->blit_animation(point_on_dst, coords, scale, animations[animation_index].first, anim_time, &player_color, percent); } /** @@ -105,7 +145,7 @@ */ ConstructionSite::ConstructionSite(const ConstructionSiteDescr& cs_descr) - : PartiallyFinishedBuilding(cs_descr), fetchfromflag_(0), builder_idle_(false) { + : PartiallyFinishedBuilding(cs_descr), fetchfromflag_(0), builder_idle_(false), settings_(nullptr) { } void ConstructionSite::update_statistics_string(std::string* s) { @@ -183,6 +223,22 @@ work_steps_ += it->second; } + + assert(!settings_); + if (upcast(const WarehouseDescr, wd, building_)) { + settings_.reset(new WarehouseSettings(*wd, owner().tribe())); + } else if (upcast(const TrainingSiteDescr, td, building_)) { + settings_.reset(new TrainingsiteSettings(*td)); + } else if (upcast(const ProductionSiteDescr, pd, building_)) { + settings_.reset(new ProductionsiteSettings(*pd)); + } else if (upcast(const MilitarySiteDescr, md, building_)) { + settings_.reset(new MilitarysiteSettings(*md)); + } else { + // TODO(Nordfriese): Add support for markets when trading is implemented + log("WARNING: Created constructionsite for a %s, which is not of any known building type\n", + building_->name().c_str()); + } + return true; } @@ -209,6 +265,48 @@ builder->reset_tasks(dynamic_cast<Game&>(egbase)); builder->set_location(&b); } + + // Apply settings + if (settings_) { + if (upcast(ProductionsiteSettings, ps, settings_.get())) { + for (const auto& pair : ps->ware_queues) { + b.inputqueue(pair.first, wwWARE).set_max_fill(pair.second.desired_fill); + b.set_priority(wwWARE, pair.first, pair.second.priority); + } + for (const auto& pair : ps->worker_queues) { + b.inputqueue(pair.first, wwWORKER).set_max_fill(pair.second.desired_fill); + b.set_priority(wwWORKER, pair.first, pair.second.priority); + } + if (upcast(TrainingsiteSettings, ts, ps)) { + assert(b.soldier_control()); + assert(ts->desired_capacity >= b.soldier_control()->min_soldier_capacity()); + assert(ts->desired_capacity <= b.soldier_control()->max_soldier_capacity()); + b.mutable_soldier_control()->set_soldier_capacity(ts->desired_capacity); + } + dynamic_cast<ProductionSite&>(b).set_stopped(ps->stopped); + } else if (upcast(MilitarysiteSettings, ms, settings_.get())) { + assert(b.soldier_control()); + assert(ms->desired_capacity >= b.soldier_control()->min_soldier_capacity()); + assert(ms->desired_capacity <= b.soldier_control()->max_soldier_capacity()); + b.mutable_soldier_control()->set_soldier_capacity(ms->desired_capacity); + dynamic_cast<MilitarySite&>(b).set_soldier_preference(ms->prefer_heroes ? + SoldierPreference::kHeroes : SoldierPreference::kRookies); + } else if (upcast(WarehouseSettings, ws, settings_.get())) { + Warehouse& site = dynamic_cast<Warehouse&>(b); + for (const auto& pair : ws->ware_preferences) { + site.set_ware_policy(pair.first, pair.second); + } + for (const auto& pair : ws->worker_preferences) { + site.set_worker_policy(pair.first, pair.second); + } + if (ws->launch_expedition) { + get_owner()->start_or_cancel_expedition(site); + } + } else { + NEVER_HERE(); + } + } + // Open the new building window if needed Notifications::publish(NoteBuilding(b.serial(), NoteBuilding::Action::kFinishWarp)); } @@ -216,6 +314,138 @@ /* =============== +Start building the next enhancement even before the base building is completed. +=============== +*/ +void ConstructionSite::enhance(Game&) { + assert(building_->enhancement() != INVALID_INDEX); + Notifications::publish(NoteImmovable(this, NoteImmovable::Ownership::LOST)); + + info_.intermediates.push_back(building_); + old_buildings_.push_back(owner().tribe().building_index(building_->name())); + building_ = owner().tribe().get_building_descr(building_->enhancement()); + assert(building_); + info_.becomes = building_; + + const std::map<DescriptionIndex, uint8_t>& buildcost = building_->enhancement_cost(); + const size_t buildcost_size = buildcost.size(); + std::set<DescriptionIndex> new_ware_types; + for (std::map<DescriptionIndex, uint8_t>::const_iterator it = buildcost.begin(); it != buildcost.end(); ++it) { + bool found = false; + for (const auto& queue : wares_) { + if (queue->get_index() == it->first) { + found = true; + break; + } + } + if (!found) { + new_ware_types.insert(it->first); + } + } + + const size_t old_size = wares_.size(); + wares_.resize(old_size + new_ware_types.size()); + std::map<DescriptionIndex, uint8_t>::const_iterator it = buildcost.begin(); + + size_t new_wares_index = 0; + for (size_t i = 0; i < buildcost_size; ++i, ++it) { + if (new_ware_types.count(it->first)) { + WaresQueue& wq = *(wares_[old_size + new_wares_index] = new WaresQueue(*this, it->first, it->second)); + wq.set_callback(ConstructionSite::wares_queue_callback, this); + wq.set_consume_interval(CONSTRUCTIONSITE_STEP_TIME); + ++new_wares_index; + } else { + assert(i >= new_wares_index); + WaresQueue& wq = *wares_[i - new_wares_index]; + wq.set_max_size(wq.get_max_size() + it->second); + wq.set_max_fill(wq.get_max_fill() + it->second); + } + work_steps_ += it->second; + } + + BuildingSettings* old_settings = settings_.release(); + if (upcast(const WarehouseDescr, wd, building_)) { + WarehouseSettings* new_settings = new WarehouseSettings(*wd, owner().tribe()); + settings_.reset(new_settings); + if (upcast(WarehouseSettings, ws, old_settings)) { + for (const auto& pair : ws->ware_preferences) { + new_settings->ware_preferences[pair.first] = pair.second; + } + for (const auto& pair : ws->worker_preferences) { + new_settings->worker_preferences[pair.first] = pair.second; + } + new_settings->launch_expedition = ws->launch_expedition && building_->get_isport(); + } + } else if (upcast(const TrainingSiteDescr, td, building_)) { + TrainingsiteSettings* new_settings = new TrainingsiteSettings(*td); + settings_.reset(new_settings); + if (upcast(ProductionsiteSettings, ps, old_settings)) { + new_settings->stopped = ps->stopped; + for (const auto& pair_old : ps->ware_queues) { + for (auto& pair_new : new_settings->ware_queues) { + if (pair_new.first == pair_old.first) { + pair_new.second.priority = pair_old.second.priority; + pair_new.second.desired_fill = std::min(pair_old.second.desired_fill, pair_new.second.max_fill); + break; + } + } + } + for (const auto& pair_old : ps->worker_queues) { + for (auto& pair_new : new_settings->worker_queues) { + if (pair_new.first == pair_old.first) { + pair_new.second.priority = pair_old.second.priority; + pair_new.second.desired_fill = std::min(pair_old.second.desired_fill, pair_new.second.max_fill); + break; + } + } + } + if (upcast(TrainingsiteSettings, ts, old_settings)) { + new_settings->desired_capacity = std::min(new_settings->max_capacity, ts->desired_capacity); + } + } + } else if (upcast(const ProductionSiteDescr, pd, building_)) { + ProductionsiteSettings* new_settings = new ProductionsiteSettings(*pd); + settings_.reset(new_settings); + if (upcast(ProductionsiteSettings, ps, old_settings)) { + new_settings->stopped = ps->stopped; + for (const auto& pair_old : ps->ware_queues) { + for (auto& pair_new : new_settings->ware_queues) { + if (pair_new.first == pair_old.first) { + pair_new.second.priority = pair_old.second.priority; + pair_new.second.desired_fill = std::min(pair_old.second.desired_fill, pair_new.second.max_fill); + break; + } + } + } + for (const auto& pair_old : ps->worker_queues) { + for (auto& pair_new : new_settings->worker_queues) { + if (pair_new.first == pair_old.first) { + pair_new.second.priority = pair_old.second.priority; + pair_new.second.desired_fill = std::min(pair_old.second.desired_fill, pair_new.second.max_fill); + break; + } + } + } + } + } else if (upcast(const MilitarySiteDescr, md, building_)) { + MilitarysiteSettings* new_settings = new MilitarysiteSettings(*md); + settings_.reset(new_settings); + if (upcast(MilitarysiteSettings, ms, old_settings)) { + new_settings->desired_capacity = std::max<uint32_t>(1, std::min<uint32_t>( + new_settings->max_capacity, ms->desired_capacity)); + new_settings->prefer_heroes = ms->prefer_heroes; + } + } else { + // TODO(Nordfriese): Add support for markets when trading is implemented + log("WARNING: Enhanced constructionsite to a %s, which is not of any known building type\n", + building_->name().c_str()); + } + Notifications::publish(NoteImmovable(this, NoteImmovable::Ownership::GAINED)); + Notifications::publish(NoteBuilding(serial(), NoteBuilding::Action::kChanged)); +} + +/* +=============== Construction sites only burn if some of the work has been completed. =============== */ @@ -345,6 +575,16 @@ /* =============== +Overwrite as many values of the current settings with those of the given settings as possible. +=============== +*/ +void ConstructionSite::apply_settings(const BuildingSettings& cs) { + assert(settings_); + settings_->apply(cs); +} + +/* +=============== Draw the construction site. =============== */ === modified file 'src/logic/map_objects/tribes/constructionsite.h' --- src/logic/map_objects/tribes/constructionsite.h 2019-05-05 14:05:07 +0000 +++ src/logic/map_objects/tribes/constructionsite.h 2019-05-14 17:38:23 +0000 @@ -20,16 +20,26 @@ #ifndef WL_LOGIC_MAP_OBJECTS_TRIBES_CONSTRUCTIONSITE_H #define WL_LOGIC_MAP_OBJECTS_TRIBES_CONSTRUCTIONSITE_H +#include <memory> #include <vector> #include "base/macros.h" +#include "logic/map_objects/tribes/building_settings.h" #include "logic/map_objects/tribes/partially_finished_building.h" #include "scripting/lua_table.h" +class FileRead; +class FileWrite; + namespace Widelands { class Building; +class MilitarySiteDescr; +class ProductionSiteDescr; class Request; +enum class StockPolicy; +class TrainingSiteDescr; +class WarehouseDescr; class WaresQueue; /// Per-player and per-field constructionsite information @@ -47,6 +57,7 @@ const BuildingDescr* becomes; // Also works as a marker telling whether there is a construction site. const BuildingDescr* was; // only valid if "becomes" is an enhanced building. + std::vector<const BuildingDescr*> intermediates; // If we enhance a building while it's still under construction uint32_t totaltime; uint32_t completedtime; }; @@ -114,6 +125,13 @@ bool fetch_from_flag(Game&) override; bool get_building_work(Game&, Worker&, bool success) override; + BuildingSettings* get_settings() const { + return settings_.get(); + } + void apply_settings(const BuildingSettings&); + + void enhance(Game&); + protected: void update_statistics_string(std::string* statistics_string) override; @@ -135,6 +153,8 @@ bool builder_idle_; // used to determine whether the builder is idle ConstructionsiteInformation info_; // asked for by player point of view for the gameview + + std::unique_ptr<BuildingSettings> settings_; }; } // namespace Widelands === modified file 'src/logic/map_objects/tribes/militarysite.cc' --- src/logic/map_objects/tribes/militarysite.cc 2019-05-11 10:06:28 +0000 +++ src/logic/map_objects/tribes/militarysite.cc 2019-05-14 17:38:23 +0000 @@ -974,6 +974,13 @@ return false; } +const BuildingSettings* MilitarySite::create_building_settings() const { + MilitarysiteSettings* settings = new MilitarysiteSettings(descr()); + settings->desired_capacity = std::min(settings->max_capacity, soldier_control_.soldier_capacity()); + settings->prefer_heroes = soldier_preference_ == SoldierPreference::kHeroes; + return settings; +} + // setters void MilitarySite::set_soldier_preference(SoldierPreference p) { === modified file 'src/logic/map_objects/tribes/militarysite.h' --- src/logic/map_objects/tribes/militarysite.h 2019-05-05 14:05:07 +0000 +++ src/logic/map_objects/tribes/militarysite.h 2019-05-14 17:38:23 +0000 @@ -108,6 +108,8 @@ return soldier_preference_; } + const BuildingSettings* create_building_settings() const override; + protected: void conquer_area(EditorGameBase&); === modified file 'src/logic/map_objects/tribes/productionsite.cc' --- src/logic/map_objects/tribes/productionsite.cc 2019-05-12 09:58:40 +0000 +++ src/logic/map_objects/tribes/productionsite.cc 2019-05-14 17:38:23 +0000 @@ -1017,6 +1017,30 @@ set_production_result(""); } +const BuildingSettings* ProductionSite::create_building_settings() const { + ProductionsiteSettings* settings = new ProductionsiteSettings(descr()); + settings->stopped = is_stopped_; + for (auto& pair : settings->ware_queues) { + pair.second.priority = get_priority(wwWARE, pair.first, false); + for (const auto& queue : input_queues_) { + if (queue->get_type() == wwWARE && queue->get_index() == pair.first) { + pair.second.desired_fill = std::min(pair.second.max_fill, queue->get_max_fill()); + break; + } + } + } + for (auto& pair : settings->worker_queues) { + pair.second.priority = get_priority(wwWORKER, pair.first, false); + for (const auto& queue : input_queues_) { + if (queue->get_type() == wwWORKER && queue->get_index() == pair.first) { + pair.second.desired_fill = std::min(pair.second.max_fill, queue->get_max_fill()); + break; + } + } + } + return settings; +} + /// Changes the default anim string to \li anim void ProductionSite::set_default_anim(std::string anim) { if (default_anim_ == anim) === modified file 'src/logic/map_objects/tribes/productionsite.h' --- src/logic/map_objects/tribes/productionsite.h 2019-05-11 12:37:45 +0000 +++ src/logic/map_objects/tribes/productionsite.h 2019-05-14 17:38:23 +0000 @@ -236,6 +236,8 @@ void set_default_anim(std::string); + const BuildingSettings* create_building_settings() const override; + protected: void update_statistics_string(std::string* statistics) override; === modified file 'src/logic/map_objects/tribes/trainingsite.cc' --- src/logic/map_objects/tribes/trainingsite.cc 2019-05-05 14:05:07 +0000 +++ src/logic/map_objects/tribes/trainingsite.cc 2019-05-14 17:38:23 +0000 @@ -525,6 +525,31 @@ } } +const BuildingSettings* TrainingSite::create_building_settings() const { + TrainingsiteSettings* settings = new TrainingsiteSettings(descr()); + settings->desired_capacity = std::min(settings->max_capacity, soldier_control_.soldier_capacity()); + settings->stopped = is_stopped_; + for (auto& pair : settings->ware_queues) { + pair.second.priority = get_priority(wwWARE, pair.first, false); + for (const auto& queue : input_queues_) { + if (queue->get_type() == wwWARE && queue->get_index() == pair.first) { + pair.second.desired_fill = std::min(pair.second.max_fill, queue->get_max_fill()); + break; + } + } + } + for (auto& pair : settings->worker_queues) { + pair.second.priority = get_priority(wwWORKER, pair.first, false); + for (const auto& queue : input_queues_) { + if (queue->get_type() == wwWORKER && queue->get_index() == pair.first) { + pair.second.desired_fill = std::min(pair.second.max_fill, queue->get_max_fill()); + break; + } + } + } + return settings; +} + /** * In addition to advancing the program, update soldier status. */ === modified file 'src/logic/map_objects/tribes/trainingsite.h' --- src/logic/map_objects/tribes/trainingsite.h 2019-05-05 14:05:07 +0000 +++ src/logic/map_objects/tribes/trainingsite.h 2019-05-14 17:38:23 +0000 @@ -199,6 +199,8 @@ void training_successful(TrainingAttribute type, uint32_t level); void training_done(); + const BuildingSettings* create_building_settings() const override; + protected: void program_end(Game&, ProgramResult) override; === modified file 'src/logic/map_objects/tribes/warehouse.cc' --- src/logic/map_objects/tribes/warehouse.cc 2019-03-01 04:19:53 +0000 +++ src/logic/map_objects/tribes/warehouse.cc 2019-05-14 17:38:23 +0000 @@ -1275,17 +1275,17 @@ } } -Warehouse::StockPolicy Warehouse::get_ware_policy(DescriptionIndex ware) const { +StockPolicy Warehouse::get_ware_policy(DescriptionIndex ware) const { assert(ware < static_cast<DescriptionIndex>(ware_policy_.size())); return ware_policy_[ware]; } -Warehouse::StockPolicy Warehouse::get_worker_policy(DescriptionIndex ware) const { +StockPolicy Warehouse::get_worker_policy(DescriptionIndex ware) const { assert(ware < static_cast<DescriptionIndex>(worker_policy_.size())); return worker_policy_[ware]; } -Warehouse::StockPolicy Warehouse::get_stock_policy(WareWorker waretype, +StockPolicy Warehouse::get_stock_policy(WareWorker waretype, DescriptionIndex wareindex) const { if (waretype == wwWORKER) return get_worker_policy(wareindex); @@ -1293,25 +1293,25 @@ return get_ware_policy(wareindex); } -void Warehouse::set_ware_policy(DescriptionIndex ware, Warehouse::StockPolicy policy) { +void Warehouse::set_ware_policy(DescriptionIndex ware, StockPolicy policy) { assert(ware < static_cast<DescriptionIndex>(ware_policy_.size())); ware_policy_[ware] = policy; } -void Warehouse::set_worker_policy(DescriptionIndex ware, Warehouse::StockPolicy policy) { +void Warehouse::set_worker_policy(DescriptionIndex ware, StockPolicy policy) { assert(ware < static_cast<DescriptionIndex>(worker_policy_.size())); worker_policy_[ware] = policy; } /** - * Check if there are remaining wares with \ref Warehouse::StockPolicy::kRemove, + * Check if there are remaining wares with \ref StockPolicy::kRemove, * and remove one of them if appropriate. */ void Warehouse::check_remove_stock(Game& game) { if (base_flag().current_wares() < base_flag().total_capacity() / 2) { for (DescriptionIndex ware = 0; ware < static_cast<DescriptionIndex>(ware_policy_.size()); ++ware) { - if (get_ware_policy(ware) != Warehouse::StockPolicy::kRemove || !get_wares().stock(ware)) + if (get_ware_policy(ware) != StockPolicy::kRemove || !get_wares().stock(ware)) continue; launch_ware(game, ware); @@ -1321,7 +1321,7 @@ for (DescriptionIndex widx = 0; widx < static_cast<DescriptionIndex>(worker_policy_.size()); ++widx) { - if (get_worker_policy(widx) != Warehouse::StockPolicy::kRemove || !get_workers().stock(widx)) + if (get_worker_policy(widx) != StockPolicy::kRemove || !get_workers().stock(widx)) continue; Worker& worker = launch_worker(game, widx, Requirements()); @@ -1337,6 +1337,18 @@ return portdock_->expedition_bootstrap()->inputqueue(index, type); } +const BuildingSettings* Warehouse::create_building_settings() const { + WarehouseSettings* settings = new WarehouseSettings(descr(), owner().tribe()); + for (auto& pair : settings->ware_preferences) { + pair.second = get_ware_policy(pair.first); + } + for (auto& pair : settings->worker_preferences) { + pair.second = get_worker_policy(pair.first); + } + settings->launch_expedition = portdock_ && portdock_->expedition_started(); + return settings; +} + void Warehouse::log_general_info(const EditorGameBase& egbase) const { Building::log_general_info(egbase); === modified file 'src/logic/map_objects/tribes/warehouse.h' --- src/logic/map_objects/tribes/warehouse.h 2019-05-05 14:05:07 +0000 +++ src/logic/map_objects/tribes/warehouse.h 2019-05-14 17:38:23 +0000 @@ -68,6 +68,40 @@ DISALLOW_COPY_AND_ASSIGN(WarehouseDescr); }; +/** + * Each ware and worker type has an associated per-warehouse + * stock policy that defines whether it will be stocked by this + * warehouse. + * + * \note The values of this enum are written directly into savegames, + * so be careful when changing them. + */ +enum class StockPolicy { + /** + * The default policy allows stocking wares without any special priority. + */ + kNormal = 0, + + /** + * As long as there are warehouses with this policy for a ware, all + * available unstocked supplies will be transferred to warehouses + * with this policy. + */ + kPrefer = 1, + + /** + * If a ware has this stock policy, no more of this ware will enter + * the warehouse. + */ + kDontStock = 2, + + /** + * Like \ref kDontStock, but in addition, existing stock of this ware + * will be transported out of the warehouse over time. + */ + kRemove = 3, +}; + class Warehouse : public Building { friend class PortDock; friend class MapBuildingdataPacket; @@ -75,39 +109,6 @@ MO_DESCR(WarehouseDescr) public: - /** - * Each ware and worker type has an associated per-warehouse - * stock policy that defines whether it will be stocked by this - * warehouse. - * - * \note The values of this enum are written directly into savegames, - * so be careful when changing them. - */ - enum class StockPolicy { - /** - * The default policy allows stocking wares without any special priority. - */ - kNormal = 0, - - /** - * As long as there are warehouses with this policy for a ware, all - * available unstocked supplies will be transferred to warehouses - * with this policy. - */ - kPrefer = 1, - - /** - * If a ware has this stock policy, no more of this ware will enter - * the warehouse. - */ - kDontStock = 2, - - /** - * Like \ref kDontStock, but in addition, existing stock of this ware - * will be transported out of the warehouse over time. - */ - kRemove = 3, - }; /** * Whether worker indices in count_workers() have to match exactly. @@ -208,6 +209,8 @@ return portdock_; } + const BuildingSettings* create_building_settings() const override; + // Returns the waresqueue of the expedition if this is a port. // Will throw an exception otherwise. InputQueue& inputqueue(DescriptionIndex, WareWorker) override; === modified file 'src/logic/player.cc' --- src/logic/player.cc 2019-05-11 18:19:20 +0000 +++ src/logic/player.cc 2019-05-14 17:38:23 +0000 @@ -760,6 +760,7 @@ } else { workers = building->get_workers(); } + const BuildingSettings* settings = building->create_building_settings(); if (index_of_new_building != INVALID_INDEX) { // For enhancing, register whether the window was open @@ -770,7 +771,7 @@ // pointer. if (index_of_new_building != INVALID_INDEX) building = &egbase().warp_constructionsite( - position, player_number_, index_of_new_building, false, former_buildings); + position, player_number_, index_of_new_building, false, former_buildings, settings); else building = &egbase().warp_dismantlesite(position, player_number_, false, former_buildings); === modified file 'src/logic/playercommand.cc' --- src/logic/playercommand.cc 2019-05-07 12:26:11 +0000 +++ src/logic/playercommand.cc 2019-05-14 17:38:23 +0000 @@ -108,6 +108,14 @@ PLCMD_SHIP_SINK = 29, PLCMD_SHIP_CANCELEXPEDITION = 30, PLCMD_PROPOSE_TRADE = 31, + PLCMD_CONSTRUCTIONSITE_SOLDIERCAPACITY = 32, + PLCMD_CONSTRUCTIONSITE_PREFERHEROES = 33, + PLCMD_CONSTRUCTIONSITE_INPUTQUEUE_PRIORITY = 34, + PLCMD_CONSTRUCTIONSITE_INPUTQUEUE_MAXFILL = 35, + PLCMD_CONSTRUCTIONSITE_STOCKPOLICY = 36, + PLCMD_CONSTRUCTIONSITE_LAUNCHEXPEDITION = 37, + PLCMD_CONSTRUCTIONSITE_ENHANCE = 38, + PLCMD_CONSTRUCTIONSITE_STARTSTOP = 39, }; /*** class PlayerCommand ***/ @@ -176,6 +184,22 @@ return new CmdEvictWorker(des); case PLCMD_MILITARYSITESETSOLDIERPREFERENCE: return new CmdMilitarySiteSetSoldierPreference(des); + case PLCMD_CONSTRUCTIONSITE_SOLDIERCAPACITY: + return new CmdConstructionsiteSoldierCapacity(des); + case PLCMD_CONSTRUCTIONSITE_PREFERHEROES: + return new CmdConstructionsitePreferHeroes(des); + case PLCMD_CONSTRUCTIONSITE_INPUTQUEUE_PRIORITY: + return new CmdConstructionsiteInputQueuePriority(des); + case PLCMD_CONSTRUCTIONSITE_INPUTQUEUE_MAXFILL: + return new CmdConstructionsiteInputQueueMaxFill(des); + case PLCMD_CONSTRUCTIONSITE_STOCKPOLICY: + return new CmdConstructionsiteStockPolicy(des); + case PLCMD_CONSTRUCTIONSITE_LAUNCHEXPEDITION: + return new CmdConstructionsiteLaunchExpedition(des); + case PLCMD_CONSTRUCTIONSITE_ENHANCE: + return new CmdConstructionsiteEnhance(des); + case PLCMD_CONSTRUCTIONSITE_STARTSTOP: + return new CmdConstructionsiteStartStop(des); default: throw wexception("PlayerCommand::deserialize(): Invalid command id encountered"); } @@ -1751,7 +1775,7 @@ Warehouse& wh, bool isworker, DescriptionIndex ware, - Warehouse::StockPolicy policy) + StockPolicy policy) : PlayerCommand(time, p) { warehouse_ = wh.serial(); isworker_ = isworker; @@ -1794,7 +1818,7 @@ warehouse_ = des.unsigned_32(); isworker_ = des.unsigned_8(); ware_ = DescriptionIndex(des.unsigned_8()); - policy_ = static_cast<Warehouse::StockPolicy>(des.unsigned_8()); + policy_ = static_cast<StockPolicy>(des.unsigned_8()); } void CmdSetStockPolicy::serialize(StreamWrite& ser) { @@ -1816,7 +1840,7 @@ warehouse_ = fr.unsigned_32(); isworker_ = fr.unsigned_8(); ware_ = DescriptionIndex(fr.unsigned_8()); - policy_ = static_cast<Warehouse::StockPolicy>(fr.unsigned_8()); + policy_ = static_cast<StockPolicy>(fr.unsigned_8()); } else { throw UnhandledVersionError( "CmdSetStockPolicy", packet_version, kCurrentPacketVersionCmdSetStockPolicy); @@ -1904,4 +1928,601 @@ // TODO(sirver,trading): Implement this. NEVER_HERE(); } + + +/*** struct CmdConstructionsiteSoldierCapacity ***/ +CmdConstructionsiteSoldierCapacity::CmdConstructionsiteSoldierCapacity(uint32_t time, + PlayerNumber p, + ConstructionSite& cs, + uint32_t cap) + : PlayerCommand(time, p), constructionsite_(cs.serial()), capacity_(cap) { +} + +CmdConstructionsiteSoldierCapacity::CmdConstructionsiteSoldierCapacity() + : PlayerCommand(), constructionsite_(0), capacity_(0) { +} + +void CmdConstructionsiteSoldierCapacity::execute(Game& game) { + if (Player* plr = game.get_player(sender())) { + if (upcast(ConstructionSite, cs, game.objects().get_object(constructionsite_))) { + if (cs->get_owner() != plr) { + log("CmdConstructionsiteSoldierCapacity: sender %u, but site owner %u\n", sender(), + cs->owner().player_number()); + return; + } + if (upcast(MilitarysiteSettings, ms, cs->get_settings())) { + assert(ms->max_capacity >= capacity_); + ms->desired_capacity = capacity_; + } else if (upcast(TrainingsiteSettings, ts, cs->get_settings())) { + assert(ts->max_capacity >= capacity_); + ts->desired_capacity = capacity_; + } + } + } +} + +CmdConstructionsiteSoldierCapacity::CmdConstructionsiteSoldierCapacity(StreamRead& des) + : PlayerCommand(0, des.unsigned_8()) { + constructionsite_ = des.unsigned_32(); + capacity_ = des.unsigned_32(); +} + +void CmdConstructionsiteSoldierCapacity::serialize(StreamWrite& ser) { + ser.unsigned_8(PLCMD_CONSTRUCTIONSITE_SOLDIERCAPACITY); + ser.unsigned_8(sender()); + ser.unsigned_32(constructionsite_); + ser.unsigned_32(capacity_); +} + +constexpr uint8_t kCurrentPacketVersionCmdConstructionsiteSoldierCapacity = 1; + +void CmdConstructionsiteSoldierCapacity::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { + try { + uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionCmdConstructionsiteSoldierCapacity) { + PlayerCommand::read(fr, egbase, mol); + constructionsite_ = fr.unsigned_32(); + capacity_ = fr.unsigned_32(); + } else { + throw UnhandledVersionError("CmdConstructionsiteSoldierCapacity", packet_version, + kCurrentPacketVersionCmdConstructionsiteSoldierCapacity); + } + } catch (const std::exception& e) { + throw GameDataError("CmdConstructionsiteSoldierCapacity: %s", e.what()); + } +} + +void CmdConstructionsiteSoldierCapacity::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) { + fw.unsigned_8(kCurrentPacketVersionCmdConstructionsiteSoldierCapacity); + PlayerCommand::write(fw, egbase, mos); + fw.unsigned_32(constructionsite_); + fw.unsigned_32(capacity_); +} + +/*** struct CmdConstructionsitePreferHeroes ***/ +CmdConstructionsitePreferHeroes::CmdConstructionsitePreferHeroes(uint32_t time, + PlayerNumber p, + ConstructionSite& cs, + bool h) + : PlayerCommand(time, p), constructionsite_(cs.serial()), heroes_(h) { +} + +CmdConstructionsitePreferHeroes::CmdConstructionsitePreferHeroes() + : PlayerCommand(), constructionsite_(0), heroes_(false) { +} + +void CmdConstructionsitePreferHeroes::execute(Game& game) { + if (Player* plr = game.get_player(sender())) { + if (upcast(ConstructionSite, cs, game.objects().get_object(constructionsite_))) { + if (cs->get_owner() != plr) { + log("CmdConstructionsitePreferHeroes: sender %u, but site owner %u\n", sender(), + cs->owner().player_number()); + return; + } + if (upcast(MilitarysiteSettings, s, cs->get_settings())) { + s->prefer_heroes = heroes_; + } + } + } +} + +CmdConstructionsitePreferHeroes::CmdConstructionsitePreferHeroes(StreamRead& des) + : PlayerCommand(0, des.unsigned_8()) { + constructionsite_ = des.unsigned_32(); + heroes_ = des.unsigned_8(); +} + +void CmdConstructionsitePreferHeroes::serialize(StreamWrite& ser) { + ser.unsigned_8(PLCMD_CONSTRUCTIONSITE_PREFERHEROES); + ser.unsigned_8(sender()); + ser.unsigned_32(constructionsite_); + ser.unsigned_8(heroes_ ? 1 : 0); +} + +constexpr uint8_t kCurrentPacketVersionCmdConstructionsitePreferHeroes = 1; + +void CmdConstructionsitePreferHeroes::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { + try { + uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionCmdConstructionsitePreferHeroes) { + PlayerCommand::read(fr, egbase, mol); + constructionsite_ = fr.unsigned_32(); + heroes_ = fr.unsigned_8(); + } else { + throw UnhandledVersionError("CmdConstructionsitePreferHeroes", packet_version, + kCurrentPacketVersionCmdConstructionsitePreferHeroes); + } + } catch (const std::exception& e) { + throw GameDataError("CmdConstructionsitePreferHeroes: %s", e.what()); + } +} + +void CmdConstructionsitePreferHeroes::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) { + fw.unsigned_8(kCurrentPacketVersionCmdConstructionsitePreferHeroes); + PlayerCommand::write(fw, egbase, mos); + fw.unsigned_32(constructionsite_); + fw.unsigned_8(heroes_ ? 1 : 0); +} + +/*** struct CmdConstructionsiteLaunchExpedition ***/ +CmdConstructionsiteLaunchExpedition::CmdConstructionsiteLaunchExpedition(uint32_t time, + PlayerNumber p, + ConstructionSite& cs, + bool l) + : PlayerCommand(time, p), constructionsite_(cs.serial()), launch_(l) { +} + +CmdConstructionsiteLaunchExpedition::CmdConstructionsiteLaunchExpedition() + : PlayerCommand(), constructionsite_(0), launch_(false) { +} + +void CmdConstructionsiteLaunchExpedition::execute(Game& game) { + if (Player* plr = game.get_player(sender())) { + if (upcast(ConstructionSite, cs, game.objects().get_object(constructionsite_))) { + if (cs->get_owner() != plr) { + log("CmdConstructionsiteLaunchExpedition: sender %u, but site owner %u\n", sender(), + cs->owner().player_number()); + return; + } + if (upcast(WarehouseSettings, s, cs->get_settings())) { + s->launch_expedition = launch_; + } + } + } +} + +CmdConstructionsiteLaunchExpedition::CmdConstructionsiteLaunchExpedition(StreamRead& des) + : PlayerCommand(0, des.unsigned_8()) { + constructionsite_ = des.unsigned_32(); + launch_ = des.unsigned_8(); +} + +void CmdConstructionsiteLaunchExpedition::serialize(StreamWrite& ser) { + ser.unsigned_8(PLCMD_CONSTRUCTIONSITE_LAUNCHEXPEDITION); + ser.unsigned_8(sender()); + ser.unsigned_32(constructionsite_); + ser.unsigned_8(launch_ ? 1 : 0); +} + +constexpr uint8_t kCurrentPacketVersionCmdConstructionsiteLaunchExpedition = 1; + +void CmdConstructionsiteLaunchExpedition::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { + try { + uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionCmdConstructionsiteLaunchExpedition) { + PlayerCommand::read(fr, egbase, mol); + constructionsite_ = fr.unsigned_32(); + launch_ = fr.unsigned_8(); + } else { + throw UnhandledVersionError("CmdConstructionsiteLaunchExpedition", packet_version, + kCurrentPacketVersionCmdConstructionsiteLaunchExpedition); + } + } catch (const std::exception& e) { + throw GameDataError("CmdConstructionsiteLaunchExpedition: %s", e.what()); + } +} + +void CmdConstructionsiteLaunchExpedition::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) { + fw.unsigned_8(kCurrentPacketVersionCmdConstructionsiteLaunchExpedition); + PlayerCommand::write(fw, egbase, mos); + fw.unsigned_32(constructionsite_); + fw.unsigned_8(launch_ ? 1 : 0); +} + +/*** struct CmdConstructionsiteStartStop ***/ +CmdConstructionsiteStartStop::CmdConstructionsiteStartStop(uint32_t time, + PlayerNumber p, + ConstructionSite& cs, + bool stop) + : PlayerCommand(time, p), constructionsite_(cs.serial()), stopped_(stop) { +} + +CmdConstructionsiteStartStop::CmdConstructionsiteStartStop() + : PlayerCommand(), constructionsite_(0), stopped_(false) { +} + +void CmdConstructionsiteStartStop::execute(Game& game) { + if (Player* plr = game.get_player(sender())) { + if (upcast(ConstructionSite, cs, game.objects().get_object(constructionsite_))) { + if (cs->get_owner() != plr) { + log("CmdConstructionsiteStartStop: sender %u, but site owner %u\n", sender(), + cs->owner().player_number()); + return; + } + if (upcast(ProductionsiteSettings, s, cs->get_settings())) { + s->stopped = stopped_; + } + } + } +} + +CmdConstructionsiteStartStop::CmdConstructionsiteStartStop(StreamRead& des) + : PlayerCommand(0, des.unsigned_8()) { + constructionsite_ = des.unsigned_32(); + stopped_ = des.unsigned_8(); +} + +void CmdConstructionsiteStartStop::serialize(StreamWrite& ser) { + ser.unsigned_8(PLCMD_CONSTRUCTIONSITE_STARTSTOP); + ser.unsigned_8(sender()); + ser.unsigned_32(constructionsite_); + ser.unsigned_8(stopped_ ? 1 : 0); +} + +constexpr uint8_t kCurrentPacketVersionCmdConstructionsiteStartStop = 1; + +void CmdConstructionsiteStartStop::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { + try { + uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionCmdConstructionsiteStartStop) { + PlayerCommand::read(fr, egbase, mol); + constructionsite_ = fr.unsigned_32(); + stopped_ = fr.unsigned_8(); + } else { + throw UnhandledVersionError("CmdConstructionsiteStartStop", packet_version, + kCurrentPacketVersionCmdConstructionsiteStartStop); + } + } catch (const std::exception& e) { + throw GameDataError("CmdConstructionsiteStartStop: %s", e.what()); + } +} + +void CmdConstructionsiteStartStop::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) { + fw.unsigned_8(kCurrentPacketVersionCmdConstructionsiteStartStop); + PlayerCommand::write(fw, egbase, mos); + fw.unsigned_32(constructionsite_); + fw.unsigned_8(stopped_ ? 1 : 0); +} + +/*** struct CmdConstructionsiteStockPolicy ***/ +CmdConstructionsiteStockPolicy::CmdConstructionsiteStockPolicy(uint32_t time, + PlayerNumber p, + ConstructionSite& cs, + WareWorker ww, + DescriptionIndex di, + StockPolicy pol) + : PlayerCommand(time, p), constructionsite_(cs.serial()), wwtype_(ww), index_(di), policy_(pol) { +} + +CmdConstructionsiteStockPolicy::CmdConstructionsiteStockPolicy() + : PlayerCommand(), constructionsite_(0), wwtype_(wwWARE), index_(INVALID_INDEX), policy_() { +} + +void CmdConstructionsiteStockPolicy::execute(Game& game) { + if (Player* plr = game.get_player(sender())) { + if (upcast(ConstructionSite, cs, game.objects().get_object(constructionsite_))) { + if (cs->get_owner() != plr) { + log("CmdConstructionsiteStockPolicy: sender %u, but site owner %u\n", sender(), + cs->owner().player_number()); + return; + } + if (upcast(WarehouseSettings, s, cs->get_settings())) { + switch (wwtype_) { + case wwWARE: + s->ware_preferences[index_] = policy_; + break; + case wwWORKER: + s->worker_preferences[index_] = policy_; + break; + NEVER_HERE(); + } + } + } + } +} + +CmdConstructionsiteStockPolicy::CmdConstructionsiteStockPolicy(StreamRead& des) + : PlayerCommand(0, des.unsigned_8()) { + constructionsite_ = des.unsigned_32(); + wwtype_ = des.unsigned_8() == 0 ? wwWARE : wwWORKER; + index_ = des.unsigned_32(); + policy_ = static_cast<StockPolicy>(des.unsigned_8()); +} + +void CmdConstructionsiteStockPolicy::serialize(StreamWrite& ser) { + ser.unsigned_8(PLCMD_CONSTRUCTIONSITE_STOCKPOLICY); + ser.unsigned_8(sender()); + ser.unsigned_32(constructionsite_); + ser.unsigned_8(wwtype_ == wwWARE ? 0 : 1); + ser.unsigned_32(index_); + ser.unsigned_8(static_cast<uint8_t>(policy_)); +} + +constexpr uint8_t kCurrentPacketVersionCmdConstructionsiteStockPolicy = 1; + +void CmdConstructionsiteStockPolicy::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { + try { + uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionCmdConstructionsiteStockPolicy) { + PlayerCommand::read(fr, egbase, mol); + constructionsite_ = fr.unsigned_32(); + wwtype_ = fr.unsigned_8() == 0 ? wwWARE : wwWORKER; + index_ = fr.unsigned_32(); + policy_ = static_cast<StockPolicy>(fr.unsigned_8()); + } else { + throw UnhandledVersionError("CmdConstructionsiteStockPolicy", packet_version, + kCurrentPacketVersionCmdConstructionsiteStockPolicy); + } + } catch (const std::exception& e) { + throw GameDataError("CmdConstructionsiteStockPolicy: %s", e.what()); + } +} + +void CmdConstructionsiteStockPolicy::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) { + fw.unsigned_8(kCurrentPacketVersionCmdConstructionsiteStockPolicy); + PlayerCommand::write(fw, egbase, mos); + fw.unsigned_32(constructionsite_); + fw.unsigned_8(wwtype_ == wwWARE ? 0 : 1); + fw.unsigned_32(index_); + fw.unsigned_8(static_cast<uint8_t>(policy_)); +} + +/*** struct CmdConstructionsiteInputQueuePriority ***/ +CmdConstructionsiteInputQueuePriority::CmdConstructionsiteInputQueuePriority(uint32_t time, + PlayerNumber p, + ConstructionSite& cs, + WareWorker ww, + DescriptionIndex di, + int32_t prio) + : PlayerCommand(time, p), constructionsite_(cs.serial()), wwtype_(ww), index_(di), priority_(prio) { +} + +CmdConstructionsiteInputQueuePriority::CmdConstructionsiteInputQueuePriority() + : PlayerCommand(), constructionsite_(0), wwtype_(wwWARE), index_(INVALID_INDEX), priority_(0) { +} + +void CmdConstructionsiteInputQueuePriority::execute(Game& game) { + if (Player* plr = game.get_player(sender())) { + if (upcast(ConstructionSite, cs, game.objects().get_object(constructionsite_))) { + if (cs->get_owner() != plr) { + log("CmdConstructionsiteInputQueuePriority: sender %u, but site owner %u\n", sender(), + cs->owner().player_number()); + return; + } + if (upcast(ProductionsiteSettings, s, cs->get_settings())) { + switch (wwtype_) { + case wwWARE: + for (auto& pair : s->ware_queues) { + if (pair.first == index_) { + pair.second.priority = priority_; + return; + } + } + NEVER_HERE(); + case wwWORKER: + for (auto& pair : s->worker_queues) { + if (pair.first == index_) { + pair.second.priority = priority_; + return; + } + } + NEVER_HERE(); + default: + NEVER_HERE(); + } + } + } + } +} + +CmdConstructionsiteInputQueuePriority::CmdConstructionsiteInputQueuePriority(StreamRead& des) + : PlayerCommand(0, des.unsigned_8()) { + constructionsite_ = des.unsigned_32(); + wwtype_ = des.unsigned_8() == 0 ? wwWARE : wwWORKER; + index_ = des.unsigned_32(); + priority_ = des.signed_32(); +} + +void CmdConstructionsiteInputQueuePriority::serialize(StreamWrite& ser) { + ser.unsigned_8(PLCMD_CONSTRUCTIONSITE_INPUTQUEUE_PRIORITY); + ser.unsigned_8(sender()); + ser.unsigned_32(constructionsite_); + ser.unsigned_8(wwtype_ == wwWARE ? 0 : 1); + ser.unsigned_32(index_); + ser.signed_32(priority_); +} + +constexpr uint8_t kCurrentPacketVersionCmdConstructionsiteInputQueuePriority = 1; + +void CmdConstructionsiteInputQueuePriority::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { + try { + uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionCmdConstructionsiteInputQueuePriority) { + PlayerCommand::read(fr, egbase, mol); + constructionsite_ = fr.unsigned_32(); + wwtype_ = fr.unsigned_8() == 0 ? wwWARE : wwWORKER; + index_ = fr.unsigned_32(); + priority_ = fr.signed_32(); + } else { + throw UnhandledVersionError("CmdConstructionsiteInputQueuePriority", packet_version, + kCurrentPacketVersionCmdConstructionsiteInputQueuePriority); + } + } catch (const std::exception& e) { + throw GameDataError("CmdConstructionsiteInputQueuePriority: %s", e.what()); + } +} + +void CmdConstructionsiteInputQueuePriority::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) { + fw.unsigned_8(kCurrentPacketVersionCmdConstructionsiteInputQueuePriority); + PlayerCommand::write(fw, egbase, mos); + fw.unsigned_32(constructionsite_); + fw.unsigned_8(wwtype_ == wwWARE ? 0 : 1); + fw.unsigned_32(index_); + fw.signed_32(priority_); +} + +/*** struct CmdConstructionsiteInputQueueMaxFill ***/ +CmdConstructionsiteInputQueueMaxFill::CmdConstructionsiteInputQueueMaxFill(uint32_t time, + PlayerNumber p, + ConstructionSite& cs, + WareWorker ww, + DescriptionIndex di, + uint32_t max) + : PlayerCommand(time, p), constructionsite_(cs.serial()), wwtype_(ww), index_(di), max_fill_(max) { +} + +CmdConstructionsiteInputQueueMaxFill::CmdConstructionsiteInputQueueMaxFill() + : PlayerCommand(), constructionsite_(0), wwtype_(wwWARE), index_(INVALID_INDEX), max_fill_(0) { +} + +void CmdConstructionsiteInputQueueMaxFill::execute(Game& game) { + if (Player* plr = game.get_player(sender())) { + if (upcast(ConstructionSite, cs, game.objects().get_object(constructionsite_))) { + if (cs->get_owner() != plr) { + log("CmdConstructionsiteInputQueueMaxFill: sender %u, but site owner %u\n", sender(), + cs->owner().player_number()); + return; + } + if (upcast(ProductionsiteSettings, s, cs->get_settings())) { + switch (wwtype_) { + case wwWARE: + for (auto& pair : s->ware_queues) { + if (pair.first == index_) { + assert(pair.second.max_fill >= max_fill_); + pair.second.desired_fill = max_fill_; + return; + } + } + NEVER_HERE(); + case wwWORKER: + for (auto& pair : s->worker_queues) { + if (pair.first == index_) { + assert(pair.second.max_fill >= max_fill_); + pair.second.desired_fill = max_fill_; + return; + } + } + NEVER_HERE(); + default: + NEVER_HERE(); + } + } + } + } +} + +CmdConstructionsiteInputQueueMaxFill::CmdConstructionsiteInputQueueMaxFill(StreamRead& des) + : PlayerCommand(0, des.unsigned_8()) { + constructionsite_ = des.unsigned_32(); + wwtype_ = des.unsigned_8() == 0 ? wwWARE : wwWORKER; + index_ = des.unsigned_32(); + max_fill_ = des.unsigned_32(); +} + +void CmdConstructionsiteInputQueueMaxFill::serialize(StreamWrite& ser) { + ser.unsigned_8(PLCMD_CONSTRUCTIONSITE_INPUTQUEUE_MAXFILL); + ser.unsigned_8(sender()); + ser.unsigned_32(constructionsite_); + ser.unsigned_8(wwtype_ == wwWARE ? 0 : 1); + ser.unsigned_32(index_); + ser.unsigned_32(max_fill_); +} + +constexpr uint8_t kCurrentPacketVersionCmdConstructionsiteInputQueueMaxFill = 1; + +void CmdConstructionsiteInputQueueMaxFill::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { + try { + uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionCmdConstructionsiteInputQueueMaxFill) { + PlayerCommand::read(fr, egbase, mol); + constructionsite_ = fr.unsigned_32(); + wwtype_ = fr.unsigned_8() == 0 ? wwWARE : wwWORKER; + index_ = fr.unsigned_32(); + max_fill_ = fr.unsigned_32(); + } else { + throw UnhandledVersionError("CmdConstructionsiteInputQueueMaxFill", packet_version, + kCurrentPacketVersionCmdConstructionsiteInputQueueMaxFill); + } + } catch (const std::exception& e) { + throw GameDataError("CmdConstructionsiteInputQueueMaxFill: %s", e.what()); + } +} + +void CmdConstructionsiteInputQueueMaxFill::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) { + fw.unsigned_8(kCurrentPacketVersionCmdConstructionsiteInputQueueMaxFill); + PlayerCommand::write(fw, egbase, mos); + fw.unsigned_32(constructionsite_); + fw.unsigned_8(wwtype_ == wwWARE ? 0 : 1); + fw.unsigned_32(index_); + fw.unsigned_32(max_fill_); +} + +/*** struct CmdConstructionsiteEnhance ***/ +CmdConstructionsiteEnhance::CmdConstructionsiteEnhance(uint32_t time, + PlayerNumber p, + ConstructionSite& cs) + : PlayerCommand(time, p), constructionsite_(cs.serial()) { +} + +CmdConstructionsiteEnhance::CmdConstructionsiteEnhance() + : PlayerCommand(), constructionsite_(0) { +} + +void CmdConstructionsiteEnhance::execute(Game& game) { + if (Player* plr = game.get_player(sender())) { + if (upcast(ConstructionSite, cs, game.objects().get_object(constructionsite_))) { + if (cs->get_owner() != plr) { + log("CmdConstructionsiteEnhance: sender %u, but site owner %u\n", sender(), + cs->owner().player_number()); + return; + } + cs->enhance(game); + } + } +} + +CmdConstructionsiteEnhance::CmdConstructionsiteEnhance(StreamRead& des) + : PlayerCommand(0, des.unsigned_8()) { + constructionsite_ = des.unsigned_32(); +} + +void CmdConstructionsiteEnhance::serialize(StreamWrite& ser) { + ser.unsigned_8(PLCMD_CONSTRUCTIONSITE_ENHANCE); + ser.unsigned_8(sender()); + ser.unsigned_32(constructionsite_); +} + +constexpr uint8_t kCurrentPacketVersionCmdConstructionsiteEnhance = 1; + +void CmdConstructionsiteEnhance::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { + try { + uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersionCmdConstructionsiteEnhance) { + PlayerCommand::read(fr, egbase, mol); + constructionsite_ = fr.unsigned_32(); + } else { + throw UnhandledVersionError("CmdConstructionsiteEnhance", packet_version, + kCurrentPacketVersionCmdConstructionsiteEnhance); + } + } catch (const std::exception& e) { + throw GameDataError("CmdConstructionsiteEnhance: %s", e.what()); + } +} + +void CmdConstructionsiteEnhance::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) { + fw.unsigned_8(kCurrentPacketVersionCmdConstructionsiteEnhance); + PlayerCommand::write(fw, egbase, mos); + fw.unsigned_32(constructionsite_); +} + } // namespace Widelands === modified file 'src/logic/playercommand.h' --- src/logic/playercommand.h 2019-05-07 12:14:02 +0000 +++ src/logic/playercommand.h 2019-05-14 17:38:23 +0000 @@ -24,6 +24,7 @@ #include "economy/flag.h" #include "logic/cmd_queue.h" +#include "logic/map_objects/tribes/constructionsite.h" #include "logic/map_objects/tribes/militarysite.h" #include "logic/map_objects/tribes/ship.h" #include "logic/map_objects/tribes/trainingsite.h" @@ -823,7 +824,7 @@ Warehouse& wh, bool isworker, DescriptionIndex ware, - Warehouse::StockPolicy policy); + StockPolicy policy); QueueCommandTypes id() const override { return QueueCommandTypes::kSetStockPolicy; @@ -844,7 +845,7 @@ Serial warehouse_; bool isworker_; DescriptionIndex ware_; - Warehouse::StockPolicy policy_; + StockPolicy policy_; }; struct CmdProposeTrade : PlayerCommand { @@ -868,6 +869,225 @@ private: Trade trade_; }; + +struct CmdConstructionsiteSoldierCapacity : PlayerCommand { + CmdConstructionsiteSoldierCapacity(uint32_t time, + PlayerNumber p, + ConstructionSite&, + uint32_t); + + QueueCommandTypes id() const override { + return QueueCommandTypes::kConstructionSiteSoldierCapacity; + } + + void execute(Game& game) override; + + // Network (de-)serialization + explicit CmdConstructionsiteSoldierCapacity(StreamRead& des); + void serialize(StreamWrite& ser) override; + + // Savegame functions + CmdConstructionsiteSoldierCapacity(); + void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; + void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; + +private: + Serial constructionsite_; + uint32_t capacity_; +}; + +struct CmdConstructionsitePreferHeroes : PlayerCommand { + CmdConstructionsitePreferHeroes(uint32_t time, + PlayerNumber p, + ConstructionSite&, + bool); + + QueueCommandTypes id() const override { + return QueueCommandTypes::kConstructionSitePreferHeroes; + } + + void execute(Game& game) override; + + // Network (de-)serialization + explicit CmdConstructionsitePreferHeroes(StreamRead& des); + void serialize(StreamWrite& ser) override; + + // Savegame functions + CmdConstructionsitePreferHeroes(); + void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; + void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; + +private: + Serial constructionsite_; + bool heroes_; +}; + +struct CmdConstructionsiteLaunchExpedition : PlayerCommand { + CmdConstructionsiteLaunchExpedition(uint32_t time, + PlayerNumber p, + ConstructionSite&, + bool); + + QueueCommandTypes id() const override { + return QueueCommandTypes::kConstructionSiteLaunchExpedition; + } + + void execute(Game& game) override; + + // Network (de-)serialization + explicit CmdConstructionsiteLaunchExpedition(StreamRead& des); + void serialize(StreamWrite& ser) override; + + // Savegame functions + CmdConstructionsiteLaunchExpedition(); + void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; + void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; + +private: + Serial constructionsite_; + bool launch_; +}; + +struct CmdConstructionsiteStartStop : PlayerCommand { + CmdConstructionsiteStartStop(uint32_t time, + PlayerNumber p, + ConstructionSite&, + bool stop); + + QueueCommandTypes id() const override { + return QueueCommandTypes::kConstructionSiteStartStop; + } + + void execute(Game& game) override; + + // Network (de-)serialization + explicit CmdConstructionsiteStartStop(StreamRead& des); + void serialize(StreamWrite& ser) override; + + // Savegame functions + CmdConstructionsiteStartStop(); + void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; + void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; + +private: + Serial constructionsite_; + bool stopped_; +}; + +struct CmdConstructionsiteStockPolicy : PlayerCommand { + CmdConstructionsiteStockPolicy(uint32_t time, + PlayerNumber p, + ConstructionSite&, + WareWorker, + DescriptionIndex, + StockPolicy); + + QueueCommandTypes id() const override { + return QueueCommandTypes::kConstructionSiteStockPolicy; + } + + void execute(Game& game) override; + + // Network (de-)serialization + explicit CmdConstructionsiteStockPolicy(StreamRead& des); + void serialize(StreamWrite& ser) override; + + // Savegame functions + CmdConstructionsiteStockPolicy(); + void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; + void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; + +private: + Serial constructionsite_; + WareWorker wwtype_; + DescriptionIndex index_; + StockPolicy policy_; +}; + +struct CmdConstructionsiteInputQueuePriority : PlayerCommand { + CmdConstructionsiteInputQueuePriority(uint32_t time, + PlayerNumber p, + ConstructionSite&, + WareWorker, + DescriptionIndex, + int32_t); + + QueueCommandTypes id() const override { + return QueueCommandTypes::kConstructionSiteInputQueuePriority; + } + + void execute(Game& game) override; + + // Network (de-)serialization + explicit CmdConstructionsiteInputQueuePriority(StreamRead& des); + void serialize(StreamWrite& ser) override; + + // Savegame functions + CmdConstructionsiteInputQueuePriority(); + void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; + void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; + +private: + Serial constructionsite_; + WareWorker wwtype_; + DescriptionIndex index_; + int32_t priority_; +}; + +struct CmdConstructionsiteInputQueueMaxFill : PlayerCommand { + CmdConstructionsiteInputQueueMaxFill(uint32_t time, + PlayerNumber p, + ConstructionSite&, + WareWorker, + DescriptionIndex, + uint32_t); + + QueueCommandTypes id() const override { + return QueueCommandTypes::kConstructionSiteInputQueueMaxFill; + } + + void execute(Game& game) override; + + // Network (de-)serialization + explicit CmdConstructionsiteInputQueueMaxFill(StreamRead& des); + void serialize(StreamWrite& ser) override; + + // Savegame functions + CmdConstructionsiteInputQueueMaxFill(); + void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; + void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; + +private: + Serial constructionsite_; + WareWorker wwtype_; + DescriptionIndex index_; + uint32_t max_fill_; +}; + +struct CmdConstructionsiteEnhance : PlayerCommand { + CmdConstructionsiteEnhance(uint32_t time, + PlayerNumber p, + ConstructionSite&); + + QueueCommandTypes id() const override { + return QueueCommandTypes::kConstructionSiteEnhance; + } + + void execute(Game& game) override; + + // Network (de-)serialization + explicit CmdConstructionsiteEnhance(StreamRead& des); + void serialize(StreamWrite& ser) override; + + // Savegame functions + CmdConstructionsiteEnhance(); + void write(FileWrite&, EditorGameBase&, MapObjectSaver&) override; + void read(FileRead&, EditorGameBase&, MapObjectLoader&) override; + +private: + Serial constructionsite_; +}; + } // namespace Widelands #endif // end of include guard: WL_LOGIC_PLAYERCOMMAND_H === modified file 'src/logic/queue_cmd_factory.cc' --- src/logic/queue_cmd_factory.cc 2019-02-23 11:00:49 +0000 +++ src/logic/queue_cmd_factory.cc 2019-05-14 17:38:23 +0000 @@ -80,6 +80,22 @@ return *new CmdMilitarySiteSetSoldierPreference(); case QueueCommandTypes::kProposeTrade: return *new CmdProposeTrade(); + case QueueCommandTypes::kConstructionSiteSoldierCapacity: + return *new CmdConstructionsiteSoldierCapacity(); + case QueueCommandTypes::kConstructionSitePreferHeroes: + return *new CmdConstructionsitePreferHeroes(); + case QueueCommandTypes::kConstructionSiteLaunchExpedition: + return *new CmdConstructionsiteLaunchExpedition(); + case QueueCommandTypes::kConstructionSiteStockPolicy: + return *new CmdConstructionsiteStockPolicy(); + case QueueCommandTypes::kConstructionSiteInputQueuePriority: + return *new CmdConstructionsiteInputQueuePriority(); + case QueueCommandTypes::kConstructionSiteInputQueueMaxFill: + return *new CmdConstructionsiteInputQueueMaxFill(); + case QueueCommandTypes::kConstructionSiteEnhance: + return *new CmdConstructionsiteEnhance(); + case QueueCommandTypes::kConstructionSiteStartStop: + return *new CmdConstructionsiteStartStop(); case QueueCommandTypes::kSinkShip: return *new CmdShipSink(); case QueueCommandTypes::kShipCancelExpedition: === modified file 'src/logic/queue_cmd_ids.h' --- src/logic/queue_cmd_ids.h 2019-02-23 11:00:49 +0000 +++ src/logic/queue_cmd_ids.h 2019-05-14 17:38:23 +0000 @@ -73,6 +73,15 @@ kMilitarysiteSetSoldierPreference, kProposeTrade, // 27 + kConstructionSiteStockPolicy, + kConstructionSiteSoldierCapacity, + kConstructionSitePreferHeroes, + kConstructionSiteLaunchExpedition, + kConstructionSiteInputQueuePriority, + kConstructionSiteInputQueueMaxFill, + kConstructionSiteEnhance, + kConstructionSiteStartStop, // 35 + kSinkShip = 121, kShipCancelExpedition, kPortStartExpedition, === modified file 'src/map_io/map_buildingdata_packet.cc' --- src/map_io/map_buildingdata_packet.cc 2019-05-12 10:53:08 +0000 +++ src/map_io/map_buildingdata_packet.cc 2019-05-14 17:38:23 +0000 @@ -60,7 +60,7 @@ // Building type package versions constexpr uint16_t kCurrentPacketVersionDismantlesite = 1; -constexpr uint16_t kCurrentPacketVersionConstructionsite = 3; +constexpr uint16_t kCurrentPacketVersionConstructionsite = 4; constexpr uint16_t kCurrentPacketPFBuilding = 1; // Responsible for warehouses and expedition bootstraps constexpr uint16_t kCurrentPacketVersionWarehouse = 7; @@ -269,7 +269,7 @@ MapObjectLoader& mol) { try { uint16_t const packet_version = fr.unsigned_16(); - if (packet_version >= kCurrentPacketVersionConstructionsite) { + if (packet_version >= 3 && packet_version <= kCurrentPacketVersionConstructionsite) { read_partially_finished_building(constructionsite, fr, game, mol); for (ConstructionSite::Wares::iterator wares_iter = constructionsite.wares_.begin(); @@ -279,6 +279,18 @@ } constructionsite.fetchfromflag_ = fr.signed_32(); + + if (packet_version >= 4) { + const uint32_t intermediates = fr.unsigned_32(); + for (uint32_t i = 0; i < intermediates; ++i) { + constructionsite.info_.intermediates.push_back(game.tribes().get_building_descr( + game.tribes().building_index(fr.c_string()))); + } + constructionsite.settings_.reset(BuildingSettings::load(game, + constructionsite.owner().tribe(), fr)); + } else { + constructionsite.settings_.reset(nullptr); + } } else { throw UnhandledVersionError("MapBuildingdataPacket - Constructionsite", packet_version, kCurrentPacketVersionConstructionsite); @@ -321,7 +333,7 @@ while (fr.unsigned_8()) { const DescriptionIndex& id = tribe.ware_index(fr.c_string()); Quantity amount = fr.unsigned_32(); - Warehouse::StockPolicy policy = static_cast<Warehouse::StockPolicy>(fr.unsigned_8()); + StockPolicy policy = static_cast<StockPolicy>(fr.unsigned_8()); if (game.tribes().ware_exists(id)) { warehouse.insert_wares(id, amount); @@ -331,7 +343,7 @@ while (fr.unsigned_8()) { const DescriptionIndex& id = tribe.worker_index(fr.c_string()); uint32_t amount = fr.unsigned_32(); - Warehouse::StockPolicy policy = static_cast<Warehouse::StockPolicy>(fr.unsigned_8()); + StockPolicy policy = static_cast<StockPolicy>(fr.unsigned_8()); if (game.tribes().worker_exists(id)) { warehouse.insert_workers(id, amount); @@ -961,6 +973,14 @@ write_partially_finished_building(constructionsite, fw, game, mos); fw.signed_32(constructionsite.fetchfromflag_); + + fw.unsigned_32(constructionsite.info_.intermediates.size()); + for (const BuildingDescr* d : constructionsite.info_.intermediates) { + fw.c_string(d->name().c_str()); + } + + assert(constructionsite.settings_); + constructionsite.settings_->save(game, fw); } void MapBuildingdataPacket::write_dismantlesite(const DismantleSite& dms, === modified file 'src/notifications/note_ids.h' --- src/notifications/note_ids.h 2019-02-23 11:00:49 +0000 +++ src/notifications/note_ids.h 2019-05-14 17:38:23 +0000 @@ -29,6 +29,7 @@ ChatMessage, LogMessage, Immovable, + ConstructionsiteEnhanced, FieldPossession, FieldTerrainChanged, ProductionSiteOutOfResources, === modified file 'src/scripting/lua_map.cc' --- src/scripting/lua_map.cc 2019-05-04 10:47:44 +0000 +++ src/scripting/lua_map.cc 2019-05-14 17:38:23 +0000 @@ -4871,39 +4871,39 @@ } // Transforms the given warehouse policy to a string which is used by the lua code -inline void wh_policy_to_string(lua_State* L, Warehouse::StockPolicy p) { +inline void wh_policy_to_string(lua_State* L, StockPolicy p) { switch (p) { - case Warehouse::StockPolicy::kNormal: + case StockPolicy::kNormal: lua_pushstring(L, "normal"); break; - case Warehouse::StockPolicy::kPrefer: + case StockPolicy::kPrefer: lua_pushstring(L, "prefer"); break; - case Warehouse::StockPolicy::kDontStock: + case StockPolicy::kDontStock: lua_pushstring(L, "dontstock"); break; - case Warehouse::StockPolicy::kRemove: + case StockPolicy::kRemove: lua_pushstring(L, "remove"); break; } } // Transforms the given string from the lua code to a warehouse policy -inline Warehouse::StockPolicy string_to_wh_policy(lua_State* L, uint32_t index) { +inline StockPolicy string_to_wh_policy(lua_State* L, uint32_t index) { std::string str = luaL_checkstring(L, index); if (str == "normal") - return Warehouse::StockPolicy::kNormal; + return StockPolicy::kNormal; else if (str == "prefer") - return Warehouse::StockPolicy::kPrefer; + return StockPolicy::kPrefer; else if (str == "dontstock") - return Warehouse::StockPolicy::kDontStock; + return StockPolicy::kDontStock; else if (str == "remove") - return Warehouse::StockPolicy::kRemove; + return StockPolicy::kRemove; else report_error(L, "<%s> is no valid warehouse policy!", str.c_str()); } inline bool -do_set_ware_policy(Warehouse* wh, const DescriptionIndex idx, const Warehouse::StockPolicy p) { +do_set_ware_policy(Warehouse* wh, const DescriptionIndex idx, const StockPolicy p) { wh->set_ware_policy(idx, p); return true; } @@ -4913,7 +4913,7 @@ * If the no ware with the given name exists for the tribe of the warehouse, return false. */ inline bool -do_set_ware_policy(Warehouse* wh, const std::string& name, const Warehouse::StockPolicy p) { +do_set_ware_policy(Warehouse* wh, const std::string& name, const StockPolicy p) { const TribeDescr& tribe = wh->owner().tribe(); DescriptionIndex idx = tribe.ware_index(name); if (!tribe.has_ware(idx)) { @@ -4923,7 +4923,7 @@ } inline bool -do_set_worker_policy(Warehouse* wh, const DescriptionIndex idx, const Warehouse::StockPolicy p) { +do_set_worker_policy(Warehouse* wh, const DescriptionIndex idx, const StockPolicy p) { const TribeDescr& tribe = wh->owner().tribe(); // If the worker does not cost anything, ignore it // Otherwise, an unlimited stream of carriers might leave the warehouse @@ -4942,7 +4942,7 @@ * If no worker with the given name exists for the tribe of the warehouse, return false. */ inline bool -do_set_worker_policy(Warehouse* wh, const std::string& name, const Warehouse::StockPolicy p) { +do_set_worker_policy(Warehouse* wh, const std::string& name, const StockPolicy p) { const TribeDescr& tribe = wh->owner().tribe(); DescriptionIndex idx = tribe.worker_index(name); if (!tribe.has_worker(idx)) { @@ -4972,7 +4972,7 @@ report_error(L, "Wrong number of arguments to set_warehouse_policies!"); Warehouse* wh = get(L, get_egbase(L)); - Warehouse::StockPolicy p = string_to_wh_policy(L, -1); + StockPolicy p = string_to_wh_policy(L, -1); lua_pop(L, 1); const TribeDescr& tribe = wh->owner().tribe(); === modified file 'src/wui/actionconfirm.cc' --- src/wui/actionconfirm.cc 2019-02-23 11:00:49 +0000 +++ src/wui/actionconfirm.cc 2019-05-14 17:38:23 +0000 @@ -92,7 +92,8 @@ struct EnhanceConfirm : public ActionConfirm { EnhanceConfirm(InteractivePlayer& parent, Widelands::Building& building, - const Widelands::DescriptionIndex& id); + const Widelands::DescriptionIndex& id, + bool still_under_construction); void think() override; void ok() override; @@ -100,6 +101,7 @@ private: // Do not make this a reference - it is a stack variable in the caller const Widelands::DescriptionIndex id_; + bool still_under_construction_; }; /** @@ -259,7 +261,8 @@ */ EnhanceConfirm::EnhanceConfirm(InteractivePlayer& parent, Widelands::Building& building, - const Widelands::DescriptionIndex& id) + const Widelands::DescriptionIndex& id, + bool still_under_construction) : ActionConfirm( parent, _("Enhance building?"), @@ -270,7 +273,8 @@ .str() : _("Do you really want to enhance this building?"), building), - id_(id) { + id_(id), + still_under_construction_(still_under_construction) { // Nothing special to do } @@ -284,7 +288,7 @@ upcast(Widelands::Building, building, object_.get(egbase)); if (!building || !iaplayer().can_act(building->owner().player_number()) || - !(building->get_playercaps() & Widelands::Building::PCap_Enhancable)) + !(still_under_construction_ || (building->get_playercaps() & Widelands::Building::PCap_Enhancable))) die(); } @@ -293,11 +297,18 @@ */ void EnhanceConfirm::ok() { Widelands::Game& game = iaplayer().game(); - upcast(Widelands::Building, building, object_.get(game)); - if (building && iaplayer().can_act(building->owner().player_number()) && - (building->get_playercaps() & Widelands::Building::PCap_Enhancable)) { - game.send_player_enhance_building(*building, id_); + if (still_under_construction_) { + upcast(Widelands::ConstructionSite, cs, object_.get(game)); + if (cs && iaplayer().can_act(cs->owner().player_number())) { + game.send_player_constructionsite_enhance(*cs); + } + } else { + upcast(Widelands::Building, building, object_.get(game)); + if (building && iaplayer().can_act(building->owner().player_number()) && + (building->get_playercaps() & Widelands::Building::PCap_Enhancable)) { + game.send_player_enhance_building(*building, id_); + } } die(); @@ -419,8 +430,9 @@ */ void show_enhance_confirm(InteractivePlayer& player, Widelands::Building& building, - const Widelands::DescriptionIndex& id) { - new EnhanceConfirm(player, building, id); + const Widelands::DescriptionIndex& id, + bool constructionsite) { + new EnhanceConfirm(player, building, id, constructionsite); } /** === modified file 'src/wui/actionconfirm.h' --- src/wui/actionconfirm.h 2019-02-23 11:00:49 +0000 +++ src/wui/actionconfirm.h 2019-05-14 17:38:23 +0000 @@ -39,7 +39,8 @@ void show_enhance_confirm(InteractivePlayer& player, Widelands::Building& building, - const Widelands::DescriptionIndex& id); + const Widelands::DescriptionIndex& id, + bool still_under_construction = false); // Ship confirm windows void show_ship_sink_confirm(InteractivePlayer& player, Widelands::Ship& ship); === modified file 'src/wui/constructionsitewindow.cc' --- src/wui/constructionsitewindow.cc 2019-02-23 11:00:49 +0000 +++ src/wui/constructionsitewindow.cc 2019-05-14 17:38:23 +0000 @@ -22,9 +22,256 @@ #include <boost/format.hpp> #include "graphic/graphic.h" +#include "wui/actionconfirm.h" #include "wui/inputqueuedisplay.h" +#include "wui/interactive_player.h" static const char pic_tab_wares[] = "images/wui/buildings/menu_tab_wares.png"; +static const char pic_tab_workers[] = "images/wui/buildings/menu_tab_workers.png"; +static const char pic_tab_settings[] = "images/wui/menus/menu_stock.png"; +static const char pic_max_fill_indicator[] = "images/wui/buildings/max_fill_indicator.png"; +static const char pic_priority_low[] = "images/wui/buildings/low_priority_button.png"; +static const char pic_priority_normal[] = "images/wui/buildings/normal_priority_button.png"; +static const char pic_priority_high[] = "images/wui/buildings/high_priority_button.png"; +static const char pic_stock_policy_prefer[] = "images/wui/buildings/stock_policy_prefer.png"; +static const char pic_stock_policy_dontstock[] = "images/wui/buildings/stock_policy_dontstock.png"; +static const char pic_stock_policy_remove[] = "images/wui/buildings/stock_policy_remove.png"; +static const char pic_stock_policy_button_normal[] = "images/wui/buildings/stock_policy_button_normal.png"; +static const char pic_stock_policy_button_prefer[] = "images/wui/buildings/stock_policy_button_prefer.png"; +static const char pic_stock_policy_button_dontstock[] = "images/wui/buildings/stock_policy_button_dontstock.png"; +static const char pic_stock_policy_button_remove[] = "images/wui/buildings/stock_policy_button_remove.png"; + +constexpr uint32_t kFakeInputQueueWareWidth = WARE_MENU_PIC_WIDTH + 2; +constexpr uint32_t kFakeInputQueueWareHeight = WARE_MENU_PIC_HEIGHT + 4; + +ConstructionSiteWindow::FakeInputQueue::FakeInputQueue(Panel* parent, + int32_t x, + int32_t y, + bool can_act, + Widelands::ConstructionSite& cs, + Widelands::WareWorker ww, + Widelands::DescriptionIndex di) + : UI::Box(parent, x, y, UI::Box::Horizontal), + constructionsite_(cs), + settings_(*dynamic_cast<Widelands::ProductionsiteSettings*>(cs.get_settings())), + type_(ww), + index_(di), + max_fill_indicator_(g_gr->images().get(pic_max_fill_indicator)) { + max_fill_ = get_settings().max_fill; + + const Widelands::Tribes& tribes = cs.owner().egbase().tribes(); + const Widelands::MapObjectDescr* w_descr = nullptr; + if (type_ == Widelands::wwWARE) { + w_descr = tribes.get_ware_descr(index_); + } else { + w_descr = tribes.get_worker_descr(index_); + } + assert(w_descr); + set_tooltip(w_descr->descname()); + icon_ = w_descr->icon(); + + UI::Button& decrease = *new UI::Button( + this, "decrease_max_fill", 0, 0, kFakeInputQueueWareWidth, kFakeInputQueueWareHeight, + UI::ButtonStyle::kWuiMenu, g_gr->images().get("images/ui_basic/scrollbar_left.png"), + _("Decrease the number of items to initially store here")); + UI::Button& increase = *new UI::Button( + this, "increase_max_fill", 0, 0, kFakeInputQueueWareWidth, kFakeInputQueueWareHeight, + UI::ButtonStyle::kWuiMenu, g_gr->images().get("images/ui_basic/scrollbar_right.png"), + _("Increase the number of items to initially store here")); + decrease.sigclicked.connect(boost::bind(&ConstructionSiteWindow::FakeInputQueue::change_fill, this, true)); + increase.sigclicked.connect(boost::bind(&ConstructionSiteWindow::FakeInputQueue::change_fill, this, false)); + decrease.set_repeating(true); + increase.set_repeating(true); + add(&decrease); + add_space(kFakeInputQueueWareWidth * max_fill_ + 8); + add(&increase); + if (type_ == Widelands::wwWARE) { + priority_low_ = new UI::Button(this, "priority_low", 0, 0, + kFakeInputQueueWareWidth, kFakeInputQueueWareHeight, + UI::ButtonStyle::kWuiMenu, g_gr->images().get(pic_priority_low), + _("Lowest priority")); + priority_normal_ = new UI::Button(this, "priority_normal", 0, 0, + kFakeInputQueueWareWidth, kFakeInputQueueWareHeight, + UI::ButtonStyle::kWuiMenu, g_gr->images().get(pic_priority_normal), + _("Normal priority")); + priority_high_ = new UI::Button(this, "priority_high", 0, 0, + kFakeInputQueueWareWidth, kFakeInputQueueWareHeight, + UI::ButtonStyle::kWuiMenu, g_gr->images().get(pic_priority_high), + _("Highest priority")); + add_space(8); + add(priority_low_, UI::Box::Resizing::kAlign, UI::Align::kCenter); + add(priority_normal_, UI::Box::Resizing::kAlign, UI::Align::kCenter); + add(priority_high_, UI::Box::Resizing::kAlign, UI::Align::kCenter); + if (can_act) { + priority_low_->sigclicked.connect(boost::bind(&ConstructionSiteWindow::FakeInputQueue::change_priority, + this, Widelands::kPriorityLow)); + priority_normal_->sigclicked.connect(boost::bind(&ConstructionSiteWindow::FakeInputQueue::change_priority, + this, Widelands::kPriorityNormal)); + priority_high_->sigclicked.connect(boost::bind(&ConstructionSiteWindow::FakeInputQueue::change_priority, + this, Widelands::kPriorityHigh)); + } + } + decrease.set_enabled(can_act); + increase.set_enabled(can_act); +} + +void ConstructionSiteWindow::FakeInputQueue::change_priority(int32_t p) { + Widelands::Game& game = dynamic_cast<Widelands::Game&>(constructionsite_.get_owner()->egbase()); + if (SDL_GetModState() & KMOD_CTRL) { + for (const auto& pair : settings_.ware_queues) { + game.send_player_constructionsite_input_queue_priority(constructionsite_, + Widelands::wwWARE, pair.first, p); + } + for (const auto& pair : settings_.worker_queues) { + game.send_player_constructionsite_input_queue_priority(constructionsite_, + Widelands::wwWORKER, pair.first, p); + } + } else { + game.send_player_constructionsite_input_queue_priority(constructionsite_, type_, index_, p); + } +} + +void ConstructionSiteWindow::FakeInputQueue::change_fill(bool lower) { + Widelands::Game& game = dynamic_cast<Widelands::Game&>(constructionsite_.get_owner()->egbase()); + if (SDL_GetModState() & KMOD_SHIFT) { + for (const auto& pair : settings_.ware_queues) { + if (SDL_GetModState() & KMOD_CTRL) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, + Widelands::wwWARE, pair.first, lower ? 0 : pair.second.max_fill); + } else if (lower && pair.second.desired_fill > 0) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, + Widelands::wwWARE, pair.first, pair.second.desired_fill - 1); + } else if (!lower && pair.second.desired_fill < pair.second.max_fill) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, + Widelands::wwWARE, pair.first, pair.second.desired_fill + 1); + } + } + for (const auto& pair : settings_.worker_queues) { + if (SDL_GetModState() & KMOD_CTRL) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, + Widelands::wwWORKER, pair.first, lower ? 0 : pair.second.max_fill); + } else if (lower && pair.second.desired_fill > 0) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, + Widelands::wwWORKER, pair.first, pair.second.desired_fill - 1); + } else if (!lower && pair.second.desired_fill < pair.second.max_fill) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, + Widelands::wwWORKER, pair.first, pair.second.desired_fill + 1); + } + } + } else { + const uint32_t fill = get_settings().desired_fill; + if (SDL_GetModState() & KMOD_CTRL) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, type_, index_, + lower ? 0 : max_fill_); + } else if (lower && fill > 0) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, type_, index_, fill - 1); + } else if (!lower && fill < max_fill_) { + game.send_player_constructionsite_input_queue_max_fill(constructionsite_, type_, index_, fill + 1); + } + } +} + +const Widelands::ProductionsiteSettings::InputQueueSetting& ConstructionSiteWindow::FakeInputQueue::get_settings() const { + switch (type_) { + case Widelands::wwWARE: + for (const auto& pair : settings_.ware_queues) { + if (pair.first == index_) { + return pair.second; + } + } + NEVER_HERE(); + case Widelands::wwWORKER: + for (const auto& pair : settings_.worker_queues) { + if (pair.first == index_) { + return pair.second; + } + } + NEVER_HERE(); + default: + NEVER_HERE(); + } +} + +void ConstructionSiteWindow::FakeInputQueue::think() { + UI::Box::think(); + if (priority_normal_) { + assert(priority_high_); + assert(priority_low_); + const int32_t priority = get_settings().priority; + priority_high_->set_enabled(priority != Widelands::kPriorityHigh); + priority_normal_->set_enabled(priority != Widelands::kPriorityNormal); + priority_low_->set_enabled(priority != Widelands::kPriorityLow); + } +#ifndef NDEBUG + else { + assert(!priority_high_); + assert(!priority_low_); + } +#endif +} + +void ConstructionSiteWindow::FakeInputQueue::draw(RenderTarget& dst) { + UI::Box::draw(dst); + + Vector2i point = Vector2i::zero(); + point.x = kFakeInputQueueWareWidth + 4; + point.y = (kFakeInputQueueWareHeight - icon_->height()) / 2; + + const uint32_t fill = get_settings().desired_fill; + uint32_t draw_yes = fill; + uint32_t draw_no = max_fill_ - draw_yes; + for (; draw_yes; --draw_yes, point.x += kFakeInputQueueWareWidth) { + dst.blitrect(Vector2i(point.x, point.y), icon_, Recti(0, 0, icon_->width(), icon_->height()), + BlendMode::UseAlpha); + } + for (; draw_no; --draw_no, point.x += kFakeInputQueueWareWidth) { + dst.blitrect_scale_monochrome(Rectf(point.x, point.y, icon_->width(), icon_->height()), icon_, + Recti(0, 0, icon_->width(), icon_->height()), + RGBAColor(191, 191, 191, 127)); + } + + point.x = 4 + kFakeInputQueueWareWidth + (fill * kFakeInputQueueWareWidth) - max_fill_indicator_->width() / 2; + // Unsigned arithmetic... + point.y = kFakeInputQueueWareHeight; + point.y -= max_fill_indicator_->height(); + point.y /= 2; + dst.blit(point, max_fill_indicator_); +} + +ConstructionSiteWindow::FakeWaresDisplay::FakeWaresDisplay(UI::Panel* parent, + bool can_act, + Widelands::ConstructionSite& cs, + Widelands::WareWorker type) + : WaresDisplay(parent, 0, 0, cs.owner().tribe(), type, can_act), + settings_(*dynamic_cast<Widelands::WarehouseSettings*>(cs.get_settings())) { +} + +void ConstructionSiteWindow::FakeWaresDisplay::draw_ware(RenderTarget& dst, Widelands::DescriptionIndex ware) { + WaresDisplay::draw_ware(dst, ware); + + const auto& map = get_type() == Widelands::wwWARE ? settings_.ware_preferences : settings_.worker_preferences; + const auto it = map.find(ware); + if (it == map.end()) { + return; + } + const Image* pic = nullptr; + switch (it->second) { + case Widelands::StockPolicy::kPrefer: + pic = g_gr->images().get(pic_stock_policy_prefer); + break; + case Widelands::StockPolicy::kDontStock: + pic = g_gr->images().get(pic_stock_policy_dontstock); + break; + case Widelands::StockPolicy::kRemove: + pic = g_gr->images().get(pic_stock_policy_remove); + break; + case Widelands::StockPolicy::kNormal: + // No icon for the normal policy + return; + } + assert(pic); + dst.blit(ware_position(ware), pic); +} ConstructionSiteWindow::ConstructionSiteWindow(InteractiveGameBase& parent, UI::UniqueWindow::Registry& reg, @@ -33,7 +280,19 @@ bool workarea_preview_wanted) : BuildingWindow(parent, reg, cs, cs.building(), avoid_fastclick), construction_site_(&cs), - progress_(nullptr) { + progress_(nullptr), + cs_enhance_(nullptr), + cs_launch_expedition_(nullptr), + cs_prefer_heroes_(nullptr), + cs_soldier_capacity_(nullptr), + cs_stopped_(nullptr), + cs_warehouse_wares_(nullptr), + cs_warehouse_workers_(nullptr), + cs_warehouse_stock_policy_normal_(nullptr), + cs_warehouse_stock_policy_prefer_(nullptr), + cs_warehouse_stock_policy_dontstock_(nullptr), + cs_warehouse_stock_policy_remove_(nullptr), + cs_warehouse_tabs_(nullptr) { init(avoid_fastclick, workarea_preview_wanted); } @@ -59,10 +318,182 @@ get_tabs()->add("wares", g_gr->images().get(pic_tab_wares), &box, _("Building materials")); + if (construction_site->get_settings()) { + const bool can_act = igbase()->can_act(construction_site->owner().player_number()); + // Create the settings. Since we don't access an actual building, we create + // a simplified faksimile of the later building window that contains only + // the relevant options. + UI::Box& settings_box = *new UI::Box(get_tabs(), 0, 0, UI::Box::Vertical); + if (upcast(Widelands::ProductionsiteSettings, ps, construction_site->get_settings())) { + for (const auto& pair : ps->ware_queues) { + FakeInputQueue* queue = new FakeInputQueue(&settings_box, 0, 0, can_act, + *construction_site, Widelands::wwWARE, pair.first); + settings_box.add(queue); + settings_box.add_space(8); + cs_ware_queues_.push_back(queue); + } + for (const auto& pair : ps->worker_queues) { + FakeInputQueue* queue = new FakeInputQueue(&settings_box, 0, 0, can_act, + *construction_site, Widelands::wwWORKER, pair.first); + settings_box.add(queue); + settings_box.add_space(8); + cs_ware_queues_.push_back(queue); + } + if (upcast(Widelands::TrainingsiteSettings, ts, ps)) { + cs_soldier_capacity_ = new UI::SpinBox(&settings_box, 0, 0, 300, 100, + ts->desired_capacity, 0, ts->max_capacity, UI::PanelStyle::kWui, _("Soldier capacity:")); + settings_box.add(cs_soldier_capacity_); + settings_box.add_space(8); + cs_soldier_capacity_->changed.connect([this]() { + igbase()->game().send_player_constructionsite_soldier_capacity( + *construction_site_.get(igbase()->egbase()), cs_soldier_capacity_->get_value()); + }); + for (UI::Button* b : cs_soldier_capacity_->get_buttons()) { + b->set_enabled(can_act); + } + } + cs_stopped_ = new UI::Checkbox(&settings_box, Vector2i::zero(), + _("Stopped"), + _("Stop this building’s work after completion")); + cs_stopped_->changed.connect([this]() { + igbase()->game().send_player_constructionsite_startstop( + *construction_site_.get(igbase()->egbase()), cs_stopped_->get_state()); + }); + settings_box.add(cs_stopped_, UI::Box::Resizing::kFullSize); + settings_box.add_space(8); + cs_stopped_->set_enabled(can_act); + } else if (upcast(Widelands::MilitarysiteSettings, ms, construction_site->get_settings())) { + cs_soldier_capacity_ = new UI::SpinBox(&settings_box, 0, 0, 300, 100, + ms->desired_capacity, 1, ms->max_capacity, UI::PanelStyle::kWui, _("Soldier capacity:")); + settings_box.add(cs_soldier_capacity_); + settings_box.add_space(8); + cs_soldier_capacity_->changed.connect([this]() { + igbase()->game().send_player_constructionsite_soldier_capacity(*construction_site_.get(igbase()->egbase()), + cs_soldier_capacity_->get_value()); + }); + for (UI::Button* b : cs_soldier_capacity_->get_buttons()) { + b->set_enabled(can_act); + } + cs_prefer_heroes_ = new UI::Checkbox(&settings_box, Vector2i::zero(), + _("Prefer heroes"), + _("Prefer heroes over rookies")); + cs_prefer_heroes_->changed.connect([this]() { + igbase()->game().send_player_constructionsite_prefer_heroes(*construction_site_.get(igbase()->egbase()), + cs_prefer_heroes_->get_state()); + }); + cs_prefer_heroes_->set_enabled(can_act); + settings_box.add(cs_prefer_heroes_, UI::Box::Resizing::kFullSize); + settings_box.add_space(8); + } else if (upcast(Widelands::WarehouseSettings, ws, construction_site->get_settings())) { + cs_warehouse_tabs_ = new UI::TabPanel(&settings_box, UI::TabPanelStyle::kWuiLight); + cs_warehouse_wares_ = new FakeWaresDisplay( + cs_warehouse_tabs_, can_act, *construction_site, Widelands::wwWARE); + cs_warehouse_workers_ = new FakeWaresDisplay( + cs_warehouse_tabs_, can_act, *construction_site, Widelands::wwWORKER); + cs_warehouse_tabs_->add("tab_wares", g_gr->images().get(pic_tab_wares), + cs_warehouse_wares_, _("Wares")); + cs_warehouse_tabs_->add("tab_workers", g_gr->images().get(pic_tab_workers), + cs_warehouse_workers_, _("Workers")); + settings_box.add(cs_warehouse_tabs_, UI::Box::Resizing::kFullSize); + settings_box.add_space(8); + UI::Box& buttonsbox = *new UI::Box(&settings_box, 0, 0, UI::Box::Horizontal); + settings_box.add(&buttonsbox, UI::Box::Resizing::kAlign, UI::Align::kCenter); + settings_box.add_space(8); + cs_warehouse_stock_policy_normal_ = new UI::Button(&buttonsbox, "stock_policy_normal", 0, 0, 34, 34, + UI::ButtonStyle::kWuiMenu, g_gr->images().get(pic_stock_policy_button_normal), + _("Normal policy")); + cs_warehouse_stock_policy_prefer_ = new UI::Button(&buttonsbox, "stock_policy_prefer", 0, 0, 34, 34, + UI::ButtonStyle::kWuiMenu, g_gr->images().get(pic_stock_policy_button_prefer), + _("Preferably store selected wares here")); + cs_warehouse_stock_policy_dontstock_ = new UI::Button(&buttonsbox, "stock_policy_dontstock", 0, 0, 34, 34, + UI::ButtonStyle::kWuiMenu, g_gr->images().get(pic_stock_policy_button_dontstock), + _("Do not store selected wares here")); + cs_warehouse_stock_policy_remove_ = new UI::Button(&buttonsbox, "stock_policy_remove", 0, 0, 34, 34, + UI::ButtonStyle::kWuiMenu, g_gr->images().get(pic_stock_policy_button_remove), + _("Remove selected wares from here")); + cs_warehouse_stock_policy_remove_->sigclicked.connect( + boost::bind(&ConstructionSiteWindow::change_policy, this, Widelands::StockPolicy::kRemove)); + cs_warehouse_stock_policy_dontstock_->sigclicked.connect( + boost::bind(&ConstructionSiteWindow::change_policy, this, Widelands::StockPolicy::kDontStock)); + cs_warehouse_stock_policy_prefer_->sigclicked.connect( + boost::bind(&ConstructionSiteWindow::change_policy, this, Widelands::StockPolicy::kPrefer)); + cs_warehouse_stock_policy_normal_->sigclicked.connect( + boost::bind(&ConstructionSiteWindow::change_policy, this, Widelands::StockPolicy::kNormal)); + buttonsbox.add(cs_warehouse_stock_policy_normal_); + buttonsbox.add_space(8); + buttonsbox.add(cs_warehouse_stock_policy_prefer_); + buttonsbox.add_space(8); + buttonsbox.add(cs_warehouse_stock_policy_dontstock_); + buttonsbox.add_space(8); + buttonsbox.add(cs_warehouse_stock_policy_remove_); + if (construction_site->get_info().becomes->get_isport()) { + cs_launch_expedition_ = new UI::Checkbox(&settings_box, Vector2i::zero(), + _("Launch expedition"), + _("Start an expedition from this port after completion")); + cs_launch_expedition_->changed.connect([this]() { + igbase()->game().send_player_constructionsite_launch_expedition( + *construction_site_.get(igbase()->egbase()), cs_launch_expedition_->get_state()); + }); + settings_box.add(cs_launch_expedition_, UI::Box::Resizing::kFullSize); + settings_box.add_space(8); + cs_launch_expedition_->set_enabled(can_act); + } + } else { + NEVER_HERE(); + } + + if (can_act && construction_site->get_info().becomes->enhancement() != Widelands::INVALID_INDEX) { + const Widelands::BuildingDescr& building_descr = *igbase()->egbase().tribes().get_building_descr( + construction_site->get_info().becomes->enhancement()); + std::string enhance_tooltip = + (boost::format(_("Enhance to %s")) % building_descr.descname().c_str()).str() + + "<br><font size=11>" + _("Construction costs:") + "</font><br>" + + waremap_to_richtext(construction_site->owner().tribe(), building_descr.enhancement_cost()); + cs_enhance_ = new UI::Button(&settings_box, "enhance", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu, + building_descr.icon(), enhance_tooltip); + cs_enhance_->sigclicked.connect([this, construction_site] { + if (SDL_GetModState() & KMOD_CTRL) { + igbase()->game().send_player_constructionsite_enhance(*construction_site); + } else { + show_enhance_confirm(dynamic_cast<InteractivePlayer&>(*igbase()), + *construction_site, construction_site->get_info().becomes->enhancement(), true); + } + }); + settings_box.add(cs_enhance_, UI::Box::Resizing::kAlign, UI::Align::kCenter); + settings_box.add_space(8); + } + if (settings_box.get_nritems()) { + get_tabs()->add("settings", g_gr->images().get(pic_tab_settings), + &settings_box, _("Settings to apply after construction")); + } + } + set_title((boost::format("(%s)") % construction_site->building().descname()).str()); think(); } +void ConstructionSiteWindow::change_policy(Widelands::StockPolicy p) { + Widelands::ConstructionSite* construction_site = construction_site_.get(igbase()->egbase()); + assert(construction_site); + upcast(Widelands::WarehouseSettings, ws, construction_site->get_settings()); + assert(ws); + if (cs_warehouse_tabs_->active() == 0) { + for (const auto& pair : ws->ware_preferences) { + if (cs_warehouse_wares_->ware_selected(pair.first)) { + igbase()->game().send_player_constructionsite_stock_policy(*construction_site, + Widelands::wwWARE, pair.first, p); + } + } + } else { + for (const auto& pair : ws->worker_preferences) { + if (cs_warehouse_workers_->ware_selected(pair.first)) { + igbase()->game().send_player_constructionsite_stock_policy(*construction_site, + Widelands::wwWORKER, pair.first, p); + } + } + } +} + /* =============== Make sure the window is redrawn when necessary. @@ -77,5 +508,30 @@ if (construction_site == nullptr) { return; } + progress_->set_state(construction_site->get_built_per64k()); + + // FakeInputQueue and FakeWaresDisplay update themselves – we need to refresh the other settings + if (upcast(Widelands::ProductionsiteSettings, ps, construction_site->get_settings())) { + assert(cs_stopped_); + cs_stopped_->set_state(ps->stopped); + } + if (upcast(Widelands::TrainingsiteSettings, ts, construction_site->get_settings())) { + assert(cs_soldier_capacity_); + cs_soldier_capacity_->set_value(ts->desired_capacity); + } else if (upcast(Widelands::MilitarysiteSettings, ms, construction_site->get_settings())) { + assert(cs_soldier_capacity_); + assert(cs_prefer_heroes_); + cs_soldier_capacity_->set_value(ms->desired_capacity); + cs_prefer_heroes_->set_state(ms->prefer_heroes); + } else if (upcast(Widelands::WarehouseSettings, ws, construction_site->get_settings())) { + if (cs_launch_expedition_) { + cs_launch_expedition_->set_state(ws->launch_expedition); + } +#ifndef NDEBUG + else { + assert(!ws->launch_expedition); + } +#endif + } } === modified file 'src/wui/constructionsitewindow.h' --- src/wui/constructionsitewindow.h 2019-02-23 11:00:49 +0000 +++ src/wui/constructionsitewindow.h 2019-05-14 17:38:23 +0000 @@ -20,8 +20,14 @@ #ifndef WL_WUI_CONSTRUCTIONSITEWINDOW_H #define WL_WUI_CONSTRUCTIONSITEWINDOW_H +#include <vector> + #include "logic/map_objects/tribes/constructionsite.h" +#include "ui_basic/button.h" +#include "ui_basic/checkbox.h" #include "ui_basic/progressbar.h" +#include "ui_basic/spinbox.h" +#include "ui_basic/tabpanel.h" #include "wui/buildingwindow.h" /** @@ -40,8 +46,72 @@ void init(bool avoid_fastclick, bool workarea_preview_wanted) override; private: + struct FakeInputQueue : UI::Box { + FakeInputQueue(Panel* parent, + int32_t x, + int32_t y, + bool can_act, + Widelands::ConstructionSite& cs, + Widelands::WareWorker ww, + Widelands::DescriptionIndex di); + + void draw(RenderTarget& dst) override; + void think() override; + + const Widelands::ProductionsiteSettings::InputQueueSetting& get_settings() const; + + private: + Widelands::ConstructionSite& constructionsite_; + Widelands::ProductionsiteSettings& settings_; + Widelands::WareWorker type_; + Widelands::DescriptionIndex index_; + + void change_fill(bool); + void change_priority(int32_t); + + uint32_t max_fill_; + const Image* icon_; + const Image* max_fill_indicator_; + + UI::Button* priority_high_; + UI::Button* priority_normal_; + UI::Button* priority_low_; + }; + + class FakeWaresDisplay : public WaresDisplay { + public: + FakeWaresDisplay(UI::Panel* parent, + bool can_act, + Widelands::ConstructionSite& cs, + Widelands::WareWorker type); + + protected: + void draw_ware(RenderTarget& dst, Widelands::DescriptionIndex ware) override; + + private: + Widelands::WarehouseSettings& settings_; + }; + Widelands::OPtr<Widelands::ConstructionSite> construction_site_; UI::ProgressBar* progress_; + + // BuildingSettings-related UI elements + UI::Button* cs_enhance_; + UI::Checkbox* cs_launch_expedition_; + UI::Checkbox* cs_prefer_heroes_; + UI::SpinBox* cs_soldier_capacity_; + std::vector<FakeInputQueue*> cs_ware_queues_; + std::vector<FakeInputQueue*> cs_worker_queues_; + UI::Checkbox* cs_stopped_; + FakeWaresDisplay* cs_warehouse_wares_; + FakeWaresDisplay* cs_warehouse_workers_; + UI::Button* cs_warehouse_stock_policy_normal_; + UI::Button* cs_warehouse_stock_policy_prefer_; + UI::Button* cs_warehouse_stock_policy_dontstock_; + UI::Button* cs_warehouse_stock_policy_remove_; + UI::TabPanel* cs_warehouse_tabs_; + void change_policy(Widelands::StockPolicy); + DISALLOW_COPY_AND_ASSIGN(ConstructionSiteWindow); }; === modified file 'src/wui/inputqueuedisplay.cc' --- src/wui/inputqueuedisplay.cc 2019-04-25 06:40:24 +0000 +++ src/wui/inputqueuedisplay.cc 2019-05-14 17:38:23 +0000 @@ -205,13 +205,13 @@ int32_t priority = building_.get_priority(type_, index_, false); switch (priority) { - case HIGH_PRIORITY: + case Widelands::kPriorityHigh: priority_radiogroup_->set_state(0); break; - case DEFAULT_PRIORITY: + case Widelands::kPriorityNormal: priority_radiogroup_->set_state(1); break; - case LOW_PRIORITY: + case Widelands::kPriorityLow: priority_radiogroup_->set_state(2); break; default: @@ -294,13 +294,13 @@ switch (state) { case 0: - priority = HIGH_PRIORITY; + priority = Widelands::kPriorityHigh; break; case 1: - priority = DEFAULT_PRIORITY; + priority = Widelands::kPriorityNormal; break; case 2: - priority = LOW_PRIORITY; + priority = Widelands::kPriorityLow; break; default: return; === modified file 'src/wui/warehousewindow.cc' --- src/wui/warehousewindow.cc 2019-02-23 11:00:49 +0000 +++ src/wui/warehousewindow.cc 2019-05-14 17:38:23 +0000 @@ -75,19 +75,19 @@ void WarehouseWaresDisplay::draw_ware(RenderTarget& dst, Widelands::DescriptionIndex ware) { WaresDisplay::draw_ware(dst, ware); - Widelands::Warehouse::StockPolicy policy = warehouse_.get_stock_policy(get_type(), ware); + Widelands::StockPolicy policy = warehouse_.get_stock_policy(get_type(), ware); const Image* pic = nullptr; switch (policy) { - case Widelands::Warehouse::StockPolicy::kPrefer: + case Widelands::StockPolicy::kPrefer: pic = g_gr->images().get(pic_policy_prefer); break; - case Widelands::Warehouse::StockPolicy::kDontStock: + case Widelands::StockPolicy::kDontStock: pic = g_gr->images().get(pic_policy_dontstock); break; - case Widelands::Warehouse::StockPolicy::kRemove: + case Widelands::StockPolicy::kRemove: pic = g_gr->images().get(pic_policy_remove); break; - case Widelands::Warehouse::StockPolicy::kNormal: + case Widelands::StockPolicy::kNormal: // don't draw anything for the normal policy return; } @@ -106,7 +106,7 @@ Widelands::Warehouse&, Widelands::WareWorker type); - void set_policy(Widelands::Warehouse::StockPolicy); + void set_policy(Widelands::StockPolicy); private: InteractiveGameBase& gb_; @@ -139,7 +139,7 @@ buttons, #policy, 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu, \ g_gr->images().get("images/wui/buildings/stock_policy_button_" #policy ".png"), tooltip), \ b->sigclicked.connect(boost::bind( \ - &WarehouseWaresPanel::set_policy, this, Widelands::Warehouse::StockPolicy::k##policyname)), \ + &WarehouseWaresPanel::set_policy, this, Widelands::StockPolicy::k##policyname)), \ buttons->add(b); ADD_POLICY_BUTTON(normal, Normal, _("Normal policy")) @@ -152,7 +152,7 @@ /** * Add Buttons policy buttons */ -void WarehouseWaresPanel::set_policy(Widelands::Warehouse::StockPolicy newpolicy) { +void WarehouseWaresPanel::set_policy(Widelands::StockPolicy newpolicy) { if (gb_.can_act(wh_.owner().player_number())) { bool is_workers = type_ == Widelands::wwWORKER; const std::set<Widelands::DescriptionIndex> indices =
_______________________________________________ Mailing list: https://launchpad.net/~widelands-dev Post to : widelands-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~widelands-dev More help : https://help.launchpad.net/ListHelp